From 52e529e04bb9d9f32ceb3a20d4c6594dd4c71b3a Mon Sep 17 00:00:00 2001 From: lash Date: Wed, 17 Jan 2018 03:54:14 +0100 Subject: [PATCH] swarm/storage: Change validator, subclassing -> member --- swarm/storage/resource.go | 83 +++++++++++++++++++-------------- swarm/storage/resource_ens.go | 84 +++++++++++++--------------------- swarm/storage/resource_test.go | 64 +++++++++++++------------- 3 files changed, 112 insertions(+), 119 deletions(-) diff --git a/swarm/storage/resource.go b/swarm/storage/resource.go index 5057a03019..7745d75fbc 100644 --- a/swarm/storage/resource.go +++ b/swarm/storage/resource.go @@ -91,15 +91,15 @@ type resource struct { // 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 // treated as a resource update chunk. -type ResourceHandler interface { - ChunkStore - NewResource(name string, frequency uint64) (*resource, error) - Update(name string, data []byte) (Key, error) - resourceHash(namehash common.Hash, period uint32, version uint32) Key + +type ResourceValidator interface { + isOwner(string) (bool, error) + nameHash(string) common.Hash } -type RawResourceHandler struct { +type ResourceHandler struct { ChunkStore + validator ResourceValidator rpcClient *rpc.Client resources map[string]*resource hashLock sync.Mutex @@ -107,11 +107,10 @@ type RawResourceHandler struct { hasher SwarmHash privKey *ecdsa.PrivateKey maxChunkData int64 - nameHashFunc func(string) common.Hash } // Create or open resource update chunk store -func NewRawResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore CloudStore, rpcClient *rpc.Client, nameHashFunc func(string) common.Hash) (*RawResourceHandler, error) { +func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore CloudStore, rpcClient *rpc.Client, validator ResourceValidator) (*ResourceHandler, error) { path := filepath.Join(datadir, "resource") dbStore, err := NewDbStore(datadir, nil, singletonSwarmDbCapacity, 0) if err != nil { @@ -123,7 +122,7 @@ func NewRawResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore } hasher := MakeHashFunc("SHA3") - rh := &RawResourceHandler{ + rh := &ResourceHandler{ ChunkStore: newResourceChunkStore(path, hasher, localStore, cloudStore), rpcClient: rpcClient, resources: make(map[string]*resource), @@ -132,14 +131,16 @@ func NewRawResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore maxChunkData: DefaultBranches * int64(hasher().Size()), } - if nameHashFunc == nil { - rh.nameHashFunc = func(name string) common.Hash { + if validator != nil { + rh.validator = validator + } else { + rh.validator = NewGenericValidator(func(name string) common.Hash { rh.hashLock.Lock() defer rh.hashLock.Unlock() rh.hasher.Reset() rh.hasher.Write([]byte(name)) return common.BytesToHash(rh.hasher.Sum(nil)) - } + }) } return rh, nil @@ -186,14 +187,21 @@ func NewResource(name string, startBlock uint64, frequency uint64, nameHashFunc // Creates a new root entry for a mutable resource identified by `name` with the specified `frequency`. // // The start block of the resource update will be the actual current block height of the connected network. -func (self *RawResourceHandler) NewResource(name string, frequency uint64) (*resource, error) { +func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resource, error) { + + ok, err := self.validator.isOwner(name) + if err != nil { + return nil, err + } else if !ok { + return nil, fmt.Errorf("Not owner of '%s'", name) + } validname, err := validateInput(name, frequency) if err != nil { return nil, err } - nameHash := self.nameHashFunc(validname) + nameHash := self.validator.nameHash(validname) // get our blockheight at this time currentblock, err := self.getBlock() @@ -236,7 +244,7 @@ func (self *RawResourceHandler) NewResource(name string, frequency uint64) (*res // // Method will fail if resource is already registered in this session, unless // `allowOverwrite` is set -func (self *RawResourceHandler) SetExternalResource(rsrc *resource, allowOverwrite bool) error { +func (self *ResourceHandler) SetExternalResource(rsrc *resource, allowOverwrite bool) error { utfname, err := idna.ToUnicode(rsrc.name) if err != nil { @@ -273,7 +281,7 @@ func (self *RawResourceHandler) SetExternalResource(rsrc *resource, allowOverwri // root chunk. // 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) -func (self *RawResourceHandler) LookupVersion(name string, period uint32, version uint32, refresh bool) (*resource, error) { +func (self *ResourceHandler) LookupVersion(name string, period uint32, version uint32, refresh bool) (*resource, error) { rsrc, err := self.loadResource(name, refresh) if err != nil { return nil, err @@ -289,7 +297,7 @@ func (self *RawResourceHandler) LookupVersion(name string, period uint32, versio // and returned. // // See also (*ResourceHandler).LookupVersion -func (self *RawResourceHandler) LookupHistorical(name string, period uint32, refresh bool) (*resource, error) { +func (self *ResourceHandler) LookupHistorical(name string, period uint32, refresh bool) (*resource, error) { rsrc, err := self.loadResource(name, refresh) if err != nil { return nil, err @@ -307,7 +315,7 @@ func (self *RawResourceHandler) LookupHistorical(name string, period uint32, ref // Version iteration is done as in (*ResourceHandler).LookupHistorical // // See also (*ResourceHandler).LookupHistorical -func (self *RawResourceHandler) LookupLatest(name string, refresh bool) (*resource, error) { +func (self *ResourceHandler) LookupLatest(name string, refresh bool) (*resource, error) { // get our blockheight at this time and the next block of the update period rsrc, err := self.loadResource(name, refresh) @@ -323,7 +331,7 @@ func (self *RawResourceHandler) LookupLatest(name string, refresh bool) (*resour } // base code for public lookup methods -func (self *RawResourceHandler) lookup(rsrc *resource, name string, period uint32, version uint32, refresh bool) (*resource, error) { +func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32, version uint32, refresh bool) (*resource, error) { if period == 0 { return nil, fmt.Errorf("period must be >0") @@ -366,7 +374,7 @@ func (self *RawResourceHandler) lookup(rsrc *resource, name string, period uint3 } // load existing mutable resource into resource struct -func (self *RawResourceHandler) loadResource(name string, refresh bool) (*resource, error) { +func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource, error) { // if the resource is not known to this session we must load it // if refresh is set, we force load @@ -379,7 +387,7 @@ func (self *RawResourceHandler) loadResource(name string, refresh bool) (*resour return nil, err } rsrc.name = validname - rsrc.nameHash = self.nameHashFunc(validname) + rsrc.nameHash = self.validator.nameHash(validname) // get the root info chunk and update the cached value chunk, err := self.Get(Key(rsrc.nameHash[:])) @@ -409,7 +417,7 @@ func (self *RawResourceHandler) loadResource(name string, refresh bool) (*resour } // update mutable resource index map with specified content -func (self *RawResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, indexname *string) (*resource, error) { +func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, indexname *string) (*resource, error) { // rsrc update data chunks are total hacks // and have no size prefix :D @@ -455,7 +463,14 @@ func parseUpdate(blob []byte) (period uint32, version uint32, ensname []byte, da // 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 -func (self *RawResourceHandler) Update(name string, data []byte) (Key, error) { +func (self *ResourceHandler) Update(name string, data []byte) (Key, error) { + + ok, err := self.validator.isOwner(name) + if err != nil { + return nil, err + } else if !ok { + return nil, fmt.Errorf("Not owner of '%s'", name) + } // can be only one chunk long minus 65 byte signature if int64(len(data)) > self.maxChunkData { @@ -527,11 +542,11 @@ func (self *RawResourceHandler) Update(name string, data []byte) (Key, error) { // Closes the datastore. // Always call this at shutdown to avoid data corruption. -func (self *RawResourceHandler) Close() { +func (self *ResourceHandler) Close() { self.ChunkStore.Close() } -func (self *RawResourceHandler) getBlock() (uint64, error) { +func (self *ResourceHandler) getBlock() (uint64, error) { // get the block height and convert to uint64 var currentblock string err := self.rpcClient.Call(¤tblock, "eth_blockNumber") @@ -544,28 +559,28 @@ func (self *RawResourceHandler) getBlock() (uint64, error) { return strconv.ParseUint(currentblock, 10, 64) } -func (self *RawResourceHandler) BlockToPeriod(name string, blocknumber uint64) uint32 { +func (self *ResourceHandler) BlockToPeriod(name string, blocknumber uint64) uint32 { return getNextPeriod(self.resources[name].startBlock, blocknumber, self.resources[name].frequency) } -func (self *RawResourceHandler) PeriodToBlock(name string, period uint32) uint64 { +func (self *ResourceHandler) PeriodToBlock(name string, period uint32) uint64 { return self.resources[name].startBlock + (uint64(period) * self.resources[name].frequency) } -func (self *RawResourceHandler) getResource(name string) *resource { +func (self *ResourceHandler) getResource(name string) *resource { self.resourceLock.RLock() defer self.resourceLock.RUnlock() rsrc := self.resources[name] return rsrc } -func (self *RawResourceHandler) setResource(name string, rsrc *resource) { +func (self *ResourceHandler) setResource(name string, rsrc *resource) { self.resourceLock.Lock() defer self.resourceLock.Unlock() self.resources[name] = rsrc } -func (self *RawResourceHandler) resourceHash(namehash common.Hash, period uint32, version uint32) Key { +func (self *ResourceHandler) resourceHash(namehash common.Hash, period uint32, version uint32) Key { // format is: hash(namehash|period|version) self.hashLock.Lock() defer self.hashLock.Unlock() @@ -579,7 +594,7 @@ func (self *RawResourceHandler) resourceHash(namehash common.Hash, period uint32 return self.hasher.Sum(nil) } -func (self *RawResourceHandler) signContent(data []byte) ([]byte, error) { +func (self *ResourceHandler) signContent(data []byte) ([]byte, error) { self.hashLock.Lock() self.hasher.Reset() self.hasher.Write(data) @@ -596,7 +611,7 @@ func (self *RawResourceHandler) signContent(data []byte) ([]byte, error) { return datawithsign, nil } -func (self *RawResourceHandler) getContentAccount(chunkdata []byte) (common.Address, error) { +func (self *ResourceHandler) getContentAccount(chunkdata []byte) (common.Address, error) { if len(chunkdata) <= signatureLength { return common.Address{}, fmt.Errorf("zero-length data") } @@ -612,7 +627,7 @@ func (self *RawResourceHandler) getContentAccount(chunkdata []byte) (common.Addr return crypto.PubkeyToAddress(*pub), nil } -func (self *RawResourceHandler) verifyContent(chunkdata []byte) error { +func (self *ResourceHandler) verifyContent(chunkdata []byte) error { address, err := self.getContentAccount(chunkdata) if err != nil { return err @@ -621,7 +636,7 @@ func (self *RawResourceHandler) verifyContent(chunkdata []byte) error { return nil } -func (self *RawResourceHandler) hasUpdate(name string, period uint32) bool { +func (self *ResourceHandler) hasUpdate(name string, period uint32) bool { if self.resources[name].lastPeriod == period { return true } diff --git a/swarm/storage/resource_ens.go b/swarm/storage/resource_ens.go index 2870061bb8..a82311b08f 100644 --- a/swarm/storage/resource_ens.go +++ b/swarm/storage/resource_ens.go @@ -1,74 +1,54 @@ package storage import ( - "crypto/ecdsa" - "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/contracts/ens" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rpc" ) -// Implements Mutable Resources as offchain ENS resolvers -// -// The data part of the update is forced to be a valid ENS content hash -// -// Also, the ENSResourceHandler only allows creation and update of -// Resources from the ENS owner's address -// -type ENSResourceHandler struct { - *RawResourceHandler - addr common.Address - ensapi *ens.ENS +// ENS validation of mutable resource owners +type ENSValidator struct { + owner common.Address + api *ens.ENS } -func NewENSResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore CloudStore, rpcClient *rpc.Client, backend bind.ContractBackend, ensAddr common.Address) (*ENSResourceHandler, error) { - transactOpts := bind.NewKeyedTransactor(privKey) - ensinstance, err := ens.NewENS(transactOpts, ensAddr, backend) +func NewENSValidator(owneraddress common.Address, contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts) (*ENSValidator, error) { + var err error + validator := &ENSValidator{} + validator.api, err = ens.NewENS(transactOpts, contractaddress, backend) if err != nil { return nil, err } - rh, err := NewRawResourceHandler(privKey, datadir, cloudStore, rpcClient, ens.EnsNode) - if err != nil { - return nil, err - } - rh.nameHashFunc = func(name string) common.Hash { - return ens.EnsNode(name) - } - - return &ENSResourceHandler{ - RawResourceHandler: rh, - addr: crypto.PubkeyToAddress(privKey.PublicKey), - ensapi: ensinstance, - }, nil + validator.owner = owneraddress + return validator, nil } -func (self *ENSResourceHandler) NewResource(name string, frequency uint64) (*resource, error) { - ok, err := self.IsOwner(name) +func (self *ENSValidator) isOwner(name string) (bool, error) { + owneraddr, err := self.api.Owner(self.nameHash(name)) if err != nil { - return nil, err - } else if !ok { - return nil, fmt.Errorf("Not Owner") + return false, err } - return self.RawResourceHandler.NewResource(name, frequency) + return owneraddr == self.owner, nil } -func (self *ENSResourceHandler) Update(name string, data []byte) (Key, error) { - ok, err := self.IsOwner(name) - if err != nil { - return nil, err - } else if !ok { - return nil, fmt.Errorf("Not Owner") - } - return self.RawResourceHandler.Update(name, data) +func (self *ENSValidator) nameHash(name string) common.Hash { + return ens.EnsNode(name) } -func (self *ENSResourceHandler) IsOwner(name string) (bool, error) { - owneraddr, err := self.ensapi.Owner(self.RawResourceHandler.nameHashFunc(name)) - if err != nil { - return false, fmt.Errorf("ENS error: %v", err) - } - return owneraddr == self.addr, nil +// Default fallthrough validation of mutable resource ownership +type GenericValidator struct { + hashFunc func(string) common.Hash +} + +func NewGenericValidator(hashFunc func(string) common.Hash) *GenericValidator { + return &GenericValidator{ + hashFunc: hashFunc, + } +} +func (self *GenericValidator) isOwner(name string) (bool, error) { + return true, nil +} + +func (self *GenericValidator) nameHash(name string) common.Hash { + return self.hashFunc(name) } diff --git a/swarm/storage/resource_test.go b/swarm/storage/resource_test.go index b699733447..951899ebfa 100644 --- a/swarm/storage/resource_test.go +++ b/swarm/storage/resource_test.go @@ -74,7 +74,7 @@ func TestResourceSignature(t *testing.T) { } // set up rpc and create resourcehandler - rh, _, err, teardownTest := setupTest(privkey, nil, zeroAddr) + rh, _, err, teardownTest := setupTest(privkey, nil, nil) if err != nil { teardownTest(t, err) } @@ -110,8 +110,7 @@ func TestResourceSignature(t *testing.T) { // check that we can recover the owner account from the update chunk's signature // TODO: change this to verifyContent on ENS integration - rawrh := rh.(*RawResourceHandler) - recoveredaddress, err := rawrh.getContentAccount(chunk.SData) + recoveredaddress, err := rh.getContentAccount(chunk.SData) if err != nil { teardownTest(t, err) } @@ -136,7 +135,7 @@ func TestResourceReverseLookup(t *testing.T) { backend := &fakeBackend{ blocknumber: startBlock, } - rh, _, err, teardownTest := setupTest(privkey, backend, zeroAddr) + rh, _, err, teardownTest := setupTest(privkey, backend, nil) if err != nil { teardownTest(t, err) } @@ -154,8 +153,7 @@ func TestResourceReverseLookup(t *testing.T) { if err != nil { teardownTest(t, err) } - rawrh := rh.(*RawResourceHandler) - chunk, err := rawrh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(resourcekey)) + chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(resourcekey)) if err != nil { teardownTest(t, err) } @@ -197,7 +195,7 @@ func TestResourceHandler(t *testing.T) { backend := &fakeBackend{ blocknumber: startBlock, } - rh, datadir, err, teardownTest := setupTest(privkey, backend, zeroAddr) + rh, datadir, err, teardownTest := setupTest(privkey, backend, nil) if err != nil { teardownTest(t, err) } @@ -213,9 +211,8 @@ func TestResourceHandler(t *testing.T) { } // check that the new resource is stored correctly - rawrh := rh.(*RawResourceHandler) - namehash := rawrh.nameHashFunc(resourcevalidname) - chunk, err := rawrh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:])) + namehash := rh.validator.nameHash(resourcevalidname) + chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:])) if err != nil { teardownTest(t, err) } else if len(chunk.SData) < 16 { @@ -265,7 +262,7 @@ func TestResourceHandler(t *testing.T) { // it will match on second iteration startblocknumber + (resourceFrequency * 3) fwdBlocks(int(resourceFrequency*2)-1, backend) - rh2, err := NewRawResourceHandler(privkey, datadir, &testCloudStore{}, rawrh.rpcClient, nil) + rh2, err := NewResourceHandler(privkey, datadir, &testCloudStore{}, rh.rpcClient, nil) _, err = rh2.LookupLatest(domainName, true) if err != nil { teardownTest(t, err) @@ -282,7 +279,7 @@ func TestResourceHandler(t *testing.T) { teardownTest(t, fmt.Errorf("resource period was %d, expected 3", rh2.resources[domainName].lastPeriod)) } - rsrc, err := NewResource(domainName, startblocknumber, resourceFrequency, rh2.nameHashFunc) + rsrc, err := NewResource(domainName, startblocknumber, resourceFrequency, rh2.validator.nameHash) if err != nil { teardownTest(t, err) } @@ -342,15 +339,24 @@ func TestResourceENSOwner(t *testing.T) { return } + // ens address and transact options + addr := crypto.PubkeyToAddress(privkey.PublicKey) + transactOpts := bind.NewKeyedTransactor(privkey) + // set up ENS sim domainparts := strings.Split(domainName, ".") - addr, contractbackend, err := setupENS(privkey, domainparts[0], domainparts[1]) + contractAddr, contractbackend, err := setupENS(addr, transactOpts, domainparts[0], domainparts[1]) + if err != nil { + t.Fatal(err) + } + + validator, err := NewENSValidator(addr, contractAddr, contractbackend, transactOpts) if err != nil { t.Fatal(err) } // set up rpc and create resourcehandler with ENS sim backend - rh, _, err, teardownTest := setupTest(privkey, contractbackend, addr) + rh, _, err, teardownTest := setupTest(privkey, contractbackend, validator) if err != nil { teardownTest(t, err) } @@ -358,20 +364,20 @@ func TestResourceENSOwner(t *testing.T) { // create new resource when we are owner = ok _, err = rh.NewResource(domainName, 42) if err != nil { - teardownTest(t, err) + teardownTest(t, fmt.Errorf("Create resource fail: %v", err)) } // update resource when we are owner = ok _, err = rh.Update(domainName, []byte("foo")) if err != nil { - teardownTest(t, err) + teardownTest(t, fmt.Errorf("Update resource fail: %v", err)) } // create new resource when we are NOT owner = !ok - rawrh := rh.(*ENSResourceHandler) - rawrh.privKey = privkeytwo - rawrh.addr = crypto.PubkeyToAddress(privkeytwo.PublicKey) - _, err = rawrh.NewResource(domainName, 42) + addrtwo := crypto.PubkeyToAddress(privkeytwo.PublicKey) + validator.owner = addrtwo + + _, err = rh.NewResource(domainName, 42) if err == nil { teardownTest(t, fmt.Errorf("Expected resource create fail due to owner mismatch")) } @@ -392,7 +398,7 @@ func fwdBlocks(count int, backend *fakeBackend) { } // create rpc and resourcehandler -func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend, ensaddr common.Address) (rh ResourceHandler, datadir string, err error, teardown func(*testing.T, error)) { +func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend, validator ResourceValidator) (rh *ResourceHandler, datadir string, err error, teardown func(*testing.T, error)) { var fsClean func() var rpcClean func() @@ -443,11 +449,7 @@ func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend, } // choose if with ens or not - if ensaddr != zeroAddr { - rh, err = NewENSResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient, contractbackend, ensaddr) - } else { - rh, err = NewRawResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient, nil) - } + rh, err = NewResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient, validator) teardown = func(t *testing.T, err error) { cleanF() if err != nil { @@ -459,7 +461,7 @@ func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend, } // Set up simulated ENS backend for use with ENSResourceHandler tests -func setupENS(privkey *ecdsa.PrivateKey, sub string, top string) (common.Address, bind.ContractBackend, error) { +func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string, top string) (common.Address, bind.ContractBackend, error) { // create the domain hash values to pass to the ENS contract methods var tophash [32]byte @@ -472,16 +474,12 @@ func setupENS(privkey *ecdsa.PrivateKey, sub string, top string) (common.Address hasher.Write([]byte(sub)) copy(subhash[:], hasher.Sum(nil)) - // private key -> address is owner of domain - addr := crypto.PubkeyToAddress(privkey.PublicKey) - // initialize contract backend and deploy - transactOpts := bind.NewKeyedTransactor(privkey) contractBackend := &fakeBackend{ SimulatedBackend: backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}), } - ensAddr, _, ensinstance, err := contract.DeployENS(transactOpts, contractBackend) + contractAddress, _, ensinstance, err := contract.DeployENS(transactOpts, contractBackend) if err != nil { return zeroAddr, nil, fmt.Errorf("can't deploy: %v", err) } @@ -502,7 +500,7 @@ func setupENS(privkey *ecdsa.PrivateKey, sub string, top string) (common.Address } contractBackend.Commit() - return ensAddr, contractBackend, nil + return contractAddress, contractBackend, nil } type testCloudStore struct {