swarm/storage, swarm/api: Add error granularity for mut.rsrc

This commit is contained in:
lash 2018-01-23 23:44:27 +01:00
parent ef5eeb6320
commit e61c739107
4 changed files with 94 additions and 39 deletions

View file

@ -370,11 +370,7 @@ func (self *Api) ResourceLookup(ctx context.Context, name string, period uint32,
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)
return nil, nil, storage.NewResourceError(storage.ErrInval, "Period can't be 0")
}
_, err = self.resource.LookupVersionByName(ctx, name, period, version, true)
} else if period != 0 {

View file

@ -301,7 +301,7 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
}
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))
s.translateResourceError(w, r, "Resource creation fail", err)
return
}
outdata = key.Hex()
@ -314,7 +314,7 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
}
_, _, _, err = s.api.ResourceUpdate(r.Context(), r.uri.Addr, data)
if err != nil {
s.Error(w, r, fmt.Errorf("Update resource failed: %v", err))
s.translateResourceError(w, r, "Mutable resource update fail", err)
return
}
@ -327,6 +327,31 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
w.WriteHeader(http.StatusOK)
}
func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supErr string, err error) {
code := 0
defaulterr := fmt.Errorf("%s: %v", supErr, err)
rsrcerr, ok := err.(*storage.ResourceError)
if !ok {
code = rsrcerr.Code()
}
switch code {
case storage.ErrInval:
s.BadRequest(w, r, defaulterr.Error())
case storage.ErrNoent, storage.ErrSync, storage.ErrNodata:
s.NotFound(w, r, defaulterr)
return
case storage.ErrAcces, storage.ErrNokey:
ShowError(w, &r.Request, defaulterr.Error(), http.StatusUnauthorized)
return
case storage.ErrFbig:
ShowError(w, &r.Request, defaulterr.Error(), http.StatusRequestEntityTooLarge)
return
}
s.Error(w, r, defaulterr)
return
}
// Retrieve mutable resource updates:
// bzz-resource://<id> - get latest update
// bzz-resource://<id>/<n> - get latest update on period n
@ -372,7 +397,7 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name strin
return
}
if err != nil {
s.Error(w, r, fmt.Errorf("Mutable resource lookup failed: %v", err))
s.translateResourceError(w, r, "Mutable resource lookup fail", err)
return
}
log.Debug("Found update", "key", updateKey)

13
swarm/storage/error.go Normal file
View file

@ -0,0 +1,13 @@
package storage
const (
ErrCustom = iota
ErrNoent
ErrIO
ErrAcces
ErrInval
ErrFbig
ErrNodata
ErrNokey
ErrSync
)

View file

@ -27,6 +27,30 @@ const (
hasherCount = 8
)
type ResourceError struct {
code int
err string
}
func (e *ResourceError) Error() string {
return e.err
}
func (e *ResourceError) Code() int {
return e.code
}
func NewResourceError(code int, s string) error {
r := &ResourceError{
err: s,
}
switch code {
case ErrNoent, ErrIO, ErrAcces, ErrFbig, ErrInval, ErrNodata, ErrNokey, ErrSync:
r.code = code
}
return r
}
type Signature [signatureLength]byte
type SignFunc func(common.Hash) (Signature, error)
@ -149,7 +173,7 @@ func NewResourceHandler(datadir string, cloudStore CloudStore, ethClient ethApi,
path := filepath.Join(datadir, DbDirName)
dbStore, err := NewDbStore(datadir, hashfunc, singletonSwarmDbCapacity, 0)
if err != nil {
return nil, err
return nil, NewResourceError(ErrIO, fmt.Sprintf("datastore failed to initialize: %v", err))
}
localStore := &LocalStore{
memStore: NewMemStore(dbStore, singletonSwarmDbCapacity),
@ -204,7 +228,7 @@ func (self *ResourceHandler) HashSize() int {
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 nil, nil, NewResourceError(ErrNoent, "Resource does not exist or is not synced")
}
return rsrc.lastKey, rsrc.data, nil
}
@ -213,7 +237,7 @@ 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 0, NewResourceError(ErrNoent, "Resource does not exist or is not synced")
}
return rsrc.lastPeriod, nil
}
@ -221,7 +245,7 @@ func (self *ResourceHandler) GetLastPeriod(name string) (uint32, error) {
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 0, NewResourceError(ErrNoent, "Resource does not exist or is not synced")
}
return rsrc.version, nil
}
@ -240,11 +264,11 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ
// frequency 0 is invalid
if frequency == 0 {
return nil, errors.New("Frequency cannot be 0")
return nil, NewResourceError(ErrInval, "Frequency cannot be 0")
}
if !isSafeName(name) {
return nil, fmt.Errorf("Invalid name: '%s'", name)
return nil, NewResourceError(ErrInval, fmt.Sprintf("Invalid name: '%s'", name))
}
nameHash := self.nameHash(name)
@ -252,22 +276,22 @@ func (self *ResourceHandler) NewResource(ctx context.Context, name string, frequ
if self.validator != nil {
signature, err := self.validator.sign(nameHash)
if err != nil {
return nil, fmt.Errorf("Sign fail: %v", err)
return nil, NewResourceError(ErrNokey, fmt.Sprintf("Sign fail: %v", err))
}
addr, err := getAddressFromDataSig(nameHash, signature)
if err != nil {
return nil, fmt.Errorf("Retrieve address from signature fail: %v", err)
return nil, NewResourceError(ErrNokey, fmt.Sprintf("Retrieve address from signature fail: %v", err))
}
ok, err := self.validator.checkAccess(name, addr)
if err != nil {
return nil, err
} else if !ok {
return nil, fmt.Errorf("Not owner of '%s'", name)
return nil, NewResourceError(ErrAcces, fmt.Sprintf("Not owner of '%s'", name))
}
}
// get our blockheight at this time
currentblock, err := self.GetBlock(ctx)
currentblock, err := self.getBlock(ctx)
if err != nil {
return nil, err
}
@ -360,7 +384,7 @@ func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.H
if err != nil {
return nil, err
}
currentblock, err := self.GetBlock(ctx)
currentblock, err := self.getBlock(ctx)
if err != nil {
return nil, err
}
@ -372,7 +396,7 @@ func (self *ResourceHandler) LookupLatest(ctx context.Context, nameHash common.H
func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint32, refresh bool) (*resource, error) {
if period == 0 {
return nil, errors.New("period must be >0")
return nil, NewResourceError(ErrInval, "period must be >0")
}
// start from the last possible block period, and iterate previous ones until we find a match
@ -408,7 +432,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--
}
return nil, errors.New("no updates found")
return nil, NewResourceError(ErrNoent, "no updates found")
}
// load existing mutable resource into resource struct
@ -425,7 +449,7 @@ func (self *ResourceHandler) loadResource(nameHash common.Hash, name string, ref
rsrc = &resource{}
// make sure our name is safe to use
if !isSafeName(name) {
return nil, fmt.Errorf("Invalid name '%s'", name)
return nil, NewResourceError(ErrInval, fmt.Sprintf("Invalid name '%s'", name))
}
rsrc.name = &name
rsrc.nameHash = nameHash
@ -438,7 +462,7 @@ func (self *ResourceHandler) loadResource(nameHash common.Hash, name string, ref
// minimum sanity check for chunk data
if len(chunk.SData) != indexSize {
return nil, fmt.Errorf("Invalid chunk length %d, should be %d", len(chunk.SData), indexSize)
return nil, NewResourceError(ErrNodata, fmt.Sprintf("Invalid chunk length %d, should be %d", len(chunk.SData), indexSize))
}
rsrc.startBlock = binary.LittleEndian.Uint64(chunk.SData[:8])
rsrc.frequency = binary.LittleEndian.Uint64(chunk.SData[8:])
@ -457,7 +481,7 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
// retrieve metadata from chunk data and check that it matches this mutable resource
signature, period, version, name, data, err := self.parseUpdate(chunk.SData)
if *rsrc.name != name {
return nil, fmt.Errorf("Update belongs to '%s', but have '%s'", name, *rsrc.name)
return nil, NewResourceError(ErrNodata, fmt.Sprintf("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
@ -465,7 +489,7 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
digest := self.keyDataHash(chunk.Key, data)
_, err = getAddressFromDataSig(digest, *signature)
if err != nil {
return nil, fmt.Errorf("Invalid signature: %v", err)
return nil, NewResourceError(ErrAcces, fmt.Sprintf("Invalid signature: %v", err))
}
}
@ -484,16 +508,13 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (
// retrieve update metadata from chunk data
// mirrors newUpdateChunk()
func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32, uint32, string, []byte, error) {
var err error
cursor := 0
headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
cursor += 2
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, NewResourceError(ErrNodata, fmt.Sprintf("Reported headerlength %d + datalength %d longer than actual chunk data length %d", headerlength, datalength, len(chunkdata)))
}
var period uint32
var version uint32
var name string
@ -537,22 +558,22 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt
// get the cached information
rsrc := self.getResource(name)
if rsrc == nil {
return nil, errors.New("Resource object not in index")
return nil, NewResourceError(ErrNoent, "Resource object not in index")
}
if !rsrc.isSynced() {
return nil, errors.New("Resource object not in sync")
return nil, NewResourceError(ErrSync, "Resource object not in sync")
}
// an update can be only one chunk long
datalimit := self.chunkSize() - int64(signaturelength-len(name)-4-4-2-2)
if int64(len(data)) > datalimit {
return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), datalimit)
return nil, NewResourceError(ErrFbig, fmt.Sprintf("Data overflow: %d / %d bytes", len(data), datalimit))
}
// get our blockheight at this time and the next block of the update period
currentblock, err := self.GetBlock(ctx)
if err != nil {
return nil, err
return nil, NewResourceError(ErrIO, fmt.Sprintf("Could not get block height: %v", err))
}
nextperiod := getNextPeriod(rsrc.startBlock, currentblock, rsrc.frequency)
@ -573,22 +594,22 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt
digest := self.keyDataHash(key, data)
sig, err := self.validator.sign(digest)
if err != nil {
return nil, err
return nil, NewResourceError(ErrNokey, fmt.Sprintf("Sign fail: %v", err))
}
signature = &sig
// get the address of the signer (which also checks that it's a valid signature)
addr, err := getAddressFromDataSig(digest, *signature)
if err != nil {
return nil, fmt.Errorf("Invalid data/signature: %v", err)
return nil, NewResourceError(ErrNokey, fmt.Sprintf("Invalid data/signature: %v", err))
}
// check if the signer has access to update
ok, err := self.validator.checkAccess(name, addr)
if err != nil {
return nil, err
return nil, NewResourceError(ErrIO, fmt.Sprintf("Access check fail: %v", err))
} else if !ok {
return nil, fmt.Errorf("Address %x does not have access to update %s", addr, name)
return nil, NewResourceError(ErrAcces, fmt.Sprintf("Address %x does not have access to update %s", addr, name))
}
}
@ -600,7 +621,7 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt
select {
case <-chunk.dbStored:
case <-timeout.C:
return nil, NewResourceError(ErrIO, "chunk store timeout")
}
log.Trace("resource update", "name", name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
@ -618,7 +639,7 @@ func (self *ResourceHandler) Close() {
self.ChunkStore.Close()
}
func (self *ResourceHandler) GetBlock(ctx context.Context) (uint64, error) {
func (self *ResourceHandler) getBlock(ctx context.Context) (uint64, error) {
blockheader, err := self.ethClient.HeaderByNumber(ctx, nil)
if err != nil {
return 0, err