2018-06-20 07:06:27 -05:00
|
|
|
// Copyright 2018 The go-ethereum Authors
|
|
|
|
// This file is part of the go-ethereum library.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package mru
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/binary"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"math/big"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
|
|
|
"github.com/ethereum/go-ethereum/contracts/ens/contract"
|
|
|
|
"github.com/ethereum/go-ethereum/core"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/ethereum/go-ethereum/swarm/multihash"
|
|
|
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
loglevel = flag.Int("loglevel", 3, "loglevel")
|
|
|
|
testHasher = storage.MakeHashFunc(storage.SHA3Hash)()
|
|
|
|
zeroAddr = common.Address{}
|
|
|
|
startBlock = uint64(4200)
|
|
|
|
resourceFrequency = uint64(42)
|
|
|
|
cleanF func()
|
|
|
|
domainName = "føø.bar"
|
|
|
|
safeName string
|
|
|
|
nameHash common.Hash
|
|
|
|
hashfunc = storage.MakeHashFunc(storage.DefaultHash)
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
var err error
|
|
|
|
flag.Parse()
|
|
|
|
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
|
|
|
safeName, err = ToSafeName(domainName)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
nameHash = ens.EnsNode(safeName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeBackend) Commit() {
|
|
|
|
if f.SimulatedBackend != nil {
|
|
|
|
f.SimulatedBackend.Commit()
|
|
|
|
}
|
|
|
|
f.blocknumber++
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeBackend) HeaderByNumber(context context.Context, name string, bigblock *big.Int) (*types.Header, error) {
|
|
|
|
f.blocknumber++
|
|
|
|
biggie := big.NewInt(f.blocknumber)
|
|
|
|
return &types.Header{
|
|
|
|
Number: biggie,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// check that signature address matches update signer address
|
|
|
|
func TestReverse(t *testing.T) {
|
|
|
|
|
|
|
|
period := uint32(4)
|
|
|
|
version := uint32(2)
|
|
|
|
|
|
|
|
// signer containing private key
|
|
|
|
signer, err := newTestSigner()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// set up rpc and create resourcehandler
|
|
|
|
rh, _, teardownTest, err := setupTest(nil, nil, signer)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer teardownTest()
|
|
|
|
|
|
|
|
// generate a hash for block 4200 version 1
|
|
|
|
key := rh.resourceHash(period, version, ens.EnsNode(safeName))
|
|
|
|
|
|
|
|
// generate some bogus data for the chunk and sign it
|
|
|
|
data := make([]byte, 8)
|
|
|
|
_, err = rand.Read(data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
testHasher.Reset()
|
|
|
|
testHasher.Write(data)
|
|
|
|
digest := rh.keyDataHash(key, data)
|
|
|
|
sig, err := rh.signer.Sign(digest)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
chunk := newUpdateChunk(key, &sig, period, version, safeName, data, len(data))
|
|
|
|
|
|
|
|
// check that we can recover the owner account from the update chunk's signature
|
|
|
|
checksig, checkperiod, checkversion, checkname, checkdata, _, err := rh.parseUpdate(chunk.SData)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
checkdigest := rh.keyDataHash(chunk.Addr, checkdata)
|
|
|
|
recoveredaddress, err := getAddressFromDataSig(checkdigest, *checksig)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Retrieve address from signature fail: %v", err)
|
|
|
|
}
|
|
|
|
originaladdress := crypto.PubkeyToAddress(signer.PrivKey.PublicKey)
|
|
|
|
|
|
|
|
// check that the metadata retrieved from the chunk matches what we gave it
|
|
|
|
if recoveredaddress != originaladdress {
|
|
|
|
t.Fatalf("addresses dont match: %x != %x", originaladdress, recoveredaddress)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.Equal(key[:], chunk.Addr[:]) {
|
|
|
|
t.Fatalf("Expected chunk key '%x', was '%x'", key, chunk.Addr)
|
|
|
|
}
|
|
|
|
if period != checkperiod {
|
|
|
|
t.Fatalf("Expected period '%d', was '%d'", period, checkperiod)
|
|
|
|
}
|
|
|
|
if version != checkversion {
|
|
|
|
t.Fatalf("Expected version '%d', was '%d'", version, checkversion)
|
|
|
|
}
|
|
|
|
if safeName != checkname {
|
|
|
|
t.Fatalf("Expected name '%s', was '%s'", safeName, checkname)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(data, checkdata) {
|
|
|
|
t.Fatalf("Expectedn data '%x', was '%x'", data, checkdata)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// make updates and retrieve them based on periods and versions
|
|
|
|
func TestHandler(t *testing.T) {
|
|
|
|
|
|
|
|
// make fake backend, set up rpc and create resourcehandler
|
|
|
|
backend := &fakeBackend{
|
|
|
|
blocknumber: int64(startBlock),
|
|
|
|
}
|
|
|
|
rh, datadir, teardownTest, err := setupTest(backend, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer teardownTest()
|
|
|
|
|
|
|
|
// create a new resource
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
rootChunkKey, _, err := rh.New(ctx, safeName, resourceFrequency)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-07-13 10:40:28 -05:00
|
|
|
chunk, err := rh.chunkStore.Get(context.TODO(), storage.Address(rootChunkKey))
|
2018-06-20 07:06:27 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
} else if len(chunk.SData) < 16 {
|
|
|
|
t.Fatalf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData))
|
|
|
|
}
|
|
|
|
startblocknumber := binary.LittleEndian.Uint64(chunk.SData[2:10])
|
|
|
|
chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[10:])
|
|
|
|
if startblocknumber != uint64(backend.blocknumber) {
|
|
|
|
t.Fatalf("stored block number %d does not match provided block number %d", startblocknumber, backend.blocknumber)
|
|
|
|
}
|
|
|
|
if chunkfrequency != resourceFrequency {
|
|
|
|
t.Fatalf("stored frequency %d does not match provided frequency %d", chunkfrequency, resourceFrequency)
|
|
|
|
}
|
|
|
|
|
|
|
|
// data for updates:
|
|
|
|
updates := []string{
|
|
|
|
"blinky",
|
|
|
|
"pinky",
|
|
|
|
"inky",
|
|
|
|
"clyde",
|
|
|
|
}
|
|
|
|
|
|
|
|
// update halfway to first period
|
|
|
|
resourcekey := make(map[string]storage.Address)
|
|
|
|
fwdBlocks(int(resourceFrequency/2), backend)
|
|
|
|
data := []byte(updates[0])
|
|
|
|
resourcekey[updates[0]], err = rh.Update(ctx, safeName, data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// update on first period
|
|
|
|
fwdBlocks(int(resourceFrequency/2), backend)
|
|
|
|
data = []byte(updates[1])
|
|
|
|
resourcekey[updates[1]], err = rh.Update(ctx, safeName, data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// update on second period
|
|
|
|
fwdBlocks(int(resourceFrequency), backend)
|
|
|
|
data = []byte(updates[2])
|
|
|
|
resourcekey[updates[2]], err = rh.Update(ctx, safeName, data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// update just after second period
|
|
|
|
fwdBlocks(1, backend)
|
|
|
|
data = []byte(updates[3])
|
|
|
|
resourcekey[updates[3]], err = rh.Update(ctx, safeName, data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
rh.Close()
|
|
|
|
|
|
|
|
// check we can retrieve the updates after close
|
|
|
|
// it will match on second iteration startblocknumber + (resourceFrequency * 3)
|
|
|
|
fwdBlocks(int(resourceFrequency*2)-1, backend)
|
|
|
|
|
|
|
|
rhparams := &HandlerParams{
|
|
|
|
QueryMaxPeriods: &LookupParams{
|
|
|
|
Limit: false,
|
|
|
|
},
|
|
|
|
Signer: nil,
|
|
|
|
HeaderGetter: rh.headerGetter,
|
|
|
|
}
|
|
|
|
|
|
|
|
rh2, err := NewTestHandler(datadir, rhparams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-07-13 10:40:28 -05:00
|
|
|
rsrc2, err := rh2.Load(context.TODO(), rootChunkKey)
|
2018-06-20 07:06:27 -05:00
|
|
|
_, err = rh2.LookupLatest(ctx, nameHash, true, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// last update should be "clyde", version two, blockheight startblocknumber + (resourcefrequency * 3)
|
|
|
|
if !bytes.Equal(rsrc2.data, []byte(updates[len(updates)-1])) {
|
|
|
|
t.Fatalf("resource data was %v, expected %v", rsrc2.data, updates[len(updates)-1])
|
|
|
|
}
|
|
|
|
if rsrc2.version != 2 {
|
|
|
|
t.Fatalf("resource version was %d, expected 2", rsrc2.version)
|
|
|
|
}
|
|
|
|
if rsrc2.lastPeriod != 3 {
|
|
|
|
t.Fatalf("resource period was %d, expected 3", rsrc2.lastPeriod)
|
|
|
|
}
|
|
|
|
log.Debug("Latest lookup", "period", rsrc2.lastPeriod, "version", rsrc2.version, "data", rsrc2.data)
|
|
|
|
|
|
|
|
// specific block, latest version
|
|
|
|
rsrc, err := rh2.LookupHistorical(ctx, nameHash, 3, true, rh2.queryMaxPeriods)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
// check data
|
|
|
|
if !bytes.Equal(rsrc.data, []byte(updates[len(updates)-1])) {
|
|
|
|
t.Fatalf("resource data (historical) was %v, expected %v", rsrc2.data, updates[len(updates)-1])
|
|
|
|
}
|
|
|
|
log.Debug("Historical lookup", "period", rsrc2.lastPeriod, "version", rsrc2.version, "data", rsrc2.data)
|
|
|
|
|
|
|
|
// specific block, specific version
|
|
|
|
rsrc, err = rh2.LookupVersion(ctx, nameHash, 3, 1, true, rh2.queryMaxPeriods)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
// check data
|
|
|
|
if !bytes.Equal(rsrc.data, []byte(updates[2])) {
|
|
|
|
t.Fatalf("resource data (historical) was %v, expected %v", rsrc2.data, updates[2])
|
|
|
|
}
|
|
|
|
log.Debug("Specific version lookup", "period", rsrc2.lastPeriod, "version", rsrc2.version, "data", rsrc2.data)
|
|
|
|
|
|
|
|
// we are now at third update
|
|
|
|
// check backwards stepping to the first
|
|
|
|
for i := 1; i >= 0; i-- {
|
|
|
|
rsrc, err := rh2.LookupPreviousByName(ctx, safeName, rh2.queryMaxPeriods)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(rsrc.data, []byte(updates[i])) {
|
|
|
|
t.Fatalf("resource data (previous) was %v, expected %v", rsrc2.data, updates[i])
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// beyond the first should yield an error
|
|
|
|
rsrc, err = rh2.LookupPreviousByName(ctx, safeName, rh2.queryMaxPeriods)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expeected previous to fail, returned period %d version %d data %v", rsrc2.lastPeriod, rsrc2.version, rsrc2.data)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// create ENS enabled resource update, with and without valid owner
|
|
|
|
func TestENSOwner(t *testing.T) {
|
|
|
|
|
|
|
|
// signer containing private key
|
|
|
|
signer, err := newTestSigner()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ens address and transact options
|
|
|
|
addr := crypto.PubkeyToAddress(signer.PrivKey.PublicKey)
|
|
|
|
transactOpts := bind.NewKeyedTransactor(signer.PrivKey)
|
|
|
|
|
|
|
|
// set up ENS sim
|
|
|
|
domainparts := strings.Split(safeName, ".")
|
|
|
|
contractAddr, contractbackend, err := setupENS(addr, transactOpts, domainparts[0], domainparts[1])
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ensClient, err := ens.NewENS(transactOpts, contractAddr, contractbackend)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// set up rpc and create resourcehandler with ENS sim backend
|
|
|
|
rh, _, teardownTest, err := setupTest(contractbackend, ensClient, signer)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer teardownTest()
|
|
|
|
|
|
|
|
// create new resource when we are owner = ok
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
_, _, err = rh.New(ctx, safeName, resourceFrequency)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Create resource fail: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
data := []byte("foo")
|
|
|
|
// update resource when we are owner = ok
|
|
|
|
_, err = rh.Update(ctx, safeName, data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Update resource fail: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// update resource when we are not owner = !ok
|
|
|
|
signertwo, err := newTestSigner()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
rh.signer = signertwo
|
|
|
|
_, err = rh.Update(ctx, safeName, data)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("Expected resource update fail due to owner mismatch")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMultihash(t *testing.T) {
|
|
|
|
|
|
|
|
// signer containing private key
|
|
|
|
signer, err := newTestSigner()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// make fake backend, set up rpc and create resourcehandler
|
|
|
|
backend := &fakeBackend{
|
|
|
|
blocknumber: int64(startBlock),
|
|
|
|
}
|
|
|
|
|
|
|
|
// set up rpc and create resourcehandler
|
|
|
|
rh, datadir, teardownTest, err := setupTest(backend, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer teardownTest()
|
|
|
|
|
|
|
|
// create a new resource
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
_, _, err = rh.New(ctx, safeName, resourceFrequency)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// we're naïvely assuming keccak256 for swarm hashes
|
|
|
|
// if it ever changes this test should also change
|
|
|
|
multihashbytes := ens.EnsNode("foo")
|
|
|
|
multihashmulti := multihash.ToMultihash(multihashbytes.Bytes())
|
|
|
|
multihashkey, err := rh.UpdateMultihash(ctx, safeName, multihashmulti)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sha1bytes := make([]byte, multihash.MultihashLength)
|
|
|
|
sha1multi := multihash.ToMultihash(sha1bytes)
|
|
|
|
sha1key, err := rh.UpdateMultihash(ctx, safeName, sha1multi)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// invalid multihashes
|
|
|
|
_, err = rh.UpdateMultihash(ctx, safeName, multihashmulti[1:])
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("Expected update to fail with first byte skipped")
|
|
|
|
}
|
|
|
|
_, err = rh.UpdateMultihash(ctx, safeName, multihashmulti[:len(multihashmulti)-2])
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("Expected update to fail with last byte skipped")
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := getUpdateDirect(rh, multihashkey)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
multihashdecode, err := multihash.FromMultihash(data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(multihashdecode, multihashbytes.Bytes()) {
|
|
|
|
t.Fatalf("Decoded hash '%x' does not match original hash '%x'", multihashdecode, multihashbytes.Bytes())
|
|
|
|
}
|
|
|
|
data, err = getUpdateDirect(rh, sha1key)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
shadecode, err := multihash.FromMultihash(data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(shadecode, sha1bytes) {
|
|
|
|
t.Fatalf("Decoded hash '%x' does not match original hash '%x'", shadecode, sha1bytes)
|
|
|
|
}
|
|
|
|
rh.Close()
|
|
|
|
|
|
|
|
rhparams := &HandlerParams{
|
|
|
|
QueryMaxPeriods: &LookupParams{
|
|
|
|
Limit: false,
|
|
|
|
},
|
|
|
|
Signer: signer,
|
|
|
|
HeaderGetter: rh.headerGetter,
|
|
|
|
OwnerValidator: rh.ownerValidator,
|
|
|
|
}
|
|
|
|
// test with signed data
|
|
|
|
rh2, err := NewTestHandler(datadir, rhparams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
_, _, err = rh2.New(ctx, safeName, resourceFrequency)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
multihashsignedkey, err := rh2.UpdateMultihash(ctx, safeName, multihashmulti)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
sha1signedkey, err := rh2.UpdateMultihash(ctx, safeName, sha1multi)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err = getUpdateDirect(rh2, multihashsignedkey)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
multihashdecode, err = multihash.FromMultihash(data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(multihashdecode, multihashbytes.Bytes()) {
|
|
|
|
t.Fatalf("Decoded hash '%x' does not match original hash '%x'", multihashdecode, multihashbytes.Bytes())
|
|
|
|
}
|
|
|
|
data, err = getUpdateDirect(rh2, sha1signedkey)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
shadecode, err = multihash.FromMultihash(data)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal(shadecode, sha1bytes) {
|
|
|
|
t.Fatalf("Decoded hash '%x' does not match original hash '%x'", shadecode, sha1bytes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestChunkValidator(t *testing.T) {
|
|
|
|
// signer containing private key
|
|
|
|
signer, err := newTestSigner()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ens address and transact options
|
|
|
|
addr := crypto.PubkeyToAddress(signer.PrivKey.PublicKey)
|
|
|
|
transactOpts := bind.NewKeyedTransactor(signer.PrivKey)
|
|
|
|
|
|
|
|
// set up ENS sim
|
|
|
|
domainparts := strings.Split(safeName, ".")
|
|
|
|
contractAddr, contractbackend, err := setupENS(addr, transactOpts, domainparts[0], domainparts[1])
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ensClient, err := ens.NewENS(transactOpts, contractAddr, contractbackend)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// set up rpc and create resourcehandler with ENS sim backend
|
|
|
|
rh, _, teardownTest, err := setupTest(contractbackend, ensClient, signer)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer teardownTest()
|
|
|
|
|
|
|
|
// create new resource when we are owner = ok
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
key, rsrc, err := rh.New(ctx, safeName, resourceFrequency)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Create resource fail: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
data := []byte("foo")
|
|
|
|
key = rh.resourceHash(1, 1, rsrc.nameHash)
|
|
|
|
digest := rh.keyDataHash(key, data)
|
|
|
|
sig, err := rh.signer.Sign(digest)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("sign fail: %v", err)
|
|
|
|
}
|
|
|
|
chunk := newUpdateChunk(key, &sig, 1, 1, safeName, data, len(data))
|
|
|
|
if !rh.Validate(chunk.Addr, chunk.SData) {
|
|
|
|
t.Fatal("Chunk validator fail on update chunk")
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), time.Second)
|
|
|
|
defer cancel()
|
|
|
|
startBlock, err := rh.getBlock(ctx, safeName)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
chunk = rh.newMetaChunk(safeName, startBlock, resourceFrequency)
|
|
|
|
if !rh.Validate(chunk.Addr, chunk.SData) {
|
|
|
|
t.Fatal("Chunk validator fail on metadata chunk")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// tests that the content address validator correctly checks the data
|
|
|
|
// tests that resource update chunks are passed through content address validator
|
|
|
|
// the test checking the resouce update validator internal correctness is found in resource_test.go
|
|
|
|
func TestValidator(t *testing.T) {
|
|
|
|
|
|
|
|
// set up localstore
|
|
|
|
datadir, err := ioutil.TempDir("", "storage-testresourcevalidator")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(datadir)
|
|
|
|
|
|
|
|
params := storage.NewDefaultLocalStoreParams()
|
|
|
|
params.Init(datadir)
|
|
|
|
store, err := storage.NewLocalStore(params, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// add content address validator and resource validator to validators and check puts
|
|
|
|
// bad should fail, good should pass
|
|
|
|
store.Validators = append(store.Validators, storage.NewContentAddressValidator(hashfunc))
|
|
|
|
rhParams := &HandlerParams{}
|
|
|
|
rh, err := NewHandler(rhParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
store.Validators = append(store.Validators, rh)
|
|
|
|
|
|
|
|
chunks := storage.GenerateRandomChunks(storage.DefaultChunkSize, 2)
|
|
|
|
goodChunk := chunks[0]
|
|
|
|
badChunk := chunks[1]
|
|
|
|
badChunk.SData = goodChunk.SData
|
|
|
|
key := rh.resourceHash(42, 1, ens.EnsNode("xyzzy.eth"))
|
|
|
|
data := []byte("bar")
|
|
|
|
uglyChunk := newUpdateChunk(key, nil, 42, 1, "xyzzy.eth", data, len(data))
|
|
|
|
|
|
|
|
storage.PutChunks(store, goodChunk, badChunk, uglyChunk)
|
|
|
|
if err := goodChunk.GetErrored(); err != nil {
|
|
|
|
t.Fatalf("expected no error on good content address chunk with both validators, but got: %s", err)
|
|
|
|
}
|
|
|
|
if err := badChunk.GetErrored(); err == nil {
|
|
|
|
t.Fatal("expected error on bad chunk address with both validators, but got nil")
|
|
|
|
}
|
|
|
|
if err := uglyChunk.GetErrored(); err != nil {
|
|
|
|
t.Fatalf("expected no error on resource update chunk with both validators, but got: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// (redundant check)
|
|
|
|
// use only resource validator, and check puts
|
|
|
|
// bad should fail, good should fail, resource should pass
|
|
|
|
store.Validators[0] = store.Validators[1]
|
|
|
|
store.Validators = store.Validators[:1]
|
|
|
|
|
|
|
|
chunks = storage.GenerateRandomChunks(storage.DefaultChunkSize, 2)
|
|
|
|
goodChunk = chunks[0]
|
|
|
|
badChunk = chunks[1]
|
|
|
|
badChunk.SData = goodChunk.SData
|
|
|
|
|
|
|
|
key = rh.resourceHash(42, 2, ens.EnsNode("xyzzy.eth"))
|
|
|
|
data = []byte("baz")
|
|
|
|
uglyChunk = newUpdateChunk(key, nil, 42, 2, "xyzzy.eth", data, len(data))
|
|
|
|
|
|
|
|
storage.PutChunks(store, goodChunk, badChunk, uglyChunk)
|
|
|
|
if goodChunk.GetErrored() == nil {
|
|
|
|
t.Fatal("expected error on good content address chunk with resource validator only, but got nil")
|
|
|
|
}
|
|
|
|
if badChunk.GetErrored() == nil {
|
|
|
|
t.Fatal("expected error on bad content address chunk with resource validator only, but got nil")
|
|
|
|
}
|
|
|
|
if err := uglyChunk.GetErrored(); err != nil {
|
|
|
|
t.Fatalf("expected no error on resource update chunk with resource validator only, but got: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// fast-forward blockheight
|
|
|
|
func fwdBlocks(count int, backend *fakeBackend) {
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
backend.Commit()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ensOwnerValidator struct {
|
|
|
|
*ens.ENS
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e ensOwnerValidator) ValidateOwner(name string, address common.Address) (bool, error) {
|
|
|
|
addr, err := e.Owner(ens.EnsNode(name))
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return address == addr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// create rpc and resourcehandler
|
|
|
|
func setupTest(backend headerGetter, ensBackend *ens.ENS, signer Signer) (rh *Handler, datadir string, teardown func(), err error) {
|
|
|
|
|
|
|
|
var fsClean func()
|
|
|
|
var rpcClean func()
|
|
|
|
cleanF = func() {
|
|
|
|
if fsClean != nil {
|
|
|
|
fsClean()
|
|
|
|
}
|
|
|
|
if rpcClean != nil {
|
|
|
|
rpcClean()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// temp datadir
|
|
|
|
datadir, err = ioutil.TempDir("", "rh")
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", nil, err
|
|
|
|
}
|
|
|
|
fsClean = func() {
|
|
|
|
os.RemoveAll(datadir)
|
|
|
|
}
|
|
|
|
|
|
|
|
var ov ownerValidator
|
|
|
|
if ensBackend != nil {
|
|
|
|
ov = ensOwnerValidator{ensBackend}
|
|
|
|
}
|
|
|
|
|
|
|
|
rhparams := &HandlerParams{
|
|
|
|
QueryMaxPeriods: &LookupParams{
|
|
|
|
Limit: false,
|
|
|
|
},
|
|
|
|
Signer: signer,
|
|
|
|
HeaderGetter: backend,
|
|
|
|
OwnerValidator: ov,
|
|
|
|
}
|
|
|
|
rh, err = NewTestHandler(datadir, rhparams)
|
|
|
|
return rh, datadir, cleanF, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up simulated ENS backend for use with ENSHandler tests
|
|
|
|
func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string, top string) (common.Address, *fakeBackend, error) {
|
|
|
|
|
|
|
|
// create the domain hash values to pass to the ENS contract methods
|
|
|
|
var tophash [32]byte
|
|
|
|
var subhash [32]byte
|
|
|
|
|
|
|
|
testHasher.Reset()
|
|
|
|
testHasher.Write([]byte(top))
|
|
|
|
copy(tophash[:], testHasher.Sum(nil))
|
|
|
|
testHasher.Reset()
|
|
|
|
testHasher.Write([]byte(sub))
|
|
|
|
copy(subhash[:], testHasher.Sum(nil))
|
|
|
|
|
|
|
|
// initialize contract backend and deploy
|
|
|
|
contractBackend := &fakeBackend{
|
|
|
|
SimulatedBackend: backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}),
|
|
|
|
}
|
|
|
|
|
|
|
|
contractAddress, _, 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)
|
|
|
|
}
|
|
|
|
contractBackend.Commit()
|
|
|
|
|
|
|
|
if _, err = ensinstance.SetSubnodeOwner(transactOpts, [32]byte{}, tophash, addr); err != nil {
|
|
|
|
return zeroAddr, nil, fmt.Errorf("can't register top: %v", err)
|
|
|
|
}
|
|
|
|
contractBackend.Commit()
|
|
|
|
|
|
|
|
if _, err = ensinstance.SetSubnodeOwner(transactOpts, ens.EnsNode(top), subhash, addr); err != nil {
|
|
|
|
return zeroAddr, nil, fmt.Errorf("can't register top: %v", err)
|
|
|
|
}
|
|
|
|
contractBackend.Commit()
|
|
|
|
|
|
|
|
return contractAddress, contractBackend, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestSigner() (*GenericSigner, error) {
|
|
|
|
privKey, err := crypto.GenerateKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &GenericSigner{
|
|
|
|
PrivKey: privKey,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getUpdateDirect(rh *Handler, addr storage.Address) ([]byte, error) {
|
2018-07-13 10:40:28 -05:00
|
|
|
chunk, err := rh.chunkStore.Get(context.TODO(), addr)
|
2018-06-20 07:06:27 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, _, _, _, data, _, err := rh.parseUpdate(chunk.SData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return data, nil
|
|
|
|
}
|