mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 17:33:47 +00:00
swarm/storage: Add multihash support when datalength header = 0
This commit is contained in:
parent
4625dd9986
commit
195b2fa922
2 changed files with 157 additions and 8 deletions
|
|
@ -532,7 +532,16 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
|
||||||
namelength := int(headerlength) - cursor + 4
|
namelength := int(headerlength) - cursor + 4
|
||||||
name = string(chunkdata[cursor : cursor+namelength])
|
name = string(chunkdata[cursor : cursor+namelength])
|
||||||
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 {
|
||||||
|
return nil, 0, 0, "", nil, errors.New("Corrupt multihash data")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
intdatalength = int(datalength)
|
||||||
|
}
|
||||||
data = make([]byte, intdatalength)
|
data = make([]byte, intdatalength)
|
||||||
copy(data, chunkdata[cursor:cursor+intdatalength])
|
copy(data, chunkdata[cursor:cursor+intdatalength])
|
||||||
|
|
||||||
|
|
@ -553,7 +562,19 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
|
||||||
// It is the caller's responsibility to make sure that this data is not stale.
|
// 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
|
// 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, errors.New("Invalid multihash")
|
||||||
|
}
|
||||||
|
return self.update(ctx, name, data, true)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) Update(ctx context.Context, name string, data []byte) (Key, error) {
|
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
|
var signaturelength int
|
||||||
if self.validator != nil {
|
if self.validator != nil {
|
||||||
|
|
@ -618,7 +639,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
|
// send the chunk
|
||||||
self.Put(chunk)
|
self.Put(chunk)
|
||||||
|
|
@ -703,7 +728,7 @@ func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Ad
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an update chunk
|
// 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
|
// no signatures if no validator
|
||||||
var signaturelength int
|
var signaturelength int
|
||||||
|
|
@ -714,11 +739,9 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32
|
||||||
// prepend version and period to allow reverse lookups
|
// prepend version and period to allow reverse lookups
|
||||||
headerlength := len(name) + 4 + 4
|
headerlength := len(name) + 4 + 4
|
||||||
|
|
||||||
// also prepend datalength
|
actualdatalength := len(data)
|
||||||
datalength := len(data)
|
|
||||||
|
|
||||||
chunk := NewChunk(key, nil)
|
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
|
// data header length does NOT include the header length prefix bytes themselves
|
||||||
cursor := 0
|
cursor := 0
|
||||||
|
|
@ -830,3 +853,31 @@ func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
|
||||||
hasher.Write(data)
|
hasher.Write(data)
|
||||||
return common.BytesToHash(hasher.Sum(nil))
|
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
|
||||||
|
hashtype, c := binary.Uvarint(data)
|
||||||
|
log.Trace("ismultihash", "hashtype", hashtype, "c", c)
|
||||||
|
if c == 0 {
|
||||||
|
log.Debug("Corrupt multihash data, hashtype is unreadable")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
cursor += c
|
||||||
|
hashlength, c := binary.Uvarint(data[cursor:])
|
||||||
|
log.Trace("ismultihash", "hashlength", hashlength, "c", c)
|
||||||
|
if c == 0 {
|
||||||
|
log.Debug("Corrupt multihash data, hashlength is unreadable")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
cursor += c
|
||||||
|
// we cheekily assume hashlength < maxint
|
||||||
|
inthashlength := int(hashlength)
|
||||||
|
log.Trace("ismultihash", "datalen", len(data), "hashlength", inthashlength, "cursor", c)
|
||||||
|
if len(data[cursor:]) < inthashlength {
|
||||||
|
log.Debug("Corrupt multihash data, hash does not align with data boundary")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return cursor + inthashlength
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/multiformats/go-multihash"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -107,7 +109,7 @@ func TestResourceReverse(t *testing.T) {
|
||||||
t.Fatal(err)
|
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
|
// check that we can recover the owner account from the update chunk's signature
|
||||||
checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData)
|
checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData)
|
||||||
|
|
@ -456,3 +458,99 @@ func (self *testValidator) checkAccess(name string, address common.Address) (boo
|
||||||
func (self *testValidator) nameHash(name string) common.Hash {
|
func (self *testValidator) nameHash(name string) common.Hash {
|
||||||
return self.hashFunc(name)
|
return self.hashFunc(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResourceMultihash(t *testing.T) {
|
||||||
|
|
||||||
|
// signer containing private key
|
||||||
|
// signer, err := newTestSigner()
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// make fake backend, set up rpc and create resourcehandler
|
||||||
|
backend := &fakeBackend{
|
||||||
|
blocknumber: int64(startBlock),
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up rpc and create resourcehandler
|
||||||
|
rh, _, _, teardownTest, err := setupTest(backend, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer teardownTest()
|
||||||
|
|
||||||
|
// create a new resource
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
_, 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
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
swarmhashchunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(swarmhashkey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, _, _, _, data, err := rh.parseUpdate(swarmhashchunk.SData)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
sha1chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(sha1key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, _, _, _, data, err = rh.parseUpdate(sha1chunk.SData)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test with signed data
|
||||||
|
// rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.ethClient, Signer)
|
||||||
|
// swarmhashsignedkey, err := rh2.UpdateMultihash(ctx, safeName, swarmhashmulti)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue