mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 01:13:45 +00:00
348 lines
9.5 KiB
Go
348 lines
9.5 KiB
Go
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/net/idna"
|
|
|
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
)
|
|
|
|
var (
|
|
blockCount = uint64(4200)
|
|
cleanF func()
|
|
)
|
|
|
|
func init() {
|
|
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
|
}
|
|
|
|
type FakeRPC struct {
|
|
blockcount *uint64
|
|
}
|
|
|
|
func (r *FakeRPC) BlockNumber() (string, error) {
|
|
return strconv.FormatUint(*r.blockcount, 10), nil
|
|
}
|
|
|
|
func TestResourceValidContent(t *testing.T) {
|
|
|
|
rh, privkey, _, err, teardownTest := setupTest()
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// create a new resource
|
|
validname, err := idna.ToASCII("føø.bar")
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// generate a hash for block 4200 version 1
|
|
key := rh.resourceHash(ens.EnsNode(validname), 4200, 1)
|
|
chunk := NewChunk(key, nil)
|
|
|
|
// generate some bogus data for the chunk and sign it
|
|
data := make([]byte, 8)
|
|
_, err = rand.Read(data)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
rh.hasher.Reset()
|
|
rh.hasher.Write(data)
|
|
datahash := rh.hasher.Sum(nil)
|
|
sig, err := crypto.Sign(datahash, privkey)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// put sig and data in the chunk
|
|
chunk.SData = make([]byte, 8+signatureLength)
|
|
copy(chunk.SData[:signatureLength], sig)
|
|
copy(chunk.SData[signatureLength:], data)
|
|
|
|
// check that we can recover the owner account from the update chunk's signature
|
|
// TODO: change this to verifyContent on ENS integration
|
|
recoveredaddress, err := rh.getContentAccount(chunk.SData)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
originaladdress := crypto.PubkeyToAddress(privkey.PublicKey)
|
|
|
|
if recoveredaddress != originaladdress {
|
|
teardownTest(t, fmt.Errorf("addresses dont match: %x != %x", originaladdress, recoveredaddress))
|
|
}
|
|
teardownTest(t, nil)
|
|
}
|
|
|
|
func TestResourceReverseLookup(t *testing.T) {
|
|
//rh, privkey, datadir, err, teardownTest := setupTest()
|
|
rh, _, _, err, teardownTest := setupTest()
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// create a new resource
|
|
resourcename := "føø.bar"
|
|
resourcefrequency := uint64(42)
|
|
rsrc, err := rh.NewResource(resourcename, resourcefrequency)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// update data
|
|
blockCount += resourcefrequency + 1
|
|
data := []byte("foo")
|
|
resourcekey, err := rh.Update(resourcename, data)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(resourcekey))
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// check if data after header length offset is as expected
|
|
headerlength := binary.LittleEndian.Uint16(chunk.SData[signatureLength : signatureLength+2])
|
|
if !bytes.Equal(chunk.SData[signatureLength+headerlength+2:], data) {
|
|
teardownTest(t, fmt.Errorf("Expected chunk data with header length %d (pos %d) to match %x, but was %x", headerlength, signatureLength+headerlength+2, data, chunk.SData[signatureLength+headerlength+2:]))
|
|
}
|
|
|
|
// get name, period, version from chunk and check
|
|
revperiod, revversion, revname, revdata, err := parseUpdate(chunk.SData[signatureLength:])
|
|
|
|
if !bytes.Equal(revname, rsrc.ensName.Bytes()) {
|
|
teardownTest(t, fmt.Errorf("Expected retrieved name from chunk data to be '%x', was '%x'", rsrc.ensName.Bytes(), revname))
|
|
}
|
|
if !bytes.Equal(revdata, data) {
|
|
teardownTest(t, fmt.Errorf("Expected retrieved data from chunk data to be '%x', was '%x'", data, revdata))
|
|
}
|
|
|
|
if revperiod != 2 {
|
|
teardownTest(t, fmt.Errorf("Expected retrieved period from chunk data to be 1, was %d", revperiod))
|
|
}
|
|
if revversion != 1 {
|
|
teardownTest(t, fmt.Errorf("Expected retrieved version from chunk data to be 1, was %d", revversion))
|
|
}
|
|
}
|
|
|
|
func TestResourceHandler(t *testing.T) {
|
|
|
|
rh, privkey, datadir, err, teardownTest := setupTest()
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// create a new resource
|
|
resourcename := "føø.bar"
|
|
resourcevalidname, err := idna.ToASCII(resourcename)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
resourcefrequency := uint64(42)
|
|
_, err = rh.NewResource(resourcename, resourcefrequency)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// check that the new resource is stored correctly
|
|
namehash := ens.EnsNode(resourcevalidname)
|
|
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:]))
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
} else if len(chunk.SData) < 16 {
|
|
teardownTest(t, fmt.Errorf("chunk data must be minimum 16 bytes, is %d", len(chunk.SData)))
|
|
}
|
|
startblocknumber := binary.LittleEndian.Uint64(chunk.SData[8:16])
|
|
chunkfrequency := binary.LittleEndian.Uint64(chunk.SData[16:])
|
|
if startblocknumber != blockCount {
|
|
teardownTest(t, fmt.Errorf("stored block number %d does not match provided block number %d", startblocknumber, blockCount))
|
|
}
|
|
if chunkfrequency != resourcefrequency {
|
|
teardownTest(t, fmt.Errorf("stored frequency %d does not match provided frequency %d", chunkfrequency, resourcefrequency))
|
|
}
|
|
|
|
// update halfway to first period
|
|
resourcekey := make(map[string]Key)
|
|
blockCount = startblocknumber + (resourcefrequency / 2)
|
|
resourcekey["blinky"], err = rh.Update(resourcename, []byte("blinky"))
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// update on first period
|
|
blockCount = startblocknumber + resourcefrequency
|
|
resourcekey["pinky"], err = rh.Update(resourcename, []byte("pinky"))
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// update on second period
|
|
blockCount = startblocknumber + (resourcefrequency * 2)
|
|
resourcekey["inky"], err = rh.Update(resourcename, []byte("inky"))
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// update just after second period
|
|
blockCount = startblocknumber + (resourcefrequency * 2) + 1
|
|
resourcekey["clyde"], err = rh.Update(resourcename, []byte("clyde"))
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
time.Sleep(time.Second)
|
|
rh.Close()
|
|
|
|
// check we can retrieve the updates after close
|
|
// it will match on second iteration startblocknumber + (resourcefrequency * 3)
|
|
blockCount = startblocknumber + (resourcefrequency * 4)
|
|
|
|
rh2, err := NewResourceHandler(privkey, datadir, &testCloudStore{}, rh.ethapi)
|
|
_, err = rh2.LookupLatest(resourcename, true)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// last update should be "clyde", version two, blockheight startblocknumber + (resourcefrequency * 3)
|
|
if !bytes.Equal(rh2.resources[resourcename].data, []byte("clyde")) {
|
|
teardownTest(t, fmt.Errorf("resource data was %v, expected %v", rh2.resources[resourcename].data, []byte("clyde")))
|
|
}
|
|
if rh2.resources[resourcename].version != 2 {
|
|
teardownTest(t, fmt.Errorf("resource version was %d, expected 2", rh2.resources[resourcename].version))
|
|
}
|
|
if rh2.resources[resourcename].lastPeriod != 3 {
|
|
teardownTest(t, fmt.Errorf("resource period was %d, expected 3", rh2.resources[resourcename].lastPeriod))
|
|
}
|
|
|
|
rsrc, err := NewResource(resourcename, startblocknumber, resourcefrequency)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
err = rh2.SetResource(rsrc, true)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// latest block, latest version
|
|
resource, err := rh2.LookupLatest(resourcename, false) // if key is specified, refresh is implicit
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// check data
|
|
if !bytes.Equal(resource.data, []byte("clyde")) {
|
|
teardownTest(t, fmt.Errorf("resource data (latest) was %v, expected %v", rh2.resources[resourcename].data, []byte("clyde")))
|
|
}
|
|
|
|
// specific block, latest version
|
|
resource, err = rh2.LookupHistorical(resourcename, 3, true)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// check data
|
|
if !bytes.Equal(resource.data, []byte("clyde")) {
|
|
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[resourcename].data, []byte("clyde")))
|
|
}
|
|
|
|
// specific block, specific version
|
|
resource, err = rh2.LookupVersion(resourcename, 3, 1, true)
|
|
if err != nil {
|
|
teardownTest(t, err)
|
|
}
|
|
|
|
// check data
|
|
if !bytes.Equal(resource.data, []byte("inky")) {
|
|
teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[resourcename].data, []byte("inky")))
|
|
}
|
|
teardownTest(t, nil)
|
|
|
|
}
|
|
|
|
func setupTest() (rh *ResourceHandler, privkey *ecdsa.PrivateKey, datadir string, err error, teardown func(*testing.T, error)) {
|
|
|
|
var fsClean func()
|
|
var rpcClean func()
|
|
cleanF = func() {
|
|
if fsClean != nil {
|
|
fsClean()
|
|
}
|
|
if rpcClean != nil {
|
|
rpcClean()
|
|
}
|
|
}
|
|
|
|
// privkey for signing updates
|
|
privkey, err = crypto.GenerateKey()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// temp datadir
|
|
datadir, err = ioutil.TempDir("", "rh")
|
|
if err != nil {
|
|
return
|
|
}
|
|
fsClean = func() {
|
|
os.RemoveAll(datadir)
|
|
}
|
|
|
|
// starting the whole stack just to get blocknumbers is too cumbersome
|
|
// so we fake the rpc server to get blocknumbers for testing
|
|
ipcpath := filepath.Join(datadir, "test.ipc")
|
|
ipcl, err := rpc.CreateIPCListener(ipcpath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
rpcserver := rpc.NewServer()
|
|
rpcserver.RegisterName("eth", &FakeRPC{
|
|
blockcount: &blockCount,
|
|
})
|
|
go func() {
|
|
rpcserver.ServeListener(ipcl)
|
|
}()
|
|
rpcClean = func() {
|
|
rpcserver.Stop()
|
|
}
|
|
|
|
// connect to fake rpc
|
|
rpcclient, err := rpc.Dial(ipcpath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
rh, err = NewResourceHandler(privkey, datadir, &testCloudStore{}, rpcclient)
|
|
teardown = func(t *testing.T, err error) {
|
|
cleanF()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type testCloudStore struct {
|
|
}
|
|
|
|
func (c *testCloudStore) Store(*Chunk) {
|
|
}
|
|
|
|
func (c *testCloudStore) Deliver(*Chunk) {
|
|
}
|
|
|
|
func (c *testCloudStore) Retrieve(*Chunk) {
|
|
}
|