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