github.com/hyperion-hyn/go-ethereum@v2.4.0+incompatible/cmd/swarm/run_test.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU 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  // go-ethereum 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 General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"context"
    21  	"crypto/ecdsa"
    22  	"flag"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net"
    26  	"os"
    27  	"path"
    28  	"path/filepath"
    29  	"runtime"
    30  	"sync"
    31  	"syscall"
    32  	"testing"
    33  	"time"
    34  
    35  	"github.com/docker/docker/pkg/reexec"
    36  	"github.com/ethereum/go-ethereum/accounts"
    37  	"github.com/ethereum/go-ethereum/accounts/keystore"
    38  	"github.com/ethereum/go-ethereum/internal/cmdtest"
    39  	"github.com/ethereum/go-ethereum/node"
    40  	"github.com/ethereum/go-ethereum/p2p"
    41  	"github.com/ethereum/go-ethereum/rpc"
    42  	"github.com/ethereum/go-ethereum/swarm"
    43  	"github.com/ethereum/go-ethereum/swarm/api"
    44  	swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
    45  )
    46  
    47  var loglevel = flag.Int("loglevel", 3, "verbosity of logs")
    48  
    49  func init() {
    50  	// Run the app if we've been exec'd as "swarm-test" in runSwarm.
    51  	reexec.Register("swarm-test", func() {
    52  		if err := app.Run(os.Args); err != nil {
    53  			fmt.Fprintln(os.Stderr, err)
    54  			os.Exit(1)
    55  		}
    56  		os.Exit(0)
    57  	})
    58  }
    59  
    60  func serverFunc(api *api.API) swarmhttp.TestServer {
    61  	return swarmhttp.NewServer(api, "")
    62  }
    63  func TestMain(m *testing.M) {
    64  	// check if we have been reexec'd
    65  	if reexec.Init() {
    66  		return
    67  	}
    68  	os.Exit(m.Run())
    69  }
    70  
    71  func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd {
    72  	tt := cmdtest.NewTestCmd(t, nil)
    73  
    74  	// Boot "swarm". This actually runs the test binary but the TestMain
    75  	// function will prevent any tests from running.
    76  	tt.Run("swarm-test", args...)
    77  
    78  	return tt
    79  }
    80  
    81  type testCluster struct {
    82  	Nodes  []*testNode
    83  	TmpDir string
    84  }
    85  
    86  // newTestCluster starts a test swarm cluster of the given size.
    87  //
    88  // A temporary directory is created and each node gets a data directory inside
    89  // it.
    90  //
    91  // Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p
    92  // ports (assigned by first listening on 127.0.0.1:0 and then passing the ports
    93  // as flags).
    94  //
    95  // When starting more than one node, they are connected together using the
    96  // admin SetPeer RPC method.
    97  
    98  func newTestCluster(t *testing.T, size int) *testCluster {
    99  	cluster := &testCluster{}
   100  	defer func() {
   101  		if t.Failed() {
   102  			cluster.Shutdown()
   103  		}
   104  	}()
   105  
   106  	tmpdir, err := ioutil.TempDir("", "swarm-test")
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  	cluster.TmpDir = tmpdir
   111  
   112  	// start the nodes
   113  	cluster.StartNewNodes(t, size)
   114  
   115  	if size == 1 {
   116  		return cluster
   117  	}
   118  
   119  	// connect the nodes together
   120  	for _, node := range cluster.Nodes {
   121  		if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil {
   122  			t.Fatal(err)
   123  		}
   124  	}
   125  
   126  	// wait until all nodes have the correct number of peers
   127  outer:
   128  	for _, node := range cluster.Nodes {
   129  		var peers []*p2p.PeerInfo
   130  		for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) {
   131  			if err := node.Client.Call(&peers, "admin_peers"); err != nil {
   132  				t.Fatal(err)
   133  			}
   134  			if len(peers) == len(cluster.Nodes)-1 {
   135  				continue outer
   136  			}
   137  		}
   138  		t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1)
   139  	}
   140  
   141  	return cluster
   142  }
   143  
   144  func (c *testCluster) Shutdown() {
   145  	for _, node := range c.Nodes {
   146  		node.Shutdown()
   147  	}
   148  	os.RemoveAll(c.TmpDir)
   149  }
   150  
   151  func (c *testCluster) Stop() {
   152  	for _, node := range c.Nodes {
   153  		node.Shutdown()
   154  	}
   155  }
   156  
   157  func (c *testCluster) StartNewNodes(t *testing.T, size int) {
   158  	c.Nodes = make([]*testNode, 0, size)
   159  	for i := 0; i < size; i++ {
   160  		dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i))
   161  		if err := os.Mkdir(dir, 0700); err != nil {
   162  			t.Fatal(err)
   163  		}
   164  
   165  		node := newTestNode(t, dir)
   166  		node.Name = fmt.Sprintf("swarm%02d", i)
   167  
   168  		c.Nodes = append(c.Nodes, node)
   169  	}
   170  }
   171  
   172  func (c *testCluster) StartExistingNodes(t *testing.T, size int, bzzaccount string) {
   173  	c.Nodes = make([]*testNode, 0, size)
   174  	for i := 0; i < size; i++ {
   175  		dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i))
   176  		node := existingTestNode(t, dir, bzzaccount)
   177  		node.Name = fmt.Sprintf("swarm%02d", i)
   178  
   179  		c.Nodes = append(c.Nodes, node)
   180  	}
   181  }
   182  
   183  func (c *testCluster) Cleanup() {
   184  	os.RemoveAll(c.TmpDir)
   185  }
   186  
   187  type testNode struct {
   188  	Name       string
   189  	Addr       string
   190  	URL        string
   191  	Enode      string
   192  	Dir        string
   193  	IpcPath    string
   194  	PrivateKey *ecdsa.PrivateKey
   195  	Client     *rpc.Client
   196  	Cmd        *cmdtest.TestCmd
   197  }
   198  
   199  const testPassphrase = "swarm-test-passphrase"
   200  
   201  func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) {
   202  	// create key
   203  	conf = &node.Config{
   204  		DataDir: dir,
   205  		IPCPath: "bzzd.ipc",
   206  		NoUSB:   true,
   207  	}
   208  	n, err := node.New(conf)
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
   213  	if err != nil {
   214  		t.Fatal(err)
   215  	}
   216  
   217  	// use a unique IPCPath when running tests on Windows
   218  	if runtime.GOOS == "windows" {
   219  		conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
   220  	}
   221  
   222  	return conf, account
   223  }
   224  
   225  func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
   226  	conf, _ := getTestAccount(t, dir)
   227  	node := &testNode{Dir: dir}
   228  
   229  	// use a unique IPCPath when running tests on Windows
   230  	if runtime.GOOS == "windows" {
   231  		conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", bzzaccount)
   232  	}
   233  
   234  	// assign ports
   235  	ports, err := getAvailableTCPPorts(2)
   236  	if err != nil {
   237  		t.Fatal(err)
   238  	}
   239  	p2pPort := ports[0]
   240  	httpPort := ports[1]
   241  
   242  	// start the node
   243  	node.Cmd = runSwarm(t,
   244  		"--port", p2pPort,
   245  		"--nat", "extip:127.0.0.1",
   246  		"--nodiscover",
   247  		"--datadir", dir,
   248  		"--ipcpath", conf.IPCPath,
   249  		"--ens-api", "",
   250  		"--bzzaccount", bzzaccount,
   251  		"--bzznetworkid", "321",
   252  		"--bzzport", httpPort,
   253  		"--verbosity", fmt.Sprint(*loglevel),
   254  	)
   255  	node.Cmd.InputLine(testPassphrase)
   256  	defer func() {
   257  		if t.Failed() {
   258  			node.Shutdown()
   259  		}
   260  	}()
   261  
   262  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   263  	defer cancel()
   264  
   265  	// ensure that all ports have active listeners
   266  	// so that the next node will not get the same
   267  	// when calling getAvailableTCPPorts
   268  	err = waitTCPPorts(ctx, ports...)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	// wait for the node to start
   274  	for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
   275  		node.Client, err = rpc.Dial(conf.IPCEndpoint())
   276  		if err == nil {
   277  			break
   278  		}
   279  	}
   280  	if node.Client == nil {
   281  		t.Fatal(err)
   282  	}
   283  
   284  	// load info
   285  	var info swarm.Info
   286  	if err := node.Client.Call(&info, "bzz_info"); err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
   290  	node.URL = "http://" + node.Addr
   291  
   292  	var nodeInfo p2p.NodeInfo
   293  	if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
   294  		t.Fatal(err)
   295  	}
   296  	node.Enode = nodeInfo.Enode
   297  	node.IpcPath = conf.IPCPath
   298  	return node
   299  }
   300  
   301  func newTestNode(t *testing.T, dir string) *testNode {
   302  
   303  	conf, account := getTestAccount(t, dir)
   304  	ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1)
   305  
   306  	pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase})
   307  
   308  	node := &testNode{Dir: dir, PrivateKey: pk}
   309  
   310  	// assign ports
   311  	ports, err := getAvailableTCPPorts(2)
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  	p2pPort := ports[0]
   316  	httpPort := ports[1]
   317  
   318  	// start the node
   319  	node.Cmd = runSwarm(t,
   320  		"--port", p2pPort,
   321  		"--nat", "extip:127.0.0.1",
   322  		"--nodiscover",
   323  		"--datadir", dir,
   324  		"--ipcpath", conf.IPCPath,
   325  		"--ens-api", "",
   326  		"--bzzaccount", account.Address.String(),
   327  		"--bzznetworkid", "321",
   328  		"--bzzport", httpPort,
   329  		"--verbosity", fmt.Sprint(*loglevel),
   330  	)
   331  	node.Cmd.InputLine(testPassphrase)
   332  	defer func() {
   333  		if t.Failed() {
   334  			node.Shutdown()
   335  		}
   336  	}()
   337  
   338  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   339  	defer cancel()
   340  
   341  	// ensure that all ports have active listeners
   342  	// so that the next node will not get the same
   343  	// when calling getAvailableTCPPorts
   344  	err = waitTCPPorts(ctx, ports...)
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	}
   348  
   349  	// wait for the node to start
   350  	for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
   351  		node.Client, err = rpc.Dial(conf.IPCEndpoint())
   352  		if err == nil {
   353  			break
   354  		}
   355  	}
   356  	if node.Client == nil {
   357  		t.Fatal(err)
   358  	}
   359  
   360  	// load info
   361  	var info swarm.Info
   362  	if err := node.Client.Call(&info, "bzz_info"); err != nil {
   363  		t.Fatal(err)
   364  	}
   365  	node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
   366  	node.URL = "http://" + node.Addr
   367  
   368  	var nodeInfo p2p.NodeInfo
   369  	if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
   370  		t.Fatal(err)
   371  	}
   372  	node.Enode = nodeInfo.Enode
   373  	node.IpcPath = conf.IPCPath
   374  	return node
   375  }
   376  
   377  func (n *testNode) Shutdown() {
   378  	if n.Cmd != nil {
   379  		n.Cmd.Kill()
   380  	}
   381  }
   382  
   383  // getAvailableTCPPorts returns a set of ports that
   384  // nothing is listening on at the time.
   385  //
   386  // Function assignTCPPort cannot be called in sequence
   387  // and guardantee that the same port will be returned in
   388  // different calls as the listener is closed within the function,
   389  // not after all listeners are started and selected unique
   390  // available ports.
   391  func getAvailableTCPPorts(count int) (ports []string, err error) {
   392  	for i := 0; i < count; i++ {
   393  		l, err := net.Listen("tcp", "127.0.0.1:0")
   394  		if err != nil {
   395  			return nil, err
   396  		}
   397  		// defer close in the loop to be sure the same port will not
   398  		// be selected in the next iteration
   399  		defer l.Close()
   400  
   401  		_, port, err := net.SplitHostPort(l.Addr().String())
   402  		if err != nil {
   403  			return nil, err
   404  		}
   405  		ports = append(ports, port)
   406  	}
   407  	return ports, nil
   408  }
   409  
   410  // waitTCPPorts blocks until tcp connections can be
   411  // established on all provided ports. It runs all
   412  // ports dialers in parallel, and returns the first
   413  // encountered error.
   414  // See waitTCPPort also.
   415  func waitTCPPorts(ctx context.Context, ports ...string) error {
   416  	var err error
   417  	// mu locks err variable that is assigned in
   418  	// other goroutines
   419  	var mu sync.Mutex
   420  
   421  	// cancel is canceling all goroutines
   422  	// when the firs error is returned
   423  	// to prevent unnecessary waiting
   424  	ctx, cancel := context.WithCancel(ctx)
   425  	defer cancel()
   426  
   427  	var wg sync.WaitGroup
   428  	for _, port := range ports {
   429  		wg.Add(1)
   430  		go func(port string) {
   431  			defer wg.Done()
   432  
   433  			e := waitTCPPort(ctx, port)
   434  
   435  			mu.Lock()
   436  			defer mu.Unlock()
   437  			if e != nil && err == nil {
   438  				err = e
   439  				cancel()
   440  			}
   441  		}(port)
   442  	}
   443  	wg.Wait()
   444  
   445  	return err
   446  }
   447  
   448  // waitTCPPort blocks until tcp connection can be established
   449  // ona provided port. It has a 3 minute timeout as maximum,
   450  // to prevent long waiting, but it can be shortened with
   451  // a provided context instance. Dialer has a 10 second timeout
   452  // in every iteration, and connection refused error will be
   453  // retried in 100 milliseconds periods.
   454  func waitTCPPort(ctx context.Context, port string) error {
   455  	ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
   456  	defer cancel()
   457  
   458  	for {
   459  		c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port)
   460  		if err != nil {
   461  			if operr, ok := err.(*net.OpError); ok {
   462  				if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED {
   463  					time.Sleep(100 * time.Millisecond)
   464  					continue
   465  				}
   466  			}
   467  			return err
   468  		}
   469  		return c.Close()
   470  	}
   471  }