diff --git a/swarm/api/api.go b/swarm/api/api.go index 040aa4b4bb02..f070c6c6421c 100644 --- a/swarm/api/api.go +++ b/swarm/api/api.go @@ -581,17 +581,17 @@ func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storag } // 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) { +func (self *Api) ResourceLookup(ctx context.Context, name string, period uint32, version uint32, maxLookup *storage.ResourceLookupParams) (storage.Key, []byte, error) { var err error if version != 0 { if period == 0 { return nil, nil, storage.NewResourceError(storage.ErrInvalidValue, "Period can't be 0") } - _, err = self.resource.LookupVersionByName(ctx, name, period, version, true) + _, err = self.resource.LookupVersionByName(ctx, name, period, version, true, maxLookup) } else if period != 0 { - _, err = self.resource.LookupHistoricalByName(ctx, name, period, true) + _, err = self.resource.LookupHistoricalByName(ctx, name, period, true, maxLookup) } else { - _, err = self.resource.LookupLatestByName(ctx, name, true) + _, err = self.resource.LookupLatestByName(ctx, name, true, maxLookup) } if err != nil { return nil, nil, err diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 1735d1d04922..6636f4c6a5ab 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -413,6 +413,7 @@ func (s *Server) HandleGetResource(w http.ResponseWriter, r *Request) { s.handleGetResource(w, r, r.uri.Addr) } +// TODO: Enable pass maxPeriod parameter func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name string) { log.Debug("handle.get.resource", "ruid", r.ruid) var params []string @@ -428,7 +429,7 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name strin log.Debug("handlegetdb", "name", name, "ruid", r.ruid) switch len(params) { case 0: - updateKey, data, err = s.api.ResourceLookup(r.Context(), name, 0, 0) + updateKey, data, err = s.api.ResourceLookup(r.Context(), name, 0, 0, nil) case 2: version, err = strconv.ParseUint(params[1], 10, 32) if err != nil { @@ -438,13 +439,13 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name strin if err != nil { break } - updateKey, data, err = s.api.ResourceLookup(r.Context(), name, uint32(period), uint32(version)) + updateKey, data, err = s.api.ResourceLookup(r.Context(), name, uint32(period), uint32(version), nil) 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)) + updateKey, data, err = s.api.ResourceLookup(r.Context(), name, uint32(period), uint32(version), nil) default: Respond(w, r, "invalid mutable resource request", http.StatusBadRequest) return diff --git a/swarm/storage/error.go b/swarm/storage/error.go index 56e459731c22..9a8676c8a33e 100644 --- a/swarm/storage/error.go +++ b/swarm/storage/error.go @@ -9,5 +9,6 @@ const ( ErrNothingToReturn ErrInvalidSignature ErrNotSynced + ErrPeriodDepth ErrCnt ) diff --git a/swarm/storage/ldbstore.go b/swarm/storage/ldbstore.go index e60b0fc5225f..47a4d312b525 100644 --- a/swarm/storage/ldbstore.go +++ b/swarm/storage/ldbstore.go @@ -155,6 +155,10 @@ func NewLDBStore(path string, hash SwarmHasher, capacity uint64, po func(Key) ui return s, nil } +func (self *LDBStore) SetTrusted() { + self.trusted = true +} + // NewMockDbStore creates a new instance of DbStore with // mockStore set to a provided value. If mockStore argument is nil, // this function behaves exactly as NewDbStore. diff --git a/swarm/storage/resource.go b/swarm/storage/resource.go index f2567a762be5..2d3c2de9bd9b 100644 --- a/swarm/storage/resource.go +++ b/swarm/storage/resource.go @@ -48,7 +48,7 @@ func NewResourceError(code int, s string) error { err: s, } switch code { - case ErrNotFound, ErrIO, ErrUnauthorized, ErrInvalidValue, ErrDataOverflow, ErrNothingToReturn, ErrInvalidSignature, ErrNotSynced: + case ErrNotFound, ErrIO, ErrUnauthorized, ErrInvalidValue, ErrDataOverflow, ErrNothingToReturn, ErrInvalidSignature, ErrNotSynced, ErrPeriodDepth: r.code = code } return r @@ -56,6 +56,11 @@ func NewResourceError(code int, s string) error { type Signature [signatureLength]byte +type ResourceLookupParams struct { + Limit bool + Max uint32 +} + type SignFunc func(common.Hash) (Signature, error) type nameHashFunc func(string) common.Hash @@ -157,28 +162,40 @@ type headerGetter interface { // TODO: Include modtime in chunk data + signature type ResourceHandler struct { ChunkStore - validator ResourceValidator - ethClient headerGetter - resources map[string]*resource - hashPool sync.Pool - resourceLock sync.RWMutex - nameHash nameHashFunc - storeTimeout time.Duration + validator ResourceValidator + ethClient headerGetter + resources map[string]*resource + hashPool sync.Pool + resourceLock sync.RWMutex + nameHash nameHashFunc + storeTimeout time.Duration + queryMaxPeriods *ResourceLookupParams +} + +type ResourceHandlerParams struct { + Validator ResourceValidator + QueryMaxPeriods *ResourceLookupParams } // Create or open resource update chunk store -func NewResourceHandler(hasher SwarmHasher, chunkStore ChunkStore, ethClient headerGetter, validator ResourceValidator) (*ResourceHandler, error) { +func NewResourceHandler(hasher SwarmHasher, chunkStore ChunkStore, ethClient headerGetter, params *ResourceHandlerParams) (*ResourceHandler, error) { + if params.QueryMaxPeriods == nil { + params.QueryMaxPeriods = &ResourceLookupParams{ + Limit: false, + } + } rh := &ResourceHandler{ ChunkStore: chunkStore, ethClient: ethClient, resources: make(map[string]*resource), - validator: validator, + validator: params.Validator, storeTimeout: defaultStoreTimeout, hashPool: sync.Pool{ New: func() interface{} { return MakeHashFunc(SHA3Hash)() }, }, + queryMaxPeriods: params.QueryMaxPeriods, } if rh.validator != nil { @@ -320,17 +337,19 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ // 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 *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) +// If maxPeriod is -1, the default QueryMaxPeriod from ResourceHandlerParams will be used +// if maxPeriod is 0, there will be no limit on period hops +// if maxPeriod > 0, the given value will be the limit of period hops +func (self *ResourceHandler) LookupVersionByName(ctx context.Context, name string, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) { + return self.LookupVersion(ctx, self.nameHash(name), name, period, version, refresh, maxLookup) } -func (self *ResourceHandler) LookupVersion(ctx context.Context, nameHash common.Hash, name string, period uint32, version uint32, refresh bool) (*resource, error) { +func (self *ResourceHandler) LookupVersion(ctx context.Context, nameHash common.Hash, name string, period uint32, version uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) { rsrc, err := self.loadResource(nameHash, name, refresh) if err != nil { return nil, err } - return self.lookup(rsrc, period, version, refresh) + return self.lookup(rsrc, period, version, refresh, maxLookup) } // Retrieves the latest version of the resource update identified by `name` @@ -341,16 +360,16 @@ func (self *ResourceHandler) LookupVersion(ctx context.Context, nameHash common. // and returned. // // See also (*ResourceHandler).LookupVersion -func (self *ResourceHandler) LookupHistoricalByName(ctx context.Context, name string, period uint32, refresh bool) (*resource, error) { - return self.LookupHistorical(ctx, self.nameHash(name), name, period, refresh) +func (self *ResourceHandler) LookupHistoricalByName(ctx context.Context, name string, period uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) { + return self.LookupHistorical(ctx, self.nameHash(name), name, period, refresh, maxLookup) } -func (self *ResourceHandler) LookupHistorical(ctx context.Context, nameHash common.Hash, name string, period uint32, refresh bool) (*resource, error) { +func (self *ResourceHandler) LookupHistorical(ctx context.Context, nameHash common.Hash, name string, period uint32, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) { rsrc, err := self.loadResource(nameHash, name, refresh) if err != nil { return nil, err } - return self.lookup(rsrc, period, 0, refresh) + return self.lookup(rsrc, period, 0, refresh, maxLookup) } // Retrieves the latest version of the resource update identified by `name` @@ -363,11 +382,11 @@ func (self *ResourceHandler) LookupHistorical(ctx context.Context, nameHash comm // Version iteration is done as in (*ResourceHandler).LookupHistorical // // See also (*ResourceHandler).LookupHistorical -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) LookupLatestByName(ctx context.Context, name string, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) { + return self.LookupLatest(ctx, self.nameHash(name), name, refresh, maxLookup) } -func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.Hash, name string, refresh bool) (*resource, error) { +func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.Hash, name string, refresh bool, maxLookup *ResourceLookupParams) (*resource, error) { // get our blockheight at this time and the next block of the update period rsrc, err := self.loadResource(nameHash, name, refresh) @@ -379,11 +398,11 @@ func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.H return nil, err } nextperiod := getNextPeriod(rsrc.startBlock, currentblock, rsrc.frequency) - return self.lookup(rsrc, nextperiod, 0, refresh) + return self.lookup(rsrc, nextperiod, 0, refresh, maxLookup) } // base code for public lookup methods -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, maxLookup *ResourceLookupParams) (*resource, error) { if period == 0 { return nil, NewResourceError(ErrInvalidValue, "period must be >0") @@ -398,7 +417,14 @@ func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint3 version = 1 } + var hops uint32 + if maxLookup == nil { + maxLookup = self.queryMaxPeriods + } for period > 0 { + if maxLookup.Limit && hops > maxLookup.Max { + return nil, NewResourceError(ErrPeriodDepth, fmt.Sprintf("Lookup exceeded max period hops (%d)", maxLookup.Max)) + } key := self.resourceHash(period, version, rsrc.nameHash) chunk, err := self.Get(key) if err == nil { @@ -421,6 +447,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint3 } log.Trace("rsrc update not found, checking previous period", "period", period, "key", key) period-- + hops++ } return nil, NewResourceError(ErrNotFound, "no updates found") } @@ -865,12 +892,13 @@ func isMultihash(data []byte) int { return cursor + inthashlength } -// TODO: this should not be exposed, but swarm/testutil/http.go needs it -func NewTestResourceHandler(datadir string, ethClient headerGetter, validator ResourceValidator) (*ResourceHandler, error) { +// TODO: this should not be part of production code, but currently swarm/testutil/http.go needs it +func NewTestResourceHandler(datadir string, ethClient headerGetter, validator ResourceValidator, maxLimit *ResourceLookupParams) (*ResourceHandler, error) { path := filepath.Join(datadir, DbDirName) basekey := make([]byte, 32) hasher := MakeHashFunc(SHA3Hash) dbStore, err := NewLDBStore(path, hasher, singletonSwarmDbCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) }) + dbStore.SetTrusted() if err != nil { return nil, err } @@ -879,5 +907,14 @@ func NewTestResourceHandler(datadir string, ethClient headerGetter, validator Re DbStore: dbStore, } resourceChunkStore := NewResourceChunkStore(localStore, nil) - return NewResourceHandler(hasher, resourceChunkStore, ethClient, validator) + if maxLimit == nil { + maxLimit = &ResourceLookupParams{ + Limit: false, + } + } + params := &ResourceHandlerParams{ + Validator: validator, + QueryMaxPeriods: maxLimit, + } + return NewResourceHandler(hasher, resourceChunkStore, ethClient, params) } diff --git a/swarm/storage/resource_test.go b/swarm/storage/resource_test.go index dcd940829fe0..3b4b5d43d09a 100644 --- a/swarm/storage/resource_test.go +++ b/swarm/storage/resource_test.go @@ -40,11 +40,8 @@ var ( func init() { var err error - 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))))) - } + log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) safeName, err = ToSafeName(domainName) if err != nil { panic(err) @@ -222,8 +219,14 @@ func TestResourceHandler(t *testing.T) { // it will match on second iteration startblocknumber + (resourceFrequency * 3) fwdBlocks(int(resourceFrequency*2)-1, backend) - rh2, err := NewTestResourceHandler(datadir, rh.ethClient, nil) - _, err = rh2.LookupLatestByName(ctx, safeName, true) + lookupParams := &ResourceLookupParams{ + Limit: false, + } + rh2, err := NewTestResourceHandler(datadir, rh.ethClient, nil, lookupParams) + if err != nil { + t.Fatal(err) + } + _, err = rh2.LookupLatestByName(ctx, safeName, true, nil) if err != nil { t.Fatal(err) } @@ -241,7 +244,7 @@ func TestResourceHandler(t *testing.T) { log.Debug("Latest lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) // specific block, latest version - rsrc, err := rh2.LookupHistoricalByName(ctx, safeName, 3, true) + rsrc, err := rh2.LookupHistoricalByName(ctx, safeName, 3, true, lookupParams) if err != nil { t.Fatal(err) } @@ -252,7 +255,7 @@ func TestResourceHandler(t *testing.T) { log.Debug("Historical lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) // specific block, specific version - rsrc, err = rh2.LookupVersionByName(ctx, safeName, 3, 1, true) + rsrc, err = rh2.LookupVersionByName(ctx, safeName, 3, 1, true, lookupParams) if err != nil { t.Fatal(err) } @@ -350,7 +353,7 @@ func TestResourceMultihash(t *testing.T) { rh.Close() // test with signed data - rh2, err := NewTestResourceHandler(datadir, rh.ethClient, validator) + rh2, err := NewTestResourceHandler(datadir, rh.ethClient, validator, nil) if err != nil { t.Fatal(err) } @@ -481,7 +484,7 @@ func setupTest(backend headerGetter, validator ResourceValidator) (rh *ResourceH os.RemoveAll(datadir) } - rh, err = NewTestResourceHandler(datadir, backend, validator) + rh, err = NewTestResourceHandler(datadir, backend, validator, &ResourceLookupParams{Limit: false}) return rh, datadir, signer, cleanF, nil } diff --git a/swarm/swarm.go b/swarm/swarm.go index 491c755e3693..e76ca8848f3d 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -198,11 +198,13 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api. var resourceHandler *storage.ResourceHandler // if use resource updates if self.config.ResourceEnabled && resolver != nil { - resourceValidator := storage.NewENSValidator(config.EnsRoot, resolver, storage.NewGenericResourceSigner(self.privateKey)) - resolver.SetNameHash(resourceValidator.NameHash) + resourceparams := &storage.ResourceHandlerParams{ + Validator: storage.NewENSValidator(config.EnsRoot, resolver, storage.NewGenericResourceSigner(self.privateKey)), + } + resolver.SetNameHash(resourceparams.Validator.NameHash) hashfunc := storage.MakeHashFunc(storage.SHA3Hash) chunkStore := storage.NewResourceChunkStore(self.lstore, func(*storage.Chunk) error { return nil }) - resourceHandler, err = storage.NewResourceHandler(hashfunc, chunkStore, resolver, resourceValidator) + resourceHandler, err = storage.NewResourceHandler(hashfunc, chunkStore, resolver, resourceparams) if err != nil { return nil, err } diff --git a/swarm/testutil/http.go b/swarm/testutil/http.go index 32866aec9871..2de0c0f96611 100644 --- a/swarm/testutil/http.go +++ b/swarm/testutil/http.go @@ -70,7 +70,7 @@ func NewTestSwarmServer(t *testing.T) *TestSwarmServer { t.Fatal(err) } - rh, err := storage.NewTestResourceHandler(resourceDir, &fakeBackend{}, nil) + rh, err := storage.NewTestResourceHandler(resourceDir, &fakeBackend{}, nil, &storage.ResourceLookupParams{Limit: false}) if err != nil { t.Fatal(err) }