github.com/codingfuture/orig-energi3@v0.8.4/swarm/network_test.go (about)

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