mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 01:13:45 +00:00
swarm/storage: Check ENS ownership on resource create
This commit is contained in:
parent
dfb45e382c
commit
91cfddd375
3 changed files with 79 additions and 37 deletions
|
|
@ -40,10 +40,6 @@ type resource struct {
|
|||
// The update scheme is built on swarm chunks with chunk keys following
|
||||
// a predictable, versionable pattern.
|
||||
//
|
||||
// The data of the chunk contains the content hash of the version in question.
|
||||
// In order to be valid, the hash is signed by the owner of the ENS record
|
||||
// of the mutable resource.
|
||||
//
|
||||
// Updates are defined to be periodic in nature, where periods are
|
||||
// expressed in terms of number of blocks.
|
||||
//
|
||||
|
|
@ -56,7 +52,7 @@ type resource struct {
|
|||
|
||||
// The root entry tells the requester from when the mutable resource was
|
||||
// first added (block number) and in which block number to look for the
|
||||
// actual updates. Thus, a resource update for identifier "foo.bar"
|
||||
// actual updates. Thus, a resource update for identifier "føø.bar"
|
||||
// starting at block 4200 with frequency 42 will have updates on block 4242,
|
||||
// 4284, 4326 and so on.
|
||||
//
|
||||
|
|
@ -66,27 +62,28 @@ type resource struct {
|
|||
//
|
||||
// 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,
|
||||
// and pointed to by an actual ENS entry (or manifest entry) instead.
|
||||
// and pointed to by an external resource (ENS or manifest entry)
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// sha256(namehash|blocknumber|version)
|
||||
// sha256(namehash|period|version)
|
||||
//
|
||||
// The blocknumber here is the next block period after the current block
|
||||
// calculated from the start block and frequency of the resource update.
|
||||
// Using our previous example, this means that an update made at block 4285,
|
||||
// and even 4284, will have 4326 as the block number.
|
||||
// The period is (currentblock - startblock) / frequency
|
||||
//
|
||||
// Using our previous example, this means that a period 3 will have 4326 as
|
||||
// the block number.
|
||||
//
|
||||
// If more than one update is made to the same block number, incremental
|
||||
// version numbers are used successively.
|
||||
//
|
||||
// A lookup agent need only know the identifier name in order to get the versions
|
||||
//
|
||||
// the data itself is prefixed with a signed hash of the data. The sigining key
|
||||
// is used to verify the authenticity of the update, for example by looking
|
||||
// up the ownership of the namehash in ENS and comparing to the address derived
|
||||
// from it
|
||||
// the chunk data is: sign(resourcedata)|resourcedata
|
||||
// the resourcedata is: headerlength|period|version|name|data
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// NOTE: the following is yet to be implemented
|
||||
// The resource update chunks will be stored in the swarm, but receive special
|
||||
|
|
@ -94,8 +91,6 @@ type resource struct {
|
|||
// 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
|
||||
// treated as a resource update chunk.
|
||||
//
|
||||
// TODO: signature validation
|
||||
type ResourceHandler interface {
|
||||
ChunkStore
|
||||
NewResource(name string, frequency uint64) (*resource, error)
|
||||
|
|
|
|||
|
|
@ -2,24 +2,31 @@ package storage
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Implements Mutable Resources as offchain ENS resolvers
|
||||
//
|
||||
// The data part of the update is forced to be a valid ENS content hash
|
||||
//
|
||||
// Also, the ENSResourceHandler only allows creation and update of
|
||||
// Resources from the ENS owner's address
|
||||
//
|
||||
type ENSResourceHandler struct {
|
||||
ResourceHandler
|
||||
ethapi *ethclient.Client
|
||||
*RawResourceHandler
|
||||
addr common.Address
|
||||
ensapi *ens.ENS
|
||||
}
|
||||
|
||||
func NewENSResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore CloudStore, rpcClient *rpc.Client, ensAddr common.Address) (*ENSResourceHandler, error) {
|
||||
func NewENSResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore CloudStore, rpcClient *rpc.Client, backend bind.ContractBackend, ensAddr common.Address) (*ENSResourceHandler, error) {
|
||||
transactOpts := bind.NewKeyedTransactor(privKey)
|
||||
ethapi := ethclient.NewClient(rpcClient)
|
||||
ensinstance, err := ens.NewENS(transactOpts, ensAddr, ethapi)
|
||||
ensinstance, err := ens.NewENS(transactOpts, ensAddr, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -32,8 +39,19 @@ func NewENSResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore
|
|||
}
|
||||
|
||||
return &ENSResourceHandler{
|
||||
ResourceHandler: rh,
|
||||
ethapi: ethclient.NewClient(rpcClient),
|
||||
ensapi: ensinstance,
|
||||
RawResourceHandler: rh,
|
||||
addr: crypto.PubkeyToAddress(privKey.PublicKey),
|
||||
ensapi: ensinstance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (self *ENSResourceHandler) NewResource(name string, frequency uint64) (*resource, error) {
|
||||
owneraddr, err := self.ensapi.Owner(self.RawResourceHandler.nameHashFunc(name))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ENS error: %v", err)
|
||||
}
|
||||
if owneraddr != self.addr {
|
||||
return nil, fmt.Errorf("not owner")
|
||||
}
|
||||
return self.RawResourceHandler.NewResource(name, frequency)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ func init() {
|
|||
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||
}
|
||||
|
||||
// simulated backend does not have the blocknumber call
|
||||
// so we use this wrapper to fake returning the block count
|
||||
type fakeBackend struct {
|
||||
*backends.SimulatedBackend
|
||||
blocknumber uint64
|
||||
|
|
@ -317,14 +319,21 @@ func TestResourceHandler(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestResourceENS(t *testing.T) {
|
||||
func TestResourceENSNew(t *testing.T) {
|
||||
|
||||
// privkey for signing updates
|
||||
// privkey for ens owner
|
||||
privkey, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// privkey for signing updates
|
||||
privkeytwo, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// set up ENS sim
|
||||
domainparts := strings.Split(domainName, ".")
|
||||
addr, contractbackend, err := setupENS(privkey, domainparts[0], domainparts[1])
|
||||
if err != nil {
|
||||
|
|
@ -336,14 +345,31 @@ func TestResourceENS(t *testing.T) {
|
|||
teardownTest(t, err)
|
||||
}
|
||||
|
||||
// create new resource when we are owner = ok
|
||||
_, err = rh.NewResource(domainName, 42)
|
||||
if err != nil {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
||||
// create new resource when we are NOT owner = !ok
|
||||
rawrh := rh.(*ENSResourceHandler)
|
||||
rawrh.privKey = privkeytwo
|
||||
rawrh.addr = crypto.PubkeyToAddress(privkeytwo.PublicKey)
|
||||
_, err = rawrh.NewResource(domainName, 42)
|
||||
if err == nil {
|
||||
teardownTest(t, fmt.Errorf("Expected resource create fail due to owner mismatch"))
|
||||
}
|
||||
|
||||
teardownTest(t, nil)
|
||||
}
|
||||
|
||||
// fast-forward blockheight
|
||||
func fwdBlocks(count int, backend *fakeBackend) {
|
||||
for i := 0; i < count; i++ {
|
||||
backend.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend, ensaddr common.Address) (rh ResourceHandler, datadir string, err error, teardown func(*testing.T, error)) {
|
||||
|
||||
var fsClean func()
|
||||
|
|
@ -394,8 +420,9 @@ func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend,
|
|||
return
|
||||
}
|
||||
|
||||
// choose if with ens or not
|
||||
if ensaddr != zeroAddr {
|
||||
rh, err = NewENSResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient, ensaddr)
|
||||
rh, err = NewENSResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient, contractbackend, ensaddr)
|
||||
} else {
|
||||
rh, err = NewRawResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient, nil)
|
||||
}
|
||||
|
|
@ -408,7 +435,11 @@ func setupTest(privkey *ecdsa.PrivateKey, contractbackend bind.ContractBackend,
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// Set up simulated ENS backend for use with ENSResourceHandler tests
|
||||
func setupENS(privkey *ecdsa.PrivateKey, sub string, top string) (common.Address, bind.ContractBackend, error) {
|
||||
|
||||
// create the domain hash values to pass to the ENS contract methods
|
||||
var tophash [32]byte
|
||||
var subhash [32]byte
|
||||
hashfunc.Reset()
|
||||
|
|
@ -417,17 +448,22 @@ func setupENS(privkey *ecdsa.PrivateKey, sub string, top string) (common.Address
|
|||
hashfunc.Reset()
|
||||
hashfunc.Write([]byte(sub))
|
||||
copy(subhash[:], hashfunc.Sum(nil))
|
||||
|
||||
// private key -> address is owner of domain
|
||||
addr := crypto.PubkeyToAddress(privkey.PublicKey)
|
||||
|
||||
// initialize contract backend and deploy
|
||||
transactOpts := bind.NewKeyedTransactor(privkey)
|
||||
contractBackend := &fakeBackend{
|
||||
SimulatedBackend: backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}),
|
||||
}
|
||||
transactOpts := bind.NewKeyedTransactor(privkey)
|
||||
|
||||
ensAddr, _, ensinstance, err := contract.DeployENS(transactOpts, contractBackend)
|
||||
if err != nil {
|
||||
return zeroAddr, nil, fmt.Errorf("can't deploy: %v", err)
|
||||
}
|
||||
|
||||
// update the registry for the correct owner address
|
||||
if _, err = ensinstance.SetOwner(transactOpts, [32]byte{}, addr); err != nil {
|
||||
return zeroAddr, nil, fmt.Errorf("can't setowner: %v", err)
|
||||
}
|
||||
|
|
@ -443,13 +479,6 @@ func setupENS(privkey *ecdsa.PrivateKey, sub string, top string) (common.Address
|
|||
}
|
||||
contractBackend.Commit()
|
||||
|
||||
nodeowner, err := ensinstance.Owner(&bind.CallOpts{}, ens.EnsNode(strings.Join([]string{sub, top}, ".")))
|
||||
if err != nil {
|
||||
return zeroAddr, nil, fmt.Errorf("can't retrieve owner: %v", err)
|
||||
} else if !bytes.Equal(nodeowner.Bytes(), addr.Bytes()) {
|
||||
return zeroAddr, nil, fmt.Errorf("retrieved owner doesn't match; expected '%x', got '%x'", addr, nodeowner)
|
||||
}
|
||||
|
||||
return ensAddr, contractBackend, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue