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