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