Skip to content
This repository was archived by the owner on Aug 2, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions swarm/api/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,22 +415,16 @@ func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supEr
}
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
Expand Down
73 changes: 63 additions & 10 deletions swarm/storage/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package storage
import (
"context"
"encoding/binary"
"errors"
"fmt"
"math/big"
"path/filepath"
Expand Down Expand Up @@ -44,8 +45,11 @@ func NewResourceError(code int, s string) error {
panic("no such error code!")
}
r := &ResourceError{
err: s,
code: code,
err: s,
}
switch code {
case ErrNotFound, ErrIO, ErrUnauthorized, ErrInvalidValue, ErrDataOverflow, ErrNothingToReturn, ErrInvalidSignature, ErrNotSynced:
r.code = code
}
return r
}
Expand Down Expand Up @@ -513,7 +517,17 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
namelength := int(headerlength) - cursor + 4
name = string(chunkdata[cursor : cursor+namelength])
cursor += namelength
intdatalength := int(datalength)
var intdatalength int
if datalength == 0 {
intdatalength = isMultihash(chunkdata[cursor:])
multihashboundary := cursor + intdatalength
if len(chunkdata) != multihashboundary && len(chunkdata) < multihashboundary+signatureLength {
log.Debug("multihash error", "chunkdatalen", len(chunkdata), "multihashboundary", multihashboundary)
return nil, 0, 0, "", nil, errors.New("Corrupt multihash data")
}
} else {
intdatalength = int(datalength)
}
data = make([]byte, intdatalength)
copy(data, chunkdata[cursor:cursor+intdatalength])

Expand All @@ -534,7 +548,19 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
// 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 *ResourceHandler) UpdateMultihash(ctx context.Context, name string, data []byte) (Key, error) {
if isMultihash(data) == 0 {
return nil, NewResourceError(ErrNothingToReturn, "Invalid multihash")
}
return self.update(ctx, name, data, true)
}

func (self *ResourceHandler) Update(ctx context.Context, name string, data []byte) (Key, error) {
return self.update(ctx, name, data, false)
}

func (self *ResourceHandler) update(ctx context.Context, name string, data []byte, multihash bool) (Key, error) {

var signaturelength int
if self.validator != nil {
Expand Down Expand Up @@ -599,7 +625,11 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt
}
}

chunk := newUpdateChunk(key, signature, nextperiod, version, name, data)
var datalength int
if !multihash {
datalength = len(data)
}
chunk := newUpdateChunk(key, signature, nextperiod, version, name, data, datalength)

// send the chunk
self.Put(chunk)
Expand Down Expand Up @@ -684,7 +714,7 @@ func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Ad
}

// create an update chunk
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, datalength int) *Chunk {

// no signatures if no validator
var signaturelength int
Expand All @@ -695,11 +725,9 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32
// prepend version and period to allow reverse lookups
headerlength := len(name) + 4 + 4

// also prepend datalength
datalength := len(data)

actualdatalength := len(data)
chunk := NewChunk(key, nil)
chunk.SData = make([]byte, 4+signaturelength+headerlength+datalength) // initial 4 are uint16 length descriptors for headerlength and datalength
chunk.SData = make([]byte, 4+signaturelength+headerlength+actualdatalength) // initial 4 are uint16 length descriptors for headerlength and datalength

// data header length does NOT include the header length prefix bytes themselves
cursor := 0
Expand All @@ -726,7 +754,7 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32

// if signature is present it's the last item in the chunk data
if signature != nil {
cursor += datalength
cursor += actualdatalength
copy(chunk.SData[cursor:], signature[:])
}

Expand Down Expand Up @@ -812,6 +840,31 @@ func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
return common.BytesToHash(hasher.Sum(nil))
}

// if first byte is the start of a multihash this function will try to parse it
// if successful it returns the length of multihash data, 0 otherwise
func isMultihash(data []byte) int {
cursor := 0
_, c := binary.Uvarint(data)
if c <= 0 {
log.Warn("Corrupt multihash data, hashtype is unreadable")
return 0
}
cursor += c
hashlength, c := binary.Uvarint(data[cursor:])
if c <= 0 {
log.Warn("Corrupt multihash data, hashlength is unreadable")
return 0
}
cursor += c
// we cheekily assume hashlength < maxint
inthashlength := int(hashlength)
if len(data[cursor:]) < inthashlength {
log.Warn("Corrupt multihash data, hash does not align with data boundary")
return 0
}
return cursor + inthashlength
}

// TODO: this should not be exposed, but swarm/testutil/http.go needs it
func NewTestResourceHandler(datadir string, ethClient ethApi, validator ResourceValidator) (*ResourceHandler, error) {
path := filepath.Join(datadir, DbDirName)
Expand Down
143 changes: 142 additions & 1 deletion swarm/storage/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"testing"
"time"

"github.com/multiformats/go-multihash"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -107,7 +109,7 @@ func TestResourceReverse(t *testing.T) {
t.Fatal(err)
}

chunk := newUpdateChunk(key, &sig, period, version, safeName, data)
chunk := newUpdateChunk(key, &sig, period, version, safeName, data, len(data))

// check that we can recover the owner account from the update chunk's signature
checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData)
Expand Down Expand Up @@ -262,6 +264,133 @@ func TestResourceHandler(t *testing.T) {

}

func TestResourceMultihash(t *testing.T) {

// signer containing private key
signer, err := newTestSigner()
if err != nil {
t.Fatal(err)
}
validator := newTestValidator(signer.signContent)

// make fake backend, set up rpc and create resourcehandler
backend := &fakeBackend{
blocknumber: int64(startBlock),
}

// set up rpc and create resourcehandler
rh, datadir, _, teardownTest, err := setupTest(backend, nil)
if err != nil {
t.Fatal(err)
}
defer teardownTest()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

teardownTest() should be right after the setupTest, call, otherwise it will not run if setupTest gives back an error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setupTest will only return Error if it can't make tempdir, in which case the teardownTest will be nil.


// create a new resource
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This context usage is ineffective. Since cancel is being deferred, it will only be called after the call to NewResource returns, therefore nothing will get cancelled.

We should just pass context.Background() directly to NewResource.

_, err = rh.NewResource(ctx, safeName, resourceFrequency)
if err != nil {
t.Fatal(err)
}

// we're naïvely assuming keccak256 for swarm hashes
// if it ever changes this test should also change
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to PR the swarm BMT hash to multihash?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, do we? Let's discuss separately.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should, along with a spec of its exact construction. We currently have a hack in go-meta to register it ourselves.

swarmhashbytes := rh.nameHash("foo")
swarmhashmulti, err := multihash.Encode(swarmhashbytes.Bytes(), multihash.KECCAK_256)
if err != nil {
t.Fatal(err)
}
swarmhashkey, err := rh.UpdateMultihash(ctx, safeName, swarmhashmulti)
if err != nil {
t.Fatal(err)
}

sha1bytes := make([]byte, multihash.DefaultLengths[multihash.SHA1])
sha1multi, err := multihash.Encode(sha1bytes, multihash.SHA1)
if err != nil {
t.Fatal(err)
}
sha1key, err := rh.UpdateMultihash(ctx, safeName, sha1multi)
if err != nil {
t.Fatal(err)
}

// invalid multihashes
_, err = rh.UpdateMultihash(ctx, safeName, swarmhashmulti[1:])
if err == nil {
t.Fatalf("Expected update to fail with first byte skipped")
}
_, err = rh.UpdateMultihash(ctx, safeName, swarmhashmulti[:len(swarmhashmulti)-2])
if err == nil {
t.Fatalf("Expected update to fail with last byte skipped")
}

data, err := getUpdateDirect(rh, swarmhashkey)
if err != nil {
t.Fatal(err)
}
swarmhashdecode, err := multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(swarmhashdecode.Digest, swarmhashbytes.Bytes()) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", swarmhashdecode.Digest, swarmhashbytes.Bytes())
}
data, err = getUpdateDirect(rh, sha1key)
if err != nil {
t.Fatal(err)
}
sha1decode, err := multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(sha1decode.Digest, sha1bytes) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", sha1decode.Digest, sha1bytes)
}
rh.Close()

// test with signed data
rh2, err := NewTestResourceHandler(datadir, rh.ethClient, validator)
if err != nil {
t.Fatal(err)
}
_, err = rh2.NewResource(ctx, safeName, resourceFrequency)
if err != nil {
t.Fatal(err)
}
swarmhashsignedkey, err := rh2.UpdateMultihash(ctx, safeName, swarmhashmulti)
if err != nil {
t.Fatal(err)
}
sha1signedkey, err := rh2.UpdateMultihash(ctx, safeName, sha1multi)
if err != nil {
t.Fatal(err)
}

data, err = getUpdateDirect(rh2, swarmhashsignedkey)
if err != nil {
t.Fatal(err)
}
swarmhashdecode, err = multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(swarmhashdecode.Digest, swarmhashbytes.Bytes()) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", swarmhashdecode.Digest, swarmhashbytes.Bytes())
}
data, err = getUpdateDirect(rh2, sha1signedkey)
if err != nil {
t.Fatal(err)
}
sha1decode, err = multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(sha1decode.Digest, sha1bytes) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", sha1decode.Digest, sha1bytes)
}
}

// create ENS enabled resource update, with and without valid owner
func TestResourceENSOwner(t *testing.T) {

Expand Down Expand Up @@ -444,3 +573,15 @@ func (self *testValidator) checkAccess(name string, address common.Address) (boo
func (self *testValidator) nameHash(name string) common.Hash {
return self.hashFunc(name)
}

func getUpdateDirect(rh *ResourceHandler, key Key) ([]byte, error) {
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(key)
if err != nil {
return nil, err
}
_, _, _, _, data, err := rh.parseUpdate(chunk.SData)
if err != nil {
return nil, err
}
return data, nil
}
13 changes: 13 additions & 0 deletions vendor/github.com/jbenet/go-base58/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading