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