github.com/divan/go-ethereum@v1.8.14-0.20180820134928-1de9ada4016d/swarm/network_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  package swarm
    18  
    19  import (
    20  	"context"
    21  	"flag"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"math/rand"
    25  	"os"
    26  	"sync"
    27  	"sync/atomic"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/ethereum/go-ethereum/crypto"
    32  	"github.com/ethereum/go-ethereum/log"
    33  	"github.com/ethereum/go-ethereum/node"
    34  	"github.com/ethereum/go-ethereum/p2p/discover"
    35  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    36  	"github.com/ethereum/go-ethereum/swarm/api"
    37  	"github.com/ethereum/go-ethereum/swarm/network"
    38  	"github.com/ethereum/go-ethereum/swarm/network/simulation"
    39  	"github.com/ethereum/go-ethereum/swarm/storage"
    40  	colorable "github.com/mattn/go-colorable"
    41  )
    42  
    43  var (
    44  	loglevel     = flag.Int("loglevel", 2, "verbosity of logs")
    45  	longrunning  = flag.Bool("longrunning", false, "do run long-running tests")
    46  	waitKademlia = flag.Bool("waitkademlia", false, "wait for healthy kademlia before checking files availability")
    47  )
    48  
    49  func init() {
    50  	rand.Seed(time.Now().UnixNano())
    51  
    52  	flag.Parse()
    53  
    54  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
    55  }
    56  
    57  // TestSwarmNetwork runs a series of test simulations with
    58  // static and dynamic Swarm nodes in network simulation, by
    59  // uploading files to every node and retrieving them.
    60  func TestSwarmNetwork(t *testing.T) {
    61  	for _, tc := range []struct {
    62  		name     string
    63  		steps    []testSwarmNetworkStep
    64  		options  *testSwarmNetworkOptions
    65  		disabled bool
    66  	}{
    67  		{
    68  			name: "10_nodes",
    69  			steps: []testSwarmNetworkStep{
    70  				{
    71  					nodeCount: 10,
    72  				},
    73  			},
    74  			options: &testSwarmNetworkOptions{
    75  				Timeout: 45 * time.Second,
    76  			},
    77  		},
    78  		{
    79  			name: "10_nodes_skip_check",
    80  			steps: []testSwarmNetworkStep{
    81  				{
    82  					nodeCount: 10,
    83  				},
    84  			},
    85  			options: &testSwarmNetworkOptions{
    86  				Timeout:   45 * time.Second,
    87  				SkipCheck: true,
    88  			},
    89  		},
    90  		{
    91  			name: "100_nodes",
    92  			steps: []testSwarmNetworkStep{
    93  				{
    94  					nodeCount: 100,
    95  				},
    96  			},
    97  			options: &testSwarmNetworkOptions{
    98  				Timeout: 3 * time.Minute,
    99  			},
   100  			disabled: !*longrunning,
   101  		},
   102  		{
   103  			name: "100_nodes_skip_check",
   104  			steps: []testSwarmNetworkStep{
   105  				{
   106  					nodeCount: 100,
   107  				},
   108  			},
   109  			options: &testSwarmNetworkOptions{
   110  				Timeout:   3 * time.Minute,
   111  				SkipCheck: true,
   112  			},
   113  			disabled: !*longrunning,
   114  		},
   115  		{
   116  			name: "inc_node_count",
   117  			steps: []testSwarmNetworkStep{
   118  				{
   119  					nodeCount: 2,
   120  				},
   121  				{
   122  					nodeCount: 5,
   123  				},
   124  				{
   125  					nodeCount: 10,
   126  				},
   127  			},
   128  			options: &testSwarmNetworkOptions{
   129  				Timeout: 90 * time.Second,
   130  			},
   131  			disabled: !*longrunning,
   132  		},
   133  		{
   134  			name: "dec_node_count",
   135  			steps: []testSwarmNetworkStep{
   136  				{
   137  					nodeCount: 10,
   138  				},
   139  				{
   140  					nodeCount: 6,
   141  				},
   142  				{
   143  					nodeCount: 3,
   144  				},
   145  			},
   146  			options: &testSwarmNetworkOptions{
   147  				Timeout: 90 * time.Second,
   148  			},
   149  			disabled: !*longrunning,
   150  		},
   151  		{
   152  			name: "dec_inc_node_count",
   153  			steps: []testSwarmNetworkStep{
   154  				{
   155  					nodeCount: 5,
   156  				},
   157  				{
   158  					nodeCount: 3,
   159  				},
   160  				{
   161  					nodeCount: 10,
   162  				},
   163  			},
   164  			options: &testSwarmNetworkOptions{
   165  				Timeout: 90 * time.Second,
   166  			},
   167  		},
   168  		{
   169  			name: "inc_dec_node_count",
   170  			steps: []testSwarmNetworkStep{
   171  				{
   172  					nodeCount: 3,
   173  				},
   174  				{
   175  					nodeCount: 5,
   176  				},
   177  				{
   178  					nodeCount: 25,
   179  				},
   180  				{
   181  					nodeCount: 10,
   182  				},
   183  				{
   184  					nodeCount: 4,
   185  				},
   186  			},
   187  			options: &testSwarmNetworkOptions{
   188  				Timeout: 5 * time.Minute,
   189  			},
   190  			disabled: !*longrunning,
   191  		},
   192  		{
   193  			name: "inc_dec_node_count_skip_check",
   194  			steps: []testSwarmNetworkStep{
   195  				{
   196  					nodeCount: 3,
   197  				},
   198  				{
   199  					nodeCount: 5,
   200  				},
   201  				{
   202  					nodeCount: 25,
   203  				},
   204  				{
   205  					nodeCount: 10,
   206  				},
   207  				{
   208  					nodeCount: 4,
   209  				},
   210  			},
   211  			options: &testSwarmNetworkOptions{
   212  				Timeout:   5 * time.Minute,
   213  				SkipCheck: true,
   214  			},
   215  			disabled: !*longrunning,
   216  		},
   217  	} {
   218  		if tc.disabled {
   219  			continue
   220  		}
   221  		t.Run(tc.name, func(t *testing.T) {
   222  			testSwarmNetwork(t, tc.options, tc.steps...)
   223  		})
   224  	}
   225  }
   226  
   227  // testSwarmNetworkStep is the configuration
   228  // for the state of the simulation network.
   229  type testSwarmNetworkStep struct {
   230  	// number of swarm nodes that must be in the Up state
   231  	nodeCount int
   232  }
   233  
   234  // file represents the file uploaded on a particular node.
   235  type file struct {
   236  	addr   storage.Address
   237  	data   string
   238  	nodeID discover.NodeID
   239  }
   240  
   241  // check represents a reference to a file that is retrieved
   242  // from a particular node.
   243  type check struct {
   244  	key    string
   245  	nodeID discover.NodeID
   246  }
   247  
   248  // testSwarmNetworkOptions contains optional parameters for running
   249  // testSwarmNetwork.
   250  type testSwarmNetworkOptions struct {
   251  	Timeout   time.Duration
   252  	SkipCheck bool
   253  }
   254  
   255  // testSwarmNetwork is a helper function used for testing different
   256  // static and dynamic Swarm network simulations.
   257  // It is responsible for:
   258  //  - Setting up a Swarm network simulation, and updates the number of nodes within the network on every step according to steps.
   259  //  - Uploading a unique file to every node on every step.
   260  //  - May wait for Kademlia on every node to be healthy.
   261  //  - Checking if a file is retrievable from all nodes.
   262  func testSwarmNetwork(t *testing.T, o *testSwarmNetworkOptions, steps ...testSwarmNetworkStep) {
   263  	if o == nil {
   264  		o = new(testSwarmNetworkOptions)
   265  	}
   266  
   267  	sim := simulation.New(map[string]simulation.ServiceFunc{
   268  		"swarm": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   269  			config := api.NewConfig()
   270  
   271  			dir, err := ioutil.TempDir("", "swarm-network-test-node")
   272  			if err != nil {
   273  				return nil, nil, err
   274  			}
   275  			cleanup = func() {
   276  				err := os.RemoveAll(dir)
   277  				if err != nil {
   278  					log.Error("cleaning up swarm temp dir", "err", err)
   279  				}
   280  			}
   281  
   282  			config.Path = dir
   283  
   284  			privkey, err := crypto.GenerateKey()
   285  			if err != nil {
   286  				return nil, cleanup, err
   287  			}
   288  
   289  			config.Init(privkey)
   290  			config.DeliverySkipCheck = o.SkipCheck
   291  
   292  			swarm, err := NewSwarm(config, nil)
   293  			if err != nil {
   294  				return nil, cleanup, err
   295  			}
   296  			bucket.Store(simulation.BucketKeyKademlia, swarm.bzz.Hive.Overlay.(*network.Kademlia))
   297  			log.Info("new swarm", "bzzKey", config.BzzKey, "baseAddr", fmt.Sprintf("%x", swarm.bzz.BaseAddr()))
   298  			return swarm, cleanup, nil
   299  		},
   300  	})
   301  	defer sim.Close()
   302  
   303  	ctx := context.Background()
   304  	if o.Timeout > 0 {
   305  		var cancel context.CancelFunc
   306  		ctx, cancel = context.WithTimeout(ctx, o.Timeout)
   307  		defer cancel()
   308  	}
   309  
   310  	files := make([]file, 0)
   311  
   312  	for i, step := range steps {
   313  		log.Debug("test sync step", "n", i+1, "nodes", step.nodeCount)
   314  
   315  		change := step.nodeCount - len(sim.UpNodeIDs())
   316  
   317  		if change > 0 {
   318  			_, err := sim.AddNodesAndConnectChain(change)
   319  			if err != nil {
   320  				t.Fatal(err)
   321  			}
   322  		} else if change < 0 {
   323  			_, err := sim.StopRandomNodes(-change)
   324  			if err != nil {
   325  				t.Fatal(err)
   326  			}
   327  		} else {
   328  			t.Logf("step %v: no change in nodes", i)
   329  			continue
   330  		}
   331  
   332  		var checkStatusM sync.Map
   333  		var nodeStatusM sync.Map
   334  		var totalFoundCount uint64
   335  
   336  		result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
   337  			nodeIDs := sim.UpNodeIDs()
   338  			shuffle(len(nodeIDs), func(i, j int) {
   339  				nodeIDs[i], nodeIDs[j] = nodeIDs[j], nodeIDs[i]
   340  			})
   341  			for _, id := range nodeIDs {
   342  				key, data, err := uploadFile(sim.Service("swarm", id).(*Swarm))
   343  				if err != nil {
   344  					return err
   345  				}
   346  				log.Trace("file uploaded", "node", id, "key", key.String())
   347  				files = append(files, file{
   348  					addr:   key,
   349  					data:   data,
   350  					nodeID: id,
   351  				})
   352  			}
   353  
   354  			if *waitKademlia {
   355  				if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
   356  					return err
   357  				}
   358  			}
   359  
   360  			// File retrieval check is repeated until all uploaded files are retrieved from all nodes
   361  			// or until the timeout is reached.
   362  			for {
   363  				if retrieve(sim, files, &checkStatusM, &nodeStatusM, &totalFoundCount) == 0 {
   364  					return nil
   365  				}
   366  			}
   367  		})
   368  
   369  		if result.Error != nil {
   370  			t.Fatal(result.Error)
   371  		}
   372  		log.Debug("done: test sync step", "n", i+1, "nodes", step.nodeCount)
   373  	}
   374  }
   375  
   376  // uploadFile, uploads a short file to the swarm instance
   377  // using the api.Put method.
   378  func uploadFile(swarm *Swarm) (storage.Address, string, error) {
   379  	b := make([]byte, 8)
   380  	_, err := rand.Read(b)
   381  	if err != nil {
   382  		return nil, "", err
   383  	}
   384  	// File data is very short, but it is ensured that its
   385  	// uniqueness is very certain.
   386  	data := fmt.Sprintf("test content %s %x", time.Now().Round(0), b)
   387  	ctx := context.TODO()
   388  	k, wait, err := swarm.api.Put(ctx, data, "text/plain", false)
   389  	if err != nil {
   390  		return nil, "", err
   391  	}
   392  	if wait != nil {
   393  		err = wait(ctx)
   394  	}
   395  	return k, data, err
   396  }
   397  
   398  // retrieve is the function that is used for checking the availability of
   399  // uploaded files in testSwarmNetwork test helper function.
   400  func retrieve(
   401  	sim *simulation.Simulation,
   402  	files []file,
   403  	checkStatusM *sync.Map,
   404  	nodeStatusM *sync.Map,
   405  	totalFoundCount *uint64,
   406  ) (missing uint64) {
   407  	shuffle(len(files), func(i, j int) {
   408  		files[i], files[j] = files[j], files[i]
   409  	})
   410  
   411  	var totalWg sync.WaitGroup
   412  	errc := make(chan error)
   413  
   414  	nodeIDs := sim.UpNodeIDs()
   415  
   416  	totalCheckCount := len(nodeIDs) * len(files)
   417  
   418  	for _, id := range nodeIDs {
   419  		if _, ok := nodeStatusM.Load(id); ok {
   420  			continue
   421  		}
   422  		start := time.Now()
   423  		var checkCount uint64
   424  		var foundCount uint64
   425  
   426  		totalWg.Add(1)
   427  
   428  		var wg sync.WaitGroup
   429  
   430  		swarm := sim.Service("swarm", id).(*Swarm)
   431  		for _, f := range files {
   432  
   433  			checkKey := check{
   434  				key:    f.addr.String(),
   435  				nodeID: id,
   436  			}
   437  			if n, ok := checkStatusM.Load(checkKey); ok && n.(int) == 0 {
   438  				continue
   439  			}
   440  
   441  			checkCount++
   442  			wg.Add(1)
   443  			go func(f file, id discover.NodeID) {
   444  				defer wg.Done()
   445  
   446  				log.Debug("api get: check file", "node", id.String(), "key", f.addr.String(), "total files found", atomic.LoadUint64(totalFoundCount))
   447  
   448  				r, _, _, _, err := swarm.api.Get(context.TODO(), api.NOOPDecrypt, f.addr, "/")
   449  				if err != nil {
   450  					errc <- fmt.Errorf("api get: node %s, key %s, kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err)
   451  					return
   452  				}
   453  				d, err := ioutil.ReadAll(r)
   454  				if err != nil {
   455  					errc <- fmt.Errorf("api get: read response: node %s, key %s: kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err)
   456  					return
   457  				}
   458  				data := string(d)
   459  				if data != f.data {
   460  					errc <- fmt.Errorf("file contend missmatch: node %s, key %s, expected %q, got %q", id, f.addr, f.data, data)
   461  					return
   462  				}
   463  				checkStatusM.Store(checkKey, 0)
   464  				atomic.AddUint64(&foundCount, 1)
   465  				log.Info("api get: file found", "node", id.String(), "key", f.addr.String(), "content", data, "files found", atomic.LoadUint64(&foundCount))
   466  			}(f, id)
   467  		}
   468  
   469  		go func(id discover.NodeID) {
   470  			defer totalWg.Done()
   471  			wg.Wait()
   472  
   473  			atomic.AddUint64(totalFoundCount, foundCount)
   474  
   475  			if foundCount == checkCount {
   476  				log.Info("all files are found for node", "id", id.String(), "duration", time.Since(start))
   477  				nodeStatusM.Store(id, 0)
   478  				return
   479  			}
   480  			log.Debug("files missing for node", "id", id.String(), "check", checkCount, "found", foundCount)
   481  		}(id)
   482  
   483  	}
   484  
   485  	go func() {
   486  		totalWg.Wait()
   487  		close(errc)
   488  	}()
   489  
   490  	var errCount int
   491  	for err := range errc {
   492  		if err != nil {
   493  			errCount++
   494  		}
   495  		log.Warn(err.Error())
   496  	}
   497  
   498  	log.Info("check stats", "total check count", totalCheckCount, "total files found", atomic.LoadUint64(totalFoundCount), "total errors", errCount)
   499  
   500  	return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount)
   501  }
   502  
   503  // Backported from stdlib https://golang.org/src/math/rand/rand.go?s=11175:11215#L333
   504  //
   505  // Replace with rand.Shuffle from go 1.10 when go 1.9 support is dropped.
   506  //
   507  // shuffle pseudo-randomizes the order of elements.
   508  // n is the number of elements. Shuffle panics if n < 0.
   509  // swap swaps the elements with indexes i and j.
   510  func shuffle(n int, swap func(i, j int)) {
   511  	if n < 0 {
   512  		panic("invalid argument to Shuffle")
   513  	}
   514  
   515  	// Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
   516  	// Shuffle really ought not be called with n that doesn't fit in 32 bits.
   517  	// Not only will it take a very long time, but with 2³¹! possible permutations,
   518  	// there's no way that any PRNG can have a big enough internal state to
   519  	// generate even a minuscule percentage of the possible permutations.
   520  	// Nevertheless, the right API signature accepts an int n, so handle it as best we can.
   521  	i := n - 1
   522  	for ; i > 1<<31-1-1; i-- {
   523  		j := int(rand.Int63n(int64(i + 1)))
   524  		swap(i, j)
   525  	}
   526  	for ; i > 0; i-- {
   527  		j := int(rand.Int31n(int32(i + 1)))
   528  		swap(i, j)
   529  	}
   530  }