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