github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/node/node_test.go (about)

     1  // Copyright 2015 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 node
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"net"
    24  	"net/http"
    25  	"reflect"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/ethereum/go-ethereum/crypto"
    30  	"github.com/ethereum/go-ethereum/ethdb"
    31  	"github.com/ethereum/go-ethereum/p2p"
    32  	"github.com/ethereum/go-ethereum/rpc"
    33  
    34  	"github.com/stretchr/testify/assert"
    35  )
    36  
    37  var (
    38  	testNodeKey, _ = crypto.GenerateKey()
    39  )
    40  
    41  func testNodeConfig() *Config {
    42  	return &Config{
    43  		Name: "test node",
    44  		P2P:  p2p.Config{PrivateKey: testNodeKey},
    45  	}
    46  }
    47  
    48  // Tests that an empty protocol stack can be closed more than once.
    49  func TestNodeCloseMultipleTimes(t *testing.T) {
    50  	stack, err := New(testNodeConfig())
    51  	if err != nil {
    52  		t.Fatalf("failed to create protocol stack: %v", err)
    53  	}
    54  	stack.Close()
    55  
    56  	// Ensure that a stopped node can be stopped again
    57  	for i := 0; i < 3; i++ {
    58  		if err := stack.Close(); err != ErrNodeStopped {
    59  			t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped)
    60  		}
    61  	}
    62  }
    63  
    64  func TestNodeStartMultipleTimes(t *testing.T) {
    65  	stack, err := New(testNodeConfig())
    66  	if err != nil {
    67  		t.Fatalf("failed to create protocol stack: %v", err)
    68  	}
    69  
    70  	// Ensure that a node can be successfully started, but only once
    71  	if err := stack.Start(); err != nil {
    72  		t.Fatalf("failed to start node: %v", err)
    73  	}
    74  	if err := stack.Start(); err != ErrNodeRunning {
    75  		t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning)
    76  	}
    77  	// Ensure that a node can be stopped, but only once
    78  	if err := stack.Close(); err != nil {
    79  		t.Fatalf("failed to stop node: %v", err)
    80  	}
    81  	if err := stack.Close(); err != ErrNodeStopped {
    82  		t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped)
    83  	}
    84  }
    85  
    86  // Tests that if the data dir is already in use, an appropriate error is returned.
    87  func TestNodeUsedDataDir(t *testing.T) {
    88  	// Create a temporary folder to use as the data directory
    89  	dir := t.TempDir()
    90  
    91  	// Create a new node based on the data directory
    92  	original, err := New(&Config{DataDir: dir})
    93  	if err != nil {
    94  		t.Fatalf("failed to create original protocol stack: %v", err)
    95  	}
    96  	defer original.Close()
    97  	if err := original.Start(); err != nil {
    98  		t.Fatalf("failed to start original protocol stack: %v", err)
    99  	}
   100  
   101  	// Create a second node based on the same data directory and ensure failure
   102  	_, err = New(&Config{DataDir: dir})
   103  	if err != ErrDatadirUsed {
   104  		t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed)
   105  	}
   106  }
   107  
   108  // Tests whether a Lifecycle can be registered.
   109  func TestLifecycleRegistry_Successful(t *testing.T) {
   110  	stack, err := New(testNodeConfig())
   111  	if err != nil {
   112  		t.Fatalf("failed to create protocol stack: %v", err)
   113  	}
   114  	defer stack.Close()
   115  
   116  	noop := NewNoop()
   117  	stack.RegisterLifecycle(noop)
   118  
   119  	if !containsLifecycle(stack.lifecycles, noop) {
   120  		t.Fatalf("lifecycle was not properly registered on the node, %v", err)
   121  	}
   122  }
   123  
   124  // Tests whether a service's protocols can be registered properly on the node's p2p server.
   125  func TestRegisterProtocols(t *testing.T) {
   126  	stack, err := New(testNodeConfig())
   127  	if err != nil {
   128  		t.Fatalf("failed to create protocol stack: %v", err)
   129  	}
   130  	defer stack.Close()
   131  
   132  	fs, err := NewFullService(stack)
   133  	if err != nil {
   134  		t.Fatalf("could not create full service: %v", err)
   135  	}
   136  
   137  	for _, protocol := range fs.Protocols() {
   138  		if !containsProtocol(stack.server.Protocols, protocol) {
   139  			t.Fatalf("protocol %v was not successfully registered", protocol)
   140  		}
   141  	}
   142  
   143  	for _, api := range fs.APIs() {
   144  		if !containsAPI(stack.rpcAPIs, api) {
   145  			t.Fatalf("api %v was not successfully registered", api)
   146  		}
   147  	}
   148  }
   149  
   150  // This test checks that open databases are closed with node.
   151  func TestNodeCloseClosesDB(t *testing.T) {
   152  	stack, _ := New(testNodeConfig())
   153  	defer stack.Close()
   154  
   155  	db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
   156  	if err != nil {
   157  		t.Fatal("can't open DB:", err)
   158  	}
   159  	if err = db.Put([]byte{}, []byte{}); err != nil {
   160  		t.Fatal("can't Put on open DB:", err)
   161  	}
   162  
   163  	stack.Close()
   164  	if err = db.Put([]byte{}, []byte{}); err == nil {
   165  		t.Fatal("Put succeeded after node is closed")
   166  	}
   167  }
   168  
   169  // This test checks that OpenDatabase can be used from within a Lifecycle Start method.
   170  func TestNodeOpenDatabaseFromLifecycleStart(t *testing.T) {
   171  	stack, _ := New(testNodeConfig())
   172  	defer stack.Close()
   173  
   174  	var db ethdb.Database
   175  	var err error
   176  	stack.RegisterLifecycle(&InstrumentedService{
   177  		startHook: func() {
   178  			db, err = stack.OpenDatabase("mydb", 0, 0, "", false)
   179  			if err != nil {
   180  				t.Fatal("can't open DB:", err)
   181  			}
   182  		},
   183  		stopHook: func() {
   184  			db.Close()
   185  		},
   186  	})
   187  
   188  	stack.Start()
   189  	stack.Close()
   190  }
   191  
   192  // This test checks that OpenDatabase can be used from within a Lifecycle Stop method.
   193  func TestNodeOpenDatabaseFromLifecycleStop(t *testing.T) {
   194  	stack, _ := New(testNodeConfig())
   195  	defer stack.Close()
   196  
   197  	stack.RegisterLifecycle(&InstrumentedService{
   198  		stopHook: func() {
   199  			db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
   200  			if err != nil {
   201  				t.Fatal("can't open DB:", err)
   202  			}
   203  			db.Close()
   204  		},
   205  	})
   206  
   207  	stack.Start()
   208  	stack.Close()
   209  }
   210  
   211  // Tests that registered Lifecycles get started and stopped correctly.
   212  func TestLifecycleLifeCycle(t *testing.T) {
   213  	stack, _ := New(testNodeConfig())
   214  	defer stack.Close()
   215  
   216  	started := make(map[string]bool)
   217  	stopped := make(map[string]bool)
   218  
   219  	// Create a batch of instrumented services
   220  	lifecycles := map[string]Lifecycle{
   221  		"A": &InstrumentedService{
   222  			startHook: func() { started["A"] = true },
   223  			stopHook:  func() { stopped["A"] = true },
   224  		},
   225  		"B": &InstrumentedService{
   226  			startHook: func() { started["B"] = true },
   227  			stopHook:  func() { stopped["B"] = true },
   228  		},
   229  		"C": &InstrumentedService{
   230  			startHook: func() { started["C"] = true },
   231  			stopHook:  func() { stopped["C"] = true },
   232  		},
   233  	}
   234  	// register lifecycles on node
   235  	for _, lifecycle := range lifecycles {
   236  		stack.RegisterLifecycle(lifecycle)
   237  	}
   238  	// Start the node and check that all services are running
   239  	if err := stack.Start(); err != nil {
   240  		t.Fatalf("failed to start protocol stack: %v", err)
   241  	}
   242  	for id := range lifecycles {
   243  		if !started[id] {
   244  			t.Fatalf("service %s: freshly started service not running", id)
   245  		}
   246  		if stopped[id] {
   247  			t.Fatalf("service %s: freshly started service already stopped", id)
   248  		}
   249  	}
   250  	// Stop the node and check that all services have been stopped
   251  	if err := stack.Close(); err != nil {
   252  		t.Fatalf("failed to stop protocol stack: %v", err)
   253  	}
   254  	for id := range lifecycles {
   255  		if !stopped[id] {
   256  			t.Fatalf("service %s: freshly terminated service still running", id)
   257  		}
   258  	}
   259  }
   260  
   261  // Tests that if a Lifecycle fails to start, all others started before it will be
   262  // shut down.
   263  func TestLifecycleStartupError(t *testing.T) {
   264  	stack, err := New(testNodeConfig())
   265  	if err != nil {
   266  		t.Fatalf("failed to create protocol stack: %v", err)
   267  	}
   268  	defer stack.Close()
   269  
   270  	started := make(map[string]bool)
   271  	stopped := make(map[string]bool)
   272  
   273  	// Create a batch of instrumented services
   274  	lifecycles := map[string]Lifecycle{
   275  		"A": &InstrumentedService{
   276  			startHook: func() { started["A"] = true },
   277  			stopHook:  func() { stopped["A"] = true },
   278  		},
   279  		"B": &InstrumentedService{
   280  			startHook: func() { started["B"] = true },
   281  			stopHook:  func() { stopped["B"] = true },
   282  		},
   283  		"C": &InstrumentedService{
   284  			startHook: func() { started["C"] = true },
   285  			stopHook:  func() { stopped["C"] = true },
   286  		},
   287  	}
   288  	// register lifecycles on node
   289  	for _, lifecycle := range lifecycles {
   290  		stack.RegisterLifecycle(lifecycle)
   291  	}
   292  
   293  	// Register a service that fails to construct itself
   294  	failure := errors.New("fail")
   295  	failer := &InstrumentedService{start: failure}
   296  	stack.RegisterLifecycle(failer)
   297  
   298  	// Start the protocol stack and ensure all started services stop
   299  	if err := stack.Start(); err != failure {
   300  		t.Fatalf("stack startup failure mismatch: have %v, want %v", err, failure)
   301  	}
   302  	for id := range lifecycles {
   303  		if started[id] && !stopped[id] {
   304  			t.Fatalf("service %s: started but not stopped", id)
   305  		}
   306  		delete(started, id)
   307  		delete(stopped, id)
   308  	}
   309  }
   310  
   311  // Tests that even if a registered Lifecycle fails to shut down cleanly, it does
   312  // not influence the rest of the shutdown invocations.
   313  func TestLifecycleTerminationGuarantee(t *testing.T) {
   314  	stack, err := New(testNodeConfig())
   315  	if err != nil {
   316  		t.Fatalf("failed to create protocol stack: %v", err)
   317  	}
   318  	defer stack.Close()
   319  
   320  	started := make(map[string]bool)
   321  	stopped := make(map[string]bool)
   322  
   323  	// Create a batch of instrumented services
   324  	lifecycles := map[string]Lifecycle{
   325  		"A": &InstrumentedService{
   326  			startHook: func() { started["A"] = true },
   327  			stopHook:  func() { stopped["A"] = true },
   328  		},
   329  		"B": &InstrumentedService{
   330  			startHook: func() { started["B"] = true },
   331  			stopHook:  func() { stopped["B"] = true },
   332  		},
   333  		"C": &InstrumentedService{
   334  			startHook: func() { started["C"] = true },
   335  			stopHook:  func() { stopped["C"] = true },
   336  		},
   337  	}
   338  	// register lifecycles on node
   339  	for _, lifecycle := range lifecycles {
   340  		stack.RegisterLifecycle(lifecycle)
   341  	}
   342  
   343  	// Register a service that fails to shot down cleanly
   344  	failure := errors.New("fail")
   345  	failer := &InstrumentedService{stop: failure}
   346  	stack.RegisterLifecycle(failer)
   347  
   348  	// Start the protocol stack, and ensure that a failing shut down terminates all
   349  	// Start the stack and make sure all is online
   350  	if err := stack.Start(); err != nil {
   351  		t.Fatalf("failed to start protocol stack: %v", err)
   352  	}
   353  	for id := range lifecycles {
   354  		if !started[id] {
   355  			t.Fatalf("service %s: service not running", id)
   356  		}
   357  		if stopped[id] {
   358  			t.Fatalf("service %s: service already stopped", id)
   359  		}
   360  	}
   361  	// Stop the stack, verify failure and check all terminations
   362  	err = stack.Close()
   363  	if err, ok := err.(*StopError); !ok {
   364  		t.Fatalf("termination failure mismatch: have %v, want StopError", err)
   365  	} else {
   366  		failer := reflect.TypeOf(&InstrumentedService{})
   367  		if err.Services[failer] != failure {
   368  			t.Fatalf("failer termination failure mismatch: have %v, want %v", err.Services[failer], failure)
   369  		}
   370  		if len(err.Services) != 1 {
   371  			t.Fatalf("failure count mismatch: have %d, want %d", len(err.Services), 1)
   372  		}
   373  	}
   374  	for id := range lifecycles {
   375  		if !stopped[id] {
   376  			t.Fatalf("service %s: service not terminated", id)
   377  		}
   378  		delete(started, id)
   379  		delete(stopped, id)
   380  	}
   381  
   382  	stack.server = &p2p.Server{}
   383  	stack.server.PrivateKey = testNodeKey
   384  }
   385  
   386  // Tests whether a handler can be successfully mounted on the canonical HTTP server
   387  // on the given prefix
   388  func TestRegisterHandler_Successful(t *testing.T) {
   389  	node := createNode(t, 7878, 7979)
   390  	defer node.Close()
   391  	// create and mount handler
   392  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   393  		w.Write([]byte("success"))
   394  	})
   395  	node.RegisterHandler("test", "/test", handler)
   396  
   397  	// start node
   398  	if err := node.Start(); err != nil {
   399  		t.Fatalf("could not start node: %v", err)
   400  	}
   401  
   402  	// create HTTP request
   403  	httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7878/test", nil)
   404  	if err != nil {
   405  		t.Error("could not issue new http request ", err)
   406  	}
   407  
   408  	// check response
   409  	resp := doHTTPRequest(t, httpReq)
   410  	buf := make([]byte, 7)
   411  	_, err = io.ReadFull(resp.Body, buf)
   412  	if err != nil {
   413  		t.Fatalf("could not read response: %v", err)
   414  	}
   415  	assert.Equal(t, "success", string(buf))
   416  }
   417  
   418  // Tests that the given handler will not be successfully mounted since no HTTP server
   419  // is enabled for RPC
   420  func TestRegisterHandler_Unsuccessful(t *testing.T) {
   421  	node, err := New(&DefaultConfig)
   422  	if err != nil {
   423  		t.Fatalf("could not create new node: %v", err)
   424  	}
   425  
   426  	// create and mount handler
   427  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   428  		w.Write([]byte("success"))
   429  	})
   430  	node.RegisterHandler("test", "/test", handler)
   431  }
   432  
   433  // Tests whether websocket requests can be handled on the same port as a regular http server.
   434  func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) {
   435  	node := startHTTP(t, 0, 0)
   436  	defer node.Close()
   437  
   438  	ws := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
   439  
   440  	if node.WSEndpoint() != ws {
   441  		t.Fatalf("endpoints should be the same")
   442  	}
   443  	if !checkRPC(ws) {
   444  		t.Fatalf("ws request failed")
   445  	}
   446  	if !checkRPC(node.HTTPEndpoint()) {
   447  		t.Fatalf("http request failed")
   448  	}
   449  }
   450  
   451  func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) {
   452  	// try and get a free port
   453  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   454  	if err != nil {
   455  		t.Fatal("can't listen:", err)
   456  	}
   457  	port := listener.Addr().(*net.TCPAddr).Port
   458  	listener.Close()
   459  
   460  	node := startHTTP(t, 0, port)
   461  	defer node.Close()
   462  
   463  	wsOnHTTP := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
   464  	ws := fmt.Sprintf("ws://127.0.0.1:%d", port)
   465  
   466  	if node.WSEndpoint() == wsOnHTTP {
   467  		t.Fatalf("endpoints should not be the same")
   468  	}
   469  	// ensure ws endpoint matches the expected endpoint
   470  	if node.WSEndpoint() != ws {
   471  		t.Fatalf("ws endpoint is incorrect: expected %s, got %s", ws, node.WSEndpoint())
   472  	}
   473  
   474  	if !checkRPC(ws) {
   475  		t.Fatalf("ws request failed")
   476  	}
   477  	if !checkRPC(node.HTTPEndpoint()) {
   478  		t.Fatalf("http request failed")
   479  	}
   480  }
   481  
   482  type rpcPrefixTest struct {
   483  	httpPrefix, wsPrefix string
   484  	// These lists paths on which JSON-RPC should be served / not served.
   485  	wantHTTP   []string
   486  	wantNoHTTP []string
   487  	wantWS     []string
   488  	wantNoWS   []string
   489  }
   490  
   491  func TestNodeRPCPrefix(t *testing.T) {
   492  	t.Parallel()
   493  
   494  	tests := []rpcPrefixTest{
   495  		// both off
   496  		{
   497  			httpPrefix: "", wsPrefix: "",
   498  			wantHTTP:   []string{"/", "/?p=1"},
   499  			wantNoHTTP: []string{"/test", "/test?p=1"},
   500  			wantWS:     []string{"/", "/?p=1"},
   501  			wantNoWS:   []string{"/test", "/test?p=1"},
   502  		},
   503  		// only http prefix
   504  		{
   505  			httpPrefix: "/testprefix", wsPrefix: "",
   506  			wantHTTP:   []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
   507  			wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"},
   508  			wantWS:     []string{"/", "/?p=1"},
   509  			wantNoWS:   []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"},
   510  		},
   511  		// only ws prefix
   512  		{
   513  			httpPrefix: "", wsPrefix: "/testprefix",
   514  			wantHTTP:   []string{"/", "/?p=1"},
   515  			wantNoHTTP: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"},
   516  			wantWS:     []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
   517  			wantNoWS:   []string{"/", "/?p=1", "/test", "/test?p=1"},
   518  		},
   519  		// both set
   520  		{
   521  			httpPrefix: "/testprefix", wsPrefix: "/testprefix",
   522  			wantHTTP:   []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
   523  			wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"},
   524  			wantWS:     []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
   525  			wantNoWS:   []string{"/", "/?p=1", "/test", "/test?p=1"},
   526  		},
   527  	}
   528  
   529  	for _, test := range tests {
   530  		test := test
   531  		name := fmt.Sprintf("http=%s ws=%s", test.httpPrefix, test.wsPrefix)
   532  		t.Run(name, func(t *testing.T) {
   533  			cfg := &Config{
   534  				HTTPHost:       "127.0.0.1",
   535  				HTTPPathPrefix: test.httpPrefix,
   536  				WSHost:         "127.0.0.1",
   537  				WSPathPrefix:   test.wsPrefix,
   538  			}
   539  			node, err := New(cfg)
   540  			if err != nil {
   541  				t.Fatal("can't create node:", err)
   542  			}
   543  			defer node.Close()
   544  			if err := node.Start(); err != nil {
   545  				t.Fatal("can't start node:", err)
   546  			}
   547  			test.check(t, node)
   548  		})
   549  	}
   550  }
   551  
   552  func (test rpcPrefixTest) check(t *testing.T, node *Node) {
   553  	t.Helper()
   554  	httpBase := "http://" + node.http.listenAddr()
   555  	wsBase := "ws://" + node.http.listenAddr()
   556  
   557  	if node.WSEndpoint() != wsBase+test.wsPrefix {
   558  		t.Errorf("Error: node has wrong WSEndpoint %q", node.WSEndpoint())
   559  	}
   560  
   561  	for _, path := range test.wantHTTP {
   562  		resp := rpcRequest(t, httpBase+path)
   563  		if resp.StatusCode != 200 {
   564  			t.Errorf("Error: %s: bad status code %d, want 200", path, resp.StatusCode)
   565  		}
   566  	}
   567  	for _, path := range test.wantNoHTTP {
   568  		resp := rpcRequest(t, httpBase+path)
   569  		if resp.StatusCode != 404 {
   570  			t.Errorf("Error: %s: bad status code %d, want 404", path, resp.StatusCode)
   571  		}
   572  	}
   573  	for _, path := range test.wantWS {
   574  		err := wsRequest(t, wsBase+path)
   575  		if err != nil {
   576  			t.Errorf("Error: %s: WebSocket connection failed: %v", path, err)
   577  		}
   578  	}
   579  	for _, path := range test.wantNoWS {
   580  		err := wsRequest(t, wsBase+path)
   581  		if err == nil {
   582  			t.Errorf("Error: %s: WebSocket connection succeeded for path in wantNoWS", path)
   583  		}
   584  
   585  	}
   586  }
   587  
   588  func createNode(t *testing.T, httpPort, wsPort int) *Node {
   589  	conf := &Config{
   590  		HTTPHost: "127.0.0.1",
   591  		HTTPPort: httpPort,
   592  		WSHost:   "127.0.0.1",
   593  		WSPort:   wsPort,
   594  	}
   595  	node, err := New(conf)
   596  	if err != nil {
   597  		t.Fatalf("could not create a new node: %v", err)
   598  	}
   599  	return node
   600  }
   601  
   602  func startHTTP(t *testing.T, httpPort, wsPort int) *Node {
   603  	node := createNode(t, httpPort, wsPort)
   604  	err := node.Start()
   605  	if err != nil {
   606  		t.Fatalf("could not start http service on node: %v", err)
   607  	}
   608  
   609  	return node
   610  }
   611  
   612  func doHTTPRequest(t *testing.T, req *http.Request) *http.Response {
   613  	client := http.DefaultClient
   614  	resp, err := client.Do(req)
   615  	if err != nil {
   616  		t.Fatalf("could not issue a GET request to the given endpoint: %v", err)
   617  
   618  	}
   619  	return resp
   620  }
   621  
   622  func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool {
   623  	for _, a := range stackProtocols {
   624  		if reflect.DeepEqual(a, protocol) {
   625  			return true
   626  		}
   627  	}
   628  	return false
   629  }
   630  
   631  func containsAPI(stackAPIs []rpc.API, api rpc.API) bool {
   632  	for _, a := range stackAPIs {
   633  		if reflect.DeepEqual(a, api) {
   634  			return true
   635  		}
   636  	}
   637  	return false
   638  }