go-ethereum/swarm/storage/resource_test.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) {
}