github.com/snowblossomcoin/go-ethereum@v1.9.25/rpc/client_test.go (about)

     1  // Copyright 2016 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 rpc
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math/rand"
    23  	"net"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"os"
    27  	"reflect"
    28  	"runtime"
    29  	"strings"
    30  	"sync"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/davecgh/go-spew/spew"
    35  	"github.com/ethereum/go-ethereum/log"
    36  )
    37  
    38  func TestClientRequest(t *testing.T) {
    39  	server := newTestServer()
    40  	defer server.Stop()
    41  	client := DialInProc(server)
    42  	defer client.Close()
    43  
    44  	var resp echoResult
    45  	if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
    46  		t.Fatal(err)
    47  	}
    48  	if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) {
    49  		t.Errorf("incorrect result %#v", resp)
    50  	}
    51  }
    52  
    53  func TestClientResponseType(t *testing.T) {
    54  	server := newTestServer()
    55  	defer server.Stop()
    56  	client := DialInProc(server)
    57  	defer client.Close()
    58  
    59  	if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
    60  		t.Errorf("Passing nil as result should be fine, but got an error: %v", err)
    61  	}
    62  	var resultVar echoResult
    63  	// Note: passing the var, not a ref
    64  	err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"})
    65  	if err == nil {
    66  		t.Error("Passing a var as result should be an error")
    67  	}
    68  }
    69  
    70  // This test checks that server-returned errors with code and data come out of Client.Call.
    71  func TestClientErrorData(t *testing.T) {
    72  	server := newTestServer()
    73  	defer server.Stop()
    74  	client := DialInProc(server)
    75  	defer client.Close()
    76  
    77  	var resp interface{}
    78  	err := client.Call(&resp, "test_returnError")
    79  	if err == nil {
    80  		t.Fatal("expected error")
    81  	}
    82  
    83  	// Check code.
    84  	if e, ok := err.(Error); !ok {
    85  		t.Fatalf("client did not return rpc.Error, got %#v", e)
    86  	} else if e.ErrorCode() != (testError{}.ErrorCode()) {
    87  		t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode())
    88  	}
    89  	// Check data.
    90  	if e, ok := err.(DataError); !ok {
    91  		t.Fatalf("client did not return rpc.DataError, got %#v", e)
    92  	} else if e.ErrorData() != (testError{}.ErrorData()) {
    93  		t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData())
    94  	}
    95  }
    96  
    97  func TestClientBatchRequest(t *testing.T) {
    98  	server := newTestServer()
    99  	defer server.Stop()
   100  	client := DialInProc(server)
   101  	defer client.Close()
   102  
   103  	batch := []BatchElem{
   104  		{
   105  			Method: "test_echo",
   106  			Args:   []interface{}{"hello", 10, &echoArgs{"world"}},
   107  			Result: new(echoResult),
   108  		},
   109  		{
   110  			Method: "test_echo",
   111  			Args:   []interface{}{"hello2", 11, &echoArgs{"world"}},
   112  			Result: new(echoResult),
   113  		},
   114  		{
   115  			Method: "no_such_method",
   116  			Args:   []interface{}{1, 2, 3},
   117  			Result: new(int),
   118  		},
   119  	}
   120  	if err := client.BatchCall(batch); err != nil {
   121  		t.Fatal(err)
   122  	}
   123  	wantResult := []BatchElem{
   124  		{
   125  			Method: "test_echo",
   126  			Args:   []interface{}{"hello", 10, &echoArgs{"world"}},
   127  			Result: &echoResult{"hello", 10, &echoArgs{"world"}},
   128  		},
   129  		{
   130  			Method: "test_echo",
   131  			Args:   []interface{}{"hello2", 11, &echoArgs{"world"}},
   132  			Result: &echoResult{"hello2", 11, &echoArgs{"world"}},
   133  		},
   134  		{
   135  			Method: "no_such_method",
   136  			Args:   []interface{}{1, 2, 3},
   137  			Result: new(int),
   138  			Error:  &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"},
   139  		},
   140  	}
   141  	if !reflect.DeepEqual(batch, wantResult) {
   142  		t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult))
   143  	}
   144  }
   145  
   146  func TestClientNotify(t *testing.T) {
   147  	server := newTestServer()
   148  	defer server.Stop()
   149  	client := DialInProc(server)
   150  	defer client.Close()
   151  
   152  	if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
   153  		t.Fatal(err)
   154  	}
   155  }
   156  
   157  // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) }
   158  func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
   159  func TestClientCancelHTTP(t *testing.T)      { testClientCancel("http", t) }
   160  func TestClientCancelIPC(t *testing.T)       { testClientCancel("ipc", t) }
   161  
   162  // This test checks that requests made through CallContext can be canceled by canceling
   163  // the context.
   164  func testClientCancel(transport string, t *testing.T) {
   165  	// These tests take a lot of time, run them all at once.
   166  	// You probably want to run with -parallel 1 or comment out
   167  	// the call to t.Parallel if you enable the logging.
   168  	t.Parallel()
   169  
   170  	server := newTestServer()
   171  	defer server.Stop()
   172  
   173  	// What we want to achieve is that the context gets canceled
   174  	// at various stages of request processing. The interesting cases
   175  	// are:
   176  	//  - cancel during dial
   177  	//  - cancel while performing a HTTP request
   178  	//  - cancel while waiting for a response
   179  	//
   180  	// To trigger those, the times are chosen such that connections
   181  	// are killed within the deadline for every other call (maxKillTimeout
   182  	// is 2x maxCancelTimeout).
   183  	//
   184  	// Once a connection is dead, there is a fair chance it won't connect
   185  	// successfully because the accept is delayed by 1s.
   186  	maxContextCancelTimeout := 300 * time.Millisecond
   187  	fl := &flakeyListener{
   188  		maxAcceptDelay: 1 * time.Second,
   189  		maxKillTimeout: 600 * time.Millisecond,
   190  	}
   191  
   192  	var client *Client
   193  	switch transport {
   194  	case "ws", "http":
   195  		c, hs := httpTestClient(server, transport, fl)
   196  		defer hs.Close()
   197  		client = c
   198  	case "ipc":
   199  		c, l := ipcTestClient(server, fl)
   200  		defer l.Close()
   201  		client = c
   202  	default:
   203  		panic("unknown transport: " + transport)
   204  	}
   205  
   206  	// The actual test starts here.
   207  	var (
   208  		wg       sync.WaitGroup
   209  		nreqs    = 10
   210  		ncallers = 10
   211  	)
   212  	caller := func(index int) {
   213  		defer wg.Done()
   214  		for i := 0; i < nreqs; i++ {
   215  			var (
   216  				ctx     context.Context
   217  				cancel  func()
   218  				timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout)))
   219  			)
   220  			if index < ncallers/2 {
   221  				// For half of the callers, create a context without deadline
   222  				// and cancel it later.
   223  				ctx, cancel = context.WithCancel(context.Background())
   224  				time.AfterFunc(timeout, cancel)
   225  			} else {
   226  				// For the other half, create a context with a deadline instead. This is
   227  				// different because the context deadline is used to set the socket write
   228  				// deadline.
   229  				ctx, cancel = context.WithTimeout(context.Background(), timeout)
   230  			}
   231  
   232  			// Now perform a call with the context.
   233  			// The key thing here is that no call will ever complete successfully.
   234  			err := client.CallContext(ctx, nil, "test_block")
   235  			switch {
   236  			case err == nil:
   237  				_, hasDeadline := ctx.Deadline()
   238  				t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline)
   239  				// default:
   240  				// 	t.Logf("got expected error with %v wait time: %v", timeout, err)
   241  			}
   242  			cancel()
   243  		}
   244  	}
   245  	wg.Add(ncallers)
   246  	for i := 0; i < ncallers; i++ {
   247  		go caller(i)
   248  	}
   249  	wg.Wait()
   250  }
   251  
   252  func TestClientSubscribeInvalidArg(t *testing.T) {
   253  	server := newTestServer()
   254  	defer server.Stop()
   255  	client := DialInProc(server)
   256  	defer client.Close()
   257  
   258  	check := func(shouldPanic bool, arg interface{}) {
   259  		defer func() {
   260  			err := recover()
   261  			if shouldPanic && err == nil {
   262  				t.Errorf("EthSubscribe should've panicked for %#v", arg)
   263  			}
   264  			if !shouldPanic && err != nil {
   265  				t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg)
   266  				buf := make([]byte, 1024*1024)
   267  				buf = buf[:runtime.Stack(buf, false)]
   268  				t.Error(err)
   269  				t.Error(string(buf))
   270  			}
   271  		}()
   272  		client.EthSubscribe(context.Background(), arg, "foo_bar")
   273  	}
   274  	check(true, nil)
   275  	check(true, 1)
   276  	check(true, (chan int)(nil))
   277  	check(true, make(<-chan int))
   278  	check(false, make(chan int))
   279  	check(false, make(chan<- int))
   280  }
   281  
   282  func TestClientSubscribe(t *testing.T) {
   283  	server := newTestServer()
   284  	defer server.Stop()
   285  	client := DialInProc(server)
   286  	defer client.Close()
   287  
   288  	nc := make(chan int)
   289  	count := 10
   290  	sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0)
   291  	if err != nil {
   292  		t.Fatal("can't subscribe:", err)
   293  	}
   294  	for i := 0; i < count; i++ {
   295  		if val := <-nc; val != i {
   296  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   297  		}
   298  	}
   299  
   300  	sub.Unsubscribe()
   301  	select {
   302  	case v := <-nc:
   303  		t.Fatal("received value after unsubscribe:", v)
   304  	case err := <-sub.Err():
   305  		if err != nil {
   306  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   307  		}
   308  	case <-time.After(1 * time.Second):
   309  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   310  	}
   311  }
   312  
   313  // In this test, the connection drops while Subscribe is waiting for a response.
   314  func TestClientSubscribeClose(t *testing.T) {
   315  	server := newTestServer()
   316  	service := &notificationTestService{
   317  		gotHangSubscriptionReq:  make(chan struct{}),
   318  		unblockHangSubscription: make(chan struct{}),
   319  	}
   320  	if err := server.RegisterName("nftest2", service); err != nil {
   321  		t.Fatal(err)
   322  	}
   323  
   324  	defer server.Stop()
   325  	client := DialInProc(server)
   326  	defer client.Close()
   327  
   328  	var (
   329  		nc   = make(chan int)
   330  		errc = make(chan error, 1)
   331  		sub  *ClientSubscription
   332  		err  error
   333  	)
   334  	go func() {
   335  		sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999)
   336  		errc <- err
   337  	}()
   338  
   339  	<-service.gotHangSubscriptionReq
   340  	client.Close()
   341  	service.unblockHangSubscription <- struct{}{}
   342  
   343  	select {
   344  	case err := <-errc:
   345  		if err == nil {
   346  			t.Errorf("Subscribe returned nil error after Close")
   347  		}
   348  		if sub != nil {
   349  			t.Error("Subscribe returned non-nil subscription after Close")
   350  		}
   351  	case <-time.After(1 * time.Second):
   352  		t.Fatalf("Subscribe did not return within 1s after Close")
   353  	}
   354  }
   355  
   356  // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the
   357  // client hangs during shutdown when Unsubscribe races with Client.Close.
   358  func TestClientCloseUnsubscribeRace(t *testing.T) {
   359  	server := newTestServer()
   360  	defer server.Stop()
   361  
   362  	for i := 0; i < 20; i++ {
   363  		client := DialInProc(server)
   364  		nc := make(chan int)
   365  		sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1)
   366  		if err != nil {
   367  			t.Fatal(err)
   368  		}
   369  		go client.Close()
   370  		go sub.Unsubscribe()
   371  		select {
   372  		case <-sub.Err():
   373  		case <-time.After(5 * time.Second):
   374  			t.Fatal("subscription not closed within timeout")
   375  		}
   376  	}
   377  }
   378  
   379  // This test checks that Client doesn't lock up when a single subscriber
   380  // doesn't read subscription events.
   381  func TestClientNotificationStorm(t *testing.T) {
   382  	server := newTestServer()
   383  	defer server.Stop()
   384  
   385  	doTest := func(count int, wantError bool) {
   386  		client := DialInProc(server)
   387  		defer client.Close()
   388  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   389  		defer cancel()
   390  
   391  		// Subscribe on the server. It will start sending many notifications
   392  		// very quickly.
   393  		nc := make(chan int)
   394  		sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0)
   395  		if err != nil {
   396  			t.Fatal("can't subscribe:", err)
   397  		}
   398  		defer sub.Unsubscribe()
   399  
   400  		// Process each notification, try to run a call in between each of them.
   401  		for i := 0; i < count; i++ {
   402  			select {
   403  			case val := <-nc:
   404  				if val != i {
   405  					t.Fatalf("(%d/%d) unexpected value %d", i, count, val)
   406  				}
   407  			case err := <-sub.Err():
   408  				if wantError && err != ErrSubscriptionQueueOverflow {
   409  					t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow)
   410  				} else if !wantError {
   411  					t.Fatalf("(%d/%d) got unexpected error %q", i, count, err)
   412  				}
   413  				return
   414  			}
   415  			var r int
   416  			err := client.CallContext(ctx, &r, "nftest_echo", i)
   417  			if err != nil {
   418  				if !wantError {
   419  					t.Fatalf("(%d/%d) call error: %v", i, count, err)
   420  				}
   421  				return
   422  			}
   423  		}
   424  		if wantError {
   425  			t.Fatalf("didn't get expected error")
   426  		}
   427  	}
   428  
   429  	doTest(8000, false)
   430  	doTest(23000, true)
   431  }
   432  
   433  func TestClientSetHeader(t *testing.T) {
   434  	var gotHeader bool
   435  	srv := newTestServer()
   436  	httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   437  		if r.Header.Get("test") == "ok" {
   438  			gotHeader = true
   439  		}
   440  		srv.ServeHTTP(w, r)
   441  	}))
   442  	defer httpsrv.Close()
   443  	defer srv.Stop()
   444  
   445  	client, err := Dial(httpsrv.URL)
   446  	if err != nil {
   447  		t.Fatal(err)
   448  	}
   449  	defer client.Close()
   450  
   451  	client.SetHeader("test", "ok")
   452  	if _, err := client.SupportedModules(); err != nil {
   453  		t.Fatal(err)
   454  	}
   455  	if !gotHeader {
   456  		t.Fatal("client did not set custom header")
   457  	}
   458  
   459  	// Check that Content-Type can be replaced.
   460  	client.SetHeader("content-type", "application/x-garbage")
   461  	_, err = client.SupportedModules()
   462  	if err == nil {
   463  		t.Fatal("no error for invalid content-type header")
   464  	} else if !strings.Contains(err.Error(), "Unsupported Media Type") {
   465  		t.Fatalf("error is not related to content-type: %q", err)
   466  	}
   467  }
   468  
   469  func TestClientHTTP(t *testing.T) {
   470  	server := newTestServer()
   471  	defer server.Stop()
   472  
   473  	client, hs := httpTestClient(server, "http", nil)
   474  	defer hs.Close()
   475  	defer client.Close()
   476  
   477  	// Launch concurrent requests.
   478  	var (
   479  		results    = make([]echoResult, 100)
   480  		errc       = make(chan error, len(results))
   481  		wantResult = echoResult{"a", 1, new(echoArgs)}
   482  	)
   483  	defer client.Close()
   484  	for i := range results {
   485  		i := i
   486  		go func() {
   487  			errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args)
   488  		}()
   489  	}
   490  
   491  	// Wait for all of them to complete.
   492  	timeout := time.NewTimer(5 * time.Second)
   493  	defer timeout.Stop()
   494  	for i := range results {
   495  		select {
   496  		case err := <-errc:
   497  			if err != nil {
   498  				t.Fatal(err)
   499  			}
   500  		case <-timeout.C:
   501  			t.Fatalf("timeout (got %d/%d) results)", i+1, len(results))
   502  		}
   503  	}
   504  
   505  	// Check results.
   506  	for i := range results {
   507  		if !reflect.DeepEqual(results[i], wantResult) {
   508  			t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult)
   509  		}
   510  	}
   511  }
   512  
   513  func TestClientReconnect(t *testing.T) {
   514  	startServer := func(addr string) (*Server, net.Listener) {
   515  		srv := newTestServer()
   516  		l, err := net.Listen("tcp", addr)
   517  		if err != nil {
   518  			t.Fatal("can't listen:", err)
   519  		}
   520  		go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
   521  		return srv, l
   522  	}
   523  
   524  	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
   525  	defer cancel()
   526  
   527  	// Start a server and corresponding client.
   528  	s1, l1 := startServer("127.0.0.1:0")
   529  	client, err := DialContext(ctx, "ws://"+l1.Addr().String())
   530  	if err != nil {
   531  		t.Fatal("can't dial", err)
   532  	}
   533  
   534  	// Perform a call. This should work because the server is up.
   535  	var resp echoResult
   536  	if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
   537  		t.Fatal(err)
   538  	}
   539  
   540  	// Shut down the server and allow for some cool down time so we can listen on the same
   541  	// address again.
   542  	l1.Close()
   543  	s1.Stop()
   544  	time.Sleep(2 * time.Second)
   545  
   546  	// Try calling again. It shouldn't work.
   547  	if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil {
   548  		t.Error("successful call while the server is down")
   549  		t.Logf("resp: %#v", resp)
   550  	}
   551  
   552  	// Start it up again and call again. The connection should be reestablished.
   553  	// We spawn multiple calls here to check whether this hangs somehow.
   554  	s2, l2 := startServer(l1.Addr().String())
   555  	defer l2.Close()
   556  	defer s2.Stop()
   557  
   558  	start := make(chan struct{})
   559  	errors := make(chan error, 20)
   560  	for i := 0; i < cap(errors); i++ {
   561  		go func() {
   562  			<-start
   563  			var resp echoResult
   564  			errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
   565  		}()
   566  	}
   567  	close(start)
   568  	errcount := 0
   569  	for i := 0; i < cap(errors); i++ {
   570  		if err = <-errors; err != nil {
   571  			errcount++
   572  		}
   573  	}
   574  	t.Logf("%d errors, last error: %v", errcount, err)
   575  	if errcount > 1 {
   576  		t.Errorf("expected one error after disconnect, got %d", errcount)
   577  	}
   578  }
   579  
   580  func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
   581  	// Create the HTTP server.
   582  	var hs *httptest.Server
   583  	switch transport {
   584  	case "ws":
   585  		hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"}))
   586  	case "http":
   587  		hs = httptest.NewUnstartedServer(srv)
   588  	default:
   589  		panic("unknown HTTP transport: " + transport)
   590  	}
   591  	// Wrap the listener if required.
   592  	if fl != nil {
   593  		fl.Listener = hs.Listener
   594  		hs.Listener = fl
   595  	}
   596  	// Connect the client.
   597  	hs.Start()
   598  	client, err := Dial(transport + "://" + hs.Listener.Addr().String())
   599  	if err != nil {
   600  		panic(err)
   601  	}
   602  	return client, hs
   603  }
   604  
   605  func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) {
   606  	// Listen on a random endpoint.
   607  	endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63())
   608  	if runtime.GOOS == "windows" {
   609  		endpoint = `\\.\pipe\` + endpoint
   610  	} else {
   611  		endpoint = os.TempDir() + "/" + endpoint
   612  	}
   613  	l, err := ipcListen(endpoint)
   614  	if err != nil {
   615  		panic(err)
   616  	}
   617  	// Connect the listener to the server.
   618  	if fl != nil {
   619  		fl.Listener = l
   620  		l = fl
   621  	}
   622  	go srv.ServeListener(l)
   623  	// Connect the client.
   624  	client, err := Dial(endpoint)
   625  	if err != nil {
   626  		panic(err)
   627  	}
   628  	return client, l
   629  }
   630  
   631  // flakeyListener kills accepted connections after a random timeout.
   632  type flakeyListener struct {
   633  	net.Listener
   634  	maxKillTimeout time.Duration
   635  	maxAcceptDelay time.Duration
   636  }
   637  
   638  func (l *flakeyListener) Accept() (net.Conn, error) {
   639  	delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay)))
   640  	time.Sleep(delay)
   641  
   642  	c, err := l.Listener.Accept()
   643  	if err == nil {
   644  		timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout)))
   645  		time.AfterFunc(timeout, func() {
   646  			log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout))
   647  			c.Close()
   648  		})
   649  	}
   650  	return c, err
   651  }