mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
swarm/storage: Simplify code, correct content hashing
This commit is contained in:
parent
f482ecc6de
commit
e1e867cdf9
3 changed files with 361 additions and 432 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -19,12 +18,18 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
signatureLength = 65
|
signatureLength = 65
|
||||||
indexSize = 24
|
indexSize = 16
|
||||||
|
dbDirName = "resource"
|
||||||
|
chunkSize = 4096 // temporary until we implement DPA in the resourcehandler
|
||||||
)
|
)
|
||||||
|
|
||||||
type Signature [signatureLength]byte
|
type Signature [signatureLength]byte
|
||||||
|
|
||||||
func NewSignature(b []byte) (Signature, error) {
|
var emptySignature Signature
|
||||||
|
|
||||||
|
type SignFunc func(common.Hash) (Signature, error)
|
||||||
|
|
||||||
|
func bytesToSignature(b []byte) (Signature, error) {
|
||||||
var s Signature
|
var s Signature
|
||||||
if len(b) != signatureLength {
|
if len(b) != signatureLength {
|
||||||
return [signatureLength]byte{}, fmt.Errorf("Must be %d bytes", signatureLength)
|
return [signatureLength]byte{}, fmt.Errorf("Must be %d bytes", signatureLength)
|
||||||
|
|
@ -46,6 +51,11 @@ type resource struct {
|
||||||
updated time.Time
|
updated time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Expire content after a defined period (to force resync)
|
||||||
|
func (r *resource) isSynced() bool {
|
||||||
|
return !r.updated.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
// Mutable resource is an entity which allows updates to a resource
|
// Mutable resource is an entity which allows updates to a resource
|
||||||
// without resorting to ENS on each update.
|
// without resorting to ENS on each update.
|
||||||
// The update scheme is built on swarm chunks with chunk keys following
|
// The update scheme is built on swarm chunks with chunk keys following
|
||||||
|
|
@ -104,8 +114,9 @@ type resource struct {
|
||||||
// treated as a resource update chunk.
|
// treated as a resource update chunk.
|
||||||
|
|
||||||
type ResourceValidator interface {
|
type ResourceValidator interface {
|
||||||
isOwner(string, common.Address) (bool, error)
|
checkAccess(string, common.Address) (bool, error)
|
||||||
nameHash(string) common.Hash
|
nameHash(string) common.Hash
|
||||||
|
sign(common.Hash) (Signature, error) // SignFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceHandler struct {
|
type ResourceHandler struct {
|
||||||
|
|
@ -116,13 +127,15 @@ type ResourceHandler struct {
|
||||||
hashLock sync.Mutex
|
hashLock sync.Mutex
|
||||||
resourceLock sync.RWMutex
|
resourceLock sync.RWMutex
|
||||||
hasher SwarmHash
|
hasher SwarmHash
|
||||||
maxChunkData int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or open resource update chunk store
|
// Create or open resource update chunk store
|
||||||
func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore CloudStore, rpcClient *rpc.Client, validator ResourceValidator) (*ResourceHandler, error) {
|
func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Client, validator ResourceValidator) (*ResourceHandler, error) {
|
||||||
path := filepath.Join(datadir, "resource")
|
|
||||||
dbStore, err := NewDbStore(datadir, nil, singletonSwarmDbCapacity, 0)
|
hashfunc := MakeHashFunc(SHA3Hash)
|
||||||
|
|
||||||
|
path := filepath.Join(datadir, dbDirName)
|
||||||
|
dbStore, err := NewDbStore(datadir, hashfunc, singletonSwarmDbCapacity, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -130,14 +143,12 @@ func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore Cl
|
||||||
memStore: NewMemStore(dbStore, singletonSwarmDbCapacity),
|
memStore: NewMemStore(dbStore, singletonSwarmDbCapacity),
|
||||||
DbStore: dbStore,
|
DbStore: dbStore,
|
||||||
}
|
}
|
||||||
hasher := MakeHashFunc("SHA3")
|
|
||||||
|
|
||||||
rh := &ResourceHandler{
|
rh := &ResourceHandler{
|
||||||
ChunkStore: newResourceChunkStore(path, hasher, localStore, cloudStore),
|
ChunkStore: newResourceChunkStore(path, hashfunc, localStore, cloudStore),
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
resources: make(map[string]*resource),
|
resources: make(map[string]*resource),
|
||||||
hasher: hasher(),
|
hasher: hashfunc(),
|
||||||
maxChunkData: DefaultBranches * int64(hasher().Size()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if validator != nil {
|
if validator != nil {
|
||||||
|
|
@ -149,73 +160,58 @@ func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore Cl
|
||||||
rh.hasher.Reset()
|
rh.hasher.Reset()
|
||||||
rh.hasher.Write([]byte(name))
|
rh.hasher.Write([]byte(name))
|
||||||
return common.BytesToHash(rh.hasher.Sum(nil))
|
return common.BytesToHash(rh.hasher.Sum(nil))
|
||||||
})
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rh, nil
|
return rh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateInput(name string, frequency uint64) (string, error) {
|
// \TODO should be hashsize * branches from the chosen chunker, implement with dpa
|
||||||
// frequency 0 is invalid
|
func (self *ResourceHandler) chunkSize() int64 {
|
||||||
if frequency == 0 {
|
return chunkSize
|
||||||
return "", fmt.Errorf("Frequency cannot be 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// must have name
|
|
||||||
if name == "" {
|
|
||||||
return "", fmt.Errorf("Name cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure our ens identifier is idna safe
|
|
||||||
validname, err := idna.ToASCII(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return validname, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a standalone resource object
|
|
||||||
//
|
|
||||||
// Can be passed to SetResource if external root data lookups are used
|
|
||||||
func NewResource(name string, startBlock uint64, frequency uint64, nameHashFunc func(name string) common.Hash) (*resource, error) {
|
|
||||||
|
|
||||||
validname, err := validateInput(name, frequency)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &resource{
|
|
||||||
name: validname,
|
|
||||||
nameHash: nameHashFunc(validname),
|
|
||||||
startBlock: startBlock,
|
|
||||||
frequency: frequency,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new root entry for a mutable resource identified by `name` with the specified `frequency`.
|
// Creates a new root entry for a mutable resource identified by `name` with the specified `frequency`.
|
||||||
//
|
//
|
||||||
|
// The signature data should match the hash of the idna-converted name by the validator's namehash function, NOT the raw name bytes.
|
||||||
|
//
|
||||||
// The start block of the resource update will be the actual current block height of the connected network.
|
// The start block of the resource update will be the actual current block height of the connected network.
|
||||||
func (self *ResourceHandler) NewResource(name string, frequency uint64, signature Signature) (*resource, error) {
|
func (self *ResourceHandler) NewResource(name string, frequency uint64, verify bool) (*resource, error) {
|
||||||
|
|
||||||
addr, err := self.getAddressFromDataSig([]byte(name), signature)
|
// frequency 0 is invalid
|
||||||
if err != nil {
|
if frequency == 0 {
|
||||||
return nil, fmt.Errorf("Corrupt signature")
|
return nil, fmt.Errorf("Frequency cannot be 0")
|
||||||
}
|
}
|
||||||
ok, err := self.validator.isOwner(name, addr)
|
|
||||||
|
// must have name
|
||||||
|
if name == "" {
|
||||||
|
return nil, fmt.Errorf("Empty name")
|
||||||
|
}
|
||||||
|
|
||||||
|
validName, err := toSafeName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nameHash := self.validator.nameHash(validName)
|
||||||
|
|
||||||
|
if verify {
|
||||||
|
signature, err := self.validator.sign(nameHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Sign fail: %v", err)
|
||||||
|
}
|
||||||
|
addr, err := getAddressFromDataSig(nameHash, signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Retrieve address from signature fail: %v", err)
|
||||||
|
}
|
||||||
|
ok, err := self.validator.checkAccess(name, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
return nil, fmt.Errorf("Not owner of '%s'", name)
|
return nil, fmt.Errorf("Not owner of '%s'", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
validname, err := validateInput(name, frequency)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nameHash := self.validator.nameHash(validname)
|
|
||||||
|
|
||||||
// get our blockheight at this time
|
// get our blockheight at this time
|
||||||
currentblock, err := self.getBlock()
|
currentblock, err := self.getBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -224,22 +220,19 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64, signatur
|
||||||
|
|
||||||
// chunk with key equal to namehash points to data of first blockheight + update frequency
|
// chunk with key equal to namehash points to data of first blockheight + update frequency
|
||||||
// from this we know from what blockheight we should look for updates, and how often
|
// from this we know from what blockheight we should look for updates, and how often
|
||||||
chunk := NewChunk(Key(nameHash[:]), nil)
|
chunk := NewChunk(Key(nameHash.Bytes()), nil)
|
||||||
chunk.SData = make([]byte, indexSize)
|
chunk.SData = make([]byte, indexSize)
|
||||||
|
|
||||||
// resource update root chunks follow same convention as "normal" chunks
|
|
||||||
// with 8 bytes prefix specifying size
|
|
||||||
val := make([]byte, 8)
|
val := make([]byte, 8)
|
||||||
chunk.SData[0] = 16 // size, little-endian
|
|
||||||
binary.LittleEndian.PutUint64(val, currentblock)
|
binary.LittleEndian.PutUint64(val, currentblock)
|
||||||
copy(chunk.SData[8:16], val)
|
copy(chunk.SData[:8], val)
|
||||||
binary.LittleEndian.PutUint64(val, frequency)
|
binary.LittleEndian.PutUint64(val, frequency)
|
||||||
copy(chunk.SData[16:], val)
|
copy(chunk.SData[8:], val)
|
||||||
self.Put(chunk)
|
self.Put(chunk)
|
||||||
log.Debug("new resource", "name", validname, "key", nameHash, "startBlock", currentblock, "frequency", frequency)
|
log.Debug("new resource", "name", validName, "key", nameHash, "startBlock", currentblock, "frequency", frequency)
|
||||||
|
|
||||||
rsrc := &resource{
|
rsrc := &resource{
|
||||||
name: validname,
|
name: validName,
|
||||||
nameHash: nameHash,
|
nameHash: nameHash,
|
||||||
startBlock: currentblock,
|
startBlock: currentblock,
|
||||||
frequency: frequency,
|
frequency: frequency,
|
||||||
|
|
@ -250,42 +243,6 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64, signatur
|
||||||
return self.resources[name], nil
|
return self.resources[name], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set an externally defined resource object
|
|
||||||
//
|
|
||||||
// If the resource update root chunk is located externally (for example as a normal
|
|
||||||
// chunk looked up by ENS) the data would be manually added with this method).
|
|
||||||
//
|
|
||||||
// Method will fail if resource is already registered in this session, unless
|
|
||||||
// `allowOverwrite` is set
|
|
||||||
func (self *ResourceHandler) SetExternalResource(rsrc *resource, allowOverwrite bool) error {
|
|
||||||
|
|
||||||
utfname, err := idna.ToUnicode(rsrc.name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Invalid IDNA rsrc name '%s'", rsrc.name)
|
|
||||||
}
|
|
||||||
if !allowOverwrite {
|
|
||||||
self.resourceLock.Lock()
|
|
||||||
_, ok := self.resources[utfname]
|
|
||||||
self.resourceLock.Unlock()
|
|
||||||
if ok {
|
|
||||||
return fmt.Errorf("Resource exists")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get our blockheight at this time
|
|
||||||
currentblock, err := self.getBlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rsrc.startBlock > currentblock {
|
|
||||||
return fmt.Errorf("Startblock cannot be higher than current block (%d > %d)", rsrc.startBlock, currentblock)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.resources[utfname] = rsrc
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Searches and retrieves the specific version of the resource update identified by `name`
|
// Searches and retrieves the specific version of the resource update identified by `name`
|
||||||
// at the specific block height
|
// at the specific block height
|
||||||
//
|
//
|
||||||
|
|
@ -360,7 +317,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32,
|
||||||
}
|
}
|
||||||
|
|
||||||
for period > 0 {
|
for period > 0 {
|
||||||
key := self.resourceHash(rsrc.nameHash, period, version)
|
key := self.resourceHash(period, version, rsrc.nameHash)
|
||||||
chunk, err := self.Get(key)
|
chunk, err := self.Get(key)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if specificversion {
|
if specificversion {
|
||||||
|
|
@ -370,7 +327,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32,
|
||||||
log.Trace("rsrc update version 1 found, checking for version updates", "period", period, "key", key)
|
log.Trace("rsrc update version 1 found, checking for version updates", "period", period, "key", key)
|
||||||
for {
|
for {
|
||||||
newversion := version + 1
|
newversion := version + 1
|
||||||
key := self.resourceHash(rsrc.nameHash, period, newversion)
|
key := self.resourceHash(period, newversion, rsrc.nameHash)
|
||||||
newchunk, err := self.Get(key)
|
newchunk, err := self.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return self.updateResourceIndex(rsrc, chunk, &name)
|
return self.updateResourceIndex(rsrc, chunk, &name)
|
||||||
|
|
@ -394,8 +351,8 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource,
|
||||||
rsrc := self.getResource(name)
|
rsrc := self.getResource(name)
|
||||||
if rsrc == nil || refresh {
|
if rsrc == nil || refresh {
|
||||||
rsrc = &resource{}
|
rsrc = &resource{}
|
||||||
// make sure our ens identifier is idna safe
|
// make sure our name is safe to use
|
||||||
validname, err := idna.ToASCII(name)
|
validname, err := toSafeName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -409,17 +366,11 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource,
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanity check for chunk data
|
// sanity check for chunk data
|
||||||
// data is prefixed by 8 bytes of size
|
if len(chunk.SData) != indexSize {
|
||||||
if len(chunk.SData) < indexSize {
|
return nil, fmt.Errorf("Invalid chunk length %d, should be %d", len(chunk.SData), indexSize)
|
||||||
return nil, fmt.Errorf("Invalid chunk length %d", len(chunk.SData))
|
|
||||||
} else {
|
|
||||||
chunklength := binary.LittleEndian.Uint64(chunk.SData[:8])
|
|
||||||
if chunklength != uint64(16) {
|
|
||||||
return nil, fmt.Errorf("Invalid chunk length header %d", chunklength)
|
|
||||||
}
|
}
|
||||||
}
|
rsrc.startBlock = binary.LittleEndian.Uint64(chunk.SData[:8])
|
||||||
rsrc.startBlock = binary.LittleEndian.Uint64(chunk.SData[8:16])
|
rsrc.frequency = binary.LittleEndian.Uint64(chunk.SData[8:])
|
||||||
rsrc.frequency = binary.LittleEndian.Uint64(chunk.SData[16:])
|
|
||||||
} else {
|
} else {
|
||||||
rsrc.name = self.resources[name].name
|
rsrc.name = self.resources[name].name
|
||||||
rsrc.nameHash = self.resources[name].nameHash
|
rsrc.nameHash = self.resources[name].nameHash
|
||||||
|
|
@ -432,15 +383,21 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource,
|
||||||
// update mutable resource index map with specified content
|
// update mutable resource index map with specified content
|
||||||
func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, indexname *string) (*resource, error) {
|
func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, indexname *string) (*resource, error) {
|
||||||
|
|
||||||
// rsrc update data chunks are total hacks
|
|
||||||
// and have no size prefix :D
|
|
||||||
err := self.verifyContent(chunk.SData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update our rsrcs entry map
|
// update our rsrcs entry map
|
||||||
period, version, _, data, err := parseUpdate(chunk.SData[signatureLength:])
|
signature, period, version, name, data, err := parseUpdate(chunk.SData)
|
||||||
|
if rsrc.name != name {
|
||||||
|
return nil, fmt.Errorf("Update belongs to '%s', but have '%s'", name, rsrc.name)
|
||||||
|
}
|
||||||
|
self.hashLock.Lock()
|
||||||
|
self.hasher.Reset()
|
||||||
|
self.hasher.Write(chunk.Key[:])
|
||||||
|
self.hasher.Write(data)
|
||||||
|
digest := self.hasher.Sum(nil)
|
||||||
|
self.hashLock.Unlock()
|
||||||
|
_, err = getAddressFromDataSig(common.BytesToHash(digest), signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid signature: %v", err)
|
||||||
|
}
|
||||||
rsrc.lastPeriod = period
|
rsrc.lastPeriod = period
|
||||||
rsrc.version = version
|
rsrc.version = version
|
||||||
rsrc.updated = time.Now()
|
rsrc.updated = time.Now()
|
||||||
|
|
@ -451,22 +408,23 @@ func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, i
|
||||||
return rsrc, nil
|
return rsrc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUpdate(blob []byte) (period uint32, version uint32, ensname []byte, data []byte, err error) {
|
func parseUpdate(chunkdata []byte) (signature Signature, period uint32, version uint32, name string, data []byte, err error) {
|
||||||
headerlength := binary.LittleEndian.Uint16(blob[:2])
|
copy(signature[:], chunkdata[:signatureLength])
|
||||||
if int(headerlength+2) > len(blob) {
|
cursor := signatureLength
|
||||||
return 0, 0, nil, nil, fmt.Errorf("Reported header length %d longer than actual data length %d", headerlength, len(blob))
|
headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
|
||||||
|
if int(headerlength+2) > len(chunkdata) {
|
||||||
|
return emptySignature, 0, 0, "", nil, fmt.Errorf("Reported header length %d longer than actual data length %d", headerlength, len(chunkdata))
|
||||||
}
|
}
|
||||||
cursor := 2
|
cursor += 2
|
||||||
period = binary.LittleEndian.Uint32(blob[cursor : cursor+4])
|
period = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4])
|
||||||
cursor += 4
|
cursor += 4
|
||||||
version = binary.LittleEndian.Uint32(blob[cursor : cursor+4])
|
version = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4])
|
||||||
cursor += 4
|
cursor += 4
|
||||||
namelength := int(headerlength) - cursor + 2
|
namelength := int(headerlength) - cursor + signatureLength + 2
|
||||||
ensname = make([]byte, namelength)
|
name = string(chunkdata[cursor : cursor+namelength])
|
||||||
copy(ensname, blob[cursor:])
|
|
||||||
cursor += namelength
|
cursor += namelength
|
||||||
data = make([]byte, len(blob)-cursor)
|
data = make([]byte, len(chunkdata)-cursor)
|
||||||
copy(data, blob[cursor:])
|
copy(data, chunkdata[cursor:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -476,32 +434,18 @@ func parseUpdate(blob []byte) (period uint32, version uint32, ensname []byte, da
|
||||||
// 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) Update(name string, data []byte, signature Signature) (Key, error) {
|
func (self *ResourceHandler) Update(indexname string, data []byte) (Key, error) {
|
||||||
|
|
||||||
addr, err := self.getAddressFromDataSig(data, signature)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Invalid data/signature: %v", err)
|
|
||||||
}
|
|
||||||
ok, err := self.validator.isOwner(name, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !ok {
|
|
||||||
return nil, fmt.Errorf("Not owner of '%s'", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// can be only one chunk long minus 65 byte signature
|
|
||||||
if int64(len(data)) > self.maxChunkData {
|
|
||||||
return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), 4096-signatureLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the cached information
|
// get the cached information
|
||||||
self.resourceLock.Lock()
|
rsrc := self.getResource(indexname)
|
||||||
defer self.resourceLock.Unlock()
|
if !rsrc.isSynced() {
|
||||||
resource, ok := self.resources[name]
|
return nil, fmt.Errorf("Resource object not in sync")
|
||||||
if !ok {
|
}
|
||||||
return nil, fmt.Errorf("No such resource")
|
|
||||||
} else if resource.updated.IsZero() {
|
// an update can be only one chunk long
|
||||||
return nil, fmt.Errorf("Invalid resource")
|
datalimit := self.chunkSize() - int64(signatureLength-len(self.resources[indexname].name)-8)
|
||||||
|
if int64(len(data)) > datalimit {
|
||||||
|
return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), datalimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get our blockheight at this time and the next block of the update period
|
// get our blockheight at this time and the next block of the update period
|
||||||
|
|
@ -509,21 +453,59 @@ func (self *ResourceHandler) Update(name string, data []byte, signature Signatur
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nextperiod := getNextPeriod(resource.startBlock, currentblock, resource.frequency)
|
nextperiod := getNextPeriod(rsrc.startBlock, currentblock, rsrc.frequency)
|
||||||
|
|
||||||
// if we already have an update for this block then increment version
|
// if we already have an update for this block then increment version
|
||||||
|
// (resource object MUST be in sync for version to be correct)
|
||||||
var version uint32
|
var version uint32
|
||||||
if self.hasUpdate(name, nextperiod) {
|
if self.hasUpdate(indexname, nextperiod) {
|
||||||
version = resource.version
|
version = rsrc.version
|
||||||
}
|
}
|
||||||
version++
|
version++
|
||||||
|
|
||||||
|
// calculate the chunk key
|
||||||
|
key := self.resourceHash(nextperiod, version, rsrc.nameHash)
|
||||||
|
|
||||||
|
// sign the data hash with the key
|
||||||
|
digest := self.keyDataHash(key, data)
|
||||||
|
signature, err := self.validator.sign(digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the signer has access to update
|
||||||
|
ok, err := self.validator.checkAccess(indexname, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !ok {
|
||||||
|
return nil, fmt.Errorf("Address %x does not have access to update %s", addr, indexname)
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk := newUpdateChunk(key, signature, nextperiod, version, self.resources[indexname].name, data)
|
||||||
|
|
||||||
|
// send the chunk
|
||||||
|
self.Put(chunk)
|
||||||
|
log.Trace("resource update", "name", rsrc.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
|
||||||
|
|
||||||
|
// update our resources map entry and return the new key
|
||||||
|
rsrc.lastPeriod = nextperiod
|
||||||
|
rsrc.version = version
|
||||||
|
rsrc.data = make([]byte, len(data))
|
||||||
|
copy(rsrc.data, data)
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdateChunk(key Key, signature Signature, period uint32, version uint32, name string, data []byte) *Chunk {
|
||||||
// create the update chunk
|
// create the update chunk
|
||||||
// prepend version and period to allow reverse lookups
|
// prepend version and period to allow reverse lookups
|
||||||
// data header length does NOT include the header length prefix bytes themselves
|
headerlength := uint16(len(name) + 4 + 4)
|
||||||
headerlength := uint16(len(resource.nameHash) + 4 + 4)
|
|
||||||
|
|
||||||
key := self.resourceHash(resource.nameHash, nextperiod, version)
|
|
||||||
chunk := NewChunk(key, nil)
|
chunk := NewChunk(key, nil)
|
||||||
chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data))
|
chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data))
|
||||||
|
|
||||||
|
|
@ -531,32 +513,24 @@ func (self *ResourceHandler) Update(name string, data []byte, signature Signatur
|
||||||
copy(chunk.SData, signature[:])
|
copy(chunk.SData, signature[:])
|
||||||
cursor += signatureLength
|
cursor += signatureLength
|
||||||
|
|
||||||
|
// data header length does NOT include the header length prefix bytes themselves
|
||||||
binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength)
|
binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength)
|
||||||
cursor += 2
|
cursor += 2
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(chunk.SData[cursor:], nextperiod)
|
binary.LittleEndian.PutUint32(chunk.SData[cursor:], period)
|
||||||
cursor += 4
|
cursor += 4
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(chunk.SData[cursor:], version)
|
binary.LittleEndian.PutUint32(chunk.SData[cursor:], version)
|
||||||
cursor += 4
|
cursor += 4
|
||||||
|
|
||||||
copy(chunk.SData[cursor:], resource.nameHash[:])
|
namebytes := []byte(name)
|
||||||
cursor += len(resource.nameHash)
|
copy(chunk.SData[cursor:], namebytes)
|
||||||
|
cursor += len(namebytes)
|
||||||
|
|
||||||
copy(chunk.SData[cursor:], data)
|
copy(chunk.SData[cursor:], data)
|
||||||
|
|
||||||
chunk.Size = int64(len(chunk.SData))
|
chunk.Size = int64(len(chunk.SData))
|
||||||
|
return chunk
|
||||||
// send the chunk
|
|
||||||
self.Put(chunk)
|
|
||||||
log.Trace("resource update", "name", resource.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
|
|
||||||
|
|
||||||
// update our resources map entry and return the new key
|
|
||||||
resource.lastPeriod = nextperiod
|
|
||||||
resource.version = version
|
|
||||||
resource.data = make([]byte, len(data))
|
|
||||||
copy(resource.data, data)
|
|
||||||
return key, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closes the datastore.
|
// Closes the datastore.
|
||||||
|
|
@ -599,80 +573,37 @@ func (self *ResourceHandler) setResource(name string, rsrc *resource) {
|
||||||
self.resources[name] = rsrc
|
self.resources[name] = rsrc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) resourceHash(namehash common.Hash, period uint32, version uint32) Key {
|
func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehash common.Hash) Key {
|
||||||
// format is: hash(namehash|period|version)
|
// format is: hash(namehash|period|version)
|
||||||
self.hashLock.Lock()
|
self.hashLock.Lock()
|
||||||
defer self.hashLock.Unlock()
|
defer self.hashLock.Unlock()
|
||||||
self.hasher.Reset()
|
self.hasher.Reset()
|
||||||
self.hasher.Write(namehash[:])
|
|
||||||
b := make([]byte, 4)
|
b := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(b, period)
|
binary.LittleEndian.PutUint32(b, period)
|
||||||
self.hasher.Write(b)
|
self.hasher.Write(b)
|
||||||
binary.LittleEndian.PutUint32(b, version)
|
binary.LittleEndian.PutUint32(b, version)
|
||||||
self.hasher.Write(b)
|
self.hasher.Write(b)
|
||||||
|
self.hasher.Write(namehash[:])
|
||||||
return self.hasher.Sum(nil)
|
return self.hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) getContentAccount(chunkdata []byte) (common.Address, error) {
|
func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Address, error) {
|
||||||
if len(chunkdata) <= signatureLength {
|
pub, err := crypto.SigToPub(datahash.Bytes(), signature[:])
|
||||||
return common.Address{}, fmt.Errorf("zero-length data")
|
|
||||||
}
|
|
||||||
signature, err := NewSignature(chunkdata[:signatureLength])
|
|
||||||
if err != nil {
|
|
||||||
return zeroAddr, err
|
|
||||||
}
|
|
||||||
return self.getAddressFromDataSig(chunkdata[signatureLength:], signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ResourceHandler) getContentName(chunkdata []byte) (string, error) {
|
|
||||||
nameoffset := signatureLength + 2 + 4 + 4
|
|
||||||
if len(chunkdata) < nameoffset {
|
|
||||||
return "", fmt.Errorf("invalid chunk data")
|
|
||||||
}
|
|
||||||
namelength := binary.LittleEndian.Uint16(chunkdata[signatureLength : signatureLength+2])
|
|
||||||
namebytes := make([]byte, namelength)
|
|
||||||
copy(namebytes, chunkdata[nameoffset:nameoffset+int(namelength)-2-4-4])
|
|
||||||
return string(namebytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ResourceHandler) getAddressFromDataSig(data []byte, signature Signature) (common.Address, error) {
|
|
||||||
self.hashLock.Lock()
|
|
||||||
self.hasher.Reset()
|
|
||||||
self.hasher.Write(data)
|
|
||||||
datahash := self.hasher.Sum(nil)
|
|
||||||
self.hashLock.Unlock()
|
|
||||||
pub, err := crypto.SigToPub(datahash, signature[:])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
return crypto.PubkeyToAddress(*pub), nil
|
return crypto.PubkeyToAddress(*pub), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) verifyContent(chunkdata []byte) error {
|
|
||||||
address, err := self.getContentAccount(chunkdata)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name, err := self.getContentName(chunkdata)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ok, err := self.validator.isOwner(name, address)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if !ok {
|
|
||||||
return fmt.Errorf("not owner")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ResourceHandler) hasUpdate(name string, period uint32) bool {
|
func (self *ResourceHandler) hasUpdate(name string, period uint32) bool {
|
||||||
return self.resources[name].lastPeriod == period
|
return self.resources[name].lastPeriod == period
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// \TODO chunkSize is a workaround until the ChunkStore interface exports a method to get the chunk size directly
|
||||||
type resourceChunkStore struct {
|
type resourceChunkStore struct {
|
||||||
localStore ChunkStore
|
localStore ChunkStore
|
||||||
netStore ChunkStore
|
netStore ChunkStore
|
||||||
|
chunkSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResourceChunkStore(path string, hasher SwarmHasher, localStore *LocalStore, cloudStore CloudStore) *resourceChunkStore {
|
func newResourceChunkStore(path string, hasher SwarmHasher, localStore *LocalStore, cloudStore CloudStore) *resourceChunkStore {
|
||||||
|
|
@ -717,3 +648,21 @@ func getNextPeriod(start uint64, current uint64, frequency uint64) uint32 {
|
||||||
period := blockdiff / frequency
|
period := blockdiff / frequency
|
||||||
return uint32(period + 1)
|
return uint32(period + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toSafeName(name string) (string, error) {
|
||||||
|
// make sure our ens identifier is idna safe
|
||||||
|
validname, err := idna.ToASCII(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return validname, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
|
||||||
|
self.hashLock.Lock()
|
||||||
|
defer self.hashLock.Unlock()
|
||||||
|
self.hasher.Reset()
|
||||||
|
self.hasher.Write(key[:])
|
||||||
|
self.hasher.Write(data)
|
||||||
|
return common.BytesToHash(self.hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,47 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ENS validation of mutable resource owners
|
type baseValidator struct {
|
||||||
type ENSValidator struct {
|
signFunc SignFunc
|
||||||
api *ens.ENS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts) (*ENSValidator, error) {
|
func (b *baseValidator) sign(datahash common.Hash) (Signature, error) {
|
||||||
|
if b.signFunc == nil {
|
||||||
|
return emptySignature, fmt.Errorf("No signature function")
|
||||||
|
}
|
||||||
|
return b.signFunc(datahash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENS validation of mutable resource owners
|
||||||
|
type ENSValidator struct {
|
||||||
|
*baseValidator
|
||||||
|
api *ens.ENS
|
||||||
|
hashlength int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts, signFunc SignFunc) (*ENSValidator, error) {
|
||||||
var err error
|
var err error
|
||||||
validator := &ENSValidator{}
|
validator := &ENSValidator{
|
||||||
|
baseValidator: &baseValidator{
|
||||||
|
signFunc: signFunc,
|
||||||
|
},
|
||||||
|
}
|
||||||
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)
|
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
validator.hashlength = len(ens.EnsNode(dbDirName).Bytes())
|
||||||
return validator, nil
|
return validator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ENSValidator) isOwner(name string, address common.Address) (bool, error) {
|
func (self *ENSValidator) checkAccess(name string, address common.Address) (bool, error) {
|
||||||
owneraddr, err := self.api.Owner(self.nameHash(name))
|
owneraddr, err := self.api.Owner(self.nameHash(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -35,15 +55,23 @@ func (self *ENSValidator) nameHash(name string) common.Hash {
|
||||||
|
|
||||||
// Default fallthrough validation of mutable resource ownership
|
// Default fallthrough validation of mutable resource ownership
|
||||||
type GenericValidator struct {
|
type GenericValidator struct {
|
||||||
|
*baseValidator
|
||||||
hashFunc func(string) common.Hash
|
hashFunc func(string) common.Hash
|
||||||
|
hashlength int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGenericValidator(hashFunc func(string) common.Hash) *GenericValidator {
|
func NewGenericValidator(hashFunc func(string) common.Hash, signFunc SignFunc) *GenericValidator {
|
||||||
return &GenericValidator{
|
return &GenericValidator{
|
||||||
|
baseValidator: &baseValidator{
|
||||||
|
signFunc: signFunc,
|
||||||
|
},
|
||||||
hashFunc: hashFunc,
|
hashFunc: hashFunc,
|
||||||
|
hashlength: len(hashFunc(dbDirName).Bytes()),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
func (self *GenericValidator) isOwner(name string, address common.Address) (bool, error) {
|
|
||||||
|
func (self *GenericValidator) checkAccess(name string, address common.Address) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hasher = MakeHashFunc("SHA3")()
|
testHasher = MakeHashFunc(SHA3Hash)()
|
||||||
zeroAddr = common.Address{}
|
zeroAddr = common.Address{}
|
||||||
startBlock = uint64(4200)
|
startBlock = uint64(4200)
|
||||||
resourceFrequency = uint64(42)
|
resourceFrequency = uint64(42)
|
||||||
|
|
@ -67,14 +67,8 @@ func (r *FakeRPC) BlockNumber() (string, error) {
|
||||||
// check that signature address matches update signer address
|
// check that signature address matches update signer address
|
||||||
func TestResourceSignature(t *testing.T) {
|
func TestResourceSignature(t *testing.T) {
|
||||||
|
|
||||||
// privkey for signing updates
|
|
||||||
privkey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up rpc and create resourcehandler
|
// set up rpc and create resourcehandler
|
||||||
rh, _, err, teardownTest := setupTest(privkey, nil, nil)
|
rh, _, signer, teardownTest, err := setupTest(nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -86,8 +80,7 @@ func TestResourceSignature(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a hash for block 4200 version 1
|
// generate a hash for block 4200 version 1
|
||||||
key := rh.resourceHash(ens.EnsNode(validname), 1, 1)
|
key := rh.resourceHash(1, 1, rh.validator.nameHash(validname))
|
||||||
chunk := NewChunk(key, nil)
|
|
||||||
|
|
||||||
// generate some bogus data for the chunk and sign it
|
// generate some bogus data for the chunk and sign it
|
||||||
data := make([]byte, 8)
|
data := make([]byte, 8)
|
||||||
|
|
@ -95,26 +88,26 @@ func TestResourceSignature(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
hasher.Reset()
|
testHasher.Reset()
|
||||||
hasher.Write(data)
|
testHasher.Write(data)
|
||||||
datahash := hasher.Sum(nil)
|
digest := rh.keyDataHash(key, data)
|
||||||
sig, err := crypto.Sign(datahash, privkey)
|
sig, err := rh.validator.sign(digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// put sig and data in the chunk
|
chunk := newUpdateChunk(key, sig, 1, 1, validname, data)
|
||||||
chunk.SData = make([]byte, 8+signatureLength)
|
|
||||||
copy(chunk.SData[:signatureLength], sig)
|
log.Warn("key", "chunk", chunk.Key, "real", key)
|
||||||
copy(chunk.SData[signatureLength:], 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
|
||||||
// TODO: change this to verifyContent on ENS integration
|
checksig, _, _, _, newdata, err := parseUpdate(chunk.SData)
|
||||||
recoveredaddress, err := rh.getContentAccount(chunk.SData)
|
checkdigest := rh.keyDataHash(chunk.Key, newdata)
|
||||||
|
recoveredaddress, err := getAddressFromDataSig(checkdigest, checksig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
originaladdress := crypto.PubkeyToAddress(privkey.PublicKey)
|
originaladdress := crypto.PubkeyToAddress(signer.privKey.PublicKey)
|
||||||
|
|
||||||
if recoveredaddress != originaladdress {
|
if recoveredaddress != originaladdress {
|
||||||
teardownTest(t, fmt.Errorf("addresses dont match: %x != %x", originaladdress, recoveredaddress))
|
teardownTest(t, fmt.Errorf("addresses dont match: %x != %x", originaladdress, recoveredaddress))
|
||||||
|
|
@ -122,90 +115,69 @@ func TestResourceSignature(t *testing.T) {
|
||||||
teardownTest(t, nil)
|
teardownTest(t, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine resource update metadata from chunk data
|
//
|
||||||
func TestResourceReverseLookup(t *testing.T) {
|
//// determine resource update metadata from chunk data
|
||||||
|
//func TestResourceReverseLookup(t *testing.T) {
|
||||||
// privkey for signing updates
|
//
|
||||||
privkey, err := crypto.GenerateKey()
|
// // make fake backend, set up rpc and create resourcehandler
|
||||||
if err != nil {
|
// backend := &fakeBackend{
|
||||||
return
|
// blocknumber: startBlock,
|
||||||
}
|
// }
|
||||||
|
// rh, _, _, teardownTest, err := setupTest(backend, nil)
|
||||||
// make fake backend, set up rpc and create resourcehandler
|
// if err != nil {
|
||||||
backend := &fakeBackend{
|
// teardownTest(t, err)
|
||||||
blocknumber: startBlock,
|
// }
|
||||||
}
|
//
|
||||||
rh, _, err, teardownTest := setupTest(privkey, backend, nil)
|
// rsrc, err := rh.NewResource(domainName, resourceFrequency, false)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
teardownTest(t, err)
|
// teardownTest(t, err)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// create a new resource
|
// // update data
|
||||||
signature, err := signContent(privkey, []byte(domainName))
|
// fwdBlocks(int(resourceFrequency+1), backend)
|
||||||
if err != nil {
|
// data := []byte("foo")
|
||||||
teardownTest(t, err)
|
// resourcekey, err := rh.Update(domainName, data)
|
||||||
}
|
// if err != nil {
|
||||||
|
// teardownTest(t, err)
|
||||||
rsrc, err := rh.NewResource(domainName, resourceFrequency, signature)
|
// }
|
||||||
if err != nil {
|
// chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(resourcekey))
|
||||||
teardownTest(t, err)
|
// if err != nil {
|
||||||
}
|
// teardownTest(t, err)
|
||||||
|
// }
|
||||||
// update data
|
//
|
||||||
fwdBlocks(int(resourceFrequency+1), backend)
|
// // check if data after header length offset is as expected
|
||||||
data := []byte("foo")
|
// headerlength := binary.LittleEndian.Uint16(chunk.SData[signatureLength : signatureLength+2])
|
||||||
signature, err = signContent(privkey, data)
|
// if !bytes.Equal(chunk.SData[signatureLength+headerlength+2:], data) {
|
||||||
if err != nil {
|
// teardownTest(t, fmt.Errorf("Expected chunk data with header length %d (pos %d) to match %x, but was %x", headerlength, signatureLength+headerlength+2, data, chunk.SData[signatureLength+headerlength+2:]))
|
||||||
teardownTest(t, err)
|
// }
|
||||||
}
|
//
|
||||||
|
// // get name, period, version from chunk and check
|
||||||
resourcekey, err := rh.Update(domainName, data, signature)
|
// _, revperiod, revversion, revname, revdata, err := parseUpdate(chunk.SData[signatureLength:])
|
||||||
if err != nil {
|
//
|
||||||
teardownTest(t, err)
|
// //if !bytes.Equal(revname, rsrc.nameHash.Bytes()) {
|
||||||
}
|
// if revname == rsrc.name {
|
||||||
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(resourcekey))
|
// teardownTest(t, fmt.Errorf("Expected retrieved name from chunk data to be '%x', was '%x'", rsrc.nameHash.Bytes(), revname))
|
||||||
if err != nil {
|
// }
|
||||||
teardownTest(t, err)
|
// if !bytes.Equal(revdata, data) {
|
||||||
}
|
// teardownTest(t, fmt.Errorf("Expected retrieved data from chunk data to be '%x', was '%x'", data, revdata))
|
||||||
|
// }
|
||||||
// check if data after header length offset is as expected
|
//
|
||||||
headerlength := binary.LittleEndian.Uint16(chunk.SData[signatureLength : signatureLength+2])
|
// if revperiod != 2 {
|
||||||
if !bytes.Equal(chunk.SData[signatureLength+headerlength+2:], data) {
|
// teardownTest(t, fmt.Errorf("Expected retrieved period from chunk data to be 1, was %d", revperiod))
|
||||||
teardownTest(t, fmt.Errorf("Expected chunk data with header length %d (pos %d) to match %x, but was %x", headerlength, signatureLength+headerlength+2, data, chunk.SData[signatureLength+headerlength+2:]))
|
// }
|
||||||
}
|
// if revversion != 1 {
|
||||||
|
// teardownTest(t, fmt.Errorf("Expected retrieved version from chunk data to be 1, was %d", revversion))
|
||||||
// get name, period, version from chunk and check
|
// }
|
||||||
revperiod, revversion, revname, revdata, err := parseUpdate(chunk.SData[signatureLength:])
|
//}
|
||||||
|
//
|
||||||
if !bytes.Equal(revname, rsrc.nameHash.Bytes()) {
|
|
||||||
teardownTest(t, fmt.Errorf("Expected retrieved name from chunk data to be '%x', was '%x'", rsrc.nameHash.Bytes(), revname))
|
|
||||||
}
|
|
||||||
if !bytes.Equal(revdata, data) {
|
|
||||||
teardownTest(t, fmt.Errorf("Expected retrieved data from chunk data to be '%x', was '%x'", data, revdata))
|
|
||||||
}
|
|
||||||
|
|
||||||
if revperiod != 2 {
|
|
||||||
teardownTest(t, fmt.Errorf("Expected retrieved period from chunk data to be 1, was %d", revperiod))
|
|
||||||
}
|
|
||||||
if revversion != 1 {
|
|
||||||
teardownTest(t, fmt.Errorf("Expected retrieved version from chunk data to be 1, was %d", revversion))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make updates and retrieve them based on periods and versions
|
// make updates and retrieve them based on periods and versions
|
||||||
func TestResourceHandler(t *testing.T) {
|
func TestResourceHandler(t *testing.T) {
|
||||||
|
|
||||||
// privkey for signing updates
|
|
||||||
privkey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make fake backend, set up rpc and create resourcehandler
|
// make fake backend, set up rpc and create resourcehandler
|
||||||
backend := &fakeBackend{
|
backend := &fakeBackend{
|
||||||
blocknumber: startBlock,
|
blocknumber: startBlock,
|
||||||
}
|
}
|
||||||
rh, datadir, err, teardownTest := setupTest(privkey, backend, nil)
|
rh, datadir, _, teardownTest, err := setupTest(backend, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -215,8 +187,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
signature, err := signContent(privkey, []byte(resourcevalidname))
|
_, err = rh.NewResource(domainName, resourceFrequency, false)
|
||||||
_, err = rh.NewResource(domainName, resourceFrequency, signature)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -229,8 +200,8 @@ func TestResourceHandler(t *testing.T) {
|
||||||
} else if len(chunk.SData) < 16 {
|
} else if len(chunk.SData) < 16 {
|
||||||
teardownTest(t, fmt.Errorf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData)))
|
teardownTest(t, fmt.Errorf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData)))
|
||||||
}
|
}
|
||||||
startblocknumber := binary.LittleEndian.Uint64(chunk.SData[8:16])
|
startblocknumber := binary.LittleEndian.Uint64(chunk.SData[:8])
|
||||||
chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[16:])
|
chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[8:])
|
||||||
if startblocknumber != backend.blocknumber {
|
if startblocknumber != backend.blocknumber {
|
||||||
teardownTest(t, fmt.Errorf("stored block number %d does not match provided block number %d", startblocknumber, backend.blocknumber))
|
teardownTest(t, fmt.Errorf("stored block number %d does not match provided block number %d", startblocknumber, backend.blocknumber))
|
||||||
}
|
}
|
||||||
|
|
@ -242,11 +213,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
resourcekey := make(map[string]Key)
|
resourcekey := make(map[string]Key)
|
||||||
fwdBlocks(int(resourceFrequency/2), backend)
|
fwdBlocks(int(resourceFrequency/2), backend)
|
||||||
data := []byte("blinky")
|
data := []byte("blinky")
|
||||||
signature, err = signContent(privkey, data)
|
resourcekey["blinky"], err = rh.Update(domainName, data)
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
resourcekey["blinky"], err = rh.Update(domainName, data, signature)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -254,11 +221,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
// update on first period
|
// update on first period
|
||||||
fwdBlocks(int(resourceFrequency/2), backend)
|
fwdBlocks(int(resourceFrequency/2), backend)
|
||||||
data = []byte("pinky")
|
data = []byte("pinky")
|
||||||
signature, err = signContent(privkey, data)
|
resourcekey["pinky"], err = rh.Update(domainName, data)
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
resourcekey["pinky"], err = rh.Update(domainName, data, signature)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -266,11 +229,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
// update on second period
|
// update on second period
|
||||||
fwdBlocks(int(resourceFrequency), backend)
|
fwdBlocks(int(resourceFrequency), backend)
|
||||||
data = []byte("inky")
|
data = []byte("inky")
|
||||||
signature, err = signContent(privkey, data)
|
resourcekey["inky"], err = rh.Update(domainName, data)
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
resourcekey["inky"], err = rh.Update(domainName, data, signature)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -278,11 +237,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
// update just after second period
|
// update just after second period
|
||||||
fwdBlocks(1, backend)
|
fwdBlocks(1, backend)
|
||||||
data = []byte("clyde")
|
data = []byte("clyde")
|
||||||
signature, err = signContent(privkey, data)
|
resourcekey["clyde"], err = rh.Update(domainName, data)
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
resourcekey["clyde"], err = rh.Update(domainName, data, signature)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +248,7 @@ func TestResourceHandler(t *testing.T) {
|
||||||
// it will match on second iteration startblocknumber + (resourceFrequency * 3)
|
// it will match on second iteration startblocknumber + (resourceFrequency * 3)
|
||||||
fwdBlocks(int(resourceFrequency*2)-1, backend)
|
fwdBlocks(int(resourceFrequency*2)-1, backend)
|
||||||
|
|
||||||
rh2, err := NewResourceHandler(privkey, datadir, &testCloudStore{}, rh.rpcClient, nil)
|
rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.rpcClient, nil)
|
||||||
_, err = rh2.LookupLatest(domainName, true)
|
_, err = rh2.LookupLatest(domainName, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
|
|
@ -310,45 +265,25 @@ func TestResourceHandler(t *testing.T) {
|
||||||
teardownTest(t, fmt.Errorf("resource period was %d, expected 3", rh2.resources[domainName].lastPeriod))
|
teardownTest(t, fmt.Errorf("resource period was %d, expected 3", rh2.resources[domainName].lastPeriod))
|
||||||
}
|
}
|
||||||
|
|
||||||
rsrc, err := NewResource(domainName, startblocknumber, resourceFrequency, rh2.validator.nameHash)
|
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
err = rh2.SetExternalResource(rsrc, true)
|
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// latest block, latest version
|
|
||||||
resource, err := rh2.LookupLatest(domainName, false) // if key is specified, refresh is implicit
|
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check data
|
|
||||||
if !bytes.Equal(resource.data, []byte("clyde")) {
|
|
||||||
teardownTest(t, fmt.Errorf("resource data (latest) was %v, expected %v", rh2.resources[domainName].data, []byte("clyde")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// specific block, latest version
|
// specific block, latest version
|
||||||
resource, err = rh2.LookupHistorical(domainName, 3, true)
|
rsrc, err := rh2.LookupHistorical(domainName, 3, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check data
|
// check data
|
||||||
if !bytes.Equal(resource.data, []byte("clyde")) {
|
if !bytes.Equal(rsrc.data, []byte("clyde")) {
|
||||||
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("clyde")))
|
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("clyde")))
|
||||||
}
|
}
|
||||||
|
|
||||||
// specific block, specific version
|
// specific block, specific version
|
||||||
resource, err = rh2.LookupVersion(domainName, 3, 1, true)
|
rsrc, err = rh2.LookupVersion(domainName, 3, 1, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check data
|
// check data
|
||||||
if !bytes.Equal(resource.data, []byte("inky")) {
|
if !bytes.Equal(rsrc.data, []byte("inky")) {
|
||||||
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("inky")))
|
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("inky")))
|
||||||
}
|
}
|
||||||
teardownTest(t, nil)
|
teardownTest(t, nil)
|
||||||
|
|
@ -358,21 +293,15 @@ func TestResourceHandler(t *testing.T) {
|
||||||
// create ENS enabled resource update, with and without valid owner
|
// create ENS enabled resource update, with and without valid owner
|
||||||
func TestResourceENSOwner(t *testing.T) {
|
func TestResourceENSOwner(t *testing.T) {
|
||||||
|
|
||||||
// privkey for signing updates
|
// signer containing private key
|
||||||
privkey, err := crypto.GenerateKey()
|
signer, err := newTestSigner()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
t.Fatal(err)
|
||||||
}
|
|
||||||
|
|
||||||
// privkey for checking wrong owner
|
|
||||||
privkeytwo, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ens address and transact options
|
// ens address and transact options
|
||||||
addr := crypto.PubkeyToAddress(privkey.PublicKey)
|
addr := crypto.PubkeyToAddress(signer.privKey.PublicKey)
|
||||||
transactOpts := bind.NewKeyedTransactor(privkey)
|
transactOpts := bind.NewKeyedTransactor(signer.privKey)
|
||||||
|
|
||||||
// set up ENS sim
|
// set up ENS sim
|
||||||
domainparts := strings.Split(domainName, ".")
|
domainparts := strings.Split(domainName, ".")
|
||||||
|
|
@ -381,40 +310,37 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts)
|
validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts, signer.signContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up rpc and create resourcehandler with ENS sim backend
|
// set up rpc and create resourcehandler with ENS sim backend
|
||||||
rh, _, err, teardownTest := setupTest(privkey, contractbackend, validator)
|
rh, _, _, teardownTest, err := setupTest(contractbackend, validator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := signContent(privkey, []byte(domainName))
|
|
||||||
if err != nil {
|
|
||||||
teardownTest(t, err)
|
|
||||||
}
|
|
||||||
// create new resource when we are owner = ok
|
// create new resource when we are owner = ok
|
||||||
_, err = rh.NewResource(domainName, 42, signature)
|
_, err = rh.NewResource(domainName, resourceFrequency, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, fmt.Errorf("Create resource fail: %v", err))
|
teardownTest(t, fmt.Errorf("Create resource fail: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []byte("foo")
|
data := []byte("foo")
|
||||||
signature, err = signContent(privkey, data)
|
|
||||||
|
|
||||||
// update resource when we are owner = ok
|
// update resource when we are owner = ok
|
||||||
_, err = rh.Update(domainName, data, signature)
|
_, err = rh.Update(domainName, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, fmt.Errorf("Update resource fail: %v", err))
|
teardownTest(t, fmt.Errorf("Update resource fail: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new resource when we are NOT owner = !ok
|
|
||||||
signaturetwo, err := signContent(privkeytwo, data)
|
|
||||||
// update resource when we are owner = ok
|
// update resource when we are owner = ok
|
||||||
_, err = rh.Update(domainName, data, signaturetwo)
|
signertwo, err := newTestSigner()
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
|
rh.validator.(*ENSValidator).signFunc = signertwo.signContent
|
||||||
|
_, err = rh.Update(domainName, data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch"))
|
teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch"))
|
||||||
}
|
}
|
||||||
|
|
@ -430,7 +356,7 @@ func fwdBlocks(count int, backend *fakeBackend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create rpc and resourcehandler
|
// create rpc and resourcehandler
|
||||||
func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend, validator ResourceValidator) (rh *ResourceHandler, datadir string, err error, teardown func(*testing.T, error)) {
|
func setupTest(contractbackend bind.ContractBackend, validator ResourceValidator) (rh *ResourceHandler, datadir string, signer *testSigner, teardown func(*testing.T, error), err error) {
|
||||||
|
|
||||||
var fsClean func()
|
var fsClean func()
|
||||||
var rpcClean func()
|
var rpcClean func()
|
||||||
|
|
@ -443,6 +369,15 @@ func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if validator == nil {
|
||||||
|
// create a new signer, which creates the private key
|
||||||
|
signer, err = newTestSigner()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
validator = NewGenericValidator(testHashFunc, signer.signContent)
|
||||||
|
}
|
||||||
|
|
||||||
// temp datadir
|
// temp datadir
|
||||||
datadir, err = ioutil.TempDir("", "rh")
|
datadir, err = ioutil.TempDir("", "rh")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -480,8 +415,7 @@ func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// choose if with ens or not
|
rh, err = NewResourceHandler(datadir, &testCloudStore{}, rpcclient, validator)
|
||||||
rh, err = NewResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient, validator)
|
|
||||||
teardown = func(t *testing.T, err error) {
|
teardown = func(t *testing.T, err error) {
|
||||||
cleanF()
|
cleanF()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -499,12 +433,12 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string,
|
||||||
var tophash [32]byte
|
var tophash [32]byte
|
||||||
var subhash [32]byte
|
var subhash [32]byte
|
||||||
|
|
||||||
hasher.Reset()
|
testHasher.Reset()
|
||||||
hasher.Write([]byte(top))
|
testHasher.Write([]byte(top))
|
||||||
copy(tophash[:], hasher.Sum(nil))
|
copy(tophash[:], testHasher.Sum(nil))
|
||||||
hasher.Reset()
|
testHasher.Reset()
|
||||||
hasher.Write([]byte(sub))
|
testHasher.Write([]byte(sub))
|
||||||
copy(subhash[:], hasher.Sum(nil))
|
copy(subhash[:], testHasher.Sum(nil))
|
||||||
|
|
||||||
// initialize contract backend and deploy
|
// initialize contract backend and deploy
|
||||||
contractBackend := &fakeBackend{
|
contractBackend := &fakeBackend{
|
||||||
|
|
@ -535,17 +469,35 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string,
|
||||||
return contractAddress, contractBackend, nil
|
return contractAddress, contractBackend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func signContent(privKey *ecdsa.PrivateKey, data []byte) (Signature, error) {
|
func testHashFunc(name string) common.Hash {
|
||||||
hasher.Reset()
|
testHasher.Reset()
|
||||||
hasher.Write(data)
|
testHasher.Write([]byte(name))
|
||||||
datahash := hasher.Sum(nil)
|
return common.BytesToHash(testHasher.Sum(nil))
|
||||||
|
|
||||||
signaturebytes, err := crypto.Sign(datahash, privKey)
|
|
||||||
if err != nil {
|
|
||||||
return [signatureLength]byte{}, err
|
|
||||||
}
|
}
|
||||||
signature, err := NewSignature(signaturebytes)
|
|
||||||
return signature, err
|
type testSigner struct {
|
||||||
|
privKey *ecdsa.PrivateKey
|
||||||
|
hasher SwarmHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestSigner() (*testSigner, error) {
|
||||||
|
privKey, err := crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &testSigner{
|
||||||
|
privKey: privKey,
|
||||||
|
hasher: testHasher,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *testSigner) signContent(data common.Hash) (signature Signature, err error) {
|
||||||
|
signaturebytes, err := crypto.Sign(data.Bytes(), self.privKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signature, err = bytesToSignature(signaturebytes)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type testCloudStore struct {
|
type testCloudStore struct {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue