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