mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
swarm/storage: Remove signatures from non-validated resources
This commit is contained in:
parent
c051bec505
commit
1b81fb85c2
3 changed files with 260 additions and 215 deletions
|
|
@ -25,14 +25,14 @@ const (
|
||||||
|
|
||||||
type Signature [signatureLength]byte
|
type Signature [signatureLength]byte
|
||||||
|
|
||||||
var emptySignature Signature
|
|
||||||
|
|
||||||
type SignFunc func(common.Hash) (Signature, error)
|
type SignFunc func(common.Hash) (Signature, error)
|
||||||
|
|
||||||
// Encapsulates an actual resource update. When synced it contains the most recent
|
type nameHashFunc func(string) common.Hash
|
||||||
|
|
||||||
|
// Encapsulates an specific resource update. When synced it contains the most recent
|
||||||
// version of the resource update data.
|
// version of the resource update data.
|
||||||
type resource struct {
|
type resource struct {
|
||||||
name string
|
name *string
|
||||||
nameHash common.Hash
|
nameHash common.Hash
|
||||||
startBlock uint64
|
startBlock uint64
|
||||||
lastPeriod uint32
|
lastPeriod uint32
|
||||||
|
|
@ -47,6 +47,14 @@ func (r *resource) isSynced() bool {
|
||||||
return !r.updated.IsZero()
|
return !r.updated.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement to activate validation of resource updates
|
||||||
|
// Specifically signing data and verification of signatures
|
||||||
|
type ResourceValidator interface {
|
||||||
|
checkAccess(string, common.Address) (bool, error)
|
||||||
|
nameHash(string) common.Hash // nameHashFunc
|
||||||
|
sign(common.Hash) (Signature, error) // SignFunc
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -56,8 +64,9 @@ func (r *resource) isSynced() bool {
|
||||||
// expressed in terms of number of blocks.
|
// expressed in terms of number of blocks.
|
||||||
//
|
//
|
||||||
// The root entry of a mutable resource is tied to a unique identifier,
|
// The root entry of a mutable resource is tied to a unique identifier,
|
||||||
// typically - but not necessarily - an ens name. It also contains the
|
// typically - but not necessarily - an ens name. The identifier must be
|
||||||
// block number when the resource update was first registered, and
|
// an valid IDNA string. It also contains the block number
|
||||||
|
// when the resource update was first registered, and
|
||||||
// the block frequency with which the resource will be updated, both of
|
// the block frequency with which the resource will be updated, both of
|
||||||
// which are stored as little-endian uint64 values in the database (for a
|
// which are stored as little-endian uint64 values in the database (for a
|
||||||
// total of 16 bytes).
|
// total of 16 bytes).
|
||||||
|
|
@ -68,10 +77,6 @@ func (r *resource) isSynced() bool {
|
||||||
// starting at block 4200 with frequency 42 will have updates on block 4242,
|
// starting at block 4200 with frequency 42 will have updates on block 4242,
|
||||||
// 4284, 4326 and so on.
|
// 4284, 4326 and so on.
|
||||||
//
|
//
|
||||||
// The identifier is supplied as a string, but will be IDNA converted and
|
|
||||||
// passed through the ENS namehash function. Pure ascii identifiers without
|
|
||||||
// periods will thus merely be hashed.
|
|
||||||
//
|
|
||||||
// Note that the root entry is not required for the resource update scheme to
|
// Note that the root entry is not required for the resource update scheme to
|
||||||
// work. A normal chunk of the blocknumber/frequency data can also be created,
|
// work. A normal chunk of the blocknumber/frequency data can also be created,
|
||||||
// and pointed to by an external resource (ENS or manifest entry)
|
// and pointed to by an external resource (ENS or manifest entry)
|
||||||
|
|
@ -79,7 +84,7 @@ func (r *resource) isSynced() bool {
|
||||||
// Actual data updates are also made in the form of swarm chunks. The keys
|
// Actual data updates are also made in the form of swarm chunks. The keys
|
||||||
// of the updates are the hash of a concatenation of properties as follows:
|
// of the updates are the hash of a concatenation of properties as follows:
|
||||||
//
|
//
|
||||||
// sha256(namehash|period|version)
|
// sha256(period|version|namehash)
|
||||||
//
|
//
|
||||||
// The period is (currentblock - startblock) / frequency
|
// The period is (currentblock - startblock) / frequency
|
||||||
//
|
//
|
||||||
|
|
@ -91,8 +96,12 @@ func (r *resource) isSynced() bool {
|
||||||
//
|
//
|
||||||
// A lookup agent need only know the identifier name in order to get the versions
|
// A lookup agent need only know the identifier name in order to get the versions
|
||||||
//
|
//
|
||||||
// the chunk data is: sign(resourcedata)|resourcedata
|
// the resourcedata is:
|
||||||
// the resourcedata is: headerlength|period|version|name|data
|
// headerlength|period|version|identifier|data
|
||||||
|
//
|
||||||
|
// if a validator is active, the chunk data is:
|
||||||
|
// sign(resourcedata)|resourcedata
|
||||||
|
// otherwise, the chunk data is the same as the resourcedata
|
||||||
//
|
//
|
||||||
// headerlength is a 16 bit value containing the byte length of period|version|name
|
// headerlength is a 16 bit value containing the byte length of period|version|name
|
||||||
// period and version are both 32 bit values. name can have arbitrary length
|
// period and version are both 32 bit values. name can have arbitrary length
|
||||||
|
|
@ -103,13 +112,6 @@ func (r *resource) isSynced() bool {
|
||||||
// stored using a separate store, and forwarding/syncing protocols carry per-chunk
|
// stored using a separate store, and forwarding/syncing protocols carry per-chunk
|
||||||
// flags to tell whether the chunk can be validated or not; if not it is to be
|
// flags to tell whether the chunk can be validated or not; if not it is to be
|
||||||
// treated as a resource update chunk.
|
// treated as a resource update chunk.
|
||||||
|
|
||||||
type ResourceValidator interface {
|
|
||||||
checkAccess(string, common.Address) (bool, error)
|
|
||||||
nameHash(string) common.Hash
|
|
||||||
sign(common.Hash) (Signature, error) // SignFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceHandler struct {
|
type ResourceHandler struct {
|
||||||
ChunkStore
|
ChunkStore
|
||||||
validator ResourceValidator
|
validator ResourceValidator
|
||||||
|
|
@ -118,9 +120,12 @@ type ResourceHandler struct {
|
||||||
hashLock sync.Mutex
|
hashLock sync.Mutex
|
||||||
resourceLock sync.RWMutex
|
resourceLock sync.RWMutex
|
||||||
hasher SwarmHash
|
hasher SwarmHash
|
||||||
|
nameHash nameHashFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or open resource update chunk store
|
// Create or open resource update chunk store
|
||||||
|
//
|
||||||
|
// If validator is nil, signature and access validation will be deactivated
|
||||||
func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Client, validator ResourceValidator) (*ResourceHandler, error) {
|
func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Client, validator ResourceValidator) (*ResourceHandler, error) {
|
||||||
|
|
||||||
hashfunc := MakeHashFunc(SHA3Hash)
|
hashfunc := MakeHashFunc(SHA3Hash)
|
||||||
|
|
@ -140,18 +145,19 @@ func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Cl
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
resources: make(map[string]*resource),
|
resources: make(map[string]*resource),
|
||||||
hasher: hashfunc(),
|
hasher: hashfunc(),
|
||||||
|
validator: validator,
|
||||||
}
|
}
|
||||||
|
|
||||||
if validator != nil {
|
if rh.validator != nil {
|
||||||
rh.validator = validator
|
rh.nameHash = rh.validator.nameHash
|
||||||
} else {
|
} else {
|
||||||
rh.validator = NewGenericValidator(func(name string) common.Hash {
|
rh.nameHash = func(name string) common.Hash {
|
||||||
rh.hashLock.Lock()
|
rh.hashLock.Lock()
|
||||||
defer rh.hashLock.Unlock()
|
defer rh.hashLock.Unlock()
|
||||||
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
|
||||||
|
|
@ -167,26 +173,20 @@ func (self *ResourceHandler) chunkSize() int64 {
|
||||||
// The signature data should match the hash of the idna-converted name by the validator's namehash function, NOT the raw name bytes.
|
// 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, verify bool) (*resource, error) {
|
func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resource, error) {
|
||||||
|
|
||||||
// frequency 0 is invalid
|
// frequency 0 is invalid
|
||||||
if frequency == 0 {
|
if frequency == 0 {
|
||||||
return nil, fmt.Errorf("Frequency cannot be 0")
|
return nil, fmt.Errorf("Frequency cannot be 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// must have name
|
if !isSafeName(name) {
|
||||||
if name == "" {
|
return nil, fmt.Errorf("Invalid name: '%s'", name)
|
||||||
return nil, fmt.Errorf("Empty name")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validName, err := toSafeName(name)
|
nameHash := self.nameHash(name)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nameHash := self.validator.nameHash(validName)
|
if self.validator != nil {
|
||||||
|
|
||||||
if verify {
|
|
||||||
signature, err := self.validator.sign(nameHash)
|
signature, err := self.validator.sign(nameHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Sign fail: %v", err)
|
return nil, fmt.Errorf("Sign fail: %v", err)
|
||||||
|
|
@ -220,10 +220,10 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64, verify b
|
||||||
binary.LittleEndian.PutUint64(val, frequency)
|
binary.LittleEndian.PutUint64(val, frequency)
|
||||||
copy(chunk.SData[8:], 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", name, "key", nameHash, "startBlock", currentblock, "frequency", frequency)
|
||||||
|
|
||||||
rsrc := &resource{
|
rsrc := &resource{
|
||||||
name: validName,
|
name: &name,
|
||||||
nameHash: nameHash,
|
nameHash: nameHash,
|
||||||
startBlock: currentblock,
|
startBlock: currentblock,
|
||||||
frequency: frequency,
|
frequency: frequency,
|
||||||
|
|
@ -231,7 +231,7 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64, verify b
|
||||||
}
|
}
|
||||||
self.setResource(name, rsrc)
|
self.setResource(name, rsrc)
|
||||||
|
|
||||||
return self.resources[name], nil
|
return rsrc, 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`
|
||||||
|
|
@ -247,7 +247,7 @@ func (self *ResourceHandler) LookupVersion(name string, period uint32, version u
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return self.lookup(rsrc, name, period, version, refresh)
|
return self.lookup(rsrc, period, version, refresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves the latest version of the resource update identified by `name`
|
// Retrieves the latest version of the resource update identified by `name`
|
||||||
|
|
@ -263,7 +263,7 @@ func (self *ResourceHandler) LookupHistorical(name string, period uint32, refres
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return self.lookup(rsrc, name, period, 0, refresh)
|
return self.lookup(rsrc, period, 0, refresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves the latest version of the resource update identified by `name`
|
// Retrieves the latest version of the resource update identified by `name`
|
||||||
|
|
@ -288,11 +288,11 @@ func (self *ResourceHandler) LookupLatest(name string, refresh bool) (*resource,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nextperiod := getNextPeriod(rsrc.startBlock, currentblock, rsrc.frequency)
|
nextperiod := getNextPeriod(rsrc.startBlock, currentblock, rsrc.frequency)
|
||||||
return self.lookup(rsrc, name, nextperiod, 0, refresh)
|
return self.lookup(rsrc, nextperiod, 0, refresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// base code for public lookup methods
|
// base code for public lookup methods
|
||||||
func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32, version uint32, refresh bool) (*resource, error) {
|
func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint32, refresh bool) (*resource, error) {
|
||||||
|
|
||||||
if period == 0 {
|
if period == 0 {
|
||||||
return nil, fmt.Errorf("period must be >0")
|
return nil, fmt.Errorf("period must be >0")
|
||||||
|
|
@ -312,7 +312,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32,
|
||||||
chunk, err := self.Get(key)
|
chunk, err := self.Get(key)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if specificversion {
|
if specificversion {
|
||||||
return self.updateResourceIndex(rsrc, chunk, &name)
|
return self.updateResourceIndex(rsrc, chunk)
|
||||||
}
|
}
|
||||||
// check if we have versions > 1. If a version fails, the previous version is used and returned.
|
// check if we have versions > 1. If a version fails, the previous version is used and returned.
|
||||||
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)
|
||||||
|
|
@ -321,7 +321,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32,
|
||||||
key := self.resourceHash(period, newversion, rsrc.nameHash)
|
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)
|
||||||
}
|
}
|
||||||
log.Trace("version update found, checking next", "version", version, "period", period, "key", key)
|
log.Trace("version update found, checking next", "version", version, "period", period, "key", key)
|
||||||
chunk = newchunk
|
chunk = newchunk
|
||||||
|
|
@ -336,19 +336,18 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32,
|
||||||
|
|
||||||
// load existing mutable resource into resource struct
|
// load existing mutable resource into resource struct
|
||||||
func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource, error) {
|
func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource, error) {
|
||||||
|
|
||||||
// if the resource is not known to this session we must load it
|
// if the resource is not known to this session we must load it
|
||||||
// if refresh is set, we force load
|
// if refresh is set, we force load
|
||||||
|
|
||||||
rsrc := self.getResource(name)
|
rsrc := self.getResource(name)
|
||||||
if rsrc == nil || refresh {
|
if rsrc == nil || refresh {
|
||||||
rsrc = &resource{}
|
rsrc = &resource{}
|
||||||
// make sure our name is safe to use
|
// make sure our name is safe to use
|
||||||
validname, err := toSafeName(name)
|
if !isSafeName(name) {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("Invalid name '%s'")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
rsrc.name = validname
|
rsrc.name = &name
|
||||||
rsrc.nameHash = self.validator.nameHash(validname)
|
rsrc.nameHash = self.nameHash(name)
|
||||||
|
|
||||||
// get the root info chunk and update the cached value
|
// get the root info chunk and update the cached value
|
||||||
chunk, err := self.Get(Key(rsrc.nameHash[:]))
|
chunk, err := self.Get(Key(rsrc.nameHash[:]))
|
||||||
|
|
@ -356,7 +355,7 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanity check for chunk data
|
// minimum sanity check for chunk data
|
||||||
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, should be %d", len(chunk.SData), indexSize)
|
||||||
}
|
}
|
||||||
|
|
@ -372,46 +371,58 @@ 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) (*resource, error) {
|
||||||
|
|
||||||
// update our rsrcs entry map
|
// retrieve metadata from chunk data and check that it matches this mutable resource
|
||||||
signature, period, version, name, data, err := parseUpdate(chunk.SData)
|
signature, period, version, name, data, err := self.parseUpdate(chunk.SData)
|
||||||
if rsrc.name != name {
|
if *rsrc.name != name {
|
||||||
return nil, fmt.Errorf("Update belongs to '%s', but have '%s'", name, rsrc.name)
|
return nil, fmt.Errorf("Update belongs to '%s', but have '%s'", name, rsrc.name)
|
||||||
}
|
}
|
||||||
self.hashLock.Lock()
|
// only check signature if validator is present
|
||||||
self.hasher.Reset()
|
if self.validator != nil {
|
||||||
self.hasher.Write(chunk.Key[:])
|
digest := self.keyDataHash(chunk.Key, data)
|
||||||
self.hasher.Write(data)
|
_, err = getAddressFromDataSig(digest, *signature)
|
||||||
digest := self.hasher.Sum(nil)
|
if err != nil {
|
||||||
self.hashLock.Unlock()
|
return nil, fmt.Errorf("Invalid signature: %v", err)
|
||||||
_, err = getAddressFromDataSig(common.BytesToHash(digest), signature)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Invalid signature: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update our rsrcs entry map
|
||||||
rsrc.lastPeriod = period
|
rsrc.lastPeriod = period
|
||||||
rsrc.version = version
|
rsrc.version = version
|
||||||
rsrc.updated = time.Now()
|
rsrc.updated = time.Now()
|
||||||
rsrc.data = make([]byte, len(data))
|
rsrc.data = make([]byte, len(data))
|
||||||
copy(rsrc.data, data)
|
copy(rsrc.data, data)
|
||||||
log.Debug("Resource synced", "name", rsrc.name, "key", chunk.Key, "period", rsrc.lastPeriod, "version", rsrc.version)
|
log.Debug("Resource synced", "name", *rsrc.name, "key", chunk.Key, "period", rsrc.lastPeriod, "version", rsrc.version)
|
||||||
self.setResource(*indexname, rsrc)
|
self.setResource(*rsrc.name, rsrc)
|
||||||
return rsrc, nil
|
return rsrc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUpdate(chunkdata []byte) (signature Signature, period uint32, version uint32, name string, data []byte, err error) {
|
// retrieve update metadata from chunk data
|
||||||
copy(signature[:], chunkdata[:signatureLength])
|
// mirrors newUpdateChunk()
|
||||||
cursor := signatureLength
|
func (self *ResourceHandler) parseUpdate(chunkdata []byte) (signature *Signature, period uint32, version uint32, name string, data []byte, err error) {
|
||||||
|
cursor := 0
|
||||||
|
|
||||||
|
// omit signatures if we have no validator
|
||||||
|
var sigoffset int
|
||||||
|
if self.validator != nil {
|
||||||
|
signature = &Signature{}
|
||||||
|
copy(signature[:], chunkdata[:signatureLength])
|
||||||
|
sigoffset = signatureLength
|
||||||
|
cursor = sigoffset
|
||||||
|
}
|
||||||
|
|
||||||
headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
|
headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2])
|
||||||
if int(headerlength+2) > len(chunkdata) {
|
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))
|
err = fmt.Errorf("Reported header length %d longer than actual data length %d", headerlength, len(chunkdata))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
cursor += 2
|
cursor += 2
|
||||||
period = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4])
|
period = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4])
|
||||||
cursor += 4
|
cursor += 4
|
||||||
version = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4])
|
version = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4])
|
||||||
cursor += 4
|
cursor += 4
|
||||||
namelength := int(headerlength) - cursor + signatureLength + 2
|
namelength := int(headerlength) - cursor + sigoffset + 2
|
||||||
name = string(chunkdata[cursor : cursor+namelength])
|
name = string(chunkdata[cursor : cursor+namelength])
|
||||||
cursor += namelength
|
cursor += namelength
|
||||||
data = make([]byte, len(chunkdata)-cursor)
|
data = make([]byte, len(chunkdata)-cursor)
|
||||||
|
|
@ -425,16 +436,24 @@ func parseUpdate(chunkdata []byte) (signature Signature, period uint32, version
|
||||||
// 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(indexname string, data []byte) (Key, error) {
|
func (self *ResourceHandler) Update(name string, data []byte) (Key, error) {
|
||||||
|
|
||||||
|
var sigoffset int
|
||||||
|
if self.validator != nil {
|
||||||
|
sigoffset = signatureLength
|
||||||
|
}
|
||||||
|
|
||||||
// get the cached information
|
// get the cached information
|
||||||
rsrc := self.getResource(indexname)
|
rsrc := self.getResource(name)
|
||||||
|
if rsrc == nil {
|
||||||
|
return nil, fmt.Errorf("Resource object not in index")
|
||||||
|
}
|
||||||
if !rsrc.isSynced() {
|
if !rsrc.isSynced() {
|
||||||
return nil, fmt.Errorf("Resource object not in sync")
|
return nil, fmt.Errorf("Resource object not in sync")
|
||||||
}
|
}
|
||||||
|
|
||||||
// an update can be only one chunk long
|
// an update can be only one chunk long
|
||||||
datalimit := self.chunkSize() - int64(signatureLength-len(self.resources[indexname].name)-8)
|
datalimit := self.chunkSize() - int64(sigoffset-len(name)-8)
|
||||||
if int64(len(data)) > datalimit {
|
if int64(len(data)) > datalimit {
|
||||||
return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), datalimit)
|
return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), datalimit)
|
||||||
}
|
}
|
||||||
|
|
@ -449,7 +468,7 @@ func (self *ResourceHandler) Update(indexname string, data []byte) (Key, error)
|
||||||
// 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)
|
// (resource object MUST be in sync for version to be correct)
|
||||||
var version uint32
|
var version uint32
|
||||||
if self.hasUpdate(indexname, nextperiod) {
|
if self.hasUpdate(name, nextperiod) {
|
||||||
version = rsrc.version
|
version = rsrc.version
|
||||||
}
|
}
|
||||||
version++
|
version++
|
||||||
|
|
@ -457,32 +476,36 @@ func (self *ResourceHandler) Update(indexname string, data []byte) (Key, error)
|
||||||
// calculate the chunk key
|
// calculate the chunk key
|
||||||
key := self.resourceHash(nextperiod, version, rsrc.nameHash)
|
key := self.resourceHash(nextperiod, version, rsrc.nameHash)
|
||||||
|
|
||||||
// sign the data hash with the key
|
var signature *Signature
|
||||||
digest := self.keyDataHash(key, data)
|
if self.validator != nil {
|
||||||
signature, err := self.validator.sign(digest)
|
// sign the data hash with the key
|
||||||
if err != nil {
|
digest := self.keyDataHash(key, data)
|
||||||
return nil, err
|
sig, err := self.validator.sign(digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the signer has access to update
|
||||||
|
ok, err := self.validator.checkAccess(name, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !ok {
|
||||||
|
return nil, fmt.Errorf("Address %x does not have access to update %s", addr, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the address of the signer (which also checks that it's a valid signature)
|
chunk := newUpdateChunk(key, signature, nextperiod, version, name, data)
|
||||||
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
|
// send the chunk
|
||||||
self.Put(chunk)
|
self.Put(chunk)
|
||||||
log.Trace("resource update", "name", rsrc.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
|
log.Trace("resource update", "name", name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
|
||||||
|
|
||||||
// update our resources map entry and return the new key
|
// update our resources map entry and return the new key
|
||||||
rsrc.lastPeriod = nextperiod
|
rsrc.lastPeriod = nextperiod
|
||||||
|
|
@ -492,38 +515,6 @@ func (self *ResourceHandler) Update(indexname string, data []byte) (Key, error)
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUpdateChunk(key Key, signature Signature, period uint32, version uint32, name string, data []byte) *Chunk {
|
|
||||||
// create the update chunk
|
|
||||||
// prepend version and period to allow reverse lookups
|
|
||||||
headerlength := uint16(len(name) + 4 + 4)
|
|
||||||
|
|
||||||
chunk := NewChunk(key, nil)
|
|
||||||
chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data))
|
|
||||||
|
|
||||||
cursor := 0
|
|
||||||
copy(chunk.SData, signature[:])
|
|
||||||
cursor += signatureLength
|
|
||||||
|
|
||||||
// data header length does NOT include the header length prefix bytes themselves
|
|
||||||
binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength)
|
|
||||||
cursor += 2
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(chunk.SData[cursor:], period)
|
|
||||||
cursor += 4
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(chunk.SData[cursor:], version)
|
|
||||||
cursor += 4
|
|
||||||
|
|
||||||
namebytes := []byte(name)
|
|
||||||
copy(chunk.SData[cursor:], namebytes)
|
|
||||||
cursor += len(namebytes)
|
|
||||||
|
|
||||||
copy(chunk.SData[cursor:], data)
|
|
||||||
|
|
||||||
chunk.Size = int64(len(chunk.SData))
|
|
||||||
return chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closes the datastore.
|
// Closes the datastore.
|
||||||
// Always call this at shutdown to avoid data corruption.
|
// Always call this at shutdown to avoid data corruption.
|
||||||
func (self *ResourceHandler) Close() {
|
func (self *ResourceHandler) Close() {
|
||||||
|
|
@ -543,10 +534,12 @@ func (self *ResourceHandler) getBlock() (uint64, error) {
|
||||||
return strconv.ParseUint(currentblock, 10, 64)
|
return strconv.ParseUint(currentblock, 10, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the period index (aka major version number) from a given block number
|
||||||
func (self *ResourceHandler) BlockToPeriod(name string, blocknumber uint64) uint32 {
|
func (self *ResourceHandler) BlockToPeriod(name string, blocknumber uint64) uint32 {
|
||||||
return getNextPeriod(self.resources[name].startBlock, blocknumber, self.resources[name].frequency)
|
return getNextPeriod(self.resources[name].startBlock, blocknumber, self.resources[name].frequency)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the block number from a given period index (aka major version number)
|
||||||
func (self *ResourceHandler) PeriodToBlock(name string, period uint32) uint64 {
|
func (self *ResourceHandler) PeriodToBlock(name string, period uint32) uint64 {
|
||||||
return self.resources[name].startBlock + (uint64(period) * self.resources[name].frequency)
|
return self.resources[name].startBlock + (uint64(period) * self.resources[name].frequency)
|
||||||
}
|
}
|
||||||
|
|
@ -564,8 +557,9 @@ func (self *ResourceHandler) setResource(name string, rsrc *resource) {
|
||||||
self.resources[name] = rsrc
|
self.resources[name] = rsrc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for chunk keys
|
||||||
func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehash common.Hash) Key {
|
func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehash common.Hash) Key {
|
||||||
// format is: hash(namehash|period|version)
|
// format is: hash(period|version|namehash)
|
||||||
self.hashLock.Lock()
|
self.hashLock.Lock()
|
||||||
defer self.hashLock.Unlock()
|
defer self.hashLock.Unlock()
|
||||||
self.hasher.Reset()
|
self.hasher.Reset()
|
||||||
|
|
@ -578,6 +572,13 @@ func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehas
|
||||||
return self.hasher.Sum(nil)
|
return self.hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *ResourceHandler) hasUpdate(name string, period uint32) bool {
|
||||||
|
if self.resources[name].lastPeriod == period {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Address, error) {
|
func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Address, error) {
|
||||||
pub, err := crypto.SigToPub(datahash.Bytes(), signature[:])
|
pub, err := crypto.SigToPub(datahash.Bytes(), signature[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -586,8 +587,45 @@ func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Ad
|
||||||
return crypto.PubkeyToAddress(*pub), nil
|
return crypto.PubkeyToAddress(*pub), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) hasUpdate(name string, period uint32) bool {
|
// create an update chunk
|
||||||
return self.resources[name].lastPeriod == period
|
func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte) *Chunk {
|
||||||
|
|
||||||
|
// no signatures if no validator
|
||||||
|
var sigoffset int
|
||||||
|
if signature != nil {
|
||||||
|
sigoffset = signatureLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend version and period to allow reverse lookups
|
||||||
|
headerlength := uint16(len(name) + 4 + 4)
|
||||||
|
|
||||||
|
chunk := NewChunk(key, nil)
|
||||||
|
chunk.SData = make([]byte, sigoffset+int(headerlength)+2+len(data))
|
||||||
|
|
||||||
|
cursor := 0
|
||||||
|
if signature != nil {
|
||||||
|
copy(chunk.SData, (*signature)[:])
|
||||||
|
cursor += signatureLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// data header length does NOT include the header length prefix bytes themselves
|
||||||
|
binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength)
|
||||||
|
cursor += 2
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(chunk.SData[cursor:], period)
|
||||||
|
cursor += 4
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(chunk.SData[cursor:], version)
|
||||||
|
cursor += 4
|
||||||
|
|
||||||
|
namebytes := []byte(name)
|
||||||
|
copy(chunk.SData[cursor:], namebytes)
|
||||||
|
cursor += len(namebytes)
|
||||||
|
|
||||||
|
copy(chunk.SData[cursor:], data)
|
||||||
|
|
||||||
|
chunk.Size = int64(len(chunk.SData))
|
||||||
|
return chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
// \TODO chunkSize is a workaround until the ChunkStore interface exports a method to get the chunk size directly
|
// \TODO chunkSize is a workaround until the ChunkStore interface exports a method to get the chunk size directly
|
||||||
|
|
@ -640,8 +678,7 @@ func getNextPeriod(start uint64, current uint64, frequency uint64) uint32 {
|
||||||
return uint32(period + 1)
|
return uint32(period + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSafeName(name string) (string, error) {
|
func ToSafeName(name string) (string, error) {
|
||||||
// make sure our ens identifier is idna safe
|
|
||||||
validname, err := idna.ToASCII(name)
|
validname, err := idna.ToASCII(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -649,6 +686,22 @@ func toSafeName(name string) (string, error) {
|
||||||
return validname, nil
|
return validname, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check that name identifiers contain valid bytes
|
||||||
|
func isSafeName(name string) bool {
|
||||||
|
if name == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
validname, err := idna.ToASCII(name)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if validname != name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// convenience for creating signature hashes of update data
|
||||||
func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
|
func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
|
||||||
self.hashLock.Lock()
|
self.hashLock.Lock()
|
||||||
defer self.hashLock.Unlock()
|
defer self.hashLock.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ type baseValidator struct {
|
||||||
signFunc SignFunc
|
signFunc SignFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *baseValidator) sign(datahash common.Hash) (Signature, error) {
|
func (b *baseValidator) sign(datahash common.Hash) (signature Signature, err error) {
|
||||||
if b.signFunc == nil {
|
if b.signFunc == nil {
|
||||||
return emptySignature, fmt.Errorf("No signature function")
|
return signature, fmt.Errorf("No signature function")
|
||||||
}
|
}
|
||||||
return b.signFunc(datahash)
|
return b.signFunc(datahash)
|
||||||
}
|
}
|
||||||
|
|
@ -22,8 +22,7 @@ func (b *baseValidator) sign(datahash common.Hash) (Signature, error) {
|
||||||
// ENS validation of mutable resource owners
|
// ENS validation of mutable resource owners
|
||||||
type ENSValidator struct {
|
type ENSValidator struct {
|
||||||
*baseValidator
|
*baseValidator
|
||||||
api *ens.ENS
|
api *ens.ENS
|
||||||
hashlength int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts, signFunc SignFunc) (*ENSValidator, error) {
|
func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts, signFunc SignFunc) (*ENSValidator, error) {
|
||||||
|
|
@ -37,7 +36,6 @@ func NewENSValidator(contractaddress common.Address, backend bind.ContractBacken
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,29 +50,3 @@ func (self *ENSValidator) checkAccess(name string, address common.Address) (bool
|
||||||
func (self *ENSValidator) nameHash(name string) common.Hash {
|
func (self *ENSValidator) nameHash(name string) common.Hash {
|
||||||
return ens.EnsNode(name)
|
return ens.EnsNode(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default fallthrough validation of mutable resource ownership
|
|
||||||
type GenericValidator struct {
|
|
||||||
*baseValidator
|
|
||||||
hashFunc func(string) common.Hash
|
|
||||||
hashlength int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGenericValidator(hashFunc func(string) common.Hash, signFunc SignFunc) *GenericValidator {
|
|
||||||
return &GenericValidator{
|
|
||||||
baseValidator: &baseValidator{
|
|
||||||
signFunc: signFunc,
|
|
||||||
},
|
|
||||||
hashFunc: hashFunc,
|
|
||||||
hashlength: len(hashFunc(dbDirName).Bytes()),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *GenericValidator) checkAccess(name string, address common.Address) (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *GenericValidator) nameHash(name string) common.Hash {
|
|
||||||
return self.hashFunc(name)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/idna"
|
|
||||||
|
|
||||||
"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"
|
||||||
|
|
@ -35,10 +33,16 @@ var (
|
||||||
resourceFrequency = uint64(42)
|
resourceFrequency = uint64(42)
|
||||||
cleanF func()
|
cleanF func()
|
||||||
domainName = "føø.bar"
|
domainName = "føø.bar"
|
||||||
|
safeName string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
var err error
|
||||||
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||||
|
safeName, err = ToSafeName(domainName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulated backend does not have the blocknumber call
|
// simulated backend does not have the blocknumber call
|
||||||
|
|
@ -70,20 +74,20 @@ func TestResourceReverse(t *testing.T) {
|
||||||
period := uint32(4)
|
period := uint32(4)
|
||||||
version := uint32(2)
|
version := uint32(2)
|
||||||
|
|
||||||
// set up rpc and create resourcehandler
|
// signer containing private key
|
||||||
rh, _, signer, teardownTest, err := setupTest(nil, nil)
|
signer, err := newTestSigner()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new resource
|
// set up rpc and create resourcehandler
|
||||||
validname, err := idna.ToASCII(domainName)
|
rh, _, _, teardownTest, err := setupTest(nil, newTestValidator(signer.signContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a hash for block 4200 version 1
|
// generate a hash for block 4200 version 1
|
||||||
key := rh.resourceHash(period, version, rh.validator.nameHash(validname))
|
key := rh.resourceHash(period, version, rh.nameHash(safeName))
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
@ -99,17 +103,18 @@ func TestResourceReverse(t *testing.T) {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk := newUpdateChunk(key, sig, period, version, validname, data)
|
chunk := newUpdateChunk(key, &sig, period, version, safeName, 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 := parseUpdate(chunk.SData)
|
checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData)
|
||||||
checkdigest := rh.keyDataHash(chunk.Key, checkdata)
|
checkdigest := rh.keyDataHash(chunk.Key, checkdata)
|
||||||
recoveredaddress, err := getAddressFromDataSig(checkdigest, checksig)
|
recoveredaddress, err := getAddressFromDataSig(checkdigest, *checksig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, fmt.Errorf("Retrieve address from signature fail: %v", err))
|
||||||
}
|
}
|
||||||
originaladdress := crypto.PubkeyToAddress(signer.privKey.PublicKey)
|
originaladdress := crypto.PubkeyToAddress(signer.privKey.PublicKey)
|
||||||
|
|
||||||
|
// check that the metadata retrieved from the chunk matches what we gave it
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
@ -123,8 +128,8 @@ func TestResourceReverse(t *testing.T) {
|
||||||
if version != checkversion {
|
if version != checkversion {
|
||||||
teardownTest(t, fmt.Errorf("Expected version '%d', was '%d'", version, checkversion))
|
teardownTest(t, fmt.Errorf("Expected version '%d', was '%d'", version, checkversion))
|
||||||
}
|
}
|
||||||
if validname != checkname {
|
if safeName != checkname {
|
||||||
teardownTest(t, fmt.Errorf("Expected name '%s', was '%s'", validname, checkname))
|
teardownTest(t, fmt.Errorf("Expected name '%s', was '%s'", safeName, checkname))
|
||||||
}
|
}
|
||||||
if !bytes.Equal(data, checkdata) {
|
if !bytes.Equal(data, checkdata) {
|
||||||
teardownTest(t, fmt.Errorf("Expectedn data '%x', was '%x'", data, checkdata))
|
teardownTest(t, fmt.Errorf("Expectedn data '%x', was '%x'", data, checkdata))
|
||||||
|
|
@ -145,17 +150,16 @@ func TestResourceHandler(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new resource
|
// create a new resource
|
||||||
resourcevalidname, err := idna.ToASCII(domainName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
_, err = rh.NewResource(domainName, resourceFrequency, false)
|
_, err = rh.NewResource(safeName, resourceFrequency)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that the new resource is stored correctly
|
// check that the new resource is stored correctly
|
||||||
namehash := rh.validator.nameHash(resourcevalidname)
|
namehash := rh.nameHash(safeName)
|
||||||
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:]))
|
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
|
|
@ -175,7 +179,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")
|
||||||
resourcekey["blinky"], err = rh.Update(domainName, data)
|
resourcekey["blinky"], err = rh.Update(safeName, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +187,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")
|
||||||
resourcekey["pinky"], err = rh.Update(domainName, data)
|
resourcekey["pinky"], err = rh.Update(safeName, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +195,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")
|
||||||
resourcekey["inky"], err = rh.Update(domainName, data)
|
resourcekey["inky"], err = rh.Update(safeName, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +203,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")
|
||||||
resourcekey["clyde"], err = rh.Update(domainName, data)
|
resourcekey["clyde"], err = rh.Update(safeName, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -211,43 +215,44 @@ func TestResourceHandler(t *testing.T) {
|
||||||
fwdBlocks(int(resourceFrequency*2)-1, backend)
|
fwdBlocks(int(resourceFrequency*2)-1, backend)
|
||||||
|
|
||||||
rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.rpcClient, nil)
|
rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.rpcClient, nil)
|
||||||
_, err = rh2.LookupLatest(domainName, true)
|
_, err = rh2.LookupLatest(safeName, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// last update should be "clyde", version two, blockheight startblocknumber + (resourcefrequency * 3)
|
// last update should be "clyde", version two, blockheight startblocknumber + (resourcefrequency * 3)
|
||||||
if !bytes.Equal(rh2.resources[domainName].data, []byte("clyde")) {
|
if !bytes.Equal(rh2.resources[safeName].data, []byte("clyde")) {
|
||||||
teardownTest(t, fmt.Errorf("resource data was %v, expected %v", rh2.resources[domainName].data, []byte("clyde")))
|
teardownTest(t, fmt.Errorf("resource data was %v, expected %v", rh2.resources[safeName].data, []byte("clyde")))
|
||||||
}
|
}
|
||||||
if rh2.resources[domainName].version != 2 {
|
if rh2.resources[safeName].version != 2 {
|
||||||
teardownTest(t, fmt.Errorf("resource version was %d, expected 2", rh2.resources[domainName].version))
|
teardownTest(t, fmt.Errorf("resource version was %d, expected 2", rh2.resources[safeName].version))
|
||||||
}
|
}
|
||||||
if rh2.resources[domainName].lastPeriod != 3 {
|
if rh2.resources[safeName].lastPeriod != 3 {
|
||||||
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[safeName].lastPeriod))
|
||||||
}
|
}
|
||||||
|
log.Debug("Latest lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
|
||||||
|
|
||||||
// specific block, latest version
|
// specific block, latest version
|
||||||
rsrc, err := rh2.LookupHistorical(domainName, 3, true)
|
rsrc, err := rh2.LookupHistorical(safeName, 3, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check data
|
// check data
|
||||||
if !bytes.Equal(rsrc.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")))
|
||||||
}
|
}
|
||||||
|
log.Debug("Historical lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
|
||||||
|
|
||||||
// specific block, specific version
|
// specific block, specific version
|
||||||
rsrc, err = rh2.LookupVersion(domainName, 3, 1, true)
|
rsrc, err = rh2.LookupVersion(safeName, 3, 1, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check data
|
// check data
|
||||||
if !bytes.Equal(rsrc.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")))
|
||||||
}
|
}
|
||||||
|
log.Debug("Specific version lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data)
|
||||||
teardownTest(t, nil)
|
teardownTest(t, nil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -266,7 +271,7 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
transactOpts := bind.NewKeyedTransactor(signer.privKey)
|
transactOpts := bind.NewKeyedTransactor(signer.privKey)
|
||||||
|
|
||||||
// set up ENS sim
|
// set up ENS sim
|
||||||
domainparts := strings.Split(domainName, ".")
|
domainparts := strings.Split(safeName, ".")
|
||||||
contractAddr, contractbackend, err := setupENS(addr, transactOpts, domainparts[0], domainparts[1])
|
contractAddr, contractbackend, err := setupENS(addr, transactOpts, domainparts[0], domainparts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -284,14 +289,14 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new resource when we are owner = ok
|
// create new resource when we are owner = ok
|
||||||
_, err = rh.NewResource(domainName, resourceFrequency, true)
|
_, err = rh.NewResource(safeName, resourceFrequency)
|
||||||
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")
|
||||||
// update resource when we are owner = ok
|
// update resource when we are owner = ok
|
||||||
_, err = rh.Update(domainName, data)
|
_, err = rh.Update(safeName, 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))
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +307,7 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
rh.validator.(*ENSValidator).signFunc = signertwo.signContent
|
rh.validator.(*ENSValidator).signFunc = signertwo.signContent
|
||||||
_, err = rh.Update(domainName, data)
|
_, err = rh.Update(safeName, 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"))
|
||||||
}
|
}
|
||||||
|
|
@ -331,15 +336,6 @@ func setupTest(contractbackend bind.ContractBackend, validator ResourceValidator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
@ -431,12 +427,7 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string,
|
||||||
return contractAddress, contractBackend, nil
|
return contractAddress, contractBackend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testHashFunc(name string) common.Hash {
|
// implementation of an external signer to pass to validator
|
||||||
testHasher.Reset()
|
|
||||||
testHasher.Write([]byte(name))
|
|
||||||
return common.BytesToHash(testHasher.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
type testSigner struct {
|
type testSigner struct {
|
||||||
privKey *ecdsa.PrivateKey
|
privKey *ecdsa.PrivateKey
|
||||||
hasher SwarmHash
|
hasher SwarmHash
|
||||||
|
|
@ -453,6 +444,7 @@ func newTestSigner() (*testSigner, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matches the SignFunc type
|
||||||
func (self *testSigner) signContent(data common.Hash) (signature Signature, err error) {
|
func (self *testSigner) signContent(data common.Hash) (signature Signature, err error) {
|
||||||
signaturebytes, err := crypto.Sign(data.Bytes(), self.privKey)
|
signaturebytes, err := crypto.Sign(data.Bytes(), self.privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -473,3 +465,31 @@ func (c *testCloudStore) Deliver(*Chunk) {
|
||||||
|
|
||||||
func (c *testCloudStore) Retrieve(*Chunk) {
|
func (c *testCloudStore) Retrieve(*Chunk) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default fallthrough validation of mutable resource ownership
|
||||||
|
type testValidator struct {
|
||||||
|
*baseValidator
|
||||||
|
hashFunc func(string) common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestValidator(signFunc SignFunc) *testValidator {
|
||||||
|
return &testValidator{
|
||||||
|
baseValidator: &baseValidator{
|
||||||
|
signFunc: signFunc,
|
||||||
|
},
|
||||||
|
hashFunc: func(name string) common.Hash {
|
||||||
|
testHasher.Reset()
|
||||||
|
testHasher.Write([]byte(name))
|
||||||
|
return common.BytesToHash(testHasher.Sum(nil))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *testValidator) checkAccess(name string, address common.Address) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *testValidator) nameHash(name string) common.Hash {
|
||||||
|
return self.hashFunc(name)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue