github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/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  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/ethereum/go-ethereum/node"
    32  	"github.com/ethereum/go-ethereum/p2p"
    33  	"github.com/ethereum/go-ethereum/p2p/enode"
    34  	"github.com/ethereum/go-ethereum/p2p/protocols"
    35  	"github.com/ethereum/go-ethereum/p2p/simulations"
    36  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    37  	"github.com/ethereum/go-ethereum/rlp"
    38  	"github.com/ethereum/go-ethereum/swarm/log"
    39  	"github.com/ethereum/go-ethereum/swarm/network/simulation"
    40  	"github.com/ethereum/go-ethereum/swarm/state"
    41  	"github.com/ethereum/go-ethereum/swarm/storage"
    42  )
    43  
    44  /*
    45  The tests in this file need to be executed with
    46  
    47  			-tags=withserver
    48  
    49  Also, they will stall if executed stand-alone, because they wait
    50  for the visualization frontend to send a POST /runsim message.
    51  */
    52  
    53  //setup the sim, evaluate nodeCount and chunkCount and create the sim
    54  func setupSim(serviceMap map[string]simulation.ServiceFunc) (int, int, *simulation.Simulation) {
    55  	nodeCount := *nodes
    56  	chunkCount := *chunks
    57  
    58  	if nodeCount == 0 || chunkCount == 0 {
    59  		nodeCount = 32
    60  		chunkCount = 1
    61  	}
    62  
    63  	//setup the simulation with server, which means the sim won't run
    64  	//until it receives a POST /runsim from the frontend
    65  	sim := simulation.New(serviceMap).WithServer(":8888")
    66  	return nodeCount, chunkCount, sim
    67  }
    68  
    69  //This test requests bogus hashes into the network
    70  func TestNonExistingHashesWithServer(t *testing.T) {
    71  
    72  	nodeCount, _, sim := setupSim(retrievalSimServiceMap)
    73  	defer sim.Close()
    74  
    75  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
    76  	if err != nil {
    77  		panic(err)
    78  	}
    79  
    80  	//in order to get some meaningful visualization, it is beneficial
    81  	//to define a minimum duration of this test
    82  	testDuration := 20 * time.Second
    83  
    84  	result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
    85  		disconnected := watchDisconnections(ctx, sim)
    86  		defer func() {
    87  			if err != nil {
    88  				if yes, ok := disconnected.Load().(bool); ok && yes {
    89  					err = errors.New("disconnect events received")
    90  				}
    91  			}
    92  		}()
    93  
    94  		//check on the node's FileStore (netstore)
    95  		id := sim.Net.GetRandomUpNode().ID()
    96  		item, ok := sim.NodeItem(id, bucketKeyFileStore)
    97  		if !ok {
    98  			return errors.New("No filestore")
    99  		}
   100  		fileStore := item.(*storage.FileStore)
   101  		//create a bogus hash
   102  		fakeHash := storage.GenerateRandomChunk(1000).Address()
   103  		//try to retrieve it - will propagate RetrieveRequestMsg into the network
   104  		reader, _ := fileStore.Retrieve(context.TODO(), fakeHash)
   105  		if _, err := reader.Size(ctx, nil); err != nil {
   106  			log.Debug("expected error for non-existing chunk")
   107  		}
   108  		//sleep so that the frontend can have something to display
   109  		time.Sleep(testDuration)
   110  
   111  		return nil
   112  	})
   113  	if result.Error != nil {
   114  		sendSimTerminatedEvent(sim)
   115  		t.Fatal(result.Error)
   116  	}
   117  
   118  	sendSimTerminatedEvent(sim)
   119  
   120  }
   121  
   122  //send a termination event to the frontend
   123  func sendSimTerminatedEvent(sim *simulation.Simulation) {
   124  	evt := &simulations.Event{
   125  		Type:    EventTypeSimTerminated,
   126  		Control: false,
   127  	}
   128  	sim.Net.Events().Send(evt)
   129  }
   130  
   131  //This test is the same as the snapshot sync test,
   132  //but with a HTTP server
   133  //It also sends some custom events so that the frontend
   134  //can visualize messages like SendOfferedMsg, WantedHashesMsg, DeliveryMsg
   135  func TestSnapshotSyncWithServer(t *testing.T) {
   136  	//define a wrapper object to be able to pass around data
   137  	wrapper := &netWrapper{}
   138  
   139  	sim := simulation.New(map[string]simulation.ServiceFunc{
   140  		"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   141  			addr, netStore, delivery, clean, err := newNetStoreAndDeliveryWithRequestFunc(ctx, bucket, dummyRequestFromPeers)
   142  			if err != nil {
   143  				return nil, nil, err
   144  			}
   145  
   146  			r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
   147  				Retrieval:       RetrievalDisabled,
   148  				Syncing:         SyncingAutoSubscribe,
   149  				SyncUpdateDelay: 3 * time.Second,
   150  			}, nil)
   151  
   152  			tr := &testRegistry{
   153  				Registry: r,
   154  				w:        wrapper,
   155  			}
   156  
   157  			bucket.Store(bucketKeyRegistry, tr)
   158  
   159  			cleanup = func() {
   160  				tr.Close()
   161  				clean()
   162  			}
   163  
   164  			return tr, cleanup, nil
   165  		},
   166  	}).WithServer(":8888") //start with the HTTP server
   167  
   168  	nodeCount, chunkCount, sim := setupSim(simServiceMap)
   169  	defer sim.Close()
   170  
   171  	log.Info(fmt.Sprintf("Running the simulation with %d nodes and %d chunks", nodeCount, chunkCount))
   172  	log.Info("Initializing test config")
   173  
   174  	conf := &synctestConfig{}
   175  	//map of discover ID to indexes of chunks expected at that ID
   176  	conf.idToChunksMap = make(map[enode.ID][]int)
   177  	//map of overlay address to discover ID
   178  	conf.addrToIDMap = make(map[string]enode.ID)
   179  	//array where the generated chunk hashes will be stored
   180  	conf.hashes = make([]storage.Address, 0)
   181  	//pass the network to the wrapper object
   182  	wrapper.setNetwork(sim.Net)
   183  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   184  	if err != nil {
   185  		panic(err)
   186  	}
   187  
   188  	//run the sim
   189  	result := runSim(conf, ctx, sim, chunkCount)
   190  
   191  	//send terminated event
   192  	evt := &simulations.Event{
   193  		Type:    EventTypeSimTerminated,
   194  		Control: false,
   195  	}
   196  	go sim.Net.Events().Send(evt)
   197  
   198  	if result.Error != nil {
   199  		panic(result.Error)
   200  	}
   201  	log.Info("Simulation ended")
   202  }
   203  
   204  //testRegistry embeds registry
   205  //it allows to replace the protocol run function
   206  type testRegistry struct {
   207  	*Registry
   208  	w *netWrapper
   209  }
   210  
   211  //Protocols replaces the protocol's run function
   212  func (tr *testRegistry) Protocols() []p2p.Protocol {
   213  	regProto := tr.Registry.Protocols()
   214  	//set the `stream` protocol's run function with the testRegistry's one
   215  	regProto[0].Run = tr.runProto
   216  	return regProto
   217  }
   218  
   219  //runProto is the new overwritten protocol's run function for this test
   220  func (tr *testRegistry) runProto(p *p2p.Peer, rw p2p.MsgReadWriter) error {
   221  	//create a custom rw message ReadWriter
   222  	testRw := &testMsgReadWriter{
   223  		MsgReadWriter: rw,
   224  		Peer:          p,
   225  		w:             tr.w,
   226  		Registry:      tr.Registry,
   227  	}
   228  	//now run the actual upper layer `Registry`'s protocol function
   229  	return tr.runProtocol(p, testRw)
   230  }
   231  
   232  //testMsgReadWriter is a custom rw
   233  //it will allow us to re-use the message twice
   234  type testMsgReadWriter struct {
   235  	*Registry
   236  	p2p.MsgReadWriter
   237  	*p2p.Peer
   238  	w *netWrapper
   239  }
   240  
   241  //netWrapper wrapper object so we can pass data around
   242  type netWrapper struct {
   243  	net *simulations.Network
   244  }
   245  
   246  //set the network to the wrapper for later use (used inside the custom rw)
   247  func (w *netWrapper) setNetwork(n *simulations.Network) {
   248  	w.net = n
   249  }
   250  
   251  //get he network from the wrapper (used inside the custom rw)
   252  func (w *netWrapper) getNetwork() *simulations.Network {
   253  	return w.net
   254  }
   255  
   256  // ReadMsg reads a message from the underlying MsgReadWriter and emits a
   257  // "message received" event
   258  //we do this because we are interested in the Payload of the message for custom use
   259  //in this test, but messages can only be consumed once (stream io.Reader)
   260  func (ev *testMsgReadWriter) ReadMsg() (p2p.Msg, error) {
   261  	//read the message from the underlying rw
   262  	msg, err := ev.MsgReadWriter.ReadMsg()
   263  	if err != nil {
   264  		return msg, err
   265  	}
   266  
   267  	//don't do anything with message codes we actually are not needing/reading
   268  	subCodes := []uint64{1, 2, 10}
   269  	found := false
   270  	for _, c := range subCodes {
   271  		if c == msg.Code {
   272  			found = true
   273  		}
   274  	}
   275  	//just return if not a msg code we are interested in
   276  	if !found {
   277  		return msg, nil
   278  	}
   279  
   280  	//we use a io.TeeReader so that we can read the message twice
   281  	//the Payload is a io.Reader, so if we read from it, the actual protocol handler
   282  	//cannot access it anymore.
   283  	//But we need that handler to be able to consume the message as normal,
   284  	//as if we would not do anything here with that message
   285  	var buf bytes.Buffer
   286  	tee := io.TeeReader(msg.Payload, &buf)
   287  
   288  	mcp := &p2p.Msg{
   289  		Code:       msg.Code,
   290  		Size:       msg.Size,
   291  		ReceivedAt: msg.ReceivedAt,
   292  		Payload:    tee,
   293  	}
   294  	//assign the copy for later use
   295  	msg.Payload = &buf
   296  
   297  	//now let's look into the message
   298  	var wmsg protocols.WrappedMsg
   299  	err = mcp.Decode(&wmsg)
   300  	if err != nil {
   301  		log.Error(err.Error())
   302  		return msg, err
   303  	}
   304  	//create a new message from the code
   305  	val, ok := ev.Registry.GetSpec().NewMsg(mcp.Code)
   306  	if !ok {
   307  		return msg, errors.New(fmt.Sprintf("Invalid message code: %v", msg.Code))
   308  	}
   309  	//decode it
   310  	if err := rlp.DecodeBytes(wmsg.Payload, val); err != nil {
   311  		return msg, errors.New(fmt.Sprintf("Decoding error <= %v: %v", msg, err))
   312  	}
   313  	//now for every message type we are interested in, create a custom event and send it
   314  	var evt *simulations.Event
   315  	switch val := val.(type) {
   316  	case *OfferedHashesMsg:
   317  		evt = &simulations.Event{
   318  			Type:    EventTypeChunkOffered,
   319  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   320  			Control: false,
   321  			Data:    val.Hashes,
   322  		}
   323  	case *WantedHashesMsg:
   324  		evt = &simulations.Event{
   325  			Type:    EventTypeChunkWanted,
   326  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   327  			Control: false,
   328  		}
   329  	case *ChunkDeliveryMsgSyncing:
   330  		evt = &simulations.Event{
   331  			Type:    EventTypeChunkDelivered,
   332  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   333  			Control: false,
   334  			Data:    val.Addr.String(),
   335  		}
   336  	}
   337  	if evt != nil {
   338  		//send custom event to feed; frontend will listen to it and display
   339  		ev.w.getNetwork().Events().Send(evt)
   340  	}
   341  	return msg, nil
   342  }