diff --git a/rpc/chain/client.go b/rpc/chain/client.go index abb3e7653..26b757ccc 100644 --- a/rpc/chain/client.go +++ b/rpc/chain/client.go @@ -68,6 +68,8 @@ type ClientWithFallback struct { IsConnected bool IsConnectedLock sync.RWMutex LastCheckedAt int64 + + circuitBreakerCmdName string } // Don't mark connection as failed if we get one of these errors @@ -94,13 +96,13 @@ var propagateErrors = []error{ } type CommandResult struct { - res1 any - res2 any - vmError error + res []any + err error } func NewSimpleClient(mainLimiter *RPCLimiter, main *rpc.Client, chainID uint64) *ClientWithFallback { - hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{ + circuitBreakerCmdName := fmt.Sprintf("ethClient_%d", chainID) + hystrix.ConfigureCommand(circuitBreakerCmdName, hystrix.CommandConfig{ Timeout: 10000, MaxConcurrentRequests: 100, SleepWindow: 300000, @@ -108,20 +110,22 @@ func NewSimpleClient(mainLimiter *RPCLimiter, main *rpc.Client, chainID uint64) }) return &ClientWithFallback{ - ChainID: chainID, - main: ethclient.NewClient(main), - fallback: nil, - mainLimiter: mainLimiter, - fallbackLimiter: nil, - mainRPC: main, - fallbackRPC: nil, - IsConnected: true, - LastCheckedAt: time.Now().Unix(), + ChainID: chainID, + main: ethclient.NewClient(main), + fallback: nil, + mainLimiter: mainLimiter, + fallbackLimiter: nil, + mainRPC: main, + fallbackRPC: nil, + IsConnected: true, + LastCheckedAt: time.Now().Unix(), + circuitBreakerCmdName: circuitBreakerCmdName, } } func NewClient(mainLimiter *RPCLimiter, main *rpc.Client, fallbackLimiter *RPCLimiter, fallback *rpc.Client, chainID uint64) *ClientWithFallback { - hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{ + circuitBreakerCmdName := fmt.Sprintf("ethClient_%d", chainID) + hystrix.ConfigureCommand(circuitBreakerCmdName, hystrix.CommandConfig{ Timeout: 20000, MaxConcurrentRequests: 100, SleepWindow: 300000, @@ -133,15 +137,16 @@ func NewClient(mainLimiter *RPCLimiter, main *rpc.Client, fallbackLimiter *RPCLi fallbackEthClient = ethclient.NewClient(fallback) } return &ClientWithFallback{ - ChainID: chainID, - main: ethclient.NewClient(main), - fallback: fallbackEthClient, - mainLimiter: mainLimiter, - fallbackLimiter: fallbackLimiter, - mainRPC: main, - fallbackRPC: fallback, - IsConnected: true, - LastCheckedAt: time.Now().Unix(), + ChainID: chainID, + main: ethclient.NewClient(main), + fallback: fallbackEthClient, + mainLimiter: mainLimiter, + fallbackLimiter: fallbackLimiter, + mainRPC: main, + fallbackRPC: fallback, + IsConnected: true, + LastCheckedAt: time.Now().Unix(), + circuitBreakerCmdName: circuitBreakerCmdName, } } @@ -196,70 +201,10 @@ func (c *ClientWithFallback) GetIsConnected() bool { return c.IsConnected } -func (c *ClientWithFallback) makeCallNoReturn(ctx context.Context, main func() error, fallback func() error) error { +func (c *ClientWithFallback) makeCall(ctx context.Context, main func() ([]any, error), fallback func() ([]any, error)) ([]any, error) { resultChan := make(chan CommandResult, 1) c.LastCheckedAt = time.Now().Unix() - errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error { - err := c.mainLimiter.WaitForRequestsAvailability(1) - if err != nil { - return err - } - - err = main() - if err != nil { - if isRPSLimitError(err) { - c.mainLimiter.ReduceLimit() - } - if isVMError(err) { - resultChan <- CommandResult{vmError: err} - return nil - } - return err - } - c.SetIsConnected(true) - resultChan <- CommandResult{} - return nil - }, func(err error) error { - if c.fallback == nil { - c.SetIsConnected(false) - return err - } - - err = c.fallbackLimiter.WaitForRequestsAvailability(1) - if err != nil { - return err - } - - err = fallback() - if err != nil { - if isRPSLimitError(err) { - c.fallbackLimiter.ReduceLimit() - } - if isVMError(err) { - resultChan <- CommandResult{vmError: err} - return nil - } - c.SetIsConnected(false) - return err - } - resultChan <- CommandResult{} - return nil - }) - - select { - case result := <-resultChan: - if result.vmError != nil { - return result.vmError - } - return nil - case err := <-errChan: - return err - } -} - -func (c *ClientWithFallback) makeCallSingleReturn(ctx context.Context, main func() (any, error), fallback func() (any, error), toggleIsConnected bool) (any, error) { - resultChan := make(chan CommandResult, 1) - errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error { + errChan := hystrix.Go(c.circuitBreakerCmdName, func() error { err := c.mainLimiter.WaitForRequestsAvailability(1) if err != nil { return err @@ -269,23 +214,16 @@ func (c *ClientWithFallback) makeCallSingleReturn(ctx context.Context, main func if err != nil { if isRPSLimitError(err) { c.mainLimiter.ReduceLimit() - } - if isVMError(err) { - resultChan <- CommandResult{vmError: err} + } else if isVMError(err) { + resultChan <- CommandResult{err: err} return nil } return err } - if toggleIsConnected { - c.SetIsConnected(true) - } - resultChan <- CommandResult{res1: res} + resultChan <- CommandResult{res: res} return nil }, func(err error) error { if c.fallback == nil { - if toggleIsConnected { - c.SetIsConnected(false) - } return err } @@ -298,307 +236,255 @@ func (c *ClientWithFallback) makeCallSingleReturn(ctx context.Context, main func if err != nil { if isRPSLimitError(err) { c.fallbackLimiter.ReduceLimit() - } - if isVMError(err) { - resultChan <- CommandResult{vmError: err} + } else if isVMError(err) { + resultChan <- CommandResult{err: err} return nil } - if toggleIsConnected { - c.SetIsConnected(false) - } return err } - if toggleIsConnected { - c.SetIsConnected(true) - } - resultChan <- CommandResult{res1: res} + resultChan <- CommandResult{res: res} return nil }) select { case result := <-resultChan: - if result.vmError != nil { - return nil, result.vmError + if result.err != nil { + return nil, result.err } - return result.res1, nil + return result.res, nil case err := <-errChan: return nil, err } } -func (c *ClientWithFallback) makeCallDoubleReturn(ctx context.Context, main func() (any, any, error), fallback func() (any, any, error)) (any, any, error) { - resultChan := make(chan CommandResult, 1) - c.LastCheckedAt = time.Now().Unix() - errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error { - err := c.mainLimiter.WaitForRequestsAvailability(1) - if err != nil { - return err - } - - a, b, err := main() - if err != nil { - if isRPSLimitError(err) { - c.mainLimiter.ReduceLimit() - } - if isVMError(err) { - resultChan <- CommandResult{vmError: err} - return nil - } - return err - } - c.SetIsConnected(true) - resultChan <- CommandResult{res1: a, res2: b} - return nil - }, func(err error) error { - if c.fallback == nil { - c.SetIsConnected(false) - return err - } - - err = c.fallbackLimiter.WaitForRequestsAvailability(1) - if err != nil { - return err - } - - a, b, err := fallback() - if err != nil { - if isRPSLimitError(err) { - c.fallbackLimiter.ReduceLimit() - } - if isVMError(err) { - resultChan <- CommandResult{vmError: err} - return nil - } - c.SetIsConnected(false) - return err - } - c.SetIsConnected(true) - resultChan <- CommandResult{res1: a, res2: b} - return nil - }) - - select { - case result := <-resultChan: - if result.vmError != nil { - return nil, nil, result.vmError - } - return result.res1, result.res2, nil - case err := <-errChan: - return nil, nil, err - } -} - func (c *ClientWithFallback) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { rpcstats.CountCall("eth_BlockByHash") - block, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.BlockByHash(ctx, hash) }, - func() (any, error) { return c.fallback.BlockByHash(ctx, hash) }, - true, + func() ([]any, error) { a, err := c.main.BlockByHash(ctx, hash); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.BlockByHash(ctx, hash); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return block.(*types.Block), nil + return res[0].(*types.Block), nil } func (c *ClientWithFallback) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { rpcstats.CountCall("eth_BlockByNumber") - block, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.BlockByNumber(ctx, number) }, - func() (any, error) { return c.fallback.BlockByNumber(ctx, number) }, - true, + func() ([]any, error) { a, err := c.main.BlockByNumber(ctx, number); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.BlockByNumber(ctx, number); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return block.(*types.Block), nil + return res[0].(*types.Block), nil } func (c *ClientWithFallback) BlockNumber(ctx context.Context) (uint64, error) { rpcstats.CountCall("eth_BlockNumber") - number, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.BlockNumber(ctx) }, - func() (any, error) { return c.fallback.BlockNumber(ctx) }, - true, + func() ([]any, error) { a, err := c.main.BlockNumber(ctx); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.BlockNumber(ctx); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return 0, err } - return number.(uint64), nil + return res[0].(uint64), nil } func (c *ClientWithFallback) PeerCount(ctx context.Context) (uint64, error) { rpcstats.CountCall("eth_PeerCount") - peerCount, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.PeerCount(ctx) }, - func() (any, error) { return c.fallback.PeerCount(ctx) }, - true, + func() ([]any, error) { a, err := c.main.PeerCount(ctx); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.PeerCount(ctx); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return 0, err } - return peerCount.(uint64), nil + return res[0].(uint64), nil } func (c *ClientWithFallback) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { rpcstats.CountCall("eth_HeaderByHash") - header, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.HeaderByHash(ctx, hash) }, - func() (any, error) { return c.fallback.HeaderByHash(ctx, hash) }, - false, + func() ([]any, error) { a, err := c.main.HeaderByHash(ctx, hash); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.HeaderByHash(ctx, hash); return []any{a}, err }, ) if err != nil { return nil, err } - return header.(*types.Header), nil + return res[0].(*types.Header), nil } func (c *ClientWithFallback) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { rpcstats.CountCall("eth_HeaderByNumber") - header, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.HeaderByNumber(ctx, number) }, - func() (any, error) { return c.fallback.HeaderByNumber(ctx, number) }, - false, + func() ([]any, error) { a, err := c.main.HeaderByNumber(ctx, number); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.HeaderByNumber(ctx, number); return []any{a}, err }, ) if err != nil { return nil, err } - return header.(*types.Header), nil + return res[0].(*types.Header), nil } func (c *ClientWithFallback) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) { rpcstats.CountCall("eth_TransactionByHash") - tx, isPending, err := c.makeCallDoubleReturn( + res, err := c.makeCall( ctx, - func() (any, any, error) { return c.main.TransactionByHash(ctx, hash) }, - func() (any, any, error) { return c.fallback.TransactionByHash(ctx, hash) }, + func() ([]any, error) { a, b, err := c.main.TransactionByHash(ctx, hash); return []any{a, b}, err }, + func() ([]any, error) { a, b, err := c.fallback.TransactionByHash(ctx, hash); return []any{a, b}, err }, ) if err != nil { return nil, false, err } - return tx.(*types.Transaction), isPending.(bool), nil + return res[0].(*types.Transaction), res[1].(bool), nil } func (c *ClientWithFallback) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { rpcstats.CountCall("eth_TransactionSender") - address, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.TransactionSender(ctx, tx, block, index) }, - func() (any, error) { return c.fallback.TransactionSender(ctx, tx, block, index) }, - true, + func() ([]any, error) { a, err := c.main.TransactionSender(ctx, tx, block, index); return []any{a}, err }, + func() ([]any, error) { + a, err := c.fallback.TransactionSender(ctx, tx, block, index) + return []any{a}, err + }, ) - return address.(common.Address), err + c.toggleConnectionState(err) + + return res[0].(common.Address), err } func (c *ClientWithFallback) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { rpcstats.CountCall("eth_TransactionCount") - count, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.TransactionCount(ctx, blockHash) }, - func() (any, error) { return c.fallback.TransactionCount(ctx, blockHash) }, - true, + func() ([]any, error) { a, err := c.main.TransactionCount(ctx, blockHash); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.TransactionCount(ctx, blockHash); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return 0, err } - return count.(uint), nil + return res[0].(uint), nil } func (c *ClientWithFallback) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { rpcstats.CountCall("eth_TransactionInBlock") - transactions, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.TransactionInBlock(ctx, blockHash, index) }, - func() (any, error) { return c.fallback.TransactionInBlock(ctx, blockHash, index) }, - true, + func() ([]any, error) { + a, err := c.main.TransactionInBlock(ctx, blockHash, index) + return []any{a}, err + }, + func() ([]any, error) { + a, err := c.fallback.TransactionInBlock(ctx, blockHash, index) + return []any{a}, err + }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return transactions.(*types.Transaction), nil + return res[0].(*types.Transaction), nil } func (c *ClientWithFallback) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { rpcstats.CountCall("eth_TransactionReceipt") - receipt, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.TransactionReceipt(ctx, txHash) }, - func() (any, error) { return c.fallback.TransactionReceipt(ctx, txHash) }, - true, + func() ([]any, error) { a, err := c.main.TransactionReceipt(ctx, txHash); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.TransactionReceipt(ctx, txHash); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return receipt.(*types.Receipt), nil + return res[0].(*types.Receipt), nil } func (c *ClientWithFallback) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { rpcstats.CountCall("eth_SyncProgress") - progress, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.SyncProgress(ctx) }, - func() (any, error) { return c.fallback.SyncProgress(ctx) }, - true, + func() ([]any, error) { a, err := c.main.SyncProgress(ctx); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.SyncProgress(ctx); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return progress.(*ethereum.SyncProgress), nil + return res[0].(*ethereum.SyncProgress), nil } func (c *ClientWithFallback) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { rpcstats.CountCall("eth_SubscribeNewHead") - sub, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.SubscribeNewHead(ctx, ch) }, - func() (any, error) { return c.fallback.SubscribeNewHead(ctx, ch) }, - true, + func() ([]any, error) { a, err := c.main.SubscribeNewHead(ctx, ch); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.SubscribeNewHead(ctx, ch); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return sub.(ethereum.Subscription), nil + return res[0].(ethereum.Subscription), nil } func (c *ClientWithFallback) NetworkID() uint64 { @@ -608,337 +494,373 @@ func (c *ClientWithFallback) NetworkID() uint64 { func (c *ClientWithFallback) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { rpcstats.CountCall("eth_BalanceAt") - balance, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.BalanceAt(ctx, account, blockNumber) }, - func() (any, error) { return c.fallback.BalanceAt(ctx, account, blockNumber) }, - true, + func() ([]any, error) { a, err := c.main.BalanceAt(ctx, account, blockNumber); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.BalanceAt(ctx, account, blockNumber); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return balance.(*big.Int), nil + return res[0].(*big.Int), nil } func (c *ClientWithFallback) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { rpcstats.CountCall("eth_StorageAt") - storage, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.StorageAt(ctx, account, key, blockNumber) }, - func() (any, error) { return c.fallback.StorageAt(ctx, account, key, blockNumber) }, - true, + func() ([]any, error) { + a, err := c.main.StorageAt(ctx, account, key, blockNumber) + return []any{a}, err + }, + func() ([]any, error) { + a, err := c.fallback.StorageAt(ctx, account, key, blockNumber) + return []any{a}, err + }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return storage.([]byte), nil + return res[0].([]byte), nil } func (c *ClientWithFallback) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { rpcstats.CountCall("eth_CodeAt") - code, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.CodeAt(ctx, account, blockNumber) }, - func() (any, error) { return c.fallback.CodeAt(ctx, account, blockNumber) }, - true, + func() ([]any, error) { a, err := c.main.CodeAt(ctx, account, blockNumber); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.CodeAt(ctx, account, blockNumber); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return code.([]byte), nil + return res[0].([]byte), nil } func (c *ClientWithFallback) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { rpcstats.CountCall("eth_NonceAt") - nonce, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.NonceAt(ctx, account, blockNumber) }, - func() (any, error) { return c.fallback.NonceAt(ctx, account, blockNumber) }, - true, + func() ([]any, error) { a, err := c.main.NonceAt(ctx, account, blockNumber); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.NonceAt(ctx, account, blockNumber); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return 0, err } - return nonce.(uint64), nil + return res[0].(uint64), nil } func (c *ClientWithFallback) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { rpcstats.CountCall("eth_FilterLogs") - logs, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.FilterLogs(ctx, q) }, - func() (any, error) { return c.fallback.FilterLogs(ctx, q) }, - true, + func() ([]any, error) { a, err := c.main.FilterLogs(ctx, q); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.FilterLogs(ctx, q); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return logs.([]types.Log), nil + return res[0].([]types.Log), nil } func (c *ClientWithFallback) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { rpcstats.CountCall("eth_SubscribeFilterLogs") - sub, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.SubscribeFilterLogs(ctx, q, ch) }, - func() (any, error) { return c.fallback.SubscribeFilterLogs(ctx, q, ch) }, - true, + func() ([]any, error) { a, err := c.main.SubscribeFilterLogs(ctx, q, ch); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.SubscribeFilterLogs(ctx, q, ch); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return sub.(ethereum.Subscription), nil + return res[0].(ethereum.Subscription), nil } func (c *ClientWithFallback) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { rpcstats.CountCall("eth_PendingBalanceAt") - balance, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.PendingBalanceAt(ctx, account) }, - func() (any, error) { return c.fallback.PendingBalanceAt(ctx, account) }, - true, + func() ([]any, error) { a, err := c.main.PendingBalanceAt(ctx, account); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.PendingBalanceAt(ctx, account); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return balance.(*big.Int), nil + return res[0].(*big.Int), nil } func (c *ClientWithFallback) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { rpcstats.CountCall("eth_PendingStorageAt") - storage, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.PendingStorageAt(ctx, account, key) }, - func() (any, error) { return c.fallback.PendingStorageAt(ctx, account, key) }, - true, + func() ([]any, error) { a, err := c.main.PendingStorageAt(ctx, account, key); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.PendingStorageAt(ctx, account, key); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return storage.([]byte), nil + return res[0].([]byte), nil } func (c *ClientWithFallback) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { rpcstats.CountCall("eth_PendingCodeAt") - code, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.PendingCodeAt(ctx, account) }, - func() (any, error) { return c.fallback.PendingCodeAt(ctx, account) }, - true, + func() ([]any, error) { a, err := c.main.PendingCodeAt(ctx, account); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.PendingCodeAt(ctx, account); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return code.([]byte), nil + return res[0].([]byte), nil } func (c *ClientWithFallback) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { rpcstats.CountCall("eth_PendingNonceAt") - nonce, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.PendingNonceAt(ctx, account) }, - func() (any, error) { return c.fallback.PendingNonceAt(ctx, account) }, - true, + func() ([]any, error) { a, err := c.main.PendingNonceAt(ctx, account); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.PendingNonceAt(ctx, account); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return 0, err } - return nonce.(uint64), nil + return res[0].(uint64), nil } func (c *ClientWithFallback) PendingTransactionCount(ctx context.Context) (uint, error) { rpcstats.CountCall("eth_PendingTransactionCount") - count, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.PendingTransactionCount(ctx) }, - func() (any, error) { return c.fallback.PendingTransactionCount(ctx) }, - true, + func() ([]any, error) { a, err := c.main.PendingTransactionCount(ctx); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.PendingTransactionCount(ctx); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return 0, err } - return count.(uint), nil + return res[0].(uint), nil } func (c *ClientWithFallback) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { rpcstats.CountCall("eth_CallContract_" + msg.To.String()) - data, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.CallContract(ctx, msg, blockNumber) }, - func() (any, error) { return c.fallback.CallContract(ctx, msg, blockNumber) }, - true, + func() ([]any, error) { a, err := c.main.CallContract(ctx, msg, blockNumber); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.CallContract(ctx, msg, blockNumber); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return data.([]byte), nil + return res[0].([]byte), nil } func (c *ClientWithFallback) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { rpcstats.CountCall("eth_CallContractAtHash") - data, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.CallContractAtHash(ctx, msg, blockHash) }, - func() (any, error) { return c.fallback.CallContractAtHash(ctx, msg, blockHash) }, - true, + func() ([]any, error) { a, err := c.main.CallContractAtHash(ctx, msg, blockHash); return []any{a}, err }, + func() ([]any, error) { + a, err := c.fallback.CallContractAtHash(ctx, msg, blockHash) + return []any{a}, err + }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return data.([]byte), nil + return res[0].([]byte), nil } func (c *ClientWithFallback) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { rpcstats.CountCall("eth_PendingCallContract") - data, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.PendingCallContract(ctx, msg) }, - func() (any, error) { return c.fallback.PendingCallContract(ctx, msg) }, - true, + func() ([]any, error) { a, err := c.main.PendingCallContract(ctx, msg); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.PendingCallContract(ctx, msg); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return data.([]byte), nil + return res[0].([]byte), nil } func (c *ClientWithFallback) SuggestGasPrice(ctx context.Context) (*big.Int, error) { rpcstats.CountCall("eth_SuggestGasPrice") - gasPrice, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.SuggestGasPrice(ctx) }, - func() (any, error) { return c.fallback.SuggestGasPrice(ctx) }, - true, + func() ([]any, error) { a, err := c.main.SuggestGasPrice(ctx); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.SuggestGasPrice(ctx); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return gasPrice.(*big.Int), nil + return res[0].(*big.Int), nil } func (c *ClientWithFallback) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { rpcstats.CountCall("eth_SuggestGasTipCap") - tip, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.SuggestGasTipCap(ctx) }, - func() (any, error) { return c.fallback.SuggestGasTipCap(ctx) }, - true, + func() ([]any, error) { a, err := c.main.SuggestGasTipCap(ctx); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.SuggestGasTipCap(ctx); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return tip.(*big.Int), nil + return res[0].(*big.Int), nil } func (c *ClientWithFallback) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { rpcstats.CountCall("eth_FeeHistory") - feeHistory, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) }, - func() (any, error) { return c.fallback.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) }, - true, + func() ([]any, error) { + a, err := c.main.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) + return []any{a}, err + }, + func() ([]any, error) { + a, err := c.fallback.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) + return []any{a}, err + }, ) + c.toggleConnectionState(err) + if err != nil { return nil, err } - return feeHistory.(*ethereum.FeeHistory), nil + return res[0].(*ethereum.FeeHistory), nil } func (c *ClientWithFallback) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { rpcstats.CountCall("eth_EstimateGas") - estimate, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { return c.main.EstimateGas(ctx, msg) }, - func() (any, error) { return c.fallback.EstimateGas(ctx, msg) }, - true, + func() ([]any, error) { a, err := c.main.EstimateGas(ctx, msg); return []any{a}, err }, + func() ([]any, error) { a, err := c.fallback.EstimateGas(ctx, msg); return []any{a}, err }, ) + c.toggleConnectionState(err) + if err != nil { return 0, err } - return estimate.(uint64), nil + return res[0].(uint64), nil } func (c *ClientWithFallback) SendTransaction(ctx context.Context, tx *types.Transaction) error { rpcstats.CountCall("eth_SendTransaction") - return c.makeCallNoReturn( + _, err := c.makeCall( ctx, - func() error { return c.main.SendTransaction(ctx, tx) }, - func() error { return c.fallback.SendTransaction(ctx, tx) }, + func() ([]any, error) { return nil, c.main.SendTransaction(ctx, tx) }, + func() ([]any, error) { return nil, c.fallback.SendTransaction(ctx, tx) }, ) + return err } func (c *ClientWithFallback) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { rpcstats.CountCall("eth_CallContext") - return c.makeCallNoReturn( + _, err := c.makeCall( ctx, - func() error { return c.mainRPC.CallContext(ctx, result, method, args...) }, - func() error { return c.fallbackRPC.CallContext(ctx, result, method, args...) }, + func() ([]any, error) { return nil, c.mainRPC.CallContext(ctx, result, method, args...) }, + func() ([]any, error) { return nil, c.fallbackRPC.CallContext(ctx, result, method, args...) }, ) + return err } func (c *ClientWithFallback) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { rpcstats.CountCall("eth_BatchCallContext") - return c.makeCallNoReturn( + _, err := c.makeCall( ctx, - func() error { return c.mainRPC.BatchCallContext(ctx, b) }, - func() error { return c.fallbackRPC.BatchCallContext(ctx, b) }, + func() ([]any, error) { return nil, c.mainRPC.BatchCallContext(ctx, b) }, + func() ([]any, error) { return nil, c.fallbackRPC.BatchCallContext(ctx, b) }, ) + return err } func (c *ClientWithFallback) ToBigInt() *big.Int { @@ -971,22 +893,25 @@ func (c *ClientWithFallback) GetBaseFeeFromBlock(ctx context.Context, blockNumbe func (c *ClientWithFallback) CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error) { rpcstats.CountCall("eth_FullTransactionByBlockNumberAndIndex") - tx, err := c.makeCallSingleReturn( + res, err := c.makeCall( ctx, - func() (any, error) { - return callBlockHashByTransaction(ctx, c.mainRPC, blockNumber, index) + func() ([]any, error) { + a, err := callBlockHashByTransaction(ctx, c.mainRPC, blockNumber, index) + return []any{a}, err }, - func() (any, error) { - return callBlockHashByTransaction(ctx, c.fallbackRPC, blockNumber, index) + func() ([]any, error) { + a, err := callBlockHashByTransaction(ctx, c.fallbackRPC, blockNumber, index) + return []any{a}, err }, - true, ) + c.toggleConnectionState(err) + if err != nil { return common.HexToHash(""), err } - return tx.(common.Hash), nil + return res[0].(common.Hash), nil } func (c *ClientWithFallback) GetWalletNotifier() func(chainId uint64, message string) { @@ -996,3 +921,13 @@ func (c *ClientWithFallback) GetWalletNotifier() func(chainId uint64, message st func (c *ClientWithFallback) SetWalletNotifier(notifier func(chainId uint64, message string)) { c.WalletNotifier = notifier } + +func (c *ClientWithFallback) toggleConnectionState(err error) { + connected := true + if err != nil { + if !isVMError(err) && err != ErrRequestsOverLimit { + connected = false + } + } + c.SetIsConnected(connected) +}