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

     1  // Copyright 2020 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  	"bytes"
    21  	"io"
    22  	"net"
    23  	"net/http"
    24  	"net/url"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/ethereum/go-ethereum/rpc"
    29  	"github.com/stretchr/testify/assert"
    30  )
    31  
    32  // This test uses the admin_startRPC and admin_startWS APIs,
    33  // checking whether the HTTP server is started correctly.
    34  func TestStartRPC(t *testing.T) {
    35  	type test struct {
    36  		name string
    37  		cfg  Config
    38  		fn   func(*testing.T, *Node, *privateAdminAPI)
    39  
    40  		// Checks. These run after the node is configured and all API calls have been made.
    41  		wantReachable bool // whether the HTTP server should be reachable at all
    42  		wantHandlers  bool // whether RegisterHandler handlers should be accessible
    43  		wantRPC       bool // whether JSON-RPC/HTTP should be accessible
    44  		wantWS        bool // whether JSON-RPC/WS should be accessible
    45  	}
    46  
    47  	tests := []test{
    48  		{
    49  			name: "all off",
    50  			cfg:  Config{},
    51  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
    52  			},
    53  			wantReachable: false,
    54  			wantHandlers:  false,
    55  			wantRPC:       false,
    56  			wantWS:        false,
    57  		},
    58  		{
    59  			name: "rpc enabled through config",
    60  			cfg:  Config{HTTPHost: "127.0.0.1"},
    61  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
    62  			},
    63  			wantReachable: true,
    64  			wantHandlers:  true,
    65  			wantRPC:       true,
    66  			wantWS:        false,
    67  		},
    68  		{
    69  			name: "rpc enabled through API",
    70  			cfg:  Config{},
    71  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
    72  				_, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil)
    73  				assert.NoError(t, err)
    74  			},
    75  			wantReachable: true,
    76  			wantHandlers:  true,
    77  			wantRPC:       true,
    78  			wantWS:        false,
    79  		},
    80  		{
    81  			name: "rpc start again after failure",
    82  			cfg:  Config{},
    83  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
    84  				// Listen on a random port.
    85  				listener, err := net.Listen("tcp", "127.0.0.1:0")
    86  				if err != nil {
    87  					t.Fatal("can't listen:", err)
    88  				}
    89  				defer listener.Close()
    90  				port := listener.Addr().(*net.TCPAddr).Port
    91  
    92  				// Now try to start RPC on that port. This should fail.
    93  				_, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil)
    94  				if err == nil {
    95  					t.Fatal("StartHTTP should have failed on port", port)
    96  				}
    97  
    98  				// Try again after unblocking the port. It should work this time.
    99  				listener.Close()
   100  				_, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil)
   101  				assert.NoError(t, err)
   102  			},
   103  			wantReachable: true,
   104  			wantHandlers:  true,
   105  			wantRPC:       true,
   106  			wantWS:        false,
   107  		},
   108  		{
   109  			name: "rpc stopped through API",
   110  			cfg:  Config{HTTPHost: "127.0.0.1"},
   111  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   112  				_, err := api.StopHTTP()
   113  				assert.NoError(t, err)
   114  			},
   115  			wantReachable: false,
   116  			wantHandlers:  false,
   117  			wantRPC:       false,
   118  			wantWS:        false,
   119  		},
   120  		{
   121  			name: "rpc stopped twice",
   122  			cfg:  Config{HTTPHost: "127.0.0.1"},
   123  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   124  				_, err := api.StopHTTP()
   125  				assert.NoError(t, err)
   126  
   127  				_, err = api.StopHTTP()
   128  				assert.NoError(t, err)
   129  			},
   130  			wantReachable: false,
   131  			wantHandlers:  false,
   132  			wantRPC:       false,
   133  			wantWS:        false,
   134  		},
   135  		{
   136  			name:          "ws enabled through config",
   137  			cfg:           Config{WSHost: "127.0.0.1"},
   138  			wantReachable: true,
   139  			wantHandlers:  false,
   140  			wantRPC:       false,
   141  			wantWS:        true,
   142  		},
   143  		{
   144  			name: "ws enabled through API",
   145  			cfg:  Config{},
   146  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   147  				_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
   148  				assert.NoError(t, err)
   149  			},
   150  			wantReachable: true,
   151  			wantHandlers:  false,
   152  			wantRPC:       false,
   153  			wantWS:        true,
   154  		},
   155  		{
   156  			name: "ws stopped through API",
   157  			cfg:  Config{WSHost: "127.0.0.1"},
   158  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   159  				_, err := api.StopWS()
   160  				assert.NoError(t, err)
   161  			},
   162  			wantReachable: false,
   163  			wantHandlers:  false,
   164  			wantRPC:       false,
   165  			wantWS:        false,
   166  		},
   167  		{
   168  			name: "ws stopped twice",
   169  			cfg:  Config{WSHost: "127.0.0.1"},
   170  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   171  				_, err := api.StopWS()
   172  				assert.NoError(t, err)
   173  
   174  				_, err = api.StopWS()
   175  				assert.NoError(t, err)
   176  			},
   177  			wantReachable: false,
   178  			wantHandlers:  false,
   179  			wantRPC:       false,
   180  			wantWS:        false,
   181  		},
   182  		{
   183  			name: "ws enabled after RPC",
   184  			cfg:  Config{HTTPHost: "127.0.0.1"},
   185  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   186  				wsport := n.http.port
   187  				_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
   188  				assert.NoError(t, err)
   189  			},
   190  			wantReachable: true,
   191  			wantHandlers:  true,
   192  			wantRPC:       true,
   193  			wantWS:        true,
   194  		},
   195  		{
   196  			name: "ws enabled after RPC then stopped",
   197  			cfg:  Config{HTTPHost: "127.0.0.1"},
   198  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   199  				wsport := n.http.port
   200  				_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
   201  				assert.NoError(t, err)
   202  
   203  				_, err = api.StopWS()
   204  				assert.NoError(t, err)
   205  			},
   206  			wantReachable: true,
   207  			wantHandlers:  true,
   208  			wantRPC:       true,
   209  			wantWS:        false,
   210  		},
   211  		{
   212  			name: "rpc stopped with ws enabled",
   213  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   214  				_, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil)
   215  				assert.NoError(t, err)
   216  
   217  				wsport := n.http.port
   218  				_, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
   219  				assert.NoError(t, err)
   220  
   221  				_, err = api.StopHTTP()
   222  				assert.NoError(t, err)
   223  			},
   224  			wantReachable: false,
   225  			wantHandlers:  false,
   226  			wantRPC:       false,
   227  			wantWS:        false,
   228  		},
   229  		{
   230  			name: "rpc enabled after ws",
   231  			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
   232  				_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
   233  				assert.NoError(t, err)
   234  
   235  				wsport := n.http.port
   236  				_, err = api.StartHTTP(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
   237  				assert.NoError(t, err)
   238  			},
   239  			wantReachable: true,
   240  			wantHandlers:  true,
   241  			wantRPC:       true,
   242  			wantWS:        true,
   243  		},
   244  	}
   245  
   246  	for _, test := range tests {
   247  		test := test
   248  		t.Run(test.name, func(t *testing.T) {
   249  			t.Parallel()
   250  
   251  			// Apply some sane defaults.
   252  			config := test.cfg
   253  			// config.Logger = testlog.Logger(t, log.LvlDebug)
   254  			config.P2P.NoDiscovery = true
   255  
   256  			// Create Node.
   257  			stack, err := New(&config)
   258  			if err != nil {
   259  				t.Fatal("can't create node:", err)
   260  			}
   261  			defer stack.Close()
   262  
   263  			// Register the test handler.
   264  			stack.RegisterHandler("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   265  				w.Write([]byte("OK"))
   266  			}))
   267  
   268  			if err := stack.Start(); err != nil {
   269  				t.Fatal("can't start node:", err)
   270  			}
   271  
   272  			// Run the API call hook.
   273  			if test.fn != nil {
   274  				test.fn(t, stack, &privateAdminAPI{stack})
   275  			}
   276  
   277  			// Check if the HTTP endpoints are available.
   278  			baseURL := stack.HTTPEndpoint()
   279  			reachable := checkReachable(baseURL)
   280  			handlersAvailable := checkBodyOK(baseURL + "/test")
   281  			rpcAvailable := checkRPC(baseURL)
   282  			wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1))
   283  			if reachable != test.wantReachable {
   284  				t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable))
   285  			}
   286  			if handlersAvailable != test.wantHandlers {
   287  				t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers))
   288  			}
   289  			if rpcAvailable != test.wantRPC {
   290  				t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC))
   291  			}
   292  			if wsAvailable != test.wantWS {
   293  				t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS))
   294  			}
   295  		})
   296  	}
   297  }
   298  
   299  // checkReachable checks if the TCP endpoint in rawurl is open.
   300  func checkReachable(rawurl string) bool {
   301  	u, err := url.Parse(rawurl)
   302  	if err != nil {
   303  		panic(err)
   304  	}
   305  	conn, err := net.Dial("tcp", u.Host)
   306  	if err != nil {
   307  		return false
   308  	}
   309  	conn.Close()
   310  	return true
   311  }
   312  
   313  // checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK".
   314  func checkBodyOK(url string) bool {
   315  	resp, err := http.Get(url)
   316  	if err != nil {
   317  		return false
   318  	}
   319  	defer resp.Body.Close()
   320  
   321  	if resp.StatusCode != 200 {
   322  		return false
   323  	}
   324  	buf := make([]byte, 2)
   325  	if _, err = io.ReadFull(resp.Body, buf); err != nil {
   326  		return false
   327  	}
   328  	return bytes.Equal(buf, []byte("OK"))
   329  }
   330  
   331  // checkRPC checks whether JSON-RPC works against the given URL.
   332  func checkRPC(url string) bool {
   333  	c, err := rpc.Dial(url)
   334  	if err != nil {
   335  		return false
   336  	}
   337  	defer c.Close()
   338  
   339  	_, err = c.SupportedModules()
   340  	return err == nil
   341  }
   342  
   343  // string/int pointer helpers.
   344  func sp(s string) *string { return &s }
   345  func ip(i int) *int       { return &i }
   346  
   347  func not(ok bool) string {
   348  	if ok {
   349  		return ""
   350  	}
   351  	return "not "
   352  }