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