github.com/snowblossomcoin/go-ethereum@v1.9.25/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.StartRPC(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.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil)
    94  				if err == nil {
    95  					t.Fatal("StartRPC 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.StartRPC(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.StopRPC()
   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.StopRPC()
   125  				assert.NoError(t, err)
   126  
   127  				_, err = api.StopRPC()
   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.StartRPC(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.StopRPC()
   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.StartRPC(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  		t.Run(test.name, func(t *testing.T) {
   248  			// Apply some sane defaults.
   249  			config := test.cfg
   250  			// config.Logger = testlog.Logger(t, log.LvlDebug)
   251  			config.NoUSB = true
   252  			config.P2P.NoDiscovery = true
   253  
   254  			// Create Node.
   255  			stack, err := New(&config)
   256  			if err != nil {
   257  				t.Fatal("can't create node:", err)
   258  			}
   259  			defer stack.Close()
   260  
   261  			// Register the test handler.
   262  			stack.RegisterHandler("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   263  				w.Write([]byte("OK"))
   264  			}))
   265  
   266  			if err := stack.Start(); err != nil {
   267  				t.Fatal("can't start node:", err)
   268  			}
   269  
   270  			// Run the API call hook.
   271  			if test.fn != nil {
   272  				test.fn(t, stack, &privateAdminAPI{stack})
   273  			}
   274  
   275  			// Check if the HTTP endpoints are available.
   276  			baseURL := stack.HTTPEndpoint()
   277  			reachable := checkReachable(baseURL)
   278  			handlersAvailable := checkBodyOK(baseURL + "/test")
   279  			rpcAvailable := checkRPC(baseURL)
   280  			wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1))
   281  			if reachable != test.wantReachable {
   282  				t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable))
   283  			}
   284  			if handlersAvailable != test.wantHandlers {
   285  				t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers))
   286  			}
   287  			if rpcAvailable != test.wantRPC {
   288  				t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC))
   289  			}
   290  			if wsAvailable != test.wantWS {
   291  				t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS))
   292  			}
   293  		})
   294  	}
   295  }
   296  
   297  // checkReachable checks if the TCP endpoint in rawurl is open.
   298  func checkReachable(rawurl string) bool {
   299  	u, err := url.Parse(rawurl)
   300  	if err != nil {
   301  		panic(err)
   302  	}
   303  	conn, err := net.Dial("tcp", u.Host)
   304  	if err != nil {
   305  		return false
   306  	}
   307  	conn.Close()
   308  	return true
   309  }
   310  
   311  // checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK".
   312  func checkBodyOK(url string) bool {
   313  	resp, err := http.Get(url)
   314  	if err != nil {
   315  		return false
   316  	}
   317  	defer resp.Body.Close()
   318  
   319  	if resp.StatusCode != 200 {
   320  		return false
   321  	}
   322  	buf := make([]byte, 2)
   323  	if _, err = io.ReadFull(resp.Body, buf); err != nil {
   324  		return false
   325  	}
   326  	return bytes.Equal(buf, []byte("OK"))
   327  }
   328  
   329  // checkRPC checks whether JSON-RPC works against the given URL.
   330  func checkRPC(url string) bool {
   331  	c, err := rpc.Dial(url)
   332  	if err != nil {
   333  		return false
   334  	}
   335  	defer c.Close()
   336  
   337  	_, err = c.SupportedModules()
   338  	return err == nil
   339  }
   340  
   341  // string/int pointer helpers.
   342  func sp(s string) *string { return &s }
   343  func ip(i int) *int       { return &i }
   344  
   345  func not(ok bool) string {
   346  	if ok {
   347  		return ""
   348  	}
   349  	return "not "
   350  }