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