status-go/rpc/chain/client.go

1044 lines
28 KiB
Go
Raw Normal View History

2023-02-20 09:32:45 +00:00
package chain
import (
"context"
"errors"
2023-02-20 09:32:45 +00:00
"fmt"
"math/big"
2023-03-28 16:22:11 +00:00
"strings"
"sync/atomic"
2023-02-20 09:32:45 +00:00
"time"
"github.com/afex/hystrix-go/hystrix"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
2023-02-20 09:32:45 +00:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
2023-02-20 09:32:45 +00:00
"github.com/ethereum/go-ethereum/core/types"
2023-03-28 16:22:11 +00:00
"github.com/ethereum/go-ethereum/core/vm"
2023-02-20 09:32:45 +00:00
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/services/rpcstats"
"github.com/status-im/status-go/services/wallet/connection"
2023-02-20 09:32:45 +00:00
)
2023-09-19 11:17:36 +00:00
type BatchCallClient interface {
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
}
type ChainInterface interface {
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
2023-09-19 11:17:36 +00:00
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error)
2024-02-23 14:27:08 +00:00
GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error)
2023-09-19 11:17:36 +00:00
NetworkID() uint64
ToBigInt() *big.Int
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error)
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
BlockNumber(ctx context.Context) (uint64, error)
}
type ClientInterface interface {
ChainInterface
GetWalletNotifier() func(chainId uint64, message string)
SetWalletNotifier(notifier func(chainId uint64, message string))
connection.Connectable
bind.ContractCaller
bind.ContractTransactor
bind.ContractFilterer
GetLimiter() RequestLimiter
SetLimiter(RequestLimiter)
}
type Tagger interface {
Tag() string
SetTag(tag string)
GroupTag() string
SetGroupTag(tag string)
DeepCopyTag() Tagger
}
func DeepCopyTagger(t Tagger) Tagger {
return t.DeepCopyTag()
}
// Shallow copy of the client with a deep copy of tag and group tag
// To avoid passing tags as parameter to every chain call, it is sufficient for now
// to set the tag and group tag once on the client
func ClientWithTag(chainClient ClientInterface, tag, groupTag string) ClientInterface {
newClient := chainClient
if tagIface, ok := chainClient.(Tagger); ok {
tagIface = DeepCopyTagger(tagIface)
tagIface.SetTag(tag)
tagIface.SetGroupTag(groupTag)
newClient = tagIface.(ClientInterface)
}
return newClient
}
2023-02-20 09:32:45 +00:00
type ClientWithFallback struct {
2024-02-23 14:27:08 +00:00
ChainID uint64
main *ethclient.Client
fallback *ethclient.Client
mainLimiter *RPCRpsLimiter
fallbackLimiter *RPCRpsLimiter
commonLimiter RequestLimiter
2023-02-20 09:32:45 +00:00
mainRPC *rpc.Client
fallbackRPC *rpc.Client
2023-03-20 13:02:09 +00:00
WalletNotifier func(chainId uint64, message string)
isConnected *atomic.Bool
LastCheckedAt int64
circuitBreakerCmdName string
tag string // tag for the limiter
groupTag string // tag for the limiter group
2023-02-20 09:32:45 +00:00
}
// Don't mark connection as failed if we get one of these errors
var propagateErrors = []error{
2023-03-28 16:22:11 +00:00
vm.ErrOutOfGas,
vm.ErrCodeStoreOutOfGas,
vm.ErrDepth,
vm.ErrInsufficientBalance,
vm.ErrContractAddressCollision,
vm.ErrExecutionReverted,
vm.ErrMaxCodeSizeExceeded,
vm.ErrInvalidJump,
vm.ErrWriteProtection,
vm.ErrReturnDataOutOfBounds,
vm.ErrGasUintOverflow,
vm.ErrInvalidCode,
vm.ErrNonceUintOverflow,
// Used by balance history to check state
ethereum.NotFound,
bind.ErrNoCode,
2023-03-28 16:22:11 +00:00
}
type CommandResult struct {
res []any
err error
2023-03-28 16:22:11 +00:00
}
func NewSimpleClient(mainLimiter *RPCRpsLimiter, main *rpc.Client, chainID uint64) *ClientWithFallback {
circuitBreakerCmdName := fmt.Sprintf("ethClient_%d", chainID)
hystrix.ConfigureCommand(circuitBreakerCmdName, hystrix.CommandConfig{
2023-02-20 09:32:45 +00:00
Timeout: 10000,
MaxConcurrentRequests: 100,
SleepWindow: 300000,
ErrorPercentThreshold: 25,
})
isConnected := &atomic.Bool{}
isConnected.Store(true)
2023-02-20 09:32:45 +00:00
return &ClientWithFallback{
ChainID: chainID,
main: ethclient.NewClient(main),
fallback: nil,
mainLimiter: mainLimiter,
fallbackLimiter: nil,
mainRPC: main,
fallbackRPC: nil,
isConnected: isConnected,
LastCheckedAt: time.Now().Unix(),
circuitBreakerCmdName: circuitBreakerCmdName,
2023-02-20 09:32:45 +00:00
}
}
func NewClient(mainLimiter *RPCRpsLimiter, main *rpc.Client, fallbackLimiter *RPCRpsLimiter, fallback *rpc.Client, chainID uint64) *ClientWithFallback {
circuitBreakerCmdName := fmt.Sprintf("ethClient_%d", chainID)
hystrix.ConfigureCommand(circuitBreakerCmdName, hystrix.CommandConfig{
2023-03-28 12:46:46 +00:00
Timeout: 20000,
2023-02-20 09:32:45 +00:00
MaxConcurrentRequests: 100,
SleepWindow: 300000,
ErrorPercentThreshold: 25,
})
var fallbackEthClient *ethclient.Client
if fallback != nil {
fallbackEthClient = ethclient.NewClient(fallback)
}
isConnected := &atomic.Bool{}
isConnected.Store(true)
2023-02-20 09:32:45 +00:00
return &ClientWithFallback{
ChainID: chainID,
main: ethclient.NewClient(main),
fallback: fallbackEthClient,
mainLimiter: mainLimiter,
fallbackLimiter: fallbackLimiter,
mainRPC: main,
fallbackRPC: fallback,
isConnected: isConnected,
LastCheckedAt: time.Now().Unix(),
circuitBreakerCmdName: circuitBreakerCmdName,
2023-02-20 09:32:45 +00:00
}
}
func (c *ClientWithFallback) Close() {
c.main.Close()
if c.fallback != nil {
c.fallback.Close()
}
}
2023-03-28 16:22:11 +00:00
func isVMError(err error) bool {
if strings.HasPrefix(err.Error(), "execution reverted") {
return true
}
2024-03-07 11:05:53 +00:00
if strings.Contains(err.Error(), core.ErrInsufficientFunds.Error()) {
return true
}
for _, vmError := range propagateErrors {
2024-03-07 11:05:53 +00:00
if err == vmError {
2023-03-28 16:22:11 +00:00
return true
}
2024-03-07 11:05:53 +00:00
2023-03-28 16:22:11 +00:00
}
return false
}
2024-02-23 14:27:08 +00:00
func isRPSLimitError(err error) bool {
return strings.Contains(err.Error(), "backoff_seconds") ||
strings.Contains(err.Error(), "has exceeded its throughput limit") ||
strings.Contains(err.Error(), "request rate exceeded")
2024-02-23 14:27:08 +00:00
}
func (c *ClientWithFallback) SetIsConnected(value bool) {
2023-03-20 13:02:09 +00:00
c.LastCheckedAt = time.Now().Unix()
2023-03-23 14:08:55 +00:00
if !value {
if c.isConnected.Load() {
2023-03-23 14:08:55 +00:00
if c.WalletNotifier != nil {
c.WalletNotifier(c.ChainID, "down")
}
c.isConnected.Store(false)
2023-03-20 13:02:09 +00:00
}
2023-03-23 14:08:55 +00:00
} else {
if !c.isConnected.Load() {
c.isConnected.Store(true)
2023-03-23 14:21:02 +00:00
if c.WalletNotifier != nil {
c.WalletNotifier(c.ChainID, "up")
}
2023-03-20 13:02:09 +00:00
}
}
}
func (c *ClientWithFallback) IsConnected() bool {
return c.isConnected.Load()
}
func (c *ClientWithFallback) makeCall(ctx context.Context, main func() ([]any, error), fallback func() ([]any, error)) ([]any, error) {
if c.commonLimiter != nil {
if allow, err := c.commonLimiter.Allow(c.tag); !allow {
return nil, fmt.Errorf("tag=%s, %w", c.tag, err)
}
if allow, err := c.commonLimiter.Allow(c.groupTag); !allow {
return nil, fmt.Errorf("groupTag=%s, %w", c.groupTag, err)
}
}
2023-03-28 16:22:11 +00:00
resultChan := make(chan CommandResult, 1)
2023-02-20 09:32:45 +00:00
c.LastCheckedAt = time.Now().Unix()
errChan := hystrix.Go(c.circuitBreakerCmdName, func() error {
2024-02-23 14:27:08 +00:00
err := c.mainLimiter.WaitForRequestsAvailability(1)
if err != nil {
return err
}
2023-02-20 09:32:45 +00:00
res, err := main()
if err != nil {
2024-02-23 14:27:08 +00:00
if isRPSLimitError(err) {
c.mainLimiter.ReduceLimit()
err = c.mainLimiter.WaitForRequestsAvailability(1)
if err != nil {
return err
}
res, err = main()
if err == nil {
resultChan <- CommandResult{res: res}
return nil
}
}
if isVMError(err) {
resultChan <- CommandResult{err: err}
2023-03-29 07:03:43 +00:00
return nil
2023-03-28 16:22:11 +00:00
}
2023-02-20 09:32:45 +00:00
return err
}
resultChan <- CommandResult{res: res}
2023-02-20 09:32:45 +00:00
return nil
}, func(err error) error {
if c.fallback == nil {
return err
}
2024-02-23 14:27:08 +00:00
err = c.fallbackLimiter.WaitForRequestsAvailability(1)
if err != nil {
return err
}
2023-02-20 09:32:45 +00:00
res, err := fallback()
if err != nil {
2024-02-23 14:27:08 +00:00
if isRPSLimitError(err) {
c.fallbackLimiter.ReduceLimit()
err = c.fallbackLimiter.WaitForRequestsAvailability(1)
if err != nil {
return err
}
res, err = fallback()
if err == nil {
resultChan <- CommandResult{res: res}
return nil
}
}
if isVMError(err) {
resultChan <- CommandResult{err: err}
2023-03-29 07:03:43 +00:00
return nil
}
2023-02-20 09:32:45 +00:00
return err
}
resultChan <- CommandResult{res: res}
2023-02-20 09:32:45 +00:00
return nil
})
2023-03-29 07:03:43 +00:00
2023-02-20 09:32:45 +00:00
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
2023-03-28 16:22:11 +00:00
}
return result.res, nil
2023-02-20 09:32:45 +00:00
case err := <-errChan:
return nil, err
}
}
func (c *ClientWithFallback) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
rpcstats.CountCallWithTag("eth_BlockByHash", c.tag)
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*types.Block), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
rpcstats.CountCallWithTag("eth_BlockByNumber", c.tag)
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*types.Block), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) BlockNumber(ctx context.Context) (uint64, error) {
rpcstats.CountCallWithTag("eth_BlockNumber", c.tag)
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return 0, err
}
return res[0].(uint64), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) PeerCount(ctx context.Context) (uint64, error) {
rpcstats.CountCallWithTag("eth_PeerCount", c.tag)
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return 0, err
}
return res[0].(uint64), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
rpcstats.CountCallWithTag("eth_HeaderByHash", c.tag)
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
if err != nil {
return nil, err
}
return res[0].(*types.Header), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
rpcstats.CountCallWithTag("eth_HeaderByNumber", c.tag)
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
if err != nil {
return nil, err
}
return res[0].(*types.Header), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
rpcstats.CountCallWithTag("eth_TransactionByHash", c.tag)
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
if err != nil {
return nil, false, err
}
return res[0].(*types.Transaction), res[1].(bool), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) {
rpcstats.CountCall("eth_TransactionSender")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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
},
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
return res[0].(common.Address), err
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
rpcstats.CountCall("eth_TransactionCount")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return 0, err
}
return res[0].(uint), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
rpcstats.CountCall("eth_TransactionInBlock")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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
},
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*types.Transaction), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
rpcstats.CountCall("eth_TransactionReceipt")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*types.Receipt), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) {
rpcstats.CountCall("eth_SyncProgress")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*ethereum.SyncProgress), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
rpcstats.CountCall("eth_SubscribeNewHead")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(ethereum.Subscription), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) NetworkID() uint64 {
return c.ChainID
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
rpcstats.CountCallWithTag("eth_BalanceAt", c.tag)
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*big.Int), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
rpcstats.CountCall("eth_StorageAt")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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
},
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]byte), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
rpcstats.CountCall("eth_CodeAt")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]byte), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
rpcstats.CountCallWithTag("eth_NonceAt", c.tag)
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return 0, err
}
return res[0].(uint64), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
rpcstats.CountCallWithTag("eth_FilterLogs", c.tag)
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]types.Log), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
rpcstats.CountCall("eth_SubscribeFilterLogs")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(ethereum.Subscription), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) {
rpcstats.CountCall("eth_PendingBalanceAt")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*big.Int), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) {
rpcstats.CountCall("eth_PendingStorageAt")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]byte), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
rpcstats.CountCall("eth_PendingCodeAt")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]byte), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
rpcstats.CountCall("eth_PendingNonceAt")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return 0, err
}
return res[0].(uint64), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) PendingTransactionCount(ctx context.Context) (uint, error) {
rpcstats.CountCall("eth_PendingTransactionCount")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return 0, err
}
return res[0].(uint), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
rpcstats.CountCall("eth_CallContract_" + msg.To.String())
2023-02-20 09:32:45 +00:00
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]byte), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) {
rpcstats.CountCall("eth_CallContractAtHash")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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
},
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]byte), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
rpcstats.CountCall("eth_PendingCallContract")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].([]byte), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
rpcstats.CountCall("eth_SuggestGasPrice")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*big.Int), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
rpcstats.CountCall("eth_SuggestGasTipCap")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*big.Int), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
rpcstats.CountCall("eth_FeeHistory")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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
},
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return nil, err
}
return res[0].(*ethereum.FeeHistory), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
rpcstats.CountCall("eth_EstimateGas")
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
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 },
2023-02-20 09:32:45 +00:00
)
c.toggleConnectionState(err)
2023-02-20 09:32:45 +00:00
if err != nil {
return 0, err
}
return res[0].(uint64), nil
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) SendTransaction(ctx context.Context, tx *types.Transaction) error {
rpcstats.CountCall("eth_SendTransaction")
_, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
func() ([]any, error) { return nil, c.main.SendTransaction(ctx, tx) },
func() ([]any, error) { return nil, c.fallback.SendTransaction(ctx, tx) },
2023-02-20 09:32:45 +00:00
)
return err
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
rpcstats.CountCall("eth_CallContext")
_, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
func() ([]any, error) { return nil, c.mainRPC.CallContext(ctx, result, method, args...) },
func() ([]any, error) { return nil, c.fallbackRPC.CallContext(ctx, result, method, args...) },
2023-02-20 09:32:45 +00:00
)
return err
2023-02-20 09:32:45 +00:00
}
func (c *ClientWithFallback) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
rpcstats.CountCall("eth_BatchCallContext")
_, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
func() ([]any, error) { return nil, c.mainRPC.BatchCallContext(ctx, b) },
func() ([]any, error) { return nil, c.fallbackRPC.BatchCallContext(ctx, b) },
)
return err
}
2023-02-20 09:32:45 +00:00
func (c *ClientWithFallback) ToBigInt() *big.Int {
return big.NewInt(int64(c.ChainID))
}
2024-02-23 14:27:08 +00:00
func (c *ClientWithFallback) GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error) {
2023-02-20 09:32:45 +00:00
rpcstats.CountCall("eth_GetBaseFeeFromBlock")
2024-02-23 14:27:08 +00:00
feeHistory, err := c.FeeHistory(ctx, 1, blockNumber, nil)
2023-02-20 09:32:45 +00:00
if err != nil {
if err.Error() == "the method eth_feeHistory does not exist/is not available" {
return "", nil
}
return "", err
}
var baseGasFee string = ""
2024-02-23 14:27:08 +00:00
if len(feeHistory.BaseFee) > 0 {
baseGasFee = feeHistory.BaseFee[0].String()
2023-02-20 09:32:45 +00:00
}
return baseGasFee, err
}
// go-ethereum's `Transaction` items drop the blkHash obtained during the RPC call.
// This function preserves the additional data. This is the cheapest way to obtain
// the block hash for a given block number.
func (c *ClientWithFallback) CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error) {
rpcstats.CountCallWithTag("eth_FullTransactionByBlockNumberAndIndex", c.tag)
res, err := c.makeCall(
2024-02-23 14:27:08 +00:00
ctx,
func() ([]any, error) {
a, err := callBlockHashByTransaction(ctx, c.mainRPC, blockNumber, index)
return []any{a}, err
},
func() ([]any, error) {
a, err := callBlockHashByTransaction(ctx, c.fallbackRPC, blockNumber, index)
return []any{a}, err
},
)
c.toggleConnectionState(err)
if err != nil {
return common.HexToHash(""), err
}
return res[0].(common.Hash), nil
}
func (c *ClientWithFallback) GetWalletNotifier() func(chainId uint64, message string) {
return c.WalletNotifier
}
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) && !errors.Is(ErrRequestsOverLimit, err) {
connected = false
}
}
c.SetIsConnected(connected)
}
func (c *ClientWithFallback) Tag() string {
return c.tag
}
func (c *ClientWithFallback) SetTag(tag string) {
c.tag = tag
}
func (c *ClientWithFallback) GroupTag() string {
return c.groupTag
}
func (c *ClientWithFallback) SetGroupTag(tag string) {
c.groupTag = tag
}
func (c *ClientWithFallback) DeepCopyTag() Tagger {
copy := *c
return &copy
}
func (c *ClientWithFallback) GetLimiter() RequestLimiter {
return c.commonLimiter
}
func (c *ClientWithFallback) SetLimiter(limiter RequestLimiter) {
c.commonLimiter = limiter
}