github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/swarm/network/stream/visualized_snapshot_sync_sim_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // +build withserver
    18  
    19  package stream
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"os"
    28  	"sync"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/ShyftNetwork/go-empyrean/node"
    33  	"github.com/ShyftNetwork/go-empyrean/p2p"
    34  	"github.com/ShyftNetwork/go-empyrean/p2p/enode"
    35  	"github.com/ShyftNetwork/go-empyrean/p2p/protocols"
    36  	"github.com/ShyftNetwork/go-empyrean/p2p/simulations"
    37  	"github.com/ShyftNetwork/go-empyrean/p2p/simulations/adapters"
    38  	"github.com/ShyftNetwork/go-empyrean/rlp"
    39  	"github.com/ShyftNetwork/go-empyrean/swarm/log"
    40  	"github.com/ShyftNetwork/go-empyrean/swarm/network"
    41  	"github.com/ShyftNetwork/go-empyrean/swarm/network/simulation"
    42  	"github.com/ShyftNetwork/go-empyrean/swarm/state"
    43  	"github.com/ShyftNetwork/go-empyrean/swarm/storage"
    44  )
    45  
    46  /*
    47  The tests in this file need to be executed with
    48  
    49  			-tags=withserver
    50  
    51  Also, they will stall if executed stand-alone, because they wait
    52  for the visualization frontend to send a POST /runsim message.
    53  */
    54  
    55  //setup the sim, evaluate nodeCount and chunkCount and create the sim
    56  func setupSim(serviceMap map[string]simulation.ServiceFunc) (int, int, *simulation.Simulation) {
    57  	nodeCount := *nodes
    58  	chunkCount := *chunks
    59  
    60  	if nodeCount == 0 || chunkCount == 0 {
    61  		nodeCount = 32
    62  		chunkCount = 1
    63  	}
    64  
    65  	//setup the simulation with server, which means the sim won't run
    66  	//until it receives a POST /runsim from the frontend
    67  	sim := simulation.New(serviceMap).WithServer(":8888")
    68  	return nodeCount, chunkCount, sim
    69  }
    70  
    71  //watch for disconnections and wait for healthy
    72  func watchSim(sim *simulation.Simulation) (context.Context, context.CancelFunc) {
    73  	ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
    74  
    75  	if _, err := sim.WaitTillHealthy(ctx); err != nil {
    76  		panic(err)
    77  	}
    78  
    79  	disconnections := sim.PeerEvents(
    80  		context.Background(),
    81  		sim.NodeIDs(),
    82  		simulation.NewPeerEventsFilter().Drop(),
    83  	)
    84  
    85  	go func() {
    86  		for d := range disconnections {
    87  			log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID)
    88  			panic("unexpected disconnect")
    89  			cancelSimRun()
    90  		}
    91  	}()
    92  
    93  	return ctx, cancelSimRun
    94  }
    95  
    96  //This test requests bogus hashes into the network
    97  func TestNonExistingHashesWithServer(t *testing.T) {
    98  
    99  	nodeCount, _, sim := setupSim(retrievalSimServiceMap)
   100  	defer sim.Close()
   101  
   102  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   103  	if err != nil {
   104  		panic(err)
   105  	}
   106  
   107  	ctx, cancelSimRun := watchSim(sim)
   108  	defer cancelSimRun()
   109  
   110  	//in order to get some meaningful visualization, it is beneficial
   111  	//to define a minimum duration of this test
   112  	testDuration := 20 * time.Second
   113  
   114  	result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
   115  		//check on the node's FileStore (netstore)
   116  		id := sim.Net.GetRandomUpNode().ID()
   117  		item, ok := sim.NodeItem(id, bucketKeyFileStore)
   118  		if !ok {
   119  			t.Fatalf("No filestore")
   120  		}
   121  		fileStore := item.(*storage.FileStore)
   122  		//create a bogus hash
   123  		fakeHash := storage.GenerateRandomChunk(1000).Address()
   124  		//try to retrieve it - will propagate RetrieveRequestMsg into the network
   125  		reader, _ := fileStore.Retrieve(context.TODO(), fakeHash)
   126  		if _, err := reader.Size(ctx, nil); err != nil {
   127  			log.Debug("expected error for non-existing chunk")
   128  		}
   129  		//sleep so that the frontend can have something to display
   130  		time.Sleep(testDuration)
   131  
   132  		return nil
   133  	})
   134  	if result.Error != nil {
   135  		sendSimTerminatedEvent(sim)
   136  		t.Fatal(result.Error)
   137  	}
   138  
   139  	sendSimTerminatedEvent(sim)
   140  
   141  }
   142  
   143  //send a termination event to the frontend
   144  func sendSimTerminatedEvent(sim *simulation.Simulation) {
   145  	evt := &simulations.Event{
   146  		Type:    EventTypeSimTerminated,
   147  		Control: false,
   148  	}
   149  	sim.Net.Events().Send(evt)
   150  }
   151  
   152  //This test is the same as the snapshot sync test,
   153  //but with a HTTP server
   154  //It also sends some custom events so that the frontend
   155  //can visualize messages like SendOfferedMsg, WantedHashesMsg, DeliveryMsg
   156  func TestSnapshotSyncWithServer(t *testing.T) {
   157  	//t.Skip("temporarily disabled as simulations.WaitTillHealthy cannot be trusted")
   158  
   159  	//define a wrapper object to be able to pass around data
   160  	wrapper := &netWrapper{}
   161  
   162  	nodeCount := *nodes
   163  	chunkCount := *chunks
   164  
   165  	if nodeCount == 0 || chunkCount == 0 {
   166  		nodeCount = 32
   167  		chunkCount = 1
   168  	}
   169  
   170  	log.Info(fmt.Sprintf("Running the simulation with %d nodes and %d chunks", nodeCount, chunkCount))
   171  
   172  	sim := simulation.New(map[string]simulation.ServiceFunc{
   173  		"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   174  			n := ctx.Config.Node()
   175  			addr := network.NewAddr(n)
   176  			store, datadir, err := createTestLocalStorageForID(n.ID(), addr)
   177  			if err != nil {
   178  				return nil, nil, err
   179  			}
   180  			bucket.Store(bucketKeyStore, store)
   181  			localStore := store.(*storage.LocalStore)
   182  			netStore, err := storage.NewNetStore(localStore, nil)
   183  			if err != nil {
   184  				return nil, nil, err
   185  			}
   186  			kad := network.NewKademlia(addr.Over(), network.NewKadParams())
   187  			delivery := NewDelivery(kad, netStore)
   188  			netStore.NewNetFetcherFunc = network.NewFetcherFactory(dummyRequestFromPeers, true).New
   189  
   190  			r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
   191  				Retrieval:       RetrievalDisabled,
   192  				Syncing:         SyncingAutoSubscribe,
   193  				SyncUpdateDelay: 3 * time.Second,
   194  			}, nil)
   195  
   196  			tr := &testRegistry{
   197  				Registry: r,
   198  				w:        wrapper,
   199  			}
   200  
   201  			bucket.Store(bucketKeyRegistry, tr)
   202  
   203  			cleanup = func() {
   204  				netStore.Close()
   205  				tr.Close()
   206  				os.RemoveAll(datadir)
   207  			}
   208  
   209  			return tr, cleanup, nil
   210  		},
   211  	}).WithServer(":8888") //start with the HTTP server
   212  
   213  	nodeCount, chunkCount, sim := setupSim(simServiceMap)
   214  	defer sim.Close()
   215  
   216  	log.Info("Initializing test config")
   217  
   218  	conf := &synctestConfig{}
   219  	//map of discover ID to indexes of chunks expected at that ID
   220  	conf.idToChunksMap = make(map[enode.ID][]int)
   221  	//map of overlay address to discover ID
   222  	conf.addrToIDMap = make(map[string]enode.ID)
   223  	//array where the generated chunk hashes will be stored
   224  	conf.hashes = make([]storage.Address, 0)
   225  	//pass the network to the wrapper object
   226  	wrapper.setNetwork(sim.Net)
   227  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   228  	if err != nil {
   229  		panic(err)
   230  	}
   231  
   232  	ctx, cancelSimRun := watchSim(sim)
   233  	defer cancelSimRun()
   234  
   235  	//run the sim
   236  	result := runSim(conf, ctx, sim, chunkCount)
   237  
   238  	//send terminated event
   239  	evt := &simulations.Event{
   240  		Type:    EventTypeSimTerminated,
   241  		Control: false,
   242  	}
   243  	go sim.Net.Events().Send(evt)
   244  
   245  	if result.Error != nil {
   246  		panic(result.Error)
   247  	}
   248  	log.Info("Simulation ended")
   249  }
   250  
   251  //testRegistry embeds registry
   252  //it allows to replace the protocol run function
   253  type testRegistry struct {
   254  	*Registry
   255  	w *netWrapper
   256  }
   257  
   258  //Protocols replaces the protocol's run function
   259  func (tr *testRegistry) Protocols() []p2p.Protocol {
   260  	regProto := tr.Registry.Protocols()
   261  	//set the `stream` protocol's run function with the testRegistry's one
   262  	regProto[0].Run = tr.runProto
   263  	return regProto
   264  }
   265  
   266  //runProto is the new overwritten protocol's run function for this test
   267  func (tr *testRegistry) runProto(p *p2p.Peer, rw p2p.MsgReadWriter) error {
   268  	//create a custom rw message ReadWriter
   269  	testRw := &testMsgReadWriter{
   270  		MsgReadWriter: rw,
   271  		Peer:          p,
   272  		w:             tr.w,
   273  		Registry:      tr.Registry,
   274  	}
   275  	//now run the actual upper layer `Registry`'s protocol function
   276  	return tr.runProtocol(p, testRw)
   277  }
   278  
   279  //testMsgReadWriter is a custom rw
   280  //it will allow us to re-use the message twice
   281  type testMsgReadWriter struct {
   282  	*Registry
   283  	p2p.MsgReadWriter
   284  	*p2p.Peer
   285  	w *netWrapper
   286  }
   287  
   288  //netWrapper wrapper object so we can pass data around
   289  type netWrapper struct {
   290  	net *simulations.Network
   291  }
   292  
   293  //set the network to the wrapper for later use (used inside the custom rw)
   294  func (w *netWrapper) setNetwork(n *simulations.Network) {
   295  	w.net = n
   296  }
   297  
   298  //get he network from the wrapper (used inside the custom rw)
   299  func (w *netWrapper) getNetwork() *simulations.Network {
   300  	return w.net
   301  }
   302  
   303  // ReadMsg reads a message from the underlying MsgReadWriter and emits a
   304  // "message received" event
   305  //we do this because we are interested in the Payload of the message for custom use
   306  //in this test, but messages can only be consumed once (stream io.Reader)
   307  func (ev *testMsgReadWriter) ReadMsg() (p2p.Msg, error) {
   308  	//read the message from the underlying rw
   309  	msg, err := ev.MsgReadWriter.ReadMsg()
   310  	if err != nil {
   311  		return msg, err
   312  	}
   313  
   314  	//don't do anything with message codes we actually are not needing/reading
   315  	subCodes := []uint64{1, 2, 10}
   316  	found := false
   317  	for _, c := range subCodes {
   318  		if c == msg.Code {
   319  			found = true
   320  		}
   321  	}
   322  	//just return if not a msg code we are interested in
   323  	if !found {
   324  		return msg, nil
   325  	}
   326  
   327  	//we use a io.TeeReader so that we can read the message twice
   328  	//the Payload is a io.Reader, so if we read from it, the actual protocol handler
   329  	//cannot access it anymore.
   330  	//But we need that handler to be able to consume the message as normal,
   331  	//as if we would not do anything here with that message
   332  	var buf bytes.Buffer
   333  	tee := io.TeeReader(msg.Payload, &buf)
   334  
   335  	mcp := &p2p.Msg{
   336  		Code:       msg.Code,
   337  		Size:       msg.Size,
   338  		ReceivedAt: msg.ReceivedAt,
   339  		Payload:    tee,
   340  	}
   341  	//assign the copy for later use
   342  	msg.Payload = &buf
   343  
   344  	//now let's look into the message
   345  	var wmsg protocols.WrappedMsg
   346  	err = mcp.Decode(&wmsg)
   347  	if err != nil {
   348  		log.Error(err.Error())
   349  		return msg, err
   350  	}
   351  	//create a new message from the code
   352  	val, ok := ev.Registry.GetSpec().NewMsg(mcp.Code)
   353  	if !ok {
   354  		return msg, errors.New(fmt.Sprintf("Invalid message code: %v", msg.Code))
   355  	}
   356  	//decode it
   357  	if err := rlp.DecodeBytes(wmsg.Payload, val); err != nil {
   358  		return msg, errors.New(fmt.Sprintf("Decoding error <= %v: %v", msg, err))
   359  	}
   360  	//now for every message type we are interested in, create a custom event and send it
   361  	var evt *simulations.Event
   362  	switch val := val.(type) {
   363  	case *OfferedHashesMsg:
   364  		evt = &simulations.Event{
   365  			Type:    EventTypeChunkOffered,
   366  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   367  			Control: false,
   368  			Data:    val.Hashes,
   369  		}
   370  	case *WantedHashesMsg:
   371  		evt = &simulations.Event{
   372  			Type:    EventTypeChunkWanted,
   373  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   374  			Control: false,
   375  		}
   376  	case *ChunkDeliveryMsgSyncing:
   377  		evt = &simulations.Event{
   378  			Type:    EventTypeChunkDelivered,
   379  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   380  			Control: false,
   381  			Data:    val.Addr.String(),
   382  		}
   383  	}
   384  	if evt != nil {
   385  		//send custom event to feed; frontend will listen to it and display
   386  		ev.w.getNetwork().Events().Send(evt)
   387  	}
   388  	return msg, nil
   389  }