github.com/daragao/go-ethereum@v1.8.14-0.20180809141559-45eaef243198/swarm/network/stream/snapshot_sync_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  package stream
    17  
    18  import (
    19  	"context"
    20  	crand "crypto/rand"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/node"
    31  	"github.com/ethereum/go-ethereum/p2p"
    32  	"github.com/ethereum/go-ethereum/p2p/discover"
    33  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    34  	"github.com/ethereum/go-ethereum/swarm/network"
    35  	"github.com/ethereum/go-ethereum/swarm/network/simulation"
    36  	"github.com/ethereum/go-ethereum/swarm/pot"
    37  	"github.com/ethereum/go-ethereum/swarm/state"
    38  	"github.com/ethereum/go-ethereum/swarm/storage"
    39  	mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
    40  )
    41  
    42  const testMinProxBinSize = 2
    43  const MaxTimeout = 600
    44  
    45  type synctestConfig struct {
    46  	addrs            [][]byte
    47  	hashes           []storage.Address
    48  	idToChunksMap    map[discover.NodeID][]int
    49  	chunksToNodesMap map[string][]int
    50  	addrToIDMap      map[string]discover.NodeID
    51  }
    52  
    53  //This test is a syncing test for nodes.
    54  //One node is randomly selected to be the pivot node.
    55  //A configurable number of chunks and nodes can be
    56  //provided to the test, the number of chunks is uploaded
    57  //to the pivot node, and we check that nodes get the chunks
    58  //they are expected to store based on the syncing protocol.
    59  //Number of chunks and nodes can be provided via commandline too.
    60  func TestSyncingViaGlobalSync(t *testing.T) {
    61  	//if nodes/chunks have been provided via commandline,
    62  	//run the tests with these values
    63  	if *nodes != 0 && *chunks != 0 {
    64  		log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes))
    65  		testSyncingViaGlobalSync(t, *chunks, *nodes)
    66  	} else {
    67  		var nodeCnt []int
    68  		var chnkCnt []int
    69  		//if the `longrunning` flag has been provided
    70  		//run more test combinations
    71  		if *longrunning {
    72  			chnkCnt = []int{1, 8, 32, 256, 1024}
    73  			nodeCnt = []int{16, 32, 64, 128, 256}
    74  		} else {
    75  			//default test
    76  			chnkCnt = []int{4, 32}
    77  			nodeCnt = []int{32, 16}
    78  		}
    79  		for _, chnk := range chnkCnt {
    80  			for _, n := range nodeCnt {
    81  				log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n))
    82  				testSyncingViaGlobalSync(t, chnk, n)
    83  			}
    84  		}
    85  	}
    86  }
    87  
    88  func TestSyncingViaDirectSubscribe(t *testing.T) {
    89  	//if nodes/chunks have been provided via commandline,
    90  	//run the tests with these values
    91  	if *nodes != 0 && *chunks != 0 {
    92  		log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes))
    93  		err := testSyncingViaDirectSubscribe(*chunks, *nodes)
    94  		if err != nil {
    95  			t.Fatal(err)
    96  		}
    97  	} else {
    98  		var nodeCnt []int
    99  		var chnkCnt []int
   100  		//if the `longrunning` flag has been provided
   101  		//run more test combinations
   102  		if *longrunning {
   103  			chnkCnt = []int{1, 8, 32, 256, 1024}
   104  			nodeCnt = []int{32, 16}
   105  		} else {
   106  			//default test
   107  			chnkCnt = []int{4, 32}
   108  			nodeCnt = []int{32, 16}
   109  		}
   110  		for _, chnk := range chnkCnt {
   111  			for _, n := range nodeCnt {
   112  				log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n))
   113  				err := testSyncingViaDirectSubscribe(chnk, n)
   114  				if err != nil {
   115  					t.Fatal(err)
   116  				}
   117  			}
   118  		}
   119  	}
   120  }
   121  
   122  func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) {
   123  	sim := simulation.New(map[string]simulation.ServiceFunc{
   124  		"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   125  
   126  			id := ctx.Config.ID
   127  			addr := network.NewAddrFromNodeID(id)
   128  			store, datadir, err := createTestLocalStorageForID(id, addr)
   129  			if err != nil {
   130  				return nil, nil, err
   131  			}
   132  			bucket.Store(bucketKeyStore, store)
   133  			cleanup = func() {
   134  				os.RemoveAll(datadir)
   135  				store.Close()
   136  			}
   137  			localStore := store.(*storage.LocalStore)
   138  			db := storage.NewDBAPI(localStore)
   139  			kad := network.NewKademlia(addr.Over(), network.NewKadParams())
   140  			delivery := NewDelivery(kad, db)
   141  
   142  			r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{
   143  				DoSync:          true,
   144  				SyncUpdateDelay: 3 * time.Second,
   145  			})
   146  			bucket.Store(bucketKeyRegistry, r)
   147  
   148  			return r, cleanup, nil
   149  
   150  		},
   151  	})
   152  	defer sim.Close()
   153  
   154  	log.Info("Initializing test config")
   155  
   156  	conf := &synctestConfig{}
   157  	//map of discover ID to indexes of chunks expected at that ID
   158  	conf.idToChunksMap = make(map[discover.NodeID][]int)
   159  	//map of overlay address to discover ID
   160  	conf.addrToIDMap = make(map[string]discover.NodeID)
   161  	//array where the generated chunk hashes will be stored
   162  	conf.hashes = make([]storage.Address, 0)
   163  
   164  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  
   169  	ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
   170  	defer cancelSimRun()
   171  
   172  	result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
   173  		nodeIDs := sim.UpNodeIDs()
   174  		for _, n := range nodeIDs {
   175  			//get the kademlia overlay address from this ID
   176  			a := network.ToOverlayAddr(n.Bytes())
   177  			//append it to the array of all overlay addresses
   178  			conf.addrs = append(conf.addrs, a)
   179  			//the proximity calculation is on overlay addr,
   180  			//the p2p/simulations check func triggers on discover.NodeID,
   181  			//so we need to know which overlay addr maps to which nodeID
   182  			conf.addrToIDMap[string(a)] = n
   183  		}
   184  
   185  		//get the the node at that index
   186  		//this is the node selected for upload
   187  		node := sim.RandomUpNode()
   188  		item, ok := sim.NodeItem(node.ID, bucketKeyStore)
   189  		if !ok {
   190  			return fmt.Errorf("No localstore")
   191  		}
   192  		lstore := item.(*storage.LocalStore)
   193  		hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore)
   194  		if err != nil {
   195  			return err
   196  		}
   197  		conf.hashes = append(conf.hashes, hashes...)
   198  		mapKeysToNodes(conf)
   199  
   200  		if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
   201  			return err
   202  		}
   203  
   204  		// File retrieval check is repeated until all uploaded files are retrieved from all nodes
   205  		// or until the timeout is reached.
   206  		allSuccess := false
   207  		var gDir string
   208  		var globalStore *mockdb.GlobalStore
   209  		if *useMockStore {
   210  			gDir, globalStore, err = createGlobalStore()
   211  			if err != nil {
   212  				return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil")
   213  			}
   214  			defer func() {
   215  				os.RemoveAll(gDir)
   216  				err := globalStore.Close()
   217  				if err != nil {
   218  					log.Error("Error closing global store! %v", "err", err)
   219  				}
   220  			}()
   221  		}
   222  		for !allSuccess {
   223  			for _, id := range nodeIDs {
   224  				//for each expected chunk, check if it is in the local store
   225  				localChunks := conf.idToChunksMap[id]
   226  				localSuccess := true
   227  				for _, ch := range localChunks {
   228  					//get the real chunk by the index in the index array
   229  					chunk := conf.hashes[ch]
   230  					log.Trace(fmt.Sprintf("node has chunk: %s:", chunk))
   231  					//check if the expected chunk is indeed in the localstore
   232  					var err error
   233  					if *useMockStore {
   234  						//use the globalStore if the mockStore should be used; in that case,
   235  						//the complete localStore stack is bypassed for getting the chunk
   236  						_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk)
   237  					} else {
   238  						//use the actual localstore
   239  						item, ok := sim.NodeItem(id, bucketKeyStore)
   240  						if !ok {
   241  							return fmt.Errorf("Error accessing localstore")
   242  						}
   243  						lstore := item.(*storage.LocalStore)
   244  						_, err = lstore.Get(ctx, chunk)
   245  					}
   246  					if err != nil {
   247  						log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id))
   248  						localSuccess = false
   249  						// Do not get crazy with logging the warn message
   250  						time.Sleep(500 * time.Millisecond)
   251  					} else {
   252  						log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id))
   253  					}
   254  				}
   255  				allSuccess = localSuccess
   256  			}
   257  		}
   258  		if !allSuccess {
   259  			return fmt.Errorf("Not all chunks succeeded!")
   260  		}
   261  		return nil
   262  	})
   263  
   264  	if result.Error != nil {
   265  		t.Fatal(result.Error)
   266  	}
   267  }
   268  
   269  /*
   270  The test generates the given number of chunks
   271  
   272  For every chunk generated, the nearest node addresses
   273  are identified, we verify that the nodes closer to the
   274  chunk addresses actually do have the chunks in their local stores.
   275  
   276  The test loads a snapshot file to construct the swarm network,
   277  assuming that the snapshot file identifies a healthy
   278  kademlia network. The snapshot should have 'streamer' in its service list.
   279  */
   280  func testSyncingViaDirectSubscribe(chunkCount int, nodeCount int) error {
   281  	sim := simulation.New(map[string]simulation.ServiceFunc{
   282  		"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   283  
   284  			id := ctx.Config.ID
   285  			addr := network.NewAddrFromNodeID(id)
   286  			store, datadir, err := createTestLocalStorageForID(id, addr)
   287  			if err != nil {
   288  				return nil, nil, err
   289  			}
   290  			bucket.Store(bucketKeyStore, store)
   291  			cleanup = func() {
   292  				os.RemoveAll(datadir)
   293  				store.Close()
   294  			}
   295  			localStore := store.(*storage.LocalStore)
   296  			db := storage.NewDBAPI(localStore)
   297  			kad := network.NewKademlia(addr.Over(), network.NewKadParams())
   298  			delivery := NewDelivery(kad, db)
   299  
   300  			r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), nil)
   301  			bucket.Store(bucketKeyRegistry, r)
   302  
   303  			fileStore := storage.NewFileStore(storage.NewNetStore(localStore, nil), storage.NewFileStoreParams())
   304  			bucket.Store(bucketKeyFileStore, fileStore)
   305  
   306  			return r, cleanup, nil
   307  
   308  		},
   309  	})
   310  	defer sim.Close()
   311  
   312  	ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
   313  	defer cancelSimRun()
   314  
   315  	conf := &synctestConfig{}
   316  	//map of discover ID to indexes of chunks expected at that ID
   317  	conf.idToChunksMap = make(map[discover.NodeID][]int)
   318  	//map of overlay address to discover ID
   319  	conf.addrToIDMap = make(map[string]discover.NodeID)
   320  	//array where the generated chunk hashes will be stored
   321  	conf.hashes = make([]storage.Address, 0)
   322  
   323  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
   329  		nodeIDs := sim.UpNodeIDs()
   330  		for _, n := range nodeIDs {
   331  			//get the kademlia overlay address from this ID
   332  			a := network.ToOverlayAddr(n.Bytes())
   333  			//append it to the array of all overlay addresses
   334  			conf.addrs = append(conf.addrs, a)
   335  			//the proximity calculation is on overlay addr,
   336  			//the p2p/simulations check func triggers on discover.NodeID,
   337  			//so we need to know which overlay addr maps to which nodeID
   338  			conf.addrToIDMap[string(a)] = n
   339  		}
   340  
   341  		var subscriptionCount int
   342  
   343  		filter := simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("stream").MsgCode(4)
   344  		eventC := sim.PeerEvents(ctx, nodeIDs, filter)
   345  
   346  		for j, node := range nodeIDs {
   347  			log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j))
   348  			//start syncing!
   349  			item, ok := sim.NodeItem(node, bucketKeyRegistry)
   350  			if !ok {
   351  				return fmt.Errorf("No registry")
   352  			}
   353  			registry := item.(*Registry)
   354  
   355  			var cnt int
   356  			cnt, err = startSyncing(registry, conf)
   357  			if err != nil {
   358  				return err
   359  			}
   360  			//increment the number of subscriptions we need to wait for
   361  			//by the count returned from startSyncing (SYNC subscriptions)
   362  			subscriptionCount += cnt
   363  		}
   364  
   365  		for e := range eventC {
   366  			if e.Error != nil {
   367  				return e.Error
   368  			}
   369  			subscriptionCount--
   370  			if subscriptionCount == 0 {
   371  				break
   372  			}
   373  		}
   374  		//select a random node for upload
   375  		node := sim.RandomUpNode()
   376  		item, ok := sim.NodeItem(node.ID, bucketKeyStore)
   377  		if !ok {
   378  			return fmt.Errorf("No localstore")
   379  		}
   380  		lstore := item.(*storage.LocalStore)
   381  		hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore)
   382  		if err != nil {
   383  			return err
   384  		}
   385  		conf.hashes = append(conf.hashes, hashes...)
   386  		mapKeysToNodes(conf)
   387  
   388  		if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
   389  			return err
   390  		}
   391  
   392  		var gDir string
   393  		var globalStore *mockdb.GlobalStore
   394  		if *useMockStore {
   395  			gDir, globalStore, err = createGlobalStore()
   396  			if err != nil {
   397  				return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil")
   398  			}
   399  			defer os.RemoveAll(gDir)
   400  		}
   401  		// File retrieval check is repeated until all uploaded files are retrieved from all nodes
   402  		// or until the timeout is reached.
   403  		allSuccess := false
   404  		for !allSuccess {
   405  			for _, id := range nodeIDs {
   406  				//for each expected chunk, check if it is in the local store
   407  				localChunks := conf.idToChunksMap[id]
   408  				localSuccess := true
   409  				for _, ch := range localChunks {
   410  					//get the real chunk by the index in the index array
   411  					chunk := conf.hashes[ch]
   412  					log.Trace(fmt.Sprintf("node has chunk: %s:", chunk))
   413  					//check if the expected chunk is indeed in the localstore
   414  					var err error
   415  					if *useMockStore {
   416  						//use the globalStore if the mockStore should be used; in that case,
   417  						//the complete localStore stack is bypassed for getting the chunk
   418  						_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk)
   419  					} else {
   420  						//use the actual localstore
   421  						item, ok := sim.NodeItem(id, bucketKeyStore)
   422  						if !ok {
   423  							return fmt.Errorf("Error accessing localstore")
   424  						}
   425  						lstore := item.(*storage.LocalStore)
   426  						_, err = lstore.Get(ctx, chunk)
   427  					}
   428  					if err != nil {
   429  						log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id))
   430  						localSuccess = false
   431  						// Do not get crazy with logging the warn message
   432  						time.Sleep(500 * time.Millisecond)
   433  					} else {
   434  						log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id))
   435  					}
   436  				}
   437  				allSuccess = localSuccess
   438  			}
   439  		}
   440  		if !allSuccess {
   441  			return fmt.Errorf("Not all chunks succeeded!")
   442  		}
   443  		return nil
   444  	})
   445  
   446  	if result.Error != nil {
   447  		return result.Error
   448  	}
   449  
   450  	log.Info("Simulation terminated")
   451  	return nil
   452  }
   453  
   454  //the server func to start syncing
   455  //issues `RequestSubscriptionMsg` to peers, based on po, by iterating over
   456  //the kademlia's `EachBin` function.
   457  //returns the number of subscriptions requested
   458  func startSyncing(r *Registry, conf *synctestConfig) (int, error) {
   459  	var err error
   460  
   461  	kad, ok := r.delivery.overlay.(*network.Kademlia)
   462  	if !ok {
   463  		return 0, fmt.Errorf("Not a Kademlia!")
   464  	}
   465  
   466  	subCnt := 0
   467  	//iterate over each bin and solicit needed subscription to bins
   468  	kad.EachBin(r.addr.Over(), pof, 0, func(conn network.OverlayConn, po int) bool {
   469  		//identify begin and start index of the bin(s) we want to subscribe to
   470  		histRange := &Range{}
   471  
   472  		subCnt++
   473  		err = r.RequestSubscription(conf.addrToIDMap[string(conn.Address())], NewStream("SYNC", FormatSyncBinKey(uint8(po)), true), histRange, Top)
   474  		if err != nil {
   475  			log.Error(fmt.Sprintf("Error in RequestSubsciption! %v", err))
   476  			return false
   477  		}
   478  		return true
   479  
   480  	})
   481  	return subCnt, nil
   482  }
   483  
   484  //map chunk keys to addresses which are responsible
   485  func mapKeysToNodes(conf *synctestConfig) {
   486  	kmap := make(map[string][]int)
   487  	nodemap := make(map[string][]int)
   488  	//build a pot for chunk hashes
   489  	np := pot.NewPot(nil, 0)
   490  	indexmap := make(map[string]int)
   491  	for i, a := range conf.addrs {
   492  		indexmap[string(a)] = i
   493  		np, _, _ = pot.Add(np, a, pof)
   494  	}
   495  	//for each address, run EachNeighbour on the chunk hashes pot to identify closest nodes
   496  	log.Trace(fmt.Sprintf("Generated hash chunk(s): %v", conf.hashes))
   497  	for i := 0; i < len(conf.hashes); i++ {
   498  		pl := 256 //highest possible proximity
   499  		var nns []int
   500  		np.EachNeighbour([]byte(conf.hashes[i]), pof, func(val pot.Val, po int) bool {
   501  			a := val.([]byte)
   502  			if pl < 256 && pl != po {
   503  				return false
   504  			}
   505  			if pl == 256 || pl == po {
   506  				log.Trace(fmt.Sprintf("appending %s", conf.addrToIDMap[string(a)]))
   507  				nns = append(nns, indexmap[string(a)])
   508  				nodemap[string(a)] = append(nodemap[string(a)], i)
   509  			}
   510  			if pl == 256 && len(nns) >= testMinProxBinSize {
   511  				//maxProxBinSize has been reached at this po, so save it
   512  				//we will add all other nodes at the same po
   513  				pl = po
   514  			}
   515  			return true
   516  		})
   517  		kmap[string(conf.hashes[i])] = nns
   518  	}
   519  	for addr, chunks := range nodemap {
   520  		//this selects which chunks are expected to be found with the given node
   521  		conf.idToChunksMap[conf.addrToIDMap[addr]] = chunks
   522  	}
   523  	log.Debug(fmt.Sprintf("Map of expected chunks by ID: %v", conf.idToChunksMap))
   524  	conf.chunksToNodesMap = kmap
   525  }
   526  
   527  //upload a file(chunks) to a single local node store
   528  func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int, lstore *storage.LocalStore) ([]storage.Address, error) {
   529  	log.Debug(fmt.Sprintf("Uploading to node id: %s", id))
   530  	fileStore := storage.NewFileStore(lstore, storage.NewFileStoreParams())
   531  	size := chunkSize
   532  	var rootAddrs []storage.Address
   533  	for i := 0; i < chunkCount; i++ {
   534  		rk, wait, err := fileStore.Store(context.TODO(), io.LimitReader(crand.Reader, int64(size)), int64(size), false)
   535  		if err != nil {
   536  			return nil, err
   537  		}
   538  		err = wait(context.TODO())
   539  		if err != nil {
   540  			return nil, err
   541  		}
   542  		rootAddrs = append(rootAddrs, (rk))
   543  	}
   544  
   545  	return rootAddrs, nil
   546  }