github.com/status-im/status-go@v1.1.0/rpc/chain/client.go (about) 1 package chain 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math/big" 8 "strings" 9 "sync/atomic" 10 "time" 11 12 "github.com/ethereum/go-ethereum" 13 "github.com/ethereum/go-ethereum/accounts/abi/bind" 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethereum/go-ethereum/core" 16 "github.com/ethereum/go-ethereum/core/types" 17 "github.com/ethereum/go-ethereum/core/vm" 18 "github.com/ethereum/go-ethereum/ethclient" 19 "github.com/ethereum/go-ethereum/log" 20 "github.com/ethereum/go-ethereum/rpc" 21 "github.com/status-im/status-go/circuitbreaker" 22 "github.com/status-im/status-go/services/rpcstats" 23 "github.com/status-im/status-go/services/wallet/connection" 24 ) 25 26 type BatchCallClient interface { 27 BatchCallContext(ctx context.Context, b []rpc.BatchElem) error 28 } 29 30 type ChainInterface interface { 31 BatchCallClient 32 HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) 33 BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) 34 BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) 35 NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) 36 FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) 37 BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) 38 HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) 39 CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error) 40 GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error) 41 NetworkID() uint64 42 ToBigInt() *big.Int 43 CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) 44 CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) 45 CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error 46 TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) 47 TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) 48 BlockNumber(ctx context.Context) (uint64, error) 49 } 50 51 type ClientInterface interface { 52 ChainInterface 53 GetWalletNotifier() func(chainId uint64, message string) 54 SetWalletNotifier(notifier func(chainId uint64, message string)) 55 connection.Connectable 56 bind.ContractCaller 57 bind.ContractTransactor 58 bind.ContractFilterer 59 GetLimiter() RequestLimiter 60 SetLimiter(RequestLimiter) 61 } 62 63 type Tagger interface { 64 Tag() string 65 SetTag(tag string) 66 GroupTag() string 67 SetGroupTag(tag string) 68 DeepCopyTag() Tagger 69 } 70 71 func DeepCopyTagger(t Tagger) Tagger { 72 return t.DeepCopyTag() 73 } 74 75 type HealthMonitor interface { 76 GetCircuitBreaker() *circuitbreaker.CircuitBreaker 77 SetCircuitBreaker(cb *circuitbreaker.CircuitBreaker) 78 } 79 80 type Copyable interface { 81 Copy() interface{} 82 } 83 84 // Shallow copy of the client with a deep copy of tag and group tag 85 // To avoid passing tags as parameter to every chain call, it is sufficient for now 86 // to set the tag and group tag once on the client 87 func ClientWithTag(chainClient ClientInterface, tag, groupTag string) ClientInterface { 88 newClient := chainClient 89 if tagIface, ok := chainClient.(Tagger); ok { 90 tagIface = DeepCopyTagger(tagIface) 91 tagIface.SetTag(tag) 92 tagIface.SetGroupTag(groupTag) 93 newClient = tagIface.(ClientInterface) 94 } 95 96 return newClient 97 } 98 99 type EthClient struct { 100 ethClient *ethclient.Client 101 limiter *RPCRpsLimiter 102 rpcClient *rpc.Client 103 name string 104 } 105 106 func NewEthClient(ethClient *ethclient.Client, limiter *RPCRpsLimiter, rpcClient *rpc.Client, name string) *EthClient { 107 return &EthClient{ 108 ethClient: ethClient, 109 limiter: limiter, 110 rpcClient: rpcClient, 111 name: name, 112 } 113 } 114 115 type ClientWithFallback struct { 116 ChainID uint64 117 ethClients []*EthClient 118 commonLimiter RequestLimiter 119 circuitbreaker *circuitbreaker.CircuitBreaker 120 121 WalletNotifier func(chainId uint64, message string) 122 123 isConnected *atomic.Bool 124 LastCheckedAt int64 125 126 tag string // tag for the limiter 127 groupTag string // tag for the limiter group 128 } 129 130 func (c *ClientWithFallback) Copy() interface{} { 131 return &ClientWithFallback{ 132 ChainID: c.ChainID, 133 ethClients: c.ethClients, 134 commonLimiter: c.commonLimiter, 135 circuitbreaker: c.circuitbreaker, 136 WalletNotifier: c.WalletNotifier, 137 isConnected: c.isConnected, 138 LastCheckedAt: c.LastCheckedAt, 139 tag: c.tag, 140 groupTag: c.groupTag, 141 } 142 } 143 144 // Don't mark connection as failed if we get one of these errors 145 var propagateErrors = []error{ 146 vm.ErrOutOfGas, 147 vm.ErrCodeStoreOutOfGas, 148 vm.ErrDepth, 149 vm.ErrInsufficientBalance, 150 vm.ErrContractAddressCollision, 151 vm.ErrExecutionReverted, 152 vm.ErrMaxCodeSizeExceeded, 153 vm.ErrInvalidJump, 154 vm.ErrWriteProtection, 155 vm.ErrReturnDataOutOfBounds, 156 vm.ErrGasUintOverflow, 157 vm.ErrInvalidCode, 158 vm.ErrNonceUintOverflow, 159 160 // Used by balance history to check state 161 bind.ErrNoCode, 162 } 163 164 func NewSimpleClient(ethClient EthClient, chainID uint64) *ClientWithFallback { 165 cbConfig := circuitbreaker.Config{ 166 Timeout: 20000, 167 MaxConcurrentRequests: 100, 168 SleepWindow: 300000, 169 ErrorPercentThreshold: 25, 170 } 171 172 isConnected := &atomic.Bool{} 173 isConnected.Store(true) 174 return &ClientWithFallback{ 175 ChainID: chainID, 176 ethClients: []*EthClient{ðClient}, 177 isConnected: isConnected, 178 LastCheckedAt: time.Now().Unix(), 179 circuitbreaker: circuitbreaker.NewCircuitBreaker(cbConfig), 180 } 181 } 182 183 func NewClient(ethClients []*EthClient, chainID uint64) *ClientWithFallback { 184 cbConfig := circuitbreaker.Config{ 185 Timeout: 20000, 186 MaxConcurrentRequests: 100, 187 SleepWindow: 300000, 188 ErrorPercentThreshold: 25, 189 } 190 191 isConnected := &atomic.Bool{} 192 isConnected.Store(true) 193 194 return &ClientWithFallback{ 195 ChainID: chainID, 196 ethClients: ethClients, 197 isConnected: isConnected, 198 LastCheckedAt: time.Now().Unix(), 199 circuitbreaker: circuitbreaker.NewCircuitBreaker(cbConfig), 200 } 201 } 202 203 func (c *ClientWithFallback) Close() { 204 for _, client := range c.ethClients { 205 client.ethClient.Close() 206 } 207 } 208 209 // Not found should not be cancelling the requests, as that's returned 210 // when we are hitting a non archival node for example, it should continue the 211 // chain as the next provider might have archival support. 212 func isNotFoundError(err error) bool { 213 return strings.Contains(err.Error(), ethereum.NotFound.Error()) 214 } 215 216 func isVMError(err error) bool { 217 if strings.Contains(err.Error(), core.ErrInsufficientFunds.Error()) { 218 return true 219 } 220 for _, vmError := range propagateErrors { 221 if strings.Contains(err.Error(), vmError.Error()) { 222 return true 223 } 224 } 225 return false 226 } 227 228 func isRPSLimitError(err error) bool { 229 return strings.Contains(err.Error(), "backoff_seconds") || 230 strings.Contains(err.Error(), "has exceeded its throughput limit") || 231 strings.Contains(err.Error(), "request rate exceeded") 232 } 233 234 func (c *ClientWithFallback) SetIsConnected(value bool) { 235 c.LastCheckedAt = time.Now().Unix() 236 if !value { 237 if c.isConnected.Load() { 238 if c.WalletNotifier != nil { 239 c.WalletNotifier(c.ChainID, "down") 240 } 241 c.isConnected.Store(false) 242 } 243 244 } else { 245 if !c.isConnected.Load() { 246 c.isConnected.Store(true) 247 if c.WalletNotifier != nil { 248 c.WalletNotifier(c.ChainID, "up") 249 } 250 } 251 } 252 } 253 254 func (c *ClientWithFallback) IsConnected() bool { 255 return c.isConnected.Load() 256 } 257 258 func (c *ClientWithFallback) makeCall(ctx context.Context, ethClients []*EthClient, f func(client *EthClient) (interface{}, error)) (interface{}, error) { 259 if c.commonLimiter != nil { 260 if allow, err := c.commonLimiter.Allow(c.tag); !allow { 261 return nil, fmt.Errorf("tag=%s, %w", c.tag, err) 262 } 263 264 if allow, err := c.commonLimiter.Allow(c.groupTag); !allow { 265 return nil, fmt.Errorf("groupTag=%s, %w", c.groupTag, err) 266 } 267 } 268 269 c.LastCheckedAt = time.Now().Unix() 270 271 cmd := circuitbreaker.NewCommand(ctx, nil) 272 for _, provider := range ethClients { 273 provider := provider 274 cmd.Add(circuitbreaker.NewFunctor(func() ([]interface{}, error) { 275 err := provider.limiter.WaitForRequestsAvailability(1) 276 if err != nil { 277 return nil, err 278 } 279 280 res, err := f(provider) 281 if err != nil { 282 if isRPSLimitError(err) { 283 provider.limiter.ReduceLimit() 284 285 err = provider.limiter.WaitForRequestsAvailability(1) 286 if err != nil { 287 return nil, err 288 } 289 290 res, err = f(provider) 291 if err == nil { 292 return []interface{}{res}, err 293 } 294 } 295 296 if isVMError(err) || errors.Is(err, context.Canceled) { 297 cmd.Cancel() 298 } 299 300 return nil, err 301 } 302 return []interface{}{res}, err 303 }, provider.name)) 304 } 305 306 result := c.circuitbreaker.Execute(cmd) 307 if result.Error() != nil { 308 return nil, result.Error() 309 } 310 311 return result.Result()[0], nil 312 } 313 314 func (c *ClientWithFallback) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { 315 rpcstats.CountCallWithTag("eth_BlockByHash", c.tag) 316 317 res, err := c.makeCall( 318 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 319 return client.ethClient.BlockByHash(ctx, hash) 320 }, 321 ) 322 323 c.toggleConnectionState(err) 324 325 if err != nil { 326 return nil, err 327 } 328 329 return res.(*types.Block), nil 330 } 331 332 func (c *ClientWithFallback) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { 333 rpcstats.CountCallWithTag("eth_BlockByNumber", c.tag) 334 res, err := c.makeCall( 335 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 336 return client.ethClient.BlockByNumber(ctx, number) 337 }, 338 ) 339 340 c.toggleConnectionState(err) 341 342 if err != nil { 343 return nil, err 344 } 345 346 return res.(*types.Block), nil 347 } 348 349 func (c *ClientWithFallback) BlockNumber(ctx context.Context) (uint64, error) { 350 rpcstats.CountCallWithTag("eth_BlockNumber", c.tag) 351 352 res, err := c.makeCall( 353 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 354 return client.ethClient.BlockNumber(ctx) 355 }, 356 ) 357 358 c.toggleConnectionState(err) 359 360 if err != nil { 361 return 0, err 362 } 363 364 return res.(uint64), nil 365 } 366 367 func (c *ClientWithFallback) PeerCount(ctx context.Context) (uint64, error) { 368 rpcstats.CountCallWithTag("eth_PeerCount", c.tag) 369 370 res, err := c.makeCall( 371 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 372 return client.ethClient.PeerCount(ctx) 373 }, 374 ) 375 376 c.toggleConnectionState(err) 377 378 if err != nil { 379 return 0, err 380 } 381 382 return res.(uint64), nil 383 } 384 385 func (c *ClientWithFallback) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { 386 rpcstats.CountCallWithTag("eth_HeaderByHash", c.tag) 387 res, err := c.makeCall( 388 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 389 return client.ethClient.HeaderByHash(ctx, hash) 390 }, 391 ) 392 393 c.toggleConnectionState(err) 394 395 if err != nil { 396 return nil, err 397 } 398 399 return res.(*types.Header), nil 400 } 401 402 func (c *ClientWithFallback) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { 403 rpcstats.CountCallWithTag("eth_HeaderByNumber", c.tag) 404 res, err := c.makeCall( 405 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 406 return client.ethClient.HeaderByNumber(ctx, number) 407 }, 408 ) 409 410 c.toggleConnectionState(err) 411 412 if err != nil { 413 return nil, err 414 } 415 416 return res.(*types.Header), nil 417 } 418 419 func (c *ClientWithFallback) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) { 420 rpcstats.CountCallWithTag("eth_TransactionByHash", c.tag) 421 422 res, err := c.makeCall( 423 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 424 tx, isPending, err := client.ethClient.TransactionByHash(ctx, hash) 425 return []any{tx, isPending}, err 426 }, 427 ) 428 429 c.toggleConnectionState(err) 430 431 if err != nil { 432 return nil, false, err 433 } 434 435 resArr := res.([]any) 436 return resArr[0].(*types.Transaction), resArr[1].(bool), nil 437 } 438 439 func (c *ClientWithFallback) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { 440 rpcstats.CountCall("eth_TransactionSender") 441 442 res, err := c.makeCall( 443 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 444 return client.ethClient.TransactionSender(ctx, tx, block, index) 445 }, 446 ) 447 448 c.toggleConnectionState(err) 449 450 return res.(common.Address), err 451 } 452 453 func (c *ClientWithFallback) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { 454 rpcstats.CountCall("eth_TransactionCount") 455 456 res, err := c.makeCall( 457 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 458 return client.ethClient.TransactionCount(ctx, blockHash) 459 }, 460 ) 461 462 c.toggleConnectionState(err) 463 464 if err != nil { 465 return 0, err 466 } 467 468 return res.(uint), nil 469 } 470 471 func (c *ClientWithFallback) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { 472 rpcstats.CountCall("eth_TransactionInBlock") 473 474 res, err := c.makeCall( 475 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 476 return client.ethClient.TransactionInBlock(ctx, blockHash, index) 477 }, 478 ) 479 480 c.toggleConnectionState(err) 481 482 if err != nil { 483 return nil, err 484 } 485 486 return res.(*types.Transaction), nil 487 } 488 489 func (c *ClientWithFallback) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { 490 rpcstats.CountCall("eth_TransactionReceipt") 491 492 res, err := c.makeCall( 493 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 494 return client.ethClient.TransactionReceipt(ctx, txHash) 495 }, 496 ) 497 498 c.toggleConnectionState(err) 499 500 if err != nil { 501 return nil, err 502 } 503 504 return res.(*types.Receipt), nil 505 } 506 507 func (c *ClientWithFallback) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { 508 rpcstats.CountCall("eth_SyncProgress") 509 510 res, err := c.makeCall( 511 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 512 return client.ethClient.SyncProgress(ctx) 513 }, 514 ) 515 516 c.toggleConnectionState(err) 517 518 if err != nil { 519 return nil, err 520 } 521 522 return res.(*ethereum.SyncProgress), nil 523 } 524 525 func (c *ClientWithFallback) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { 526 rpcstats.CountCall("eth_SubscribeNewHead") 527 528 res, err := c.makeCall( 529 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 530 return client.ethClient.SubscribeNewHead(ctx, ch) 531 }, 532 ) 533 534 c.toggleConnectionState(err) 535 536 if err != nil { 537 return nil, err 538 } 539 540 return res.(ethereum.Subscription), nil 541 } 542 543 func (c *ClientWithFallback) NetworkID() uint64 { 544 return c.ChainID 545 } 546 547 func (c *ClientWithFallback) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { 548 rpcstats.CountCallWithTag("eth_BalanceAt", c.tag) 549 550 res, err := c.makeCall( 551 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 552 return client.ethClient.BalanceAt(ctx, account, blockNumber) 553 }, 554 ) 555 556 c.toggleConnectionState(err) 557 558 if err != nil { 559 return nil, err 560 } 561 562 return res.(*big.Int), nil 563 } 564 565 func (c *ClientWithFallback) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { 566 rpcstats.CountCall("eth_StorageAt") 567 568 res, err := c.makeCall( 569 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 570 return client.ethClient.StorageAt(ctx, account, key, blockNumber) 571 }, 572 ) 573 574 c.toggleConnectionState(err) 575 576 if err != nil { 577 return nil, err 578 } 579 580 return res.([]byte), nil 581 } 582 583 func (c *ClientWithFallback) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { 584 rpcstats.CountCall("eth_CodeAt") 585 586 res, err := c.makeCall( 587 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 588 return client.ethClient.CodeAt(ctx, account, blockNumber) 589 }, 590 ) 591 592 c.toggleConnectionState(err) 593 594 if err != nil { 595 return nil, err 596 } 597 598 return res.([]byte), nil 599 } 600 601 func (c *ClientWithFallback) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { 602 rpcstats.CountCallWithTag("eth_NonceAt", c.tag) 603 604 res, err := c.makeCall( 605 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 606 return client.ethClient.NonceAt(ctx, account, blockNumber) 607 }, 608 ) 609 610 c.toggleConnectionState(err) 611 612 if err != nil { 613 return 0, err 614 } 615 616 return res.(uint64), nil 617 } 618 619 func (c *ClientWithFallback) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { 620 rpcstats.CountCallWithTag("eth_FilterLogs", c.tag) 621 622 // Override providers name to use a separate circuit for this command as it more often fails due to rate limiting 623 ethClients := make([]*EthClient, len(c.ethClients)) 624 for i, client := range c.ethClients { 625 ethClients[i] = &EthClient{ 626 ethClient: client.ethClient, 627 limiter: client.limiter, 628 rpcClient: client.rpcClient, 629 name: client.name + "_FilterLogs", 630 } 631 } 632 633 res, err := c.makeCall( 634 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 635 return client.ethClient.FilterLogs(ctx, q) 636 }, 637 ) 638 639 // No connection state toggling here, as it often mail fail due to archive node rate limiting 640 // which does not impact other calls 641 642 if err != nil { 643 return nil, err 644 } 645 646 return res.([]types.Log), nil 647 } 648 649 func (c *ClientWithFallback) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { 650 rpcstats.CountCall("eth_SubscribeFilterLogs") 651 652 res, err := c.makeCall( 653 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 654 return client.ethClient.SubscribeFilterLogs(ctx, q, ch) 655 }, 656 ) 657 658 c.toggleConnectionState(err) 659 660 if err != nil { 661 return nil, err 662 } 663 664 return res.(ethereum.Subscription), nil 665 } 666 667 func (c *ClientWithFallback) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { 668 rpcstats.CountCall("eth_PendingBalanceAt") 669 670 res, err := c.makeCall( 671 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 672 return client.ethClient.PendingBalanceAt(ctx, account) 673 }, 674 ) 675 676 c.toggleConnectionState(err) 677 678 if err != nil { 679 return nil, err 680 } 681 682 return res.(*big.Int), nil 683 } 684 685 func (c *ClientWithFallback) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { 686 rpcstats.CountCall("eth_PendingStorageAt") 687 688 res, err := c.makeCall( 689 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 690 return client.ethClient.PendingStorageAt(ctx, account, key) 691 }, 692 ) 693 694 c.toggleConnectionState(err) 695 696 if err != nil { 697 return nil, err 698 } 699 700 return res.([]byte), nil 701 } 702 703 func (c *ClientWithFallback) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { 704 rpcstats.CountCall("eth_PendingCodeAt") 705 706 res, err := c.makeCall( 707 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 708 return client.ethClient.PendingCodeAt(ctx, account) 709 }, 710 ) 711 712 c.toggleConnectionState(err) 713 714 if err != nil { 715 return nil, err 716 } 717 718 return res.([]byte), nil 719 } 720 721 func (c *ClientWithFallback) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { 722 rpcstats.CountCall("eth_PendingNonceAt") 723 724 res, err := c.makeCall( 725 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 726 return client.ethClient.PendingNonceAt(ctx, account) 727 }, 728 ) 729 730 c.toggleConnectionState(err) 731 732 if err != nil { 733 return 0, err 734 } 735 736 return res.(uint64), nil 737 } 738 739 func (c *ClientWithFallback) PendingTransactionCount(ctx context.Context) (uint, error) { 740 rpcstats.CountCall("eth_PendingTransactionCount") 741 742 res, err := c.makeCall( 743 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 744 return client.ethClient.PendingTransactionCount(ctx) 745 }, 746 ) 747 748 c.toggleConnectionState(err) 749 750 if err != nil { 751 return 0, err 752 } 753 754 return res.(uint), nil 755 } 756 757 func (c *ClientWithFallback) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { 758 rpcstats.CountCall("eth_CallContract_" + msg.To.String()) 759 760 res, err := c.makeCall( 761 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 762 return client.ethClient.CallContract(ctx, msg, blockNumber) 763 }, 764 ) 765 766 c.toggleConnectionState(err) 767 768 if err != nil { 769 return nil, err 770 } 771 772 return res.([]byte), nil 773 } 774 775 func (c *ClientWithFallback) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { 776 rpcstats.CountCall("eth_CallContractAtHash") 777 778 res, err := c.makeCall( 779 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 780 return client.ethClient.CallContractAtHash(ctx, msg, blockHash) 781 }, 782 ) 783 784 c.toggleConnectionState(err) 785 786 if err != nil { 787 return nil, err 788 } 789 790 return res.([]byte), nil 791 } 792 793 func (c *ClientWithFallback) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { 794 rpcstats.CountCall("eth_PendingCallContract") 795 796 res, err := c.makeCall( 797 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 798 return client.ethClient.PendingCallContract(ctx, msg) 799 }, 800 ) 801 802 c.toggleConnectionState(err) 803 804 if err != nil { 805 return nil, err 806 } 807 808 return res.([]byte), nil 809 } 810 811 func (c *ClientWithFallback) SuggestGasPrice(ctx context.Context) (*big.Int, error) { 812 rpcstats.CountCall("eth_SuggestGasPrice") 813 814 res, err := c.makeCall( 815 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 816 return client.ethClient.SuggestGasPrice(ctx) 817 }, 818 ) 819 820 c.toggleConnectionState(err) 821 822 if err != nil { 823 return nil, err 824 } 825 826 return res.(*big.Int), nil 827 } 828 829 func (c *ClientWithFallback) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { 830 rpcstats.CountCall("eth_SuggestGasTipCap") 831 832 res, err := c.makeCall( 833 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 834 return client.ethClient.SuggestGasTipCap(ctx) 835 }, 836 ) 837 838 c.toggleConnectionState(err) 839 840 if err != nil { 841 return nil, err 842 } 843 844 return res.(*big.Int), nil 845 } 846 847 func (c *ClientWithFallback) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { 848 rpcstats.CountCall("eth_FeeHistory") 849 850 res, err := c.makeCall( 851 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 852 return client.ethClient.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) 853 }, 854 ) 855 856 c.toggleConnectionState(err) 857 858 if err != nil { 859 return nil, err 860 } 861 862 return res.(*ethereum.FeeHistory), nil 863 } 864 865 func (c *ClientWithFallback) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { 866 rpcstats.CountCall("eth_EstimateGas") 867 868 res, err := c.makeCall( 869 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 870 return client.ethClient.EstimateGas(ctx, msg) 871 }, 872 ) 873 874 c.toggleConnectionState(err) 875 876 if err != nil { 877 return 0, err 878 } 879 880 return res.(uint64), nil 881 } 882 883 func (c *ClientWithFallback) SendTransaction(ctx context.Context, tx *types.Transaction) error { 884 rpcstats.CountCall("eth_SendTransaction") 885 886 _, err := c.makeCall( 887 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 888 return nil, client.ethClient.SendTransaction(ctx, tx) 889 }, 890 ) 891 892 c.toggleConnectionState(err) 893 894 return err 895 } 896 897 func (c *ClientWithFallback) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { 898 rpcstats.CountCall("eth_CallContext") 899 900 _, err := c.makeCall( 901 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 902 return nil, client.rpcClient.CallContext(ctx, result, method, args...) 903 }, 904 ) 905 906 c.toggleConnectionState(err) 907 908 return err 909 } 910 911 func (c *ClientWithFallback) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { 912 rpcstats.CountCall("eth_BatchCallContext") 913 914 _, err := c.makeCall( 915 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 916 return nil, client.rpcClient.BatchCallContext(ctx, b) 917 }, 918 ) 919 920 c.toggleConnectionState(err) 921 922 return err 923 } 924 925 func (c *ClientWithFallback) ToBigInt() *big.Int { 926 return big.NewInt(int64(c.ChainID)) 927 } 928 929 func (c *ClientWithFallback) GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error) { 930 rpcstats.CountCall("eth_GetBaseFeeFromBlock") 931 932 feeHistory, err := c.FeeHistory(ctx, 1, blockNumber, nil) 933 934 if err != nil { 935 if err.Error() == "the method eth_feeHistory does not exist/is not available" { 936 return "", nil 937 } 938 return "", err 939 } 940 941 var baseGasFee string = "" 942 if len(feeHistory.BaseFee) > 0 { 943 baseGasFee = feeHistory.BaseFee[0].String() 944 } 945 946 return baseGasFee, err 947 } 948 949 // go-ethereum's `Transaction` items drop the blkHash obtained during the RPC call. 950 // This function preserves the additional data. This is the cheapest way to obtain 951 // the block hash for a given block number. 952 func (c *ClientWithFallback) CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error) { 953 rpcstats.CountCallWithTag("eth_FullTransactionByBlockNumberAndIndex", c.tag) 954 955 res, err := c.makeCall( 956 ctx, c.ethClients, func(client *EthClient) (interface{}, error) { 957 return callBlockHashByTransaction(ctx, client.rpcClient, blockNumber, index) 958 }, 959 ) 960 961 c.toggleConnectionState(err) 962 963 if err != nil { 964 return common.HexToHash(""), err 965 } 966 967 return res.(common.Hash), nil 968 } 969 970 func (c *ClientWithFallback) GetWalletNotifier() func(chainId uint64, message string) { 971 return c.WalletNotifier 972 } 973 974 func (c *ClientWithFallback) SetWalletNotifier(notifier func(chainId uint64, message string)) { 975 c.WalletNotifier = notifier 976 } 977 978 func (c *ClientWithFallback) toggleConnectionState(err error) { 979 connected := true 980 if err != nil { 981 if !isNotFoundError(err) && !isVMError(err) && !errors.Is(err, ErrRequestsOverLimit) && !errors.Is(err, context.Canceled) { 982 log.Warn("Error not in chain call", "error", err, "chain", c.ChainID) 983 connected = false 984 } else { 985 log.Warn("Error in chain call", "error", err) 986 } 987 } 988 c.SetIsConnected(connected) 989 } 990 991 func (c *ClientWithFallback) Tag() string { 992 return c.tag 993 } 994 995 func (c *ClientWithFallback) SetTag(tag string) { 996 c.tag = tag 997 } 998 999 func (c *ClientWithFallback) GroupTag() string { 1000 return c.groupTag 1001 } 1002 1003 func (c *ClientWithFallback) SetGroupTag(tag string) { 1004 c.groupTag = tag 1005 } 1006 1007 func (c *ClientWithFallback) DeepCopyTag() Tagger { 1008 copy := *c 1009 return © 1010 } 1011 1012 func (c *ClientWithFallback) GetLimiter() RequestLimiter { 1013 return c.commonLimiter 1014 } 1015 1016 func (c *ClientWithFallback) SetLimiter(limiter RequestLimiter) { 1017 c.commonLimiter = limiter 1018 } 1019 1020 func (c *ClientWithFallback) GetCircuitBreaker() *circuitbreaker.CircuitBreaker { 1021 return c.circuitbreaker 1022 } 1023 1024 func (c *ClientWithFallback) SetCircuitBreaker(cb *circuitbreaker.CircuitBreaker) { 1025 c.circuitbreaker = cb 1026 }