Merge pull request #204 from ethersphere/swarm-mutableresources-apinew

HTTP API for mutable resources
This commit is contained in:
lash 2018-01-25 14:43:47 +01:00 committed by GitHub
commit 85e2318f77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 713 additions and 302 deletions

View file

@ -560,7 +560,7 @@ func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.
} }
// In production, mockStore must be always nil. // In production, mockStore must be always nil.
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, bzzconfig.SwapEnabled, bzzconfig.SyncEnabled, bzzconfig.Cors, bzzconfig.PssEnabled, nil) return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, nil)
} }
//register within the ethereum node //register within the ethereum node
if err := stack.Register(boot); err != nil { if err := stack.Register(boot); err != nil {

View file

@ -17,6 +17,7 @@
package api package api
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -46,15 +47,17 @@ on top of the dpa
it is the public interface of the dpa which is included in the ethereum stack it is the public interface of the dpa which is included in the ethereum stack
*/ */
type Api struct { type Api struct {
dpa *storage.DPA resource *storage.ResourceHandler
dns Resolver dpa *storage.DPA
dns Resolver
} }
//the api constructor initialises //the api constructor initialises
func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) { func NewApi(dpa *storage.DPA, dns Resolver, resourceHandler *storage.ResourceHandler) (self *Api) {
self = &Api{ self = &Api{
dpa: dpa, dpa: dpa,
dns: dns, dns: dns,
resource: resourceHandler,
} }
return return
} }
@ -361,3 +364,50 @@ func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storag
} }
return key, manifestEntryMap, nil return key, manifestEntryMap, nil
} }
// Look up mutable resource updates at specific periods and versions
func (self *Api) ResourceLookup(ctx context.Context, name string, period uint32, version uint32) (storage.Key, []byte, error) {
var err error
if version != 0 {
if period == 0 {
currentblocknumber, err := self.resource.GetBlock(ctx)
if err != nil {
return nil, nil, fmt.Errorf("Could not determine latest block: %v", err)
}
period = self.resource.BlockToPeriod(name, currentblocknumber)
}
_, err = self.resource.LookupVersionByName(ctx, name, period, version, true)
} else if period != 0 {
_, err = self.resource.LookupHistoricalByName(ctx, name, period, true)
} else {
_, err = self.resource.LookupLatestByName(ctx, name, true)
}
if err != nil {
return nil, nil, err
}
return self.resource.GetContent(name)
}
func (self *Api) ResourceCreate(ctx context.Context, name string, frequency uint64) (storage.Key, error) {
rsrc, err := self.resource.NewResource(ctx, name, frequency)
if err != nil {
return nil, err
}
h := rsrc.NameHash()
return storage.Key(h[:]), nil
}
func (self *Api) ResourceUpdate(ctx context.Context, name string, data []byte) (storage.Key, uint32, uint32, error) {
key, err := self.resource.Update(ctx, name, data)
period, _ := self.resource.GetLastPeriod(name)
version, _ := self.resource.GetVersion(name)
return key, period, version, err
}
func (self *Api) ResourceHashSize() int {
return self.resource.HashSize()
}
func (self *Api) ResourceIsValidated() bool {
return self.resource.IsValidated()
}

View file

@ -40,7 +40,7 @@ func testApi(t *testing.T, f func(*Api)) {
if err != nil { if err != nil {
return return
} }
api := NewApi(dpa, nil) api := NewApi(dpa, nil, nil)
dpa.Start() dpa.Start()
f(api) f(api)
dpa.Stop() dpa.Stop()

View file

@ -46,22 +46,24 @@ type Config struct {
*network.HiveParams *network.HiveParams
Swap *swap.SwapParams Swap *swap.SwapParams
//*network.SyncParams //*network.SyncParams
Contract common.Address Contract common.Address
EnsRoot common.Address EnsRoot common.Address
EnsApi string EnsApi string
Path string Path string
ListenAddr string ListenAddr string
Port string Port string
PublicKey string PublicKey string
BzzKey string BzzKey string
NetworkId uint64 NetworkId uint64
SwapEnabled bool SwapEnabled bool
SyncEnabled bool SyncEnabled bool
PssEnabled bool PssEnabled bool
SwapApi string ResourceEnabled bool
Cors string SwapApi string
BzzAccount string Cors string
BootNodes string BzzAccount string
BootNodes string
privateKey *ecdsa.PrivateKey
} }
//create a default config with all parameters to set to defaults //create a default config with all parameters to set to defaults
@ -72,18 +74,19 @@ func NewConfig() (self *Config) {
ChunkerParams: storage.NewChunkerParams(), ChunkerParams: storage.NewChunkerParams(),
HiveParams: network.NewHiveParams(), HiveParams: network.NewHiveParams(),
//SyncParams: network.NewDefaultSyncParams(), //SyncParams: network.NewDefaultSyncParams(),
Swap: swap.NewDefaultSwapParams(), Swap: swap.NewDefaultSwapParams(),
ListenAddr: DefaultHTTPListenAddr, ListenAddr: DefaultHTTPListenAddr,
Port: DefaultHTTPPort, Port: DefaultHTTPPort,
Path: node.DefaultDataDir(), Path: node.DefaultDataDir(),
EnsApi: node.DefaultIPCEndpoint("geth"), EnsApi: node.DefaultIPCEndpoint("geth"),
EnsRoot: ens.TestNetAddress, EnsRoot: ens.TestNetAddress,
NetworkId: network.NetworkID, NetworkId: network.NetworkID,
SwapEnabled: false, SwapEnabled: false,
SyncEnabled: true, SyncEnabled: true,
PssEnabled: true, PssEnabled: true,
SwapApi: "", ResourceEnabled: true,
BootNodes: "", SwapApi: "",
BootNodes: "",
} }
return return
@ -108,8 +111,17 @@ func (self *Config) Init(prvKey *ecdsa.PrivateKey) {
self.PublicKey = pubkeyhex self.PublicKey = pubkeyhex
self.BzzKey = keyhex self.BzzKey = keyhex
self.Swap.Init(self.Contract, prvKey) if self.SwapEnabled {
//self.SyncParams.Init(self.Path) self.Swap.Init(self.Contract, prvKey)
//self.HiveParams.Init(self.Path) }
self.privateKey = prvKey
self.StoreParams.Init(self.Path) self.StoreParams.Init(self.Path)
} }
func (self *Config) ShiftPrivateKey() (privKey *ecdsa.PrivateKey) {
if self.privateKey != nil {
privKey = self.privateKey
self.privateKey = nil
}
return privKey
}

View file

@ -49,16 +49,9 @@ func TestConfig(t *testing.T) {
if one.PublicKey == "" { if one.PublicKey == "" {
t.Fatal("Expected PublicKey to be set") t.Fatal("Expected PublicKey to be set")
} }
if one.Swap.PayProfile.Beneficiary == (common.Address{}) && one.SwapEnabled {
//the Init function should append subdirs to the given path
if one.Swap.PayProfile.Beneficiary == (common.Address{}) {
t.Fatal("Failed to correctly initialize SwapParams") t.Fatal("Failed to correctly initialize SwapParams")
} }
if one.HiveParams.MaxPeersPerRequest != 5 {
t.Fatal("Failed to correctly initialize HiveParams")
}
if one.StoreParams.ChunkDbPath == one.Path { if one.StoreParams.ChunkDbPath == one.Path {
t.Fatal("Failed to correctly initialize StoreParams") t.Fatal("Failed to correctly initialize StoreParams")
} }

View file

@ -21,6 +21,7 @@ package http
import ( import (
"archive/tar" "archive/tar"
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -290,6 +291,95 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) {
fmt.Fprint(w, newKey) fmt.Fprint(w, newKey)
} }
func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
var outdata string
if r.uri.Path != "" {
frequency, err := strconv.ParseUint(r.uri.Path, 10, 64)
if err != nil {
s.BadRequest(w, r, fmt.Sprintf("Cannot parse frequency parameter: %v", err))
return
}
key, err := s.api.ResourceCreate(r.Context(), r.uri.Addr, frequency)
if err != nil {
s.Error(w, r, fmt.Errorf("Resource creation failed: %v", err))
return
}
outdata = key.Hex()
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
s.Error(w, r, err)
return
}
_, _, _, err = s.api.ResourceUpdate(r.Context(), r.uri.Addr, data)
if err != nil {
s.Error(w, r, fmt.Errorf("Update resource failed: %v", err))
return
}
if outdata != "" {
w.Header().Add("Content-type", "text/plain")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, outdata)
return
}
w.WriteHeader(http.StatusOK)
}
// Retrieve mutable resource updates:
// bzz-resource://<id> - get latest update
// bzz-resource://<id>/<n> - get latest update on period n
// bzz-resource://<id>/<n>/<m> - get update version m of period n
// <id> = ens name or hash
func (s *Server) HandleGetResource(w http.ResponseWriter, r *Request) {
s.handleGetResource(w, r, r.uri.Addr)
}
func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name string) {
var params []string
if len(r.uri.Path) > 0 {
params = strings.Split(r.uri.Path, "/")
}
var updateKey storage.Key
var period uint64
var version uint64
var data []byte
var err error
now := time.Now()
log.Debug("handlegetdb", "name", name)
switch len(params) {
case 0:
updateKey, data, err = s.api.ResourceLookup(r.Context(), name, 0, 0)
case 2:
version, err = strconv.ParseUint(params[1], 10, 32)
if err != nil {
break
}
period, err = strconv.ParseUint(params[0], 10, 32)
if err != nil {
break
}
updateKey, data, err = s.api.ResourceLookup(r.Context(), name, uint32(period), uint32(version))
case 1:
period, err = strconv.ParseUint(params[0], 10, 32)
if err != nil {
break
}
updateKey, data, err = s.api.ResourceLookup(r.Context(), name, uint32(period), uint32(version))
default:
s.BadRequest(w, r, "Invalid mutable resource request")
return
}
if err != nil {
s.Error(w, r, fmt.Errorf("Mutable resource lookup failed: %v", err))
return
}
log.Debug("Found update", "key", updateKey)
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeContent(w, &r.Request, "", now, bytes.NewReader(data))
}
// HandleGet handles a GET request to // HandleGet handles a GET request to
// - bzz-raw://<key> and responds with the raw content stored at the // - bzz-raw://<key> and responds with the raw content stored at the
// given storage key // given storage key
@ -335,7 +425,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
return api.SkipManifest return api.SkipManifest
}) })
if entry == nil { if entry == nil {
s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded")) s.NotFound(w, r, errors.New("Manifest entry could not be loaded"))
return return
} }
key = storage.Key(common.Hex2Bytes(entry.Hash)) key = storage.Key(common.Hex2Bytes(entry.Hash))
@ -357,7 +447,6 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
contentType = typ contentType = typ
} }
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
http.ServeContent(w, &r.Request, "", time.Now(), reader) http.ServeContent(w, &r.Request, "", time.Now(), reader)
case r.uri.Hash(): case r.uri.Hash():
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
@ -604,6 +693,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case "POST": case "POST":
if uri.Raw() || uri.DeprecatedRaw() { if uri.Raw() || uri.DeprecatedRaw() {
s.HandlePostRaw(w, req) s.HandlePostRaw(w, req)
} else if uri.Resource() {
s.HandlePostResource(w, req)
} else { } else {
s.HandlePostFiles(w, req) s.HandlePostFiles(w, req)
} }
@ -629,6 +720,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.HandleDelete(w, req) s.HandleDelete(w, req)
case "GET": case "GET":
if uri.Resource() {
s.HandleGetResource(w, req)
return
}
if uri.Raw() || uri.Hash() || uri.DeprecatedRaw() { if uri.Raw() || uri.Hash() || uri.DeprecatedRaw() {
s.HandleGet(w, req) s.HandleGet(w, req)
return return

View file

@ -18,6 +18,7 @@ package http_test
import ( import (
"bytes" "bytes"
"crypto/rand"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -33,6 +34,128 @@ import (
"github.com/ethereum/go-ethereum/swarm/testutil" "github.com/ethereum/go-ethereum/swarm/testutil"
) )
func TestBzzResource(t *testing.T) {
srv := testutil.NewTestSwarmServer(t)
defer srv.Close()
// our mutable resource "name"
keybytes := make([]byte, common.HashLength)
copy(keybytes, []byte{42})
srv.Hasher.Reset()
srv.Hasher.Write([]byte(fmt.Sprintf("%x", keybytes)))
keybyteshash := fmt.Sprintf("%x", srv.Hasher.Sum(nil))
// data of update 1
databytes := make([]byte, 666)
_, err := rand.Read(databytes)
if err != nil {
t.Fatal(err)
}
// creates resource and sets update 1
url := fmt.Sprintf("%s/bzz-resource:/%x/13", srv.URL, keybytes)
resp, err := http.Post(url, "application/octet-stream", bytes.NewReader(databytes))
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("err %s", resp.Status)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, []byte(keybyteshash)) {
t.Fatalf("resource update hash mismatch, expected '%s' got '%s'", keybyteshash, b)
}
t.Logf("creatreturn %v / %v", keybyteshash, b)
// get latest update (1.1) through resource directly
url = fmt.Sprintf("%s/bzz-resource:/%x", srv.URL, keybytes)
resp, err = http.Get(url)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("err %s", resp.Status)
}
b, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(databytes, b) {
t.Fatalf("Expected body '%x', got '%x'", databytes, b)
}
// update 2
url = fmt.Sprintf("%s/bzz-resource:/%x", srv.URL, keybytes)
data := []byte("foo")
resp, err = http.Post(url, "application/octet-stream", bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Update returned %s", resp.Status)
}
// get latest update (1.2) through resource directly
url = fmt.Sprintf("%s/bzz-resource:/%x", srv.URL, keybytes)
resp, err = http.Get(url)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("err %s", resp.Status)
}
b, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, b) {
t.Fatalf("Expected body '%x', got '%x'", data, b)
}
// get latest update (1.2) with specified period
url = fmt.Sprintf("%s/bzz-resource:/%x/1", srv.URL, keybytes)
resp, err = http.Get(url)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("err %s", resp.Status)
}
b, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, b) {
t.Fatalf("Expected body '%x', got '%x'", data, b)
}
// get first update (1.1) with specified period and version
url = fmt.Sprintf("%s/bzz-resource:/%x/1/1", srv.URL, keybytes)
resp, err = http.Get(url)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("err %s", resp.Status)
}
b, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(databytes, b) {
t.Fatalf("Expected body '%x', got '%x'", databytes, b)
}
}
func TestBzzGetPath(t *testing.T) { func TestBzzGetPath(t *testing.T) {
var err error var err error
@ -258,7 +381,6 @@ func TestBzzGetPath(t *testing.T) {
t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody))
} }
} }
} }
// TestBzzRootRedirect tests that getting the root path of a manifest without // TestBzzRootRedirect tests that getting the root path of a manifest without

View file

@ -33,7 +33,8 @@ import (
) )
const ( const (
ManifestType = "application/bzz-manifest+json" ManifestType = "application/bzz-manifest+json"
ResourceContentType = "application/bzz-resource"
) )
// Manifest represents a swarm manifest // Manifest represents a swarm manifest

View file

@ -69,7 +69,7 @@ func Parse(rawuri string) (*URI, error) {
// check the scheme is valid // check the scheme is valid
switch uri.Scheme { switch uri.Scheme {
case "bzz", "bzz-raw", "bzz-immutable", "bzz-list", "bzz-hash", "bzzr", "bzzi": case "bzz", "bzz-raw", "bzz-immutable", "bzz-list", "bzz-hash", "bzzr", "bzzi", "bzz-resource":
default: default:
return nil, fmt.Errorf("unknown scheme %q", u.Scheme) return nil, fmt.Errorf("unknown scheme %q", u.Scheme)
} }
@ -92,6 +92,10 @@ func Parse(rawuri string) (*URI, error) {
return uri, nil return uri, nil
} }
func (u *URI) Resource() bool {
return u.Scheme == "bzz-resource"
}
func (u *URI) Raw() bool { func (u *URI) Raw() bool {
return u.Scheme == "bzz-raw" return u.Scheme == "bzz-raw"
} }

View file

@ -812,7 +812,7 @@ func TestFUSE(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ta := &testAPI{api: api.NewApi(dpa, nil)} ta := &testAPI{api: api.NewApi(dpa, nil, nil)}
dpa.Start() dpa.Start()
defer dpa.Stop() defer dpa.Stop()

View file

@ -1,27 +1,30 @@
package storage package storage
import ( import (
"context"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"math/big"
"path/filepath" "path/filepath"
"strconv"
"sync" "sync"
"time" "time"
"golang.org/x/net/idna" "golang.org/x/net/idna"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
) )
const ( const (
signatureLength = 65 signatureLength = 65
indexSize = 16 indexSize = 16
dbDirName = "resource" DbDirName = "resource"
chunkSize = 4096 // temporary until we implement DPA in the resourcehandler chunkSize = 4096 // temporary until we implement DPA in the resourcehandler
defaultStoreTimeout = 4000 * time.Millisecond defaultStoreTimeout = 4000 * time.Millisecond
hasherCount = 8
) )
type Signature [signatureLength]byte type Signature [signatureLength]byte
@ -37,6 +40,7 @@ type resource struct {
nameHash common.Hash nameHash common.Hash
startBlock uint64 startBlock uint64
lastPeriod uint32 lastPeriod uint32
lastKey Key
frequency uint64 frequency uint64
version uint32 version uint32
data []byte data []byte
@ -44,18 +48,27 @@ type resource struct {
} }
// TODO Expire content after a defined period (to force resync) // TODO Expire content after a defined period (to force resync)
func (r *resource) isSynced() bool { func (self *resource) isSynced() bool {
return !r.updated.IsZero() return !self.updated.IsZero()
}
func (self *resource) NameHash() common.Hash {
return self.nameHash
} }
// Implement to activate validation of resource updates // Implement to activate validation of resource updates
// Specifically signing data and verification of signatures // Specifically signing data and verification of signatures
type ResourceValidator interface { type ResourceValidator interface {
hashSize() int
checkAccess(string, common.Address) (bool, error) checkAccess(string, common.Address) (bool, error)
nameHash(string) common.Hash // nameHashFunc nameHash(string) common.Hash // nameHashFunc
sign(common.Hash) (Signature, error) // SignFunc sign(common.Hash) (Signature, error) // SignFunc
} }
type ethApi interface {
HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
}
// Mutable resource is an entity which allows updates to a resource // Mutable resource is an entity which allows updates to a resource
// without resorting to ENS on each update. // without resorting to ENS on each update.
// The update scheme is built on swarm chunks with chunk keys following // The update scheme is built on swarm chunks with chunk keys following
@ -113,14 +126,15 @@ type ResourceValidator interface {
// stored using a separate store, and forwarding/syncing protocols carry per-chunk // stored using a separate store, and forwarding/syncing protocols carry per-chunk
// flags to tell whether the chunk can be validated or not; if not it is to be // flags to tell whether the chunk can be validated or not; if not it is to be
// treated as a resource update chunk. // treated as a resource update chunk.
//
// TODO: Include modtime in chunk data + signature
type ResourceHandler struct { type ResourceHandler struct {
ChunkStore ChunkStore
validator ResourceValidator validator ResourceValidator
rpcClient *rpc.Client ethClient ethApi
resources map[string]*resource resources map[string]*resource
hashLock sync.Mutex hashPool sync.Pool
resourceLock sync.RWMutex resourceLock sync.RWMutex
hasher SwarmHash
nameHash nameHashFunc nameHash nameHashFunc
storeTimeout time.Duration storeTimeout time.Duration
} }
@ -128,11 +142,11 @@ type ResourceHandler struct {
// Create or open resource update chunk store // Create or open resource update chunk store
// //
// If validator is nil, signature and access validation will be deactivated // If validator is nil, signature and access validation will be deactivated
func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Client, validator ResourceValidator) (*ResourceHandler, error) { func NewResourceHandler(datadir string, cloudStore CloudStore, ethClient ethApi, validator ResourceValidator) (*ResourceHandler, error) {
hashfunc := MakeHashFunc(SHA3Hash) hashfunc := MakeHashFunc(SHA3Hash)
path := filepath.Join(datadir, dbDirName) path := filepath.Join(datadir, DbDirName)
dbStore, err := NewDbStore(datadir, hashfunc, singletonSwarmDbCapacity, 0) dbStore, err := NewDbStore(datadir, hashfunc, singletonSwarmDbCapacity, 0)
if err != nil { if err != nil {
return nil, err return nil, err
@ -144,28 +158,74 @@ func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Cl
rh := &ResourceHandler{ rh := &ResourceHandler{
ChunkStore: newResourceChunkStore(path, hashfunc, localStore, cloudStore), ChunkStore: newResourceChunkStore(path, hashfunc, localStore, cloudStore),
rpcClient: rpcClient, ethClient: ethClient,
resources: make(map[string]*resource), resources: make(map[string]*resource),
hasher: hashfunc(),
validator: validator, validator: validator,
storeTimeout: defaultStoreTimeout, storeTimeout: defaultStoreTimeout,
hashPool: sync.Pool{
New: func() interface{} {
return MakeHashFunc(SHA3Hash)()
},
},
} }
if rh.validator != nil { if rh.validator != nil {
rh.nameHash = rh.validator.nameHash rh.nameHash = rh.validator.nameHash
} else { } else {
rh.nameHash = func(name string) common.Hash { rh.nameHash = func(name string) common.Hash {
rh.hashLock.Lock() hasher := rh.hashPool.Get().(SwarmHash)
defer rh.hashLock.Unlock() defer rh.hashPool.Put(hasher)
rh.hasher.Reset() hasher.Reset()
rh.hasher.Write([]byte(name)) hasher.Write([]byte(name))
return common.BytesToHash(rh.hasher.Sum(nil)) hashval := common.BytesToHash(hasher.Sum(nil))
log.Debug("generic namehasher", "name", name, "hash", hashval)
return hashval
} }
} }
for i := 0; i < hasherCount; i++ {
hashfunc := MakeHashFunc(SHA3Hash)()
rh.hashPool.Put(hashfunc)
}
return rh, nil return rh, nil
} }
func (self *ResourceHandler) IsValidated() bool {
return self.validator == nil
}
func (self *ResourceHandler) HashSize() int {
return self.validator.hashSize()
}
// get data from current resource
func (self *ResourceHandler) GetContent(name string) (Key, []byte, error) {
rsrc := self.getResource(name)
if rsrc == nil || !rsrc.isSynced() {
return nil, nil, errors.New("Resource does not exist or is not synced")
}
return rsrc.lastKey, rsrc.data, nil
}
func (self *ResourceHandler) GetLastPeriod(name string) (uint32, error) {
rsrc := self.getResource(name)
if rsrc == nil || !rsrc.isSynced() {
return 0, errors.New("Resource does not exist or is not synced")
}
return rsrc.lastPeriod, nil
}
func (self *ResourceHandler) GetVersion(name string) (uint32, error) {
rsrc := self.getResource(name)
if rsrc == nil || !rsrc.isSynced() {
return 0, errors.New("Resource does not exist or is not synced")
}
return rsrc.version, nil
}
// \TODO should be hashsize * branches from the chosen chunker, implement with dpa // \TODO should be hashsize * branches from the chosen chunker, implement with dpa
func (self *ResourceHandler) chunkSize() int64 { func (self *ResourceHandler) chunkSize() int64 {
return chunkSize return chunkSize
@ -176,11 +236,11 @@ func (self *ResourceHandler) chunkSize() int64 {
// The signature data should match the hash of the idna-converted name by the validator's namehash function, NOT the raw name bytes. // The signature data should match the hash of the idna-converted name by the validator's namehash function, NOT the raw name bytes.
// //
// The start block of the resource update will be the actual current block height of the connected network. // The start block of the resource update will be the actual current block height of the connected network.
func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resource, error) { func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequency uint64) (*resource, error) {
// frequency 0 is invalid // frequency 0 is invalid
if frequency == 0 { if frequency == 0 {
return nil, fmt.Errorf("Frequency cannot be 0") return nil, errors.New("Frequency cannot be 0")
} }
if !isSafeName(name) { if !isSafeName(name) {
@ -207,7 +267,7 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resour
} }
// get our blockheight at this time // get our blockheight at this time
currentblock, err := self.getBlock() currentblock, err := self.GetBlock(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -245,8 +305,14 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resour
// root chunk. // root chunk.
// It is the callers responsibility to make sure that this chunk exists (if the resource // It is the callers responsibility to make sure that this chunk exists (if the resource
// update root data was retrieved externally, it typically doesn't) // update root data was retrieved externally, it typically doesn't)
func (self *ResourceHandler) LookupVersion(name string, period uint32, version uint32, refresh bool) (*resource, error) { //
rsrc, err := self.loadResource(name, refresh) //
func (self *ResourceHandler) LookupVersionByName(ctx context.Context, name string, period uint32, version uint32, refresh bool) (*resource, error) {
return self.LookupVersion(ctx, self.nameHash(name), name, period, version, refresh)
}
func (self *ResourceHandler) LookupVersion(ctx context.Context, nameHash common.Hash, name string, period uint32, version uint32, refresh bool) (*resource, error) {
rsrc, err := self.loadResource(nameHash, name, refresh)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -261,8 +327,12 @@ func (self *ResourceHandler) LookupVersion(name string, period uint32, version u
// and returned. // and returned.
// //
// See also (*ResourceHandler).LookupVersion // See also (*ResourceHandler).LookupVersion
func (self *ResourceHandler) LookupHistorical(name string, period uint32, refresh bool) (*resource, error) { func (self *ResourceHandler) LookupHistoricalByName(ctx context.Context, name string, period uint32, refresh bool) (*resource, error) {
rsrc, err := self.loadResource(name, refresh) return self.LookupHistorical(ctx, self.nameHash(name), name, period, refresh)
}
func (self *ResourceHandler) LookupHistorical(ctx context.Context, nameHash common.Hash, name string, period uint32, refresh bool) (*resource, error) {
rsrc, err := self.loadResource(nameHash, name, refresh)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -279,14 +349,18 @@ func (self *ResourceHandler) LookupHistorical(name string, period uint32, refres
// Version iteration is done as in (*ResourceHandler).LookupHistorical // Version iteration is done as in (*ResourceHandler).LookupHistorical
// //
// See also (*ResourceHandler).LookupHistorical // See also (*ResourceHandler).LookupHistorical
func (self *ResourceHandler) LookupLatest(name string, refresh bool) (*resource, error) { func (self *ResourceHandler) LookupLatestByName(ctx context.Context, name string, refresh bool) (*resource, error) {
return self.LookupLatest(ctx, self.nameHash(name), name, refresh)
}
func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.Hash, name string, refresh bool) (*resource, error) {
// get our blockheight at this time and the next block of the update period // get our blockheight at this time and the next block of the update period
rsrc, err := self.loadResource(name, refresh) rsrc, err := self.loadResource(nameHash, name, refresh)
if err != nil { if err != nil {
return nil, err return nil, err
} }
currentblock, err := self.getBlock() currentblock, err := self.GetBlock(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -298,7 +372,7 @@ func (self *ResourceHandler) LookupLatest(name string, refresh bool) (*resource,
func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint32, refresh bool) (*resource, error) { func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint32, refresh bool) (*resource, error) {
if period == 0 { if period == 0 {
return nil, fmt.Errorf("period must be >0") return nil, errors.New("period must be >0")
} }
// start from the last possible block period, and iterate previous ones until we find a match // start from the last possible block period, and iterate previous ones until we find a match
@ -334,11 +408,15 @@ func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint3
log.Trace("rsrc update not found, checking previous period", "period", period, "key", key) log.Trace("rsrc update not found, checking previous period", "period", period, "key", key)
period-- period--
} }
return nil, fmt.Errorf("no updates found") return nil, errors.New("no updates found")
} }
// load existing mutable resource into resource struct // load existing mutable resource into resource struct
func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource, error) { func (self *ResourceHandler) loadResource(nameHash common.Hash, name string, refresh bool) (*resource, error) {
if name == "" {
name = nameHash.Hex()
}
// if the resource is not known to this session we must load it // if the resource is not known to this session we must load it
// if refresh is set, we force load // if refresh is set, we force load
@ -350,7 +428,7 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource,
return nil, fmt.Errorf("Invalid name '%s'", name) return nil, fmt.Errorf("Invalid name '%s'", name)
} }
rsrc.name = &name rsrc.name = &name
rsrc.nameHash = self.nameHash(name) rsrc.nameHash = nameHash
// get the root info chunk and update the cached value // get the root info chunk and update the cached value
chunk, err := self.Get(Key(rsrc.nameHash[:])) chunk, err := self.Get(Key(rsrc.nameHash[:]))
@ -381,6 +459,7 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
if *rsrc.name != name { if *rsrc.name != name {
return nil, fmt.Errorf("Update belongs to '%s', but have '%s'", name, *rsrc.name) return nil, fmt.Errorf("Update belongs to '%s', but have '%s'", name, *rsrc.name)
} }
log.Trace("update", "name", *rsrc.name, "rootkey", rsrc.nameHash, "updatekey", chunk.Key, "period", period, "version", version)
// only check signature if validator is present // only check signature if validator is present
if self.validator != nil { if self.validator != nil {
digest := self.keyDataHash(chunk.Key, data) digest := self.keyDataHash(chunk.Key, data)
@ -391,6 +470,7 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
} }
// update our rsrcs entry map // update our rsrcs entry map
rsrc.lastKey = chunk.Key
rsrc.lastPeriod = period rsrc.lastPeriod = period
rsrc.version = version rsrc.version = version
rsrc.updated = time.Now() rsrc.updated = time.Now()
@ -406,19 +486,11 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32, uint32, string, []byte, error) { func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32, uint32, string, []byte, error) {
var err error var err error
cursor := 0 cursor := 0
var signature *Signature
// omit signatures if we have no validator
var sigoffset int
if self.validator != nil {
signature = &Signature{}
copy(signature[:], chunkdata[:signatureLength])
sigoffset = signatureLength
cursor = sigoffset
}
headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2]) headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
if int(headerlength+2) > len(chunkdata) { cursor += 2
err = fmt.Errorf("Reported header length %d longer than actual data length %d", headerlength, len(chunkdata)) datalength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
if int(headerlength+datalength+4) > len(chunkdata) {
err = fmt.Errorf("Reported headerlength %d + datalength %d longer than actual chunk data length %d", headerlength, datalength, len(chunkdata))
return nil, 0, 0, "", nil, err return nil, 0, 0, "", nil, err
} }
@ -431,12 +503,22 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
cursor += 4 cursor += 4
version = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4]) version = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4])
cursor += 4 cursor += 4
namelength := int(headerlength) - cursor + sigoffset + 2 namelength := int(headerlength) - cursor + 4
name = string(chunkdata[cursor : cursor+namelength]) name = string(chunkdata[cursor : cursor+namelength])
cursor += namelength cursor += namelength
data = make([]byte, len(chunkdata)-cursor) intdatalength := int(datalength)
copy(data, chunkdata[cursor:]) data = make([]byte, intdatalength)
return signature, period, version, name, data, err copy(data, chunkdata[cursor:cursor+intdatalength])
// omit signatures if we have no validator
var signature *Signature
if self.validator != nil {
cursor += intdatalength
signature = &Signature{}
copy(signature[:], chunkdata[cursor:cursor+signatureLength])
}
return signature, period, version, name, data, nil
} }
// Adds an actual data update // Adds an actual data update
@ -445,30 +527,30 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
// It is the caller's responsibility to make sure that this data is not stale. // It is the caller's responsibility to make sure that this data is not stale.
// //
// A resource update cannot span chunks, and thus has max length 4096 // A resource update cannot span chunks, and thus has max length 4096
func (self *ResourceHandler) Update(name string, data []byte) (Key, error) { func (self *ResourceHandler) Update(ctx context.Context, name string, data []byte) (Key, error) {
var sigoffset int var signaturelength int
if self.validator != nil { if self.validator != nil {
sigoffset = signatureLength signaturelength = signatureLength
} }
// get the cached information // get the cached information
rsrc := self.getResource(name) rsrc := self.getResource(name)
if rsrc == nil { if rsrc == nil {
return nil, fmt.Errorf("Resource object not in index") return nil, errors.New("Resource object not in index")
} }
if !rsrc.isSynced() { if !rsrc.isSynced() {
return nil, fmt.Errorf("Resource object not in sync") return nil, errors.New("Resource object not in sync")
} }
// an update can be only one chunk long // an update can be only one chunk long
datalimit := self.chunkSize() - int64(sigoffset-len(name)-8) datalimit := self.chunkSize() - int64(signaturelength-len(name)-4-4-2-2)
if int64(len(data)) > datalimit { if int64(len(data)) > datalimit {
return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), datalimit) return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), datalimit)
} }
// get our blockheight at this time and the next block of the update period // get our blockheight at this time and the next block of the update period
currentblock, err := self.getBlock() currentblock, err := self.GetBlock(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -536,17 +618,12 @@ func (self *ResourceHandler) Close() {
self.ChunkStore.Close() self.ChunkStore.Close()
} }
func (self *ResourceHandler) getBlock() (uint64, error) { func (self *ResourceHandler) GetBlock(ctx context.Context) (uint64, error) {
// get the block height and convert to uint64 blockheader, err := self.ethClient.HeaderByNumber(ctx, nil)
var currentblock string
err := self.rpcClient.Call(&currentblock, "eth_blockNumber")
if err != nil { if err != nil {
return 0, err return 0, err
} }
if currentblock == "0x0" { return blockheader.Number.Uint64(), nil
return 0, nil
}
return strconv.ParseUint(currentblock, 10, 64)
} }
// Calculate the period index (aka major version number) from a given block number // Calculate the period index (aka major version number) from a given block number
@ -575,23 +652,20 @@ func (self *ResourceHandler) setResource(name string, rsrc *resource) {
// used for chunk keys // used for chunk keys
func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehash common.Hash) Key { func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehash common.Hash) Key {
// format is: hash(period|version|namehash) // format is: hash(period|version|namehash)
self.hashLock.Lock() hasher := self.hashPool.Get().(SwarmHash)
defer self.hashLock.Unlock() defer self.hashPool.Put(hasher)
self.hasher.Reset() hasher.Reset()
b := make([]byte, 4) b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, period) binary.LittleEndian.PutUint32(b, period)
self.hasher.Write(b) hasher.Write(b)
binary.LittleEndian.PutUint32(b, version) binary.LittleEndian.PutUint32(b, version)
self.hasher.Write(b) hasher.Write(b)
self.hasher.Write(namehash[:]) hasher.Write(namehash[:])
return self.hasher.Sum(nil) return hasher.Sum(nil)
} }
func (self *ResourceHandler) hasUpdate(name string, period uint32) bool { func (self *ResourceHandler) hasUpdate(name string, period uint32) bool {
if self.resources[name].lastPeriod == period { return self.resources[name].lastPeriod == period
return true
}
return false
} }
func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Address, error) { func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Address, error) {
@ -606,27 +680,30 @@ func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Ad
func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte) *Chunk { func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte) *Chunk {
// no signatures if no validator // no signatures if no validator
var sigoffset int var signaturelength int
if signature != nil { if signature != nil {
sigoffset = signatureLength signaturelength = signatureLength
} }
// prepend version and period to allow reverse lookups // prepend version and period to allow reverse lookups
headerlength := uint16(len(name) + 4 + 4) headerlength := len(name) + 4 + 4
// also prepend datalength
datalength := len(data)
chunk := NewChunk(key, nil) chunk := NewChunk(key, nil)
chunk.SData = make([]byte, sigoffset+int(headerlength)+2+len(data)) chunk.SData = make([]byte, 4+signaturelength+headerlength+datalength) // initial 4 are uint16 length descriptors for headerlength and datalength
cursor := 0
if signature != nil {
copy(chunk.SData, (*signature)[:])
cursor += signatureLength
}
// data header length does NOT include the header length prefix bytes themselves // data header length does NOT include the header length prefix bytes themselves
binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength) cursor := 0
binary.LittleEndian.PutUint16(chunk.SData[cursor:], uint16(headerlength))
cursor += 2 cursor += 2
// data length
binary.LittleEndian.PutUint16(chunk.SData[cursor:], uint16(datalength))
cursor += 2
// header = period + version + name
binary.LittleEndian.PutUint32(chunk.SData[cursor:], period) binary.LittleEndian.PutUint32(chunk.SData[cursor:], period)
cursor += 4 cursor += 4
@ -637,8 +714,15 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32
copy(chunk.SData[cursor:], namebytes) copy(chunk.SData[cursor:], namebytes)
cursor += len(namebytes) cursor += len(namebytes)
// add the data
copy(chunk.SData[cursor:], data) copy(chunk.SData[cursor:], data)
// if signature is present it's the last item in the chunk data
if signature != nil {
cursor += datalength
copy(chunk.SData[cursor:], signature[:])
}
chunk.Size = int64(len(chunk.SData)) chunk.Size = int64(len(chunk.SData))
return chunk return chunk
} }
@ -671,15 +755,17 @@ func (r *resourceChunkStore) Get(key Key) (*Chunk, error) {
t := time.NewTimer(time.Second * 1) t := time.NewTimer(time.Second * 1)
select { select {
case <-t.C: case <-t.C:
return nil, fmt.Errorf("timeout") return nil, errors.New("timeout")
case <-chunk.C: case <-chunk.Req.C:
log.Trace("Received resource update chunk", "peer", chunk.Req.Source) log.Trace("Received resource update chunk", "peer", chunk.Req.Source)
} }
return chunk, nil return chunk, nil
} }
func (r *resourceChunkStore) Put(chunk *Chunk) { func (r *resourceChunkStore) Put(chunk *Chunk) {
chunk.wg = &sync.WaitGroup{}
r.netStore.Put(chunk) r.netStore.Put(chunk)
chunk.wg.Wait()
} }
func (r *resourceChunkStore) Close() { func (r *resourceChunkStore) Close() {
@ -694,11 +780,7 @@ func getNextPeriod(start uint64, current uint64, frequency uint64) uint32 {
} }
func ToSafeName(name string) (string, error) { func ToSafeName(name string) (string, error) {
validname, err := idna.ToASCII(name) return idna.ToASCII(name)
if err != nil {
return "", err
}
return validname, nil
} }
// check that name identifiers contain valid bytes // check that name identifiers contain valid bytes
@ -710,18 +792,15 @@ func isSafeName(name string) bool {
if err != nil { if err != nil {
return false return false
} }
if validname != name { return validname == name
return false
}
return true
} }
// convenience for creating signature hashes of update data // convenience for creating signature hashes of update data
func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash { func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
self.hashLock.Lock() hasher := self.hashPool.Get().(SwarmHash)
defer self.hashLock.Unlock() defer self.hashPool.Put(hasher)
self.hasher.Reset() hasher.Reset()
self.hasher.Write(key[:]) hasher.Write(key[:])
self.hasher.Write(data) hasher.Write(data)
return common.BytesToHash(self.hasher.Sum(nil)) return common.BytesToHash(hasher.Sum(nil))
} }

View file

@ -1,7 +1,7 @@
package storage package storage
import ( import (
"fmt" "errors"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -10,15 +10,20 @@ import (
type baseValidator struct { type baseValidator struct {
signFunc SignFunc signFunc SignFunc
hashsize int
} }
func (b *baseValidator) sign(datahash common.Hash) (signature Signature, err error) { func (b *baseValidator) sign(datahash common.Hash) (signature Signature, err error) {
if b.signFunc == nil { if b.signFunc == nil {
return signature, fmt.Errorf("No signature function") return signature, errors.New("No signature function")
} }
return b.signFunc(datahash) return b.signFunc(datahash)
} }
func (b *baseValidator) hashSize() int {
return b.hashsize
}
// ENS validation of mutable resource owners // ENS validation of mutable resource owners
type ENSValidator struct { type ENSValidator struct {
*baseValidator *baseValidator
@ -30,6 +35,7 @@ func NewENSValidator(contractaddress common.Address, backend bind.ContractBacken
validator := &ENSValidator{ validator := &ENSValidator{
baseValidator: &baseValidator{ baseValidator: &baseValidator{
signFunc: signFunc, signFunc: signFunc,
hashsize: common.HashLength,
}, },
} }
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend) validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)

View file

@ -0,0 +1,20 @@
package storage
import (
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// matches the SignFunc type
func NewGenericResourceSigner(privKey *ecdsa.PrivateKey) SignFunc {
return func(data common.Hash) (signature Signature, err error) {
signaturebytes, err := crypto.Sign(data.Bytes(), privKey)
if err != nil {
return
}
copy(signature[:], signaturebytes)
return
}
}

View file

@ -2,15 +2,15 @@ package storage
import ( import (
"bytes" "bytes"
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
"path/filepath"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -21,9 +21,9 @@ import (
"github.com/ethereum/go-ethereum/contracts/ens" "github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/contracts/ens/contract" "github.com/ethereum/go-ethereum/contracts/ens/contract"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
) )
var ( var (
@ -38,7 +38,11 @@ var (
func init() { func init() {
var err error var err error
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) verbose := flag.Bool("v", false, "verbose")
flag.Parse()
if *verbose {
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
}
safeName, err = ToSafeName(domainName) safeName, err = ToSafeName(domainName)
if err != nil { if err != nil {
panic(err) panic(err)
@ -49,7 +53,7 @@ func init() {
// so we use this wrapper to fake returning the block count // so we use this wrapper to fake returning the block count
type fakeBackend struct { type fakeBackend struct {
*backends.SimulatedBackend *backends.SimulatedBackend
blocknumber uint64 blocknumber int64
} }
func (f *fakeBackend) Commit() { func (f *fakeBackend) Commit() {
@ -59,13 +63,12 @@ func (f *fakeBackend) Commit() {
f.blocknumber++ f.blocknumber++
} }
// for faking the rpc service, since we don't need the whole node stack func (f *fakeBackend) HeaderByNumber(context context.Context, bigblock *big.Int) (*types.Header, error) {
type FakeRPC struct { f.blocknumber++
backend *fakeBackend biggie := big.NewInt(f.blocknumber)
} return &types.Header{
Number: biggie,
func (r *FakeRPC) BlockNumber() (string, error) { }, nil
return strconv.FormatUint(r.backend.blocknumber, 10), nil
} }
// check that signature address matches update signer address // check that signature address matches update signer address
@ -83,8 +86,9 @@ func TestResourceReverse(t *testing.T) {
// set up rpc and create resourcehandler // set up rpc and create resourcehandler
rh, _, _, teardownTest, err := setupTest(nil, newTestValidator(signer.signContent)) rh, _, _, teardownTest, err := setupTest(nil, newTestValidator(signer.signContent))
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
defer teardownTest()
// generate a hash for block 4200 version 1 // generate a hash for block 4200 version 1
key := rh.resourceHash(period, version, rh.nameHash(safeName)) key := rh.resourceHash(period, version, rh.nameHash(safeName))
@ -93,48 +97,50 @@ func TestResourceReverse(t *testing.T) {
data := make([]byte, 8) data := make([]byte, 8)
_, err = rand.Read(data) _, err = rand.Read(data)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
testHasher.Reset() testHasher.Reset()
testHasher.Write(data) testHasher.Write(data)
digest := rh.keyDataHash(key, data) digest := rh.keyDataHash(key, data)
sig, err := rh.validator.sign(digest) sig, err := rh.validator.sign(digest)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
chunk := newUpdateChunk(key, &sig, period, version, safeName, data) chunk := newUpdateChunk(key, &sig, period, version, safeName, data)
// check that we can recover the owner account from the update chunk's signature // check that we can recover the owner account from the update chunk's signature
checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData) checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData)
if err != nil {
t.Fatal(err)
}
checkdigest := rh.keyDataHash(chunk.Key, checkdata) checkdigest := rh.keyDataHash(chunk.Key, checkdata)
recoveredaddress, err := getAddressFromDataSig(checkdigest, *checksig) recoveredaddress, err := getAddressFromDataSig(checkdigest, *checksig)
if err != nil { if err != nil {
teardownTest(t, fmt.Errorf("Retrieve address from signature fail: %v", err)) t.Fatalf("Retrieve address from signature fail: %v", err)
} }
originaladdress := crypto.PubkeyToAddress(signer.privKey.PublicKey) originaladdress := crypto.PubkeyToAddress(signer.privKey.PublicKey)
// check that the metadata retrieved from the chunk matches what we gave it // check that the metadata retrieved from the chunk matches what we gave it
if recoveredaddress != originaladdress { if recoveredaddress != originaladdress {
teardownTest(t, fmt.Errorf("addresses dont match: %x != %x", originaladdress, recoveredaddress)) t.Fatalf("addresses dont match: %x != %x", originaladdress, recoveredaddress)
} }
if !bytes.Equal(key[:], chunk.Key[:]) { if !bytes.Equal(key[:], chunk.Key[:]) {
teardownTest(t, fmt.Errorf("Expected chunk key '%x', was '%x'", key, chunk.Key)) t.Fatalf("Expected chunk key '%x', was '%x'", key, chunk.Key)
} }
if period != checkperiod { if period != checkperiod {
teardownTest(t, fmt.Errorf("Expected period '%d', was '%d'", period, checkperiod)) t.Fatalf("Expected period '%d', was '%d'", period, checkperiod)
} }
if version != checkversion { if version != checkversion {
teardownTest(t, fmt.Errorf("Expected version '%d', was '%d'", version, checkversion)) t.Fatalf("Expected version '%d', was '%d'", version, checkversion)
} }
if safeName != checkname { if safeName != checkname {
teardownTest(t, fmt.Errorf("Expected name '%s', was '%s'", safeName, checkname)) t.Fatalf("Expected name '%s', was '%s'", safeName, checkname)
} }
if !bytes.Equal(data, checkdata) { if !bytes.Equal(data, checkdata) {
teardownTest(t, fmt.Errorf("Expectedn data '%x', was '%x'", data, checkdata)) t.Fatalf("Expectedn data '%x', was '%x'", data, checkdata)
} }
teardownTest(t, nil)
} }
// make updates and retrieve them based on periods and versions // make updates and retrieve them based on periods and versions
@ -142,67 +148,70 @@ func TestResourceHandler(t *testing.T) {
// make fake backend, set up rpc and create resourcehandler // make fake backend, set up rpc and create resourcehandler
backend := &fakeBackend{ backend := &fakeBackend{
blocknumber: startBlock, blocknumber: int64(startBlock),
} }
rh, datadir, _, teardownTest, err := setupTest(backend, nil) rh, datadir, _, teardownTest, err := setupTest(backend, nil)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
defer teardownTest()
// create a new resource // create a new resource
_, err = rh.NewResource(safeName, resourceFrequency) ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err = rh.NewResource(ctx, safeName, resourceFrequency)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
// check that the new resource is stored correctly // check that the new resource is stored correctly
namehash := rh.nameHash(safeName) namehash := rh.nameHash(safeName)
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:])) chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:]))
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} else if len(chunk.SData) < 16 { } else if len(chunk.SData) < 16 {
teardownTest(t, fmt.Errorf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData))) t.Fatalf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData))
} }
startblocknumber := binary.LittleEndian.Uint64(chunk.SData[:8]) startblocknumber := binary.LittleEndian.Uint64(chunk.SData[:8])
chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[8:]) chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[8:])
if startblocknumber != backend.blocknumber { if startblocknumber != uint64(backend.blocknumber) {
teardownTest(t, fmt.Errorf("stored block number %d does not match provided block number %d", startblocknumber, backend.blocknumber)) t.Fatalf("stored block number %d does not match provided block number %d", startblocknumber, backend.blocknumber)
} }
if chunkfrequency != resourceFrequency { if chunkfrequency != resourceFrequency {
teardownTest(t, fmt.Errorf("stored frequency %d does not match provided frequency %d", chunkfrequency, resourceFrequency)) t.Fatalf("stored frequency %d does not match provided frequency %d", chunkfrequency, resourceFrequency)
} }
// update halfway to first period // update halfway to first period
resourcekey := make(map[string]Key) resourcekey := make(map[string]Key)
fwdBlocks(int(resourceFrequency/2), backend) fwdBlocks(int(resourceFrequency/2), backend)
data := []byte("blinky") data := []byte("blinky")
resourcekey["blinky"], err = rh.Update(safeName, data) resourcekey["blinky"], err = rh.Update(ctx, safeName, data)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
// update on first period // update on first period
fwdBlocks(int(resourceFrequency/2), backend) fwdBlocks(int(resourceFrequency/2), backend)
data = []byte("pinky") data = []byte("pinky")
resourcekey["pinky"], err = rh.Update(safeName, data) resourcekey["pinky"], err = rh.Update(ctx, safeName, data)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
// update on second period // update on second period
fwdBlocks(int(resourceFrequency), backend) fwdBlocks(int(resourceFrequency), backend)
data = []byte("inky") data = []byte("inky")
resourcekey["inky"], err = rh.Update(safeName, data) resourcekey["inky"], err = rh.Update(ctx, safeName, data)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
// update just after second period // update just after second period
fwdBlocks(1, backend) fwdBlocks(1, backend)
data = []byte("clyde") data = []byte("clyde")
resourcekey["clyde"], err = rh.Update(safeName, data) resourcekey["clyde"], err = rh.Update(ctx, safeName, data)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
time.Sleep(time.Second) time.Sleep(time.Second)
rh.Close() rh.Close()
@ -211,46 +220,45 @@ func TestResourceHandler(t *testing.T) {
// it will match on second iteration startblocknumber + (resourceFrequency * 3) // it will match on second iteration startblocknumber + (resourceFrequency * 3)
fwdBlocks(int(resourceFrequency*2)-1, backend) fwdBlocks(int(resourceFrequency*2)-1, backend)
rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.rpcClient, nil) rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.ethClient, nil)
_, err = rh2.LookupLatest(safeName, true) _, err = rh2.LookupLatestByName(ctx, safeName, true)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
// last update should be "clyde", version two, blockheight startblocknumber + (resourcefrequency * 3) // last update should be "clyde", version two, blockheight startblocknumber + (resourcefrequency * 3)
if !bytes.Equal(rh2.resources[safeName].data, []byte("clyde")) { if !bytes.Equal(rh2.resources[safeName].data, []byte("clyde")) {
teardownTest(t, fmt.Errorf("resource data was %v, expected %v", rh2.resources[safeName].data, []byte("clyde"))) t.Fatalf("resource data was %v, expected %v", rh2.resources[safeName].data, []byte("clyde"))
} }
if rh2.resources[safeName].version != 2 { if rh2.resources[safeName].version != 2 {
teardownTest(t, fmt.Errorf("resource version was %d, expected 2", rh2.resources[safeName].version)) t.Fatalf("resource version was %d, expected 2", rh2.resources[safeName].version)
} }
if rh2.resources[safeName].lastPeriod != 3 { if rh2.resources[safeName].lastPeriod != 3 {
teardownTest(t, fmt.Errorf("resource period was %d, expected 3", rh2.resources[safeName].lastPeriod)) t.Fatalf("resource period was %d, expected 3", rh2.resources[safeName].lastPeriod)
} }
log.Debug("Latest lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) log.Debug("Latest lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
// specific block, latest version // specific block, latest version
rsrc, err := rh2.LookupHistorical(safeName, 3, true) rsrc, err := rh2.LookupHistoricalByName(ctx, safeName, 3, true)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
// check data // check data
if !bytes.Equal(rsrc.data, []byte("clyde")) { if !bytes.Equal(rsrc.data, []byte("clyde")) {
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("clyde"))) t.Fatalf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("clyde"))
} }
log.Debug("Historical lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) log.Debug("Historical lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
// specific block, specific version // specific block, specific version
rsrc, err = rh2.LookupVersion(safeName, 3, 1, true) rsrc, err = rh2.LookupVersionByName(ctx, safeName, 3, 1, true)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
// check data // check data
if !bytes.Equal(rsrc.data, []byte("inky")) { if !bytes.Equal(rsrc.data, []byte("inky")) {
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("inky"))) t.Fatalf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("inky"))
} }
log.Debug("Specific version lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) log.Debug("Specific version lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
teardownTest(t, nil)
} }
@ -282,34 +290,35 @@ func TestResourceENSOwner(t *testing.T) {
// set up rpc and create resourcehandler with ENS sim backend // set up rpc and create resourcehandler with ENS sim backend
rh, _, _, teardownTest, err := setupTest(contractbackend, validator) rh, _, _, teardownTest, err := setupTest(contractbackend, validator)
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
defer teardownTest()
// create new resource when we are owner = ok // create new resource when we are owner = ok
_, err = rh.NewResource(safeName, resourceFrequency) ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err = rh.NewResource(ctx, safeName, resourceFrequency)
if err != nil { if err != nil {
teardownTest(t, fmt.Errorf("Create resource fail: %v", err)) t.Fatalf("Create resource fail: %v", err)
} }
data := []byte("foo") data := []byte("foo")
// update resource when we are owner = ok // update resource when we are owner = ok
_, err = rh.Update(safeName, data) _, err = rh.Update(ctx, safeName, data)
if err != nil { if err != nil {
teardownTest(t, fmt.Errorf("Update resource fail: %v", err)) t.Fatalf("Update resource fail: %v", err)
} }
// update resource when we are owner = ok // update resource when we are owner = ok
signertwo, err := newTestSigner() signertwo, err := newTestSigner()
if err != nil { if err != nil {
teardownTest(t, err) t.Fatal(err)
} }
rh.validator.(*ENSValidator).signFunc = signertwo.signContent rh.validator.(*ENSValidator).signFunc = signertwo.signContent
_, err = rh.Update(safeName, data) _, err = rh.Update(ctx, safeName, data)
if err == nil { if err == nil {
teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch")) t.Fatalf("Expected resource update fail due to owner mismatch")
} }
teardownTest(t, nil)
} }
// fast-forward blockheight // fast-forward blockheight
@ -320,7 +329,7 @@ func fwdBlocks(count int, backend *fakeBackend) {
} }
// create rpc and resourcehandler // create rpc and resourcehandler
func setupTest(contractbackend bind.ContractBackend, validator ResourceValidator) (rh *ResourceHandler, datadir string, signer *testSigner, teardown func(*testing.T, error), err error) { func setupTest(backend ethApi, validator ResourceValidator) (rh *ResourceHandler, datadir string, signer *testSigner, teardown func(), err error) {
var fsClean func() var fsClean func()
var rpcClean func() var rpcClean func()
@ -336,53 +345,18 @@ func setupTest(contractbackend bind.ContractBackend, validator ResourceValidator
// temp datadir // temp datadir
datadir, err = ioutil.TempDir("", "rh") datadir, err = ioutil.TempDir("", "rh")
if err != nil { if err != nil {
return return nil, "", nil, nil, err
} }
fsClean = func() { fsClean = func() {
os.RemoveAll(datadir) os.RemoveAll(datadir)
} }
// starting the whole stack just to get blocknumbers is too cumbersome rh, err = NewResourceHandler(datadir, &testCloudStore{}, backend, validator)
// so we fake the rpc server to get blocknumbers for testing return rh, datadir, signer, cleanF, nil
ipcpath := filepath.Join(datadir, "test.ipc")
ipcl, err := rpc.CreateIPCListener(ipcpath)
if err != nil {
return
}
rpcserver := rpc.NewServer()
var fake *fakeBackend
if contractbackend != nil {
fake = contractbackend.(*fakeBackend)
}
rpcserver.RegisterName("eth", &FakeRPC{
backend: fake,
})
go func() {
rpcserver.ServeListener(ipcl)
}()
rpcClean = func() {
rpcserver.Stop()
}
// connect to fake rpc
rpcclient, err := rpc.Dial(ipcpath)
if err != nil {
return
}
rh, err = NewResourceHandler(datadir, &testCloudStore{}, rpcclient, validator)
teardown = func(t *testing.T, err error) {
cleanF()
if err != nil {
t.Fatal(err)
}
}
return
} }
// Set up simulated ENS backend for use with ENSResourceHandler tests // Set up simulated ENS backend for use with ENSResourceHandler tests
func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string, top string) (common.Address, bind.ContractBackend, error) { func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string, top string) (common.Address, *fakeBackend, error) {
// create the domain hash values to pass to the ENS contract methods // create the domain hash values to pass to the ENS contract methods
var tophash [32]byte var tophash [32]byte
@ -426,8 +400,9 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string,
// implementation of an external signer to pass to validator // implementation of an external signer to pass to validator
type testSigner struct { type testSigner struct {
privKey *ecdsa.PrivateKey privKey *ecdsa.PrivateKey
hasher SwarmHash hasher SwarmHash
signContent SignFunc
} }
func newTestSigner() (*testSigner, error) { func newTestSigner() (*testSigner, error) {
@ -436,21 +411,12 @@ func newTestSigner() (*testSigner, error) {
return nil, err return nil, err
} }
return &testSigner{ return &testSigner{
privKey: privKey, privKey: privKey,
hasher: testHasher, hasher: testHasher,
signContent: NewGenericResourceSigner(privKey),
}, nil }, nil
} }
// matches the SignFunc type
func (self *testSigner) signContent(data common.Hash) (signature Signature, err error) {
signaturebytes, err := crypto.Sign(data.Bytes(), self.privKey)
if err != nil {
return
}
copy(signature[:], signaturebytes)
return
}
type testCloudStore struct { type testCloudStore struct {
} }

View file

@ -22,6 +22,8 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"net" "net"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -53,15 +55,13 @@ type Swarm struct {
storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends
dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support
//depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage //depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage
cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud) cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud)
bzz *network.Bzz // the logistic manager bzz *network.Bzz // the logistic manager
backend chequebook.Backend // simple blockchain Backend backend chequebook.Backend // simple blockchain Backend
privateKey *ecdsa.PrivateKey privateKey *ecdsa.PrivateKey
corsString string lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped
swapEnabled bool sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit
lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped ps *pss.Pss
sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit
ps *pss.Pss
} }
type SwarmAPI struct { type SwarmAPI struct {
@ -82,7 +82,8 @@ func (self *Swarm) API() *SwarmAPI {
// implements node.Service // implements node.Service
// If mockStore is not nil, it will be used as the storage for chunk data. // If mockStore is not nil, it will be used as the storage for chunk data.
// MockStore should be used only for testing. // MockStore should be used only for testing.
func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *ethclient.Client, config *api.Config, swapEnabled, syncEnabled bool, cors string, pssEnabled bool, mockStore *mock.NodeStore) (self *Swarm, err error) { func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *ethclient.Client, config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err error) {
if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) { if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) {
return nil, fmt.Errorf("empty public key") return nil, fmt.Errorf("empty public key")
} }
@ -91,11 +92,9 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *e
} }
self = &Swarm{ self = &Swarm{
config: config, config: config,
swapEnabled: swapEnabled, backend: backend,
backend: backend, privateKey: config.ShiftPrivateKey(),
privateKey: config.Swap.PrivateKey(),
corsString: cors,
} }
log.Debug(fmt.Sprintf("Setting up Swarm service components")) log.Debug(fmt.Sprintf("Setting up Swarm service components"))
@ -137,7 +136,7 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *e
log.Debug(fmt.Sprintf("-> Content Store API")) log.Debug(fmt.Sprintf("-> Content Store API"))
// Pss = postal service over swarm (devp2p over bzz) // Pss = postal service over swarm (devp2p over bzz)
if pssEnabled { if self.config.PssEnabled {
pssparams := pss.NewPssParams(self.privateKey) pssparams := pss.NewPssParams(self.privateKey)
self.ps = pss.NewPss(to, self.dpa, pssparams) self.ps = pss.NewPss(to, self.dpa, pssparams)
if pss.IsActiveHandshake { if pss.IsActiveHandshake {
@ -158,7 +157,23 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, ensClient *e
} }
log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar @ address %v", config.EnsRoot.Hex())) log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar @ address %v", config.EnsRoot.Hex()))
self.api = api.NewApi(self.dpa, self.dns) var resourceHandler *storage.ResourceHandler
// if use resource updates
if self.config.ResourceEnabled {
var resourceValidator storage.ResourceValidator
if self.dns != nil {
resourceValidator, err = storage.NewENSValidator(config.EnsRoot, ensClient, transactOpts, storage.NewGenericResourceSigner(self.privateKey))
if err != nil {
return nil, err
}
}
resourceHandler, err = storage.NewResourceHandler(filepath.Join(self.config.Path, storage.DbDirName), self.cloud, ensClient, resourceValidator)
if err != nil {
return nil, err
}
}
self.api = api.NewApi(self.dpa, self.dns, resourceHandler)
// Manifests for Smart Hosting // Manifests for Smart Hosting
log.Debug(fmt.Sprintf("-> Web3 virtual server API")) log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
@ -186,7 +201,7 @@ func (self *Swarm) Start(srv *p2p.Server) error {
log.Warn("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%x", newaddr.UAddr)) log.Warn("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%x", newaddr.UAddr))
// set chequebook // set chequebook
if self.swapEnabled { if self.config.SwapEnabled {
ctx := context.Background() // The initial setup has no deadline. ctx := context.Background() // The initial setup has no deadline.
err := self.SetChequebook(ctx) err := self.SetChequebook(ctx)
if err != nil { if err != nil {
@ -219,14 +234,14 @@ func (self *Swarm) Start(srv *p2p.Server) error {
addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port) addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port)
go httpapi.StartHttpServer(self.api, &httpapi.ServerConfig{ go httpapi.StartHttpServer(self.api, &httpapi.ServerConfig{
Addr: addr, Addr: addr,
CorsString: self.corsString, CorsString: self.config.Cors,
}) })
} }
log.Debug(fmt.Sprintf("Swarm http proxy started on port: %v", self.config.Port)) log.Debug(fmt.Sprintf("Swarm http proxy started on port: %v", self.config.Port))
if self.corsString != "" { if self.config.Cors != "" {
log.Debug(fmt.Sprintf("Swarm http proxy started with corsdomain: %v", self.corsString)) log.Debug(fmt.Sprintf("Swarm http proxy started with corsdomain: %v", self.config.Cors))
} }
return nil return nil
@ -360,7 +375,7 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) {
} }
self = &Swarm{ self = &Swarm{
api: api.NewApi(dpa, nil), api: api.NewApi(dpa, nil, nil),
config: config, config: config,
} }

View file

@ -17,16 +17,31 @@
package testutil package testutil
import ( import (
"context"
"io/ioutil" "io/ioutil"
"math/big"
"net/http/httptest" "net/http/httptest"
"os" "os"
"testing" "testing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/swarm/api"
httpapi "github.com/ethereum/go-ethereum/swarm/api/http" httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
type fakeBackend struct {
blocknumber int64
}
func (f *fakeBackend) HeaderByNumber(context context.Context, bigblock *big.Int) (*types.Header, error) {
f.blocknumber++
biggie := big.NewInt(f.blocknumber)
return &types.Header{
Number: biggie,
}, nil
}
func NewTestSwarmServer(t *testing.T) *TestSwarmServer { func NewTestSwarmServer(t *testing.T) *TestSwarmServer {
dir, err := ioutil.TempDir("", "swarm-storage-test") dir, err := ioutil.TempDir("", "swarm-storage-test")
if err != nil { if err != nil {
@ -38,7 +53,7 @@ func NewTestSwarmServer(t *testing.T) *TestSwarmServer {
CacheCapacity: 5000, CacheCapacity: 5000,
Radius: 0, Radius: 0,
} }
localStore, err := storage.NewLocalStore(storage.MakeHashFunc("SHA3"), storeparams, nil) localStore, err := storage.NewLocalStore(storage.MakeHashFunc(storage.SHA3Hash), storeparams, nil)
if err != nil { if err != nil {
os.RemoveAll(dir) os.RemoveAll(dir)
t.Fatal(err) t.Fatal(err)
@ -49,24 +64,55 @@ func NewTestSwarmServer(t *testing.T) *TestSwarmServer {
ChunkStore: localStore, ChunkStore: localStore,
} }
dpa.Start() dpa.Start()
a := api.NewApi(dpa, nil)
// mutable resources test setup
resourceDir, err := ioutil.TempDir("", "swarm-resource-test")
if err != nil {
t.Fatal(err)
}
rh, err := storage.NewResourceHandler(resourceDir, &testCloudStore{}, &fakeBackend{}, nil)
if err != nil {
t.Fatal(err)
}
a := api.NewApi(dpa, nil, rh)
srv := httptest.NewServer(httpapi.NewServer(a)) srv := httptest.NewServer(httpapi.NewServer(a))
return &TestSwarmServer{ return &TestSwarmServer{
Server: srv, Server: srv,
Dpa: dpa, Dpa: dpa,
dir: dir, dir: dir,
Hasher: storage.MakeHashFunc(storage.SHA3Hash)(),
cleanup: func() {
srv.Close()
rh.Close()
dpa.Stop()
os.RemoveAll(dir)
os.RemoveAll(resourceDir)
},
} }
} }
type TestSwarmServer struct { type TestSwarmServer struct {
*httptest.Server *httptest.Server
Hasher storage.SwarmHash
Dpa *storage.DPA Dpa *storage.DPA
dir string dir string
cleanup func()
} }
func (t *TestSwarmServer) Close() { func (t *TestSwarmServer) Close() {
t.Server.Close() t.cleanup()
t.Dpa.Stop() }
os.RemoveAll(t.dir)
type testCloudStore struct {
}
func (c *testCloudStore) Store(*storage.Chunk) {
}
func (c *testCloudStore) Deliver(*storage.Chunk) {
}
func (c *testCloudStore) Retrieve(*storage.Chunk) {
} }