aboutsummaryrefslogblamecommitdiffstats
path: root/swarm/storage/mru/handler_test.go
blob: a4c7969a39fb63ca222df8078df44f3f025f6e2f (plain) (tree)









































                                                                                  
                                  































                                                                                                                                                      
                                     








                                                                      
                                                                            








                                                               

                                                                                              












                                                                                                     
                                                        




                                                    
                                                                         






                                          
                                                                                                            












                                                                                                                    
                                                                         





                                                                                               
                                                                  






                                                    
                                                                         





                                          
                                                                  







                                                    
                                                                         





                                        
                                                                  











                                                                                                                                                              
                                                                         




                               
                            



                                                        
                                      
 
                                                                 



                            
                                                                                             
















                                                                                                                   
                                                                                            









                                                                                                                       
                                                                                                       


































                                                                                                                
                     













































































                                                                                                                   

                                               


                                        
                                         






















































                                                                                   


                                                       






                                                                           
                     








                                                                
                           






                               
                        











                                                                   
                                                                                                                    


                                                       
                                                                                                                   


                                                        
                                                                                                                    



                                 
                                                                                                                             












                                    
                                               







                                        


                                                   



























                                                                                                           
// 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"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "testing"
    "time"

    "github.com/ethereum/go-ethereum/crypto"

    "github.com/ethereum/go-ethereum/log"
    "github.com/ethereum/go-ethereum/swarm/chunk"
    "github.com/ethereum/go-ethereum/swarm/storage"
    "github.com/ethereum/go-ethereum/swarm/storage/mru/lookup"
)

var (
    loglevel  = flag.Int("loglevel", 3, "loglevel")
    startTime = Timestamp{
        Time: uint64(4200),
    }
    cleanF       func()
    subtopicName = "føø.bar"
    hashfunc     = storage.MakeHashFunc(storage.DefaultHash)
)

func init() {
    flag.Parse()
    log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
}

// simulated timeProvider
type fakeTimeProvider struct {
    currentTime uint64
}

func (f *fakeTimeProvider) Tick() {
    f.currentTime++
}

func (f *fakeTimeProvider) Set(time uint64) {
    f.currentTime = time
}

func (f *fakeTimeProvider) FastForward(offset uint64) {
    f.currentTime += offset
}

func (f *fakeTimeProvider) Now() Timestamp {
    return Timestamp{
        Time: f.currentTime,
    }
}

// make updates and retrieve them based on periods and versions
func TestFeedsHandler(t *testing.T) {

    // make fake timeProvider
    clock := &fakeTimeProvider{
        currentTime: startTime.Time, // clock starts at t=4200
    }

    // signer containing private key
    signer := newAliceSigner()

    feedsHandler, datadir, teardownTest, err := setupTest(clock, signer)
    if err != nil {
        t.Fatal(err)
    }
    defer teardownTest()

    // create a new resource
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    topic, _ := NewTopic("Mess with Swarm Feeds code and see what ghost catches you", nil)
    view := Feed{
        Topic: topic,
        User:  signer.Address(),
    }

    // data for updates:
    updates := []string{
        "blinky", // t=4200
        "pinky",  // t=4242
        "inky",   // t=4284
        "clyde",  // t=4285
    }

    request := NewFirstRequest(view.Topic) // this timestamps the update at t = 4200 (start time)
    chunkAddress := make(map[string]storage.Address)
    data := []byte(updates[0])
    request.SetData(data)
    if err := request.Sign(signer); err != nil {
        t.Fatal(err)
    }
    chunkAddress[updates[0]], err = feedsHandler.Update(ctx, request)
    if err != nil {
        t.Fatal(err)
    }

    // move the clock ahead 21 seconds
    clock.FastForward(21) // t=4221

    request, err = feedsHandler.NewRequest(ctx, &request.Feed) // this timestamps the update at t = 4221
    if err != nil {
        t.Fatal(err)
    }
    if request.Epoch.Base() != 0 || request.Epoch.Level != lookup.HighestLevel-1 {
        t.Fatalf("Suggested epoch BaseTime should be 0 and Epoch level should be %d", lookup.HighestLevel-1)
    }

    request.Epoch.Level = lookup.HighestLevel // force level 25 instead of 24 to make it fail
    data = []byte(updates[1])
    request.SetData(data)
    if err := request.Sign(signer); err != nil {
        t.Fatal(err)
    }
    chunkAddress[updates[1]], err = feedsHandler.Update(ctx, request)
    if err == nil {
        t.Fatal("Expected update to fail since an update in this epoch already exists")
    }

    // move the clock ahead 21 seconds
    clock.FastForward(21) // t=4242
    request, err = feedsHandler.NewRequest(ctx, &request.Feed)
    if err != nil {
        t.Fatal(err)
    }
    request.SetData(data)
    if err := request.Sign(signer); err != nil {
        t.Fatal(err)
    }
    chunkAddress[updates[1]], err = feedsHandler.Update(ctx, request)
    if err != nil {
        t.Fatal(err)
    }

    // move the clock ahead 42 seconds
    clock.FastForward(42) // t=4284
    request, err = feedsHandler.NewRequest(ctx, &request.Feed)
    if err != nil {
        t.Fatal(err)
    }
    data = []byte(updates[2])
    request.SetData(data)
    if err := request.Sign(signer); err != nil {
        t.Fatal(err)
    }
    chunkAddress[updates[2]], err = feedsHandler.Update(ctx, request)
    if err != nil {
        t.Fatal(err)
    }

    // move the clock ahead 1 second
    clock.FastForward(1) // t=4285
    request, err = feedsHandler.NewRequest(ctx, &request.Feed)
    if err != nil {
        t.Fatal(err)
    }
    if request.Epoch.Base() != 0 || request.Epoch.Level != 22 {
        t.Fatalf("Expected epoch base time to be %d, got %d. Expected epoch level to be %d, got %d", 0, request.Epoch.Base(), 22, request.Epoch.Level)
    }
    data = []byte(updates[3])
    request.SetData(data)

    if err := request.Sign(signer); err != nil {
        t.Fatal(err)
    }
    chunkAddress[updates[3]], err = feedsHandler.Update(ctx, request)
    if err != nil {
        t.Fatal(err)
    }

    time.Sleep(time.Second)
    feedsHandler.Close()

    // check we can retrieve the updates after close
    clock.FastForward(2000) // t=6285

    feedParams := &HandlerParams{}

    feedsHandler2, err := NewTestHandler(datadir, feedParams)
    if err != nil {
        t.Fatal(err)
    }

    rsrc2, err := feedsHandler2.Lookup(ctx, NewQueryLatest(&request.Feed, lookup.NoClue))
    if err != nil {
        t.Fatal(err)
    }

    // last update should be "clyde"
    if !bytes.Equal(rsrc2.data, []byte(updates[len(updates)-1])) {
        t.Fatalf("resource data was %v, expected %v", string(rsrc2.data), updates[len(updates)-1])
    }
    if rsrc2.Level != 22 {
        t.Fatalf("resource epoch level was %d, expected 22", rsrc2.Level)
    }
    if rsrc2.Base() != 0 {
        t.Fatalf("resource epoch base time was %d, expected 0", rsrc2.Base())
    }
    log.Debug("Latest lookup", "epoch base time", rsrc2.Base(), "epoch level", rsrc2.Level, "data", rsrc2.data)

    // specific point in time
    rsrc, err := feedsHandler2.Lookup(ctx, NewQuery(&request.Feed, 4284, lookup.NoClue))
    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", string(rsrc2.data), updates[2])
    }
    log.Debug("Historical lookup", "epoch base time", rsrc2.Base(), "epoch level", rsrc2.Level, "data", rsrc2.data)

    // beyond the first should yield an error
    rsrc, err = feedsHandler2.Lookup(ctx, NewQuery(&request.Feed, startTime.Time-1, lookup.NoClue))
    if err == nil {
        t.Fatalf("expected previous to fail, returned epoch %s data %v", rsrc.Epoch.String(), rsrc.data)
    }

}

const Day = 60 * 60 * 24
const Year = Day * 365
const Month = Day * 30

func generateData(x uint64) []byte {
    return []byte(fmt.Sprintf("%d", x))
}

func TestSparseUpdates(t *testing.T) {

    // make fake timeProvider
    timeProvider := &fakeTimeProvider{
        currentTime: startTime.Time,
    }

    // signer containing private key
    signer := newAliceSigner()

    rh, datadir, teardownTest, err := setupTest(timeProvider, signer)
    if err != nil {
        t.Fatal(err)
    }
    defer teardownTest()
    defer os.RemoveAll(datadir)

    // create a new resource
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    topic, _ := NewTopic("Very slow updates", nil)
    view := Feed{
        Topic: topic,
        User:  signer.Address(),
    }

    // publish one update every 5 years since Unix 0 until today
    today := uint64(1533799046)
    var epoch lookup.Epoch
    var lastUpdateTime uint64
    for T := uint64(0); T < today; T += 5 * Year {
        request := NewFirstRequest(view.Topic)
        request.Epoch = lookup.GetNextEpoch(epoch, T)
        request.data = generateData(T) // this generates some data that depends on T, so we can check later
        request.Sign(signer)
        if err != nil {
            t.Fatal(err)
        }

        if _, err := rh.Update(ctx, request); err != nil {
            t.Fatal(err)
        }
        epoch = request.Epoch
        lastUpdateTime = T
    }

    query := NewQuery(&view, today, lookup.NoClue)

    _, err = rh.Lookup(ctx, query)
    if err != nil {
        t.Fatal(err)
    }

    _, content, err := rh.GetContent(&view)
    if err != nil {
        t.Fatal(err)
    }

    if !bytes.Equal(generateData(lastUpdateTime), content) {
        t.Fatalf("Expected to recover last written value %d, got %s", lastUpdateTime, string(content))
    }

    // lookup the closest update to 35*Year + 6* Month (~ June 2005):
    // it should find the update we put on 35*Year, since we were updating every 5 years.

    query.TimeLimit = 35*Year + 6*Month

    _, err = rh.Lookup(ctx, query)
    if err != nil {
        t.Fatal(err)
    }

    _, content, err = rh.GetContent(&view)
    if err != nil {
        t.Fatal(err)
    }

    if !bytes.Equal(generateData(35*Year), content) {
        t.Fatalf("Expected to recover %d, got %s", 35*Year, string(content))
    }
}

func TestValidator(t *testing.T) {

    // make fake timeProvider
    timeProvider := &fakeTimeProvider{
        currentTime: startTime.Time,
    }

    // signer containing private key. Alice will be the good girl
    signer := newAliceSigner()

    // set up  sim timeProvider
    rh, _, teardownTest, err := setupTest(timeProvider, signer)
    if err != nil {
        t.Fatal(err)
    }
    defer teardownTest()

    // create new resource
    topic, _ := NewTopic(subtopicName, nil)
    feed := Feed{
        Topic: topic,
        User:  signer.Address(),
    }
    mr := NewFirstRequest(feed.Topic)

    // chunk with address
    data := []byte("foo")
    mr.SetData(data)
    if err := mr.Sign(signer); err != nil {
        t.Fatalf("sign fail: %v", err)
    }

    chunk, err := mr.toChunk()
    if err != nil {
        t.Fatal(err)
    }
    if !rh.Validate(chunk.Address(), chunk.Data()) {
        t.Fatal("Chunk validator fail on update chunk")
    }

    address := chunk.Address()
    // mess with the address
    address[0] = 11
    address[15] = 99

    if rh.Validate(address, chunk.Data()) {
        t.Fatal("Expected Validate to fail with false chunk address")
    }
}

// tests that the content address validator correctly checks the data
// tests that resource update chunks are passed through content address validator
// there is some redundancy in this test as it also tests content addressed chunks,
// which should be evaluated as invalid chunks by this validator
func TestValidatorInStore(t *testing.T) {

    // make fake timeProvider
    TimestampProvider = &fakeTimeProvider{
        currentTime: startTime.Time,
    }

    // signer containing private key
    signer := newAliceSigner()

    // set up localstore
    datadir, err := ioutil.TempDir("", "storage-testresourcevalidator")
    if err != nil {
        t.Fatal(err)
    }
    defer os.RemoveAll(datadir)

    handlerParams := storage.NewDefaultLocalStoreParams()
    handlerParams.Init(datadir)
    store, err := storage.NewLocalStore(handlerParams, nil)
    if err != nil {
        t.Fatal(err)
    }

    // set up resource handler and add is as a validator to the localstore
    fhParams := &HandlerParams{}
    fh := NewHandler(fhParams)
    store.Validators = append(store.Validators, fh)

    // create content addressed chunks, one good, one faulty
    chunks := storage.GenerateRandomChunks(chunk.DefaultSize, 2)
    goodChunk := chunks[0]
    badChunk := storage.NewChunk(chunks[1].Address(), goodChunk.Data())

    topic, _ := NewTopic("xyzzy", nil)
    feed := Feed{
        Topic: topic,
        User:  signer.Address(),
    }

    // create a resource update chunk with correct publickey
    id := ID{
        Epoch: lookup.Epoch{Time: 42,
            Level: 1,
        },
        Feed: feed,
    }

    updateAddr := id.Addr()
    data := []byte("bar")

    r := new(Request)
    r.idAddr = updateAddr
    r.Update.ID = id
    r.data = data

    r.Sign(signer)

    uglyChunk, err := r.toChunk()
    if err != nil {
        t.Fatal(err)
    }

    // put the chunks in the store and check their error status
    err = store.Put(context.Background(), goodChunk)
    if err == nil {
        t.Fatal("expected error on good content address chunk with feed update validator only, but got nil")
    }
    err = store.Put(context.Background(), badChunk)
    if err == nil {
        t.Fatal("expected error on bad content address chunk with feed update validator only, but got nil")
    }
    err = store.Put(context.Background(), uglyChunk)
    if err != nil {
        t.Fatalf("expected no error on feed update chunk with feed update validator only, but got: %s", err)
    }
}

// create rpc and resourcehandler
func setupTest(timeProvider timestampProvider, signer Signer) (fh *TestHandler, 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("", "fh")
    if err != nil {
        return nil, "", nil, err
    }
    fsClean = func() {
        os.RemoveAll(datadir)
    }

    TimestampProvider = timeProvider
    fhParams := &HandlerParams{}
    fh, err = NewTestHandler(datadir, fhParams)
    return fh, datadir, cleanF, err
}

func newAliceSigner() *GenericSigner {
    privKey, _ := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
    return NewGenericSigner(privKey)
}

func newBobSigner() *GenericSigner {
    privKey, _ := crypto.HexToECDSA("accedeaccedeaccedeaccedeaccedeaccedeaccedeaccedeaccedeaccedecaca")
    return NewGenericSigner(privKey)
}

func newCharlieSigner() *GenericSigner {
    privKey, _ := crypto.HexToECDSA("facadefacadefacadefacadefacadefacadefacadefacadefacadefacadefaca")
    return NewGenericSigner(privKey)
}

func getUpdateDirect(rh *Handler, addr storage.Address) ([]byte, error) {
    chunk, err := rh.chunkStore.Get(context.TODO(), addr)
    if err != nil {
        return nil, err
    }
    var r Request
    if err := r.fromChunk(addr, chunk.Data()); err != nil {
        return nil, err
    }
    return r.data, nil
}