mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 01:43:47 +00:00
Merge pull request #212 from ethersphere/swarm-mutableresources-errors
Add granular error reporting in mutable resources
This commit is contained in:
commit
a1a183889c
13 changed files with 261 additions and 63 deletions
|
|
@ -36,6 +36,18 @@ import (
|
|||
|
||||
var hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}")
|
||||
|
||||
type ErrResourceReturn struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func (e *ErrResourceReturn) Error() string {
|
||||
return "resourceupdate"
|
||||
}
|
||||
|
||||
func (e *ErrResourceReturn) Key() string {
|
||||
return e.key
|
||||
}
|
||||
|
||||
type Resolver interface {
|
||||
Resolve(string) (common.Hash, error)
|
||||
}
|
||||
|
|
@ -145,6 +157,18 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
|
|||
entry, _ := trie.getEntry(path)
|
||||
|
||||
if entry != nil {
|
||||
// we want to be able to serve Mutable Resource Updates transparently using the bzz:// scheme
|
||||
//
|
||||
// we use a special manifest hack for this purpose, which is pathless and where the resource root key
|
||||
// is set as the hash of the manifest (see swarm/api/manifest.go:NewResourceManifest)
|
||||
//
|
||||
// to avoid taking a performance hit hacking a storage.LazySectionReader to wrap the resource key,
|
||||
// we return a typed error instead. Since for all other purposes this is an invalid manifest,
|
||||
// any normal interfacing code will just see an error fail accordingly.
|
||||
if entry.ContentType == ResourceContentType {
|
||||
log.Warn("resource type", "hash", entry.Hash)
|
||||
return nil, entry.ContentType, http.StatusOK, &ErrResourceReturn{entry.Hash}
|
||||
}
|
||||
key = common.Hex2Bytes(entry.Hash)
|
||||
status = entry.Status
|
||||
if status == http.StatusMultipleChoices {
|
||||
|
|
@ -370,11 +394,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.ErrInvalidValue, "Period can't be 0")
|
||||
}
|
||||
_, err = self.resource.LookupVersionByName(ctx, name, period, version, true)
|
||||
} else if period != 0 {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ import (
|
|||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
type resourceResponse struct {
|
||||
Manifest storage.Key `json:"manifest"`
|
||||
Resource string `json:"resource"`
|
||||
Update storage.Key `json:"update"`
|
||||
}
|
||||
|
||||
// ServerConfig is the basic configuration needed for the HTTP server and also
|
||||
// includes CORS settings.
|
||||
type ServerConfig struct {
|
||||
|
|
@ -292,7 +298,7 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) {
|
|||
}
|
||||
|
||||
func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
|
||||
var outdata string
|
||||
var outdata []byte
|
||||
if r.uri.Path != "" {
|
||||
frequency, err := strconv.ParseUint(r.uri.Path, 10, 64)
|
||||
if err != nil {
|
||||
|
|
@ -301,10 +307,26 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
|
|||
}
|
||||
key, err := s.api.ResourceCreate(r.Context(), r.uri.Addr, frequency)
|
||||
if err != nil {
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, fmt.Errorf("Resource creation failed: %v", err)), http.StatusInternalServerError)
|
||||
code, err2 := s.translateResourceError(w, r, "Resource creation fail", err)
|
||||
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, err2), code)
|
||||
return
|
||||
}
|
||||
m, err := s.api.NewResourceManifest(r.uri.Addr)
|
||||
if err != nil {
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, fmt.Errorf("Failed to create resource manifest: %v", err)), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rsrcResponse := &resourceResponse{
|
||||
Manifest: m,
|
||||
Resource: r.uri.Addr,
|
||||
Update: key,
|
||||
}
|
||||
outdata, err = json.Marshal(rsrcResponse)
|
||||
if err != nil {
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, fmt.Errorf("Failed to create json response for %v: error was: %v", r, err)), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
outdata = key.Hex()
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
|
|
@ -314,14 +336,16 @@ func (s *Server) HandlePostResource(w http.ResponseWriter, r *Request) {
|
|||
}
|
||||
_, _, _, err = s.api.ResourceUpdate(r.Context(), r.uri.Addr, data)
|
||||
if err != nil {
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, fmt.Errorf("Update resource failed: %v", err)), http.StatusInternalServerError)
|
||||
code, err2 := s.translateResourceError(w, r, "Mutable resource update fail", err)
|
||||
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, err2), code)
|
||||
return
|
||||
}
|
||||
|
||||
if outdata != "" {
|
||||
if len(outdata) > 0 {
|
||||
w.Header().Add("Content-type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, outdata)
|
||||
fmt.Fprint(w, string(outdata))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
|
@ -372,7 +396,9 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name strin
|
|||
return
|
||||
}
|
||||
if err != nil {
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, fmt.Errorf("Mutable resource lookup failed: %v", err)), http.StatusInternalServerError)
|
||||
code, err2 := s.translateResourceError(w, r, "Mutable resource lookup fail", err)
|
||||
|
||||
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, err2), code)
|
||||
return
|
||||
}
|
||||
log.Debug("Found update", "key", updateKey)
|
||||
|
|
@ -380,6 +406,33 @@ func (s *Server) handleGetResource(w http.ResponseWriter, r *Request, name strin
|
|||
http.ServeContent(w, &r.Request, "", now, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supErr string, err error) (int, error) {
|
||||
code := 0
|
||||
defaultErr := fmt.Errorf("%s: %v", supErr, err)
|
||||
rsrcErr, ok := err.(*storage.ResourceError)
|
||||
if !ok {
|
||||
code = rsrcErr.Code()
|
||||
}
|
||||
switch code {
|
||||
case storage.ErrInvalidValue:
|
||||
//s.BadRequest(w, r, defaultErr.Error())
|
||||
return http.StatusBadRequest, defaultErr
|
||||
case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn:
|
||||
//s.NotFound(w, r, defaultErr)
|
||||
return http.StatusNotFound, defaultErr
|
||||
case storage.ErrUnauthorized, storage.ErrInvalidSignature:
|
||||
//ShowError(w, &r.Request, defaultErr.Error(), http.StatusUnauthorized)
|
||||
return http.StatusUnauthorized, defaultErr
|
||||
case storage.ErrDataOverflow:
|
||||
//ShowError(w, &r.Request, defaultErr.Error(), http.StatusRequestEntityTooLarge)
|
||||
return http.StatusRequestEntityTooLarge, defaultErr
|
||||
}
|
||||
|
||||
return http.StatusInternalServerError, defaultErr
|
||||
|
||||
//s.Error(w, r, defaultErr)
|
||||
}
|
||||
|
||||
// HandleGet handles a GET request to
|
||||
// - bzz-raw://<key> and responds with the raw content stored at the
|
||||
// given storage key
|
||||
|
|
@ -640,7 +693,14 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
|
|||
}
|
||||
|
||||
reader, contentType, status, err := s.api.Get(key, r.uri.Path)
|
||||
|
||||
if err != nil {
|
||||
// cheeky, cheeky hack. See swarm/api/api.go:Api.Get() for an explanation
|
||||
if rsrcErr, ok := err.(*api.ErrResourceReturn); ok {
|
||||
log.Trace("getting resource proxy", "err", rsrcErr.Key())
|
||||
s.handleGetResource(w, r, rsrcErr.Key())
|
||||
return
|
||||
}
|
||||
switch status {
|
||||
case http.StatusNotFound:
|
||||
ShowError(w, &r.Request, fmt.Sprintf("NOT FOUND error serving %s %s: %s", r.Method, r.uri, err), http.StatusNotFound)
|
||||
|
|
|
|||
|
|
@ -19,27 +19,44 @@ package http_test
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
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)))))
|
||||
}
|
||||
}
|
||||
|
||||
type resourceResponse struct {
|
||||
Manifest storage.Key `json:"manifest"`
|
||||
Resource string `json:"resource"`
|
||||
Update storage.Key `json:"update"`
|
||||
}
|
||||
|
||||
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})
|
||||
keybytes := []byte("foo")
|
||||
srv.Hasher.Reset()
|
||||
srv.Hasher.Write([]byte(fmt.Sprintf("%x", keybytes)))
|
||||
keybyteshash := fmt.Sprintf("%x", srv.Hasher.Sum(nil))
|
||||
|
|
@ -65,10 +82,55 @@ func TestBzzResource(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(b, []byte(keybyteshash)) {
|
||||
t.Fatalf("resource update hash mismatch, expected '%s' got '%s'", keybyteshash, b)
|
||||
rsrcResp := &resourceResponse{}
|
||||
err = json.Unmarshal(b, rsrcResp)
|
||||
if err != nil {
|
||||
t.Fatalf("data %s could not be unmarshaled: %v", b, err)
|
||||
}
|
||||
if rsrcResp.Update.Hex() != keybyteshash {
|
||||
t.Fatalf("Response resource key mismatch, expected '%s', got '%s'", keybyteshash, rsrcResp.Resource)
|
||||
}
|
||||
|
||||
// get manifest
|
||||
url = fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, rsrcResp.Manifest)
|
||||
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)
|
||||
}
|
||||
manifest := &api.Manifest{}
|
||||
err = json.Unmarshal(b, manifest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(manifest.Entries) != 1 {
|
||||
t.Fatalf("Manifest has %d entries", len(manifest.Entries))
|
||||
}
|
||||
if manifest.Entries[0].Hash != rsrcResp.Resource {
|
||||
t.Fatalf("Expected manifest path '%s', got '%s'", keybyteshash, manifest.Entries[0].Hash)
|
||||
}
|
||||
|
||||
// get bzz manifest transparent resource resolve
|
||||
url = fmt.Sprintf("%s/bzz:/%s", srv.URL, rsrcResp.Manifest)
|
||||
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)
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,23 @@ func (a *Api) NewManifest() (storage.Key, error) {
|
|||
return key, err
|
||||
}
|
||||
|
||||
// Manifest hack for supporting Mutable Resource Updates from the bzz: scheme
|
||||
// see swarm/api/api.go:Api.Get() for more information
|
||||
func (a *Api) NewResourceManifest(resourceKey string) (storage.Key, error) {
|
||||
var manifest Manifest
|
||||
entry := ManifestEntry{
|
||||
Hash: resourceKey,
|
||||
ContentType: ResourceContentType,
|
||||
}
|
||||
manifest.Entries = append(manifest.Entries, entry)
|
||||
data, err := json.Marshal(&manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, _, err := a.Store(bytes.NewReader(data), int64(len(data)))
|
||||
return key, err
|
||||
}
|
||||
|
||||
// ManifestWriter is used to add and remove entries from an underlying manifest
|
||||
type ManifestWriter struct {
|
||||
api *Api
|
||||
|
|
|
|||
|
|
@ -95,7 +95,11 @@ func newBzzBaseTester(t *testing.T, n int, addr *BzzAddr, spec *protocols.Spec,
|
|||
cs := make(map[string]chan bool)
|
||||
|
||||
srv := func(p *BzzPeer) error {
|
||||
defer close(cs[p.ID().String()])
|
||||
defer func() {
|
||||
if cs[p.ID().String()] != nil {
|
||||
close(cs[p.ID().String()])
|
||||
}
|
||||
}()
|
||||
return run(p)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -695,7 +695,7 @@ func (s *DbStore) get(key Key) (chunk *Chunk, err error) {
|
|||
chunk = NewChunk(key, nil)
|
||||
decodeData(data, chunk)
|
||||
} else {
|
||||
err = ErrNotFound
|
||||
err = ErrChunkNotFound
|
||||
}
|
||||
|
||||
return
|
||||
|
|
@ -708,8 +708,8 @@ func newMockGetDataFunc(mockStore *mock.NodeStore) func(key Key) (data []byte, e
|
|||
return func(key Key) (data []byte, err error) {
|
||||
data, err = mockStore.Get(key)
|
||||
if err == mock.ErrNotFound {
|
||||
// preserve ErrNotFound error
|
||||
err = ErrNotFound
|
||||
// preserve ErrChunkNotFound error
|
||||
err = ErrChunkNotFound
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,8 +142,8 @@ func testDbStoreNotFound(t *testing.T, mock bool) {
|
|||
defer db.close()
|
||||
|
||||
_, err = db.Get(ZeroKey)
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Expected ErrNotFound, got %v", err)
|
||||
if err != ErrChunkNotFound {
|
||||
t.Errorf("Expected ErrChunkNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrChunkNotFound = errors.New("chunk not found")
|
||||
ErrFetching = errors.New("chunk still fetching")
|
||||
// timeout interval before retrieval is timed out
|
||||
searchTimeout = 3 * time.Second
|
||||
|
|
|
|||
13
swarm/storage/error.go
Normal file
13
swarm/storage/error.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package storage
|
||||
|
||||
const (
|
||||
ErrNotFound = iota
|
||||
ErrIO
|
||||
ErrUnauthorized
|
||||
ErrInvalidValue
|
||||
ErrDataOverflow
|
||||
ErrNothingToReturn
|
||||
ErrInvalidSignature
|
||||
ErrNotSynced
|
||||
ErrCnt
|
||||
)
|
||||
|
|
@ -214,7 +214,7 @@ func (s *MemStore) Get(hash Key) (chunk *Chunk, err error) {
|
|||
l := hash.bits(bitpos, node.bits)
|
||||
st := node.subtree[l]
|
||||
if st == nil {
|
||||
return nil, ErrNotFound
|
||||
return nil, ErrChunkNotFound
|
||||
}
|
||||
bitpos += node.bits
|
||||
node = st
|
||||
|
|
@ -232,7 +232,7 @@ func (s *MemStore) Get(hash Key) (chunk *Chunk, err error) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
err = ErrNotFound
|
||||
err = ErrChunkNotFound
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ func TestMemStoreNotFound(t *testing.T) {
|
|||
defer m.Close()
|
||||
|
||||
_, err := m.Get(ZeroKey)
|
||||
if err != ErrNotFound {
|
||||
t.Errorf("Expected ErrNotFound, got %v", err)
|
||||
if err != ErrChunkNotFound {
|
||||
t.Errorf("Expected ErrChunkNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func (self *NetStore) Get(key Key) (chunk *Chunk, err error) {
|
|||
|
||||
select {
|
||||
case <-t.C:
|
||||
return nil, ErrNotFound
|
||||
return nil, ErrChunkNotFound
|
||||
case <-chunk.ReqC:
|
||||
}
|
||||
return chunk, nil
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package storage
|
|||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
|
|
@ -27,6 +26,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 {
|
||||
if code < 0 || code >= ErrCnt {
|
||||
panic("no such error code!")
|
||||
}
|
||||
r := &ResourceError{
|
||||
err: s,
|
||||
code: code,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type Signature [signatureLength]byte
|
||||
|
||||
type SignFunc func(common.Hash) (Signature, error)
|
||||
|
|
@ -189,7 +212,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(ErrNotFound, "Resource does not exist or is not synced")
|
||||
}
|
||||
return rsrc.lastKey, rsrc.data, nil
|
||||
}
|
||||
|
|
@ -198,15 +221,17 @@ 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(ErrNotFound, "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")
|
||||
if rsrc == nil {
|
||||
return 0, NewResourceError(ErrNotFound, "Resource does not exist")
|
||||
} else if !rsrc.isSynced() {
|
||||
return 0, NewResourceError(ErrNotSynced, "Resource is not synced")
|
||||
}
|
||||
return rsrc.version, nil
|
||||
}
|
||||
|
|
@ -225,11 +250,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(ErrInvalidValue, "Frequency cannot be 0")
|
||||
}
|
||||
|
||||
if !isSafeName(name) {
|
||||
return nil, fmt.Errorf("Invalid name: '%s'", name)
|
||||
return nil, NewResourceError(ErrInvalidValue, fmt.Sprintf("Invalid name: '%s'", name))
|
||||
}
|
||||
|
||||
nameHash := self.nameHash(name)
|
||||
|
|
@ -237,22 +262,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(ErrInvalidSignature, 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(ErrInvalidSignature, 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(ErrUnauthorized, 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
|
||||
}
|
||||
|
|
@ -345,7 +370,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
|
||||
}
|
||||
|
|
@ -357,7 +382,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(ErrInvalidValue, "period must be >0")
|
||||
}
|
||||
|
||||
// start from the last possible block period, and iterate previous ones until we find a match
|
||||
|
|
@ -393,7 +418,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(ErrNotFound, "no updates found")
|
||||
}
|
||||
|
||||
// load existing mutable resource into resource struct
|
||||
|
|
@ -410,7 +435,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(ErrInvalidValue, fmt.Sprintf("Invalid name '%s'", name))
|
||||
}
|
||||
rsrc.name = &name
|
||||
rsrc.nameHash = nameHash
|
||||
|
|
@ -423,7 +448,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(ErrNothingToReturn, 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:])
|
||||
|
|
@ -442,7 +467,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(ErrNothingToReturn, 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
|
||||
|
|
@ -450,7 +475,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(ErrUnauthorized, fmt.Sprintf("Invalid signature: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -469,16 +494,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(ErrNothingToReturn, 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
|
||||
|
|
@ -522,22 +544,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(ErrNotFound, "Resource object not in index")
|
||||
}
|
||||
if !rsrc.isSynced() {
|
||||
return nil, errors.New("Resource object not in sync")
|
||||
return nil, NewResourceError(ErrNotSynced, "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(ErrDataOverflow, 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)
|
||||
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)
|
||||
|
||||
|
|
@ -558,22 +580,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(ErrInvalidSignature, 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(ErrInvalidSignature, 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(ErrUnauthorized, fmt.Sprintf("Address %x does not have access to update %s", addr, name))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -585,7 +607,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)
|
||||
|
||||
|
|
@ -603,7 +625,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
|
||||
|
|
|
|||
Loading…
Reference in a new issue