feat: rpc request limiter
This commit is contained in:
parent
ae9b697eda
commit
bb3006d747
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
@ -21,10 +20,6 @@ import (
|
||||||
"github.com/status-im/status-go/services/rpcstats"
|
"github.com/status-im/status-go/services/rpcstats"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FeeHistory struct {
|
|
||||||
BaseFeePerGas []string `json:"baseFeePerGas"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BatchCallClient interface {
|
type BatchCallClient interface {
|
||||||
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
|
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
|
||||||
}
|
}
|
||||||
|
@ -39,7 +34,7 @@ type ClientInterface interface {
|
||||||
BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
|
BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
|
||||||
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
|
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
|
||||||
CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error)
|
CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error)
|
||||||
GetBaseFeeFromBlock(blockNumber *big.Int) (string, error)
|
GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error)
|
||||||
NetworkID() uint64
|
NetworkID() uint64
|
||||||
ToBigInt() *big.Int
|
ToBigInt() *big.Int
|
||||||
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
|
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
|
||||||
|
@ -61,6 +56,8 @@ type ClientWithFallback struct {
|
||||||
ChainID uint64
|
ChainID uint64
|
||||||
main *ethclient.Client
|
main *ethclient.Client
|
||||||
fallback *ethclient.Client
|
fallback *ethclient.Client
|
||||||
|
mainLimiter *RPCLimiter
|
||||||
|
fallbackLimiter *RPCLimiter
|
||||||
|
|
||||||
mainRPC *rpc.Client
|
mainRPC *rpc.Client
|
||||||
fallbackRPC *rpc.Client
|
fallbackRPC *rpc.Client
|
||||||
|
@ -99,7 +96,7 @@ type CommandResult struct {
|
||||||
vmError error
|
vmError error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSimpleClient(main *rpc.Client, chainID uint64) *ClientWithFallback {
|
func NewSimpleClient(mainLimiter *RPCLimiter, main *rpc.Client, chainID uint64) *ClientWithFallback {
|
||||||
hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{
|
hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{
|
||||||
Timeout: 10000,
|
Timeout: 10000,
|
||||||
MaxConcurrentRequests: 100,
|
MaxConcurrentRequests: 100,
|
||||||
|
@ -111,6 +108,8 @@ func NewSimpleClient(main *rpc.Client, chainID uint64) *ClientWithFallback {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
main: ethclient.NewClient(main),
|
main: ethclient.NewClient(main),
|
||||||
fallback: nil,
|
fallback: nil,
|
||||||
|
mainLimiter: mainLimiter,
|
||||||
|
fallbackLimiter: nil,
|
||||||
mainRPC: main,
|
mainRPC: main,
|
||||||
fallbackRPC: nil,
|
fallbackRPC: nil,
|
||||||
IsConnected: true,
|
IsConnected: true,
|
||||||
|
@ -118,7 +117,7 @@ func NewSimpleClient(main *rpc.Client, chainID uint64) *ClientWithFallback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(main, fallback *rpc.Client, chainID uint64) *ClientWithFallback {
|
func NewClient(mainLimiter *RPCLimiter, main *rpc.Client, fallbackLimiter *RPCLimiter, fallback *rpc.Client, chainID uint64) *ClientWithFallback {
|
||||||
hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{
|
hystrix.ConfigureCommand(fmt.Sprintf("ethClient_%d", chainID), hystrix.CommandConfig{
|
||||||
Timeout: 20000,
|
Timeout: 20000,
|
||||||
MaxConcurrentRequests: 100,
|
MaxConcurrentRequests: 100,
|
||||||
|
@ -134,6 +133,8 @@ func NewClient(main, fallback *rpc.Client, chainID uint64) *ClientWithFallback {
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
main: ethclient.NewClient(main),
|
main: ethclient.NewClient(main),
|
||||||
fallback: fallbackEthClient,
|
fallback: fallbackEthClient,
|
||||||
|
mainLimiter: mainLimiter,
|
||||||
|
fallbackLimiter: fallbackLimiter,
|
||||||
mainRPC: main,
|
mainRPC: main,
|
||||||
fallbackRPC: fallback,
|
fallbackRPC: fallback,
|
||||||
IsConnected: true,
|
IsConnected: true,
|
||||||
|
@ -160,6 +161,10 @@ func isVMError(err error) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isRPSLimitError(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "backoff_seconds")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ClientWithFallback) SetIsConnected(value bool) {
|
func (c *ClientWithFallback) SetIsConnected(value bool) {
|
||||||
c.IsConnectedLock.Lock()
|
c.IsConnectedLock.Lock()
|
||||||
defer c.IsConnectedLock.Unlock()
|
defer c.IsConnectedLock.Unlock()
|
||||||
|
@ -188,12 +193,20 @@ func (c *ClientWithFallback) GetIsConnected() bool {
|
||||||
return c.IsConnected
|
return c.IsConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientWithFallback) makeCallNoReturn(main func() error, fallback func() error) error {
|
func (c *ClientWithFallback) makeCallNoReturn(ctx context.Context, main func() error, fallback func() error) error {
|
||||||
resultChan := make(chan CommandResult, 1)
|
resultChan := make(chan CommandResult, 1)
|
||||||
c.LastCheckedAt = time.Now().Unix()
|
c.LastCheckedAt = time.Now().Unix()
|
||||||
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
|
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
|
||||||
err := main()
|
err := c.mainLimiter.WaitForRequestsAvailability(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = main()
|
||||||
|
if err != nil {
|
||||||
|
if isRPSLimitError(err) {
|
||||||
|
c.mainLimiter.ReduceLimit()
|
||||||
|
}
|
||||||
if isVMError(err) {
|
if isVMError(err) {
|
||||||
resultChan <- CommandResult{vmError: err}
|
resultChan <- CommandResult{vmError: err}
|
||||||
return nil
|
return nil
|
||||||
|
@ -209,8 +222,16 @@ func (c *ClientWithFallback) makeCallNoReturn(main func() error, fallback func()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = c.fallbackLimiter.WaitForRequestsAvailability(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = fallback()
|
err = fallback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isRPSLimitError(err) {
|
||||||
|
c.fallbackLimiter.ReduceLimit()
|
||||||
|
}
|
||||||
if isVMError(err) {
|
if isVMError(err) {
|
||||||
resultChan <- CommandResult{vmError: err}
|
resultChan <- CommandResult{vmError: err}
|
||||||
return nil
|
return nil
|
||||||
|
@ -233,11 +254,19 @@ func (c *ClientWithFallback) makeCallNoReturn(main func() error, fallback func()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientWithFallback) makeCallSingleReturn(main func() (any, error), fallback func() (any, error), toggleIsConnected bool) (any, error) {
|
func (c *ClientWithFallback) makeCallSingleReturn(ctx context.Context, main func() (any, error), fallback func() (any, error), toggleIsConnected bool) (any, error) {
|
||||||
resultChan := make(chan CommandResult, 1)
|
resultChan := make(chan CommandResult, 1)
|
||||||
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
|
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
|
||||||
|
err := c.mainLimiter.WaitForRequestsAvailability(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
res, err := main()
|
res, err := main()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isRPSLimitError(err) {
|
||||||
|
c.mainLimiter.ReduceLimit()
|
||||||
|
}
|
||||||
if isVMError(err) {
|
if isVMError(err) {
|
||||||
resultChan <- CommandResult{vmError: err}
|
resultChan <- CommandResult{vmError: err}
|
||||||
return nil
|
return nil
|
||||||
|
@ -257,8 +286,16 @@ func (c *ClientWithFallback) makeCallSingleReturn(main func() (any, error), fall
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = c.fallbackLimiter.WaitForRequestsAvailability(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
res, err := fallback()
|
res, err := fallback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isRPSLimitError(err) {
|
||||||
|
c.fallbackLimiter.ReduceLimit()
|
||||||
|
}
|
||||||
if isVMError(err) {
|
if isVMError(err) {
|
||||||
resultChan <- CommandResult{vmError: err}
|
resultChan <- CommandResult{vmError: err}
|
||||||
return nil
|
return nil
|
||||||
|
@ -286,12 +323,20 @@ func (c *ClientWithFallback) makeCallSingleReturn(main func() (any, error), fall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientWithFallback) makeCallDoubleReturn(main func() (any, any, error), fallback func() (any, any, error)) (any, any, error) {
|
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)
|
resultChan := make(chan CommandResult, 1)
|
||||||
c.LastCheckedAt = time.Now().Unix()
|
c.LastCheckedAt = time.Now().Unix()
|
||||||
errChan := hystrix.Go(fmt.Sprintf("ethClient_%d", c.ChainID), func() error {
|
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()
|
a, b, err := main()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isRPSLimitError(err) {
|
||||||
|
c.mainLimiter.ReduceLimit()
|
||||||
|
}
|
||||||
if isVMError(err) {
|
if isVMError(err) {
|
||||||
resultChan <- CommandResult{vmError: err}
|
resultChan <- CommandResult{vmError: err}
|
||||||
return nil
|
return nil
|
||||||
|
@ -307,8 +352,16 @@ func (c *ClientWithFallback) makeCallDoubleReturn(main func() (any, any, error),
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = c.fallbackLimiter.WaitForRequestsAvailability(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
a, b, err := fallback()
|
a, b, err := fallback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isRPSLimitError(err) {
|
||||||
|
c.fallbackLimiter.ReduceLimit()
|
||||||
|
}
|
||||||
if isVMError(err) {
|
if isVMError(err) {
|
||||||
resultChan <- CommandResult{vmError: err}
|
resultChan <- CommandResult{vmError: err}
|
||||||
return nil
|
return nil
|
||||||
|
@ -336,6 +389,7 @@ func (c *ClientWithFallback) BlockByHash(ctx context.Context, hash common.Hash)
|
||||||
rpcstats.CountCall("eth_BlockByHash")
|
rpcstats.CountCall("eth_BlockByHash")
|
||||||
|
|
||||||
block, err := c.makeCallSingleReturn(
|
block, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.BlockByHash(ctx, hash) },
|
func() (any, error) { return c.main.BlockByHash(ctx, hash) },
|
||||||
func() (any, error) { return c.fallback.BlockByHash(ctx, hash) },
|
func() (any, error) { return c.fallback.BlockByHash(ctx, hash) },
|
||||||
true,
|
true,
|
||||||
|
@ -351,6 +405,7 @@ func (c *ClientWithFallback) BlockByHash(ctx context.Context, hash common.Hash)
|
||||||
func (c *ClientWithFallback) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
func (c *ClientWithFallback) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||||
rpcstats.CountCall("eth_BlockByNumber")
|
rpcstats.CountCall("eth_BlockByNumber")
|
||||||
block, err := c.makeCallSingleReturn(
|
block, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.BlockByNumber(ctx, number) },
|
func() (any, error) { return c.main.BlockByNumber(ctx, number) },
|
||||||
func() (any, error) { return c.fallback.BlockByNumber(ctx, number) },
|
func() (any, error) { return c.fallback.BlockByNumber(ctx, number) },
|
||||||
true,
|
true,
|
||||||
|
@ -367,6 +422,7 @@ func (c *ClientWithFallback) BlockNumber(ctx context.Context) (uint64, error) {
|
||||||
rpcstats.CountCall("eth_BlockNumber")
|
rpcstats.CountCall("eth_BlockNumber")
|
||||||
|
|
||||||
number, err := c.makeCallSingleReturn(
|
number, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.BlockNumber(ctx) },
|
func() (any, error) { return c.main.BlockNumber(ctx) },
|
||||||
func() (any, error) { return c.fallback.BlockNumber(ctx) },
|
func() (any, error) { return c.fallback.BlockNumber(ctx) },
|
||||||
true,
|
true,
|
||||||
|
@ -383,6 +439,7 @@ func (c *ClientWithFallback) PeerCount(ctx context.Context) (uint64, error) {
|
||||||
rpcstats.CountCall("eth_PeerCount")
|
rpcstats.CountCall("eth_PeerCount")
|
||||||
|
|
||||||
peerCount, err := c.makeCallSingleReturn(
|
peerCount, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.PeerCount(ctx) },
|
func() (any, error) { return c.main.PeerCount(ctx) },
|
||||||
func() (any, error) { return c.fallback.PeerCount(ctx) },
|
func() (any, error) { return c.fallback.PeerCount(ctx) },
|
||||||
true,
|
true,
|
||||||
|
@ -398,6 +455,7 @@ func (c *ClientWithFallback) PeerCount(ctx context.Context) (uint64, error) {
|
||||||
func (c *ClientWithFallback) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
func (c *ClientWithFallback) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
rpcstats.CountCall("eth_HeaderByHash")
|
rpcstats.CountCall("eth_HeaderByHash")
|
||||||
header, err := c.makeCallSingleReturn(
|
header, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.HeaderByHash(ctx, hash) },
|
func() (any, error) { return c.main.HeaderByHash(ctx, hash) },
|
||||||
func() (any, error) { return c.fallback.HeaderByHash(ctx, hash) },
|
func() (any, error) { return c.fallback.HeaderByHash(ctx, hash) },
|
||||||
false,
|
false,
|
||||||
|
@ -413,6 +471,7 @@ func (c *ClientWithFallback) HeaderByHash(ctx context.Context, hash common.Hash)
|
||||||
func (c *ClientWithFallback) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
func (c *ClientWithFallback) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||||
rpcstats.CountCall("eth_HeaderByNumber")
|
rpcstats.CountCall("eth_HeaderByNumber")
|
||||||
header, err := c.makeCallSingleReturn(
|
header, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.HeaderByNumber(ctx, number) },
|
func() (any, error) { return c.main.HeaderByNumber(ctx, number) },
|
||||||
func() (any, error) { return c.fallback.HeaderByNumber(ctx, number) },
|
func() (any, error) { return c.fallback.HeaderByNumber(ctx, number) },
|
||||||
false,
|
false,
|
||||||
|
@ -429,6 +488,7 @@ func (c *ClientWithFallback) TransactionByHash(ctx context.Context, hash common.
|
||||||
rpcstats.CountCall("eth_TransactionByHash")
|
rpcstats.CountCall("eth_TransactionByHash")
|
||||||
|
|
||||||
tx, isPending, err := c.makeCallDoubleReturn(
|
tx, isPending, err := c.makeCallDoubleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, any, error) { return c.main.TransactionByHash(ctx, hash) },
|
func() (any, any, error) { return c.main.TransactionByHash(ctx, hash) },
|
||||||
func() (any, any, error) { return c.fallback.TransactionByHash(ctx, hash) },
|
func() (any, any, error) { return c.fallback.TransactionByHash(ctx, hash) },
|
||||||
)
|
)
|
||||||
|
@ -444,6 +504,7 @@ func (c *ClientWithFallback) TransactionSender(ctx context.Context, tx *types.Tr
|
||||||
rpcstats.CountCall("eth_TransactionSender")
|
rpcstats.CountCall("eth_TransactionSender")
|
||||||
|
|
||||||
address, err := c.makeCallSingleReturn(
|
address, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.TransactionSender(ctx, tx, block, index) },
|
func() (any, error) { return c.main.TransactionSender(ctx, tx, block, index) },
|
||||||
func() (any, error) { return c.fallback.TransactionSender(ctx, tx, block, index) },
|
func() (any, error) { return c.fallback.TransactionSender(ctx, tx, block, index) },
|
||||||
true,
|
true,
|
||||||
|
@ -456,6 +517,7 @@ func (c *ClientWithFallback) TransactionCount(ctx context.Context, blockHash com
|
||||||
rpcstats.CountCall("eth_TransactionCount")
|
rpcstats.CountCall("eth_TransactionCount")
|
||||||
|
|
||||||
count, err := c.makeCallSingleReturn(
|
count, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.TransactionCount(ctx, blockHash) },
|
func() (any, error) { return c.main.TransactionCount(ctx, blockHash) },
|
||||||
func() (any, error) { return c.fallback.TransactionCount(ctx, blockHash) },
|
func() (any, error) { return c.fallback.TransactionCount(ctx, blockHash) },
|
||||||
true,
|
true,
|
||||||
|
@ -472,6 +534,7 @@ func (c *ClientWithFallback) TransactionInBlock(ctx context.Context, blockHash c
|
||||||
rpcstats.CountCall("eth_TransactionInBlock")
|
rpcstats.CountCall("eth_TransactionInBlock")
|
||||||
|
|
||||||
transactions, err := c.makeCallSingleReturn(
|
transactions, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.TransactionInBlock(ctx, blockHash, index) },
|
func() (any, error) { return c.main.TransactionInBlock(ctx, blockHash, index) },
|
||||||
func() (any, error) { return c.fallback.TransactionInBlock(ctx, blockHash, index) },
|
func() (any, error) { return c.fallback.TransactionInBlock(ctx, blockHash, index) },
|
||||||
true,
|
true,
|
||||||
|
@ -488,6 +551,7 @@ func (c *ClientWithFallback) TransactionReceipt(ctx context.Context, txHash comm
|
||||||
rpcstats.CountCall("eth_TransactionReceipt")
|
rpcstats.CountCall("eth_TransactionReceipt")
|
||||||
|
|
||||||
receipt, err := c.makeCallSingleReturn(
|
receipt, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.TransactionReceipt(ctx, txHash) },
|
func() (any, error) { return c.main.TransactionReceipt(ctx, txHash) },
|
||||||
func() (any, error) { return c.fallback.TransactionReceipt(ctx, txHash) },
|
func() (any, error) { return c.fallback.TransactionReceipt(ctx, txHash) },
|
||||||
true,
|
true,
|
||||||
|
@ -504,6 +568,7 @@ func (c *ClientWithFallback) SyncProgress(ctx context.Context) (*ethereum.SyncPr
|
||||||
rpcstats.CountCall("eth_SyncProgress")
|
rpcstats.CountCall("eth_SyncProgress")
|
||||||
|
|
||||||
progress, err := c.makeCallSingleReturn(
|
progress, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.SyncProgress(ctx) },
|
func() (any, error) { return c.main.SyncProgress(ctx) },
|
||||||
func() (any, error) { return c.fallback.SyncProgress(ctx) },
|
func() (any, error) { return c.fallback.SyncProgress(ctx) },
|
||||||
true,
|
true,
|
||||||
|
@ -520,6 +585,7 @@ func (c *ClientWithFallback) SubscribeNewHead(ctx context.Context, ch chan<- *ty
|
||||||
rpcstats.CountCall("eth_SubscribeNewHead")
|
rpcstats.CountCall("eth_SubscribeNewHead")
|
||||||
|
|
||||||
sub, err := c.makeCallSingleReturn(
|
sub, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.SubscribeNewHead(ctx, ch) },
|
func() (any, error) { return c.main.SubscribeNewHead(ctx, ch) },
|
||||||
func() (any, error) { return c.fallback.SubscribeNewHead(ctx, ch) },
|
func() (any, error) { return c.fallback.SubscribeNewHead(ctx, ch) },
|
||||||
true,
|
true,
|
||||||
|
@ -540,6 +606,7 @@ func (c *ClientWithFallback) BalanceAt(ctx context.Context, account common.Addre
|
||||||
rpcstats.CountCall("eth_BalanceAt")
|
rpcstats.CountCall("eth_BalanceAt")
|
||||||
|
|
||||||
balance, err := c.makeCallSingleReturn(
|
balance, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.BalanceAt(ctx, account, blockNumber) },
|
func() (any, error) { return c.main.BalanceAt(ctx, account, blockNumber) },
|
||||||
func() (any, error) { return c.fallback.BalanceAt(ctx, account, blockNumber) },
|
func() (any, error) { return c.fallback.BalanceAt(ctx, account, blockNumber) },
|
||||||
true,
|
true,
|
||||||
|
@ -556,6 +623,7 @@ func (c *ClientWithFallback) StorageAt(ctx context.Context, account common.Addre
|
||||||
rpcstats.CountCall("eth_StorageAt")
|
rpcstats.CountCall("eth_StorageAt")
|
||||||
|
|
||||||
storage, err := c.makeCallSingleReturn(
|
storage, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.StorageAt(ctx, account, key, blockNumber) },
|
func() (any, error) { return c.main.StorageAt(ctx, account, key, blockNumber) },
|
||||||
func() (any, error) { return c.fallback.StorageAt(ctx, account, key, blockNumber) },
|
func() (any, error) { return c.fallback.StorageAt(ctx, account, key, blockNumber) },
|
||||||
true,
|
true,
|
||||||
|
@ -572,6 +640,7 @@ func (c *ClientWithFallback) CodeAt(ctx context.Context, account common.Address,
|
||||||
rpcstats.CountCall("eth_CodeAt")
|
rpcstats.CountCall("eth_CodeAt")
|
||||||
|
|
||||||
code, err := c.makeCallSingleReturn(
|
code, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.CodeAt(ctx, account, blockNumber) },
|
func() (any, error) { return c.main.CodeAt(ctx, account, blockNumber) },
|
||||||
func() (any, error) { return c.fallback.CodeAt(ctx, account, blockNumber) },
|
func() (any, error) { return c.fallback.CodeAt(ctx, account, blockNumber) },
|
||||||
true,
|
true,
|
||||||
|
@ -588,6 +657,7 @@ func (c *ClientWithFallback) NonceAt(ctx context.Context, account common.Address
|
||||||
rpcstats.CountCall("eth_NonceAt")
|
rpcstats.CountCall("eth_NonceAt")
|
||||||
|
|
||||||
nonce, err := c.makeCallSingleReturn(
|
nonce, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.NonceAt(ctx, account, blockNumber) },
|
func() (any, error) { return c.main.NonceAt(ctx, account, blockNumber) },
|
||||||
func() (any, error) { return c.fallback.NonceAt(ctx, account, blockNumber) },
|
func() (any, error) { return c.fallback.NonceAt(ctx, account, blockNumber) },
|
||||||
true,
|
true,
|
||||||
|
@ -604,6 +674,7 @@ func (c *ClientWithFallback) FilterLogs(ctx context.Context, q ethereum.FilterQu
|
||||||
rpcstats.CountCall("eth_FilterLogs")
|
rpcstats.CountCall("eth_FilterLogs")
|
||||||
|
|
||||||
logs, err := c.makeCallSingleReturn(
|
logs, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.FilterLogs(ctx, q) },
|
func() (any, error) { return c.main.FilterLogs(ctx, q) },
|
||||||
func() (any, error) { return c.fallback.FilterLogs(ctx, q) },
|
func() (any, error) { return c.fallback.FilterLogs(ctx, q) },
|
||||||
true,
|
true,
|
||||||
|
@ -620,6 +691,7 @@ func (c *ClientWithFallback) SubscribeFilterLogs(ctx context.Context, q ethereum
|
||||||
rpcstats.CountCall("eth_SubscribeFilterLogs")
|
rpcstats.CountCall("eth_SubscribeFilterLogs")
|
||||||
|
|
||||||
sub, err := c.makeCallSingleReturn(
|
sub, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.SubscribeFilterLogs(ctx, q, ch) },
|
func() (any, error) { return c.main.SubscribeFilterLogs(ctx, q, ch) },
|
||||||
func() (any, error) { return c.fallback.SubscribeFilterLogs(ctx, q, ch) },
|
func() (any, error) { return c.fallback.SubscribeFilterLogs(ctx, q, ch) },
|
||||||
true,
|
true,
|
||||||
|
@ -636,6 +708,7 @@ func (c *ClientWithFallback) PendingBalanceAt(ctx context.Context, account commo
|
||||||
rpcstats.CountCall("eth_PendingBalanceAt")
|
rpcstats.CountCall("eth_PendingBalanceAt")
|
||||||
|
|
||||||
balance, err := c.makeCallSingleReturn(
|
balance, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.PendingBalanceAt(ctx, account) },
|
func() (any, error) { return c.main.PendingBalanceAt(ctx, account) },
|
||||||
func() (any, error) { return c.fallback.PendingBalanceAt(ctx, account) },
|
func() (any, error) { return c.fallback.PendingBalanceAt(ctx, account) },
|
||||||
true,
|
true,
|
||||||
|
@ -652,6 +725,7 @@ func (c *ClientWithFallback) PendingStorageAt(ctx context.Context, account commo
|
||||||
rpcstats.CountCall("eth_PendingStorageAt")
|
rpcstats.CountCall("eth_PendingStorageAt")
|
||||||
|
|
||||||
storage, err := c.makeCallSingleReturn(
|
storage, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.PendingStorageAt(ctx, account, key) },
|
func() (any, error) { return c.main.PendingStorageAt(ctx, account, key) },
|
||||||
func() (any, error) { return c.fallback.PendingStorageAt(ctx, account, key) },
|
func() (any, error) { return c.fallback.PendingStorageAt(ctx, account, key) },
|
||||||
true,
|
true,
|
||||||
|
@ -668,6 +742,7 @@ func (c *ClientWithFallback) PendingCodeAt(ctx context.Context, account common.A
|
||||||
rpcstats.CountCall("eth_PendingCodeAt")
|
rpcstats.CountCall("eth_PendingCodeAt")
|
||||||
|
|
||||||
code, err := c.makeCallSingleReturn(
|
code, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.PendingCodeAt(ctx, account) },
|
func() (any, error) { return c.main.PendingCodeAt(ctx, account) },
|
||||||
func() (any, error) { return c.fallback.PendingCodeAt(ctx, account) },
|
func() (any, error) { return c.fallback.PendingCodeAt(ctx, account) },
|
||||||
true,
|
true,
|
||||||
|
@ -684,6 +759,7 @@ func (c *ClientWithFallback) PendingNonceAt(ctx context.Context, account common.
|
||||||
rpcstats.CountCall("eth_PendingNonceAt")
|
rpcstats.CountCall("eth_PendingNonceAt")
|
||||||
|
|
||||||
nonce, err := c.makeCallSingleReturn(
|
nonce, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.PendingNonceAt(ctx, account) },
|
func() (any, error) { return c.main.PendingNonceAt(ctx, account) },
|
||||||
func() (any, error) { return c.fallback.PendingNonceAt(ctx, account) },
|
func() (any, error) { return c.fallback.PendingNonceAt(ctx, account) },
|
||||||
true,
|
true,
|
||||||
|
@ -700,6 +776,7 @@ func (c *ClientWithFallback) PendingTransactionCount(ctx context.Context) (uint,
|
||||||
rpcstats.CountCall("eth_PendingTransactionCount")
|
rpcstats.CountCall("eth_PendingTransactionCount")
|
||||||
|
|
||||||
count, err := c.makeCallSingleReturn(
|
count, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.PendingTransactionCount(ctx) },
|
func() (any, error) { return c.main.PendingTransactionCount(ctx) },
|
||||||
func() (any, error) { return c.fallback.PendingTransactionCount(ctx) },
|
func() (any, error) { return c.fallback.PendingTransactionCount(ctx) },
|
||||||
true,
|
true,
|
||||||
|
@ -716,6 +793,7 @@ func (c *ClientWithFallback) CallContract(ctx context.Context, msg ethereum.Call
|
||||||
rpcstats.CountCall("eth_CallContract_" + msg.To.String())
|
rpcstats.CountCall("eth_CallContract_" + msg.To.String())
|
||||||
|
|
||||||
data, err := c.makeCallSingleReturn(
|
data, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.CallContract(ctx, msg, blockNumber) },
|
func() (any, error) { return c.main.CallContract(ctx, msg, blockNumber) },
|
||||||
func() (any, error) { return c.fallback.CallContract(ctx, msg, blockNumber) },
|
func() (any, error) { return c.fallback.CallContract(ctx, msg, blockNumber) },
|
||||||
true,
|
true,
|
||||||
|
@ -732,6 +810,7 @@ func (c *ClientWithFallback) CallContractAtHash(ctx context.Context, msg ethereu
|
||||||
rpcstats.CountCall("eth_CallContractAtHash")
|
rpcstats.CountCall("eth_CallContractAtHash")
|
||||||
|
|
||||||
data, err := c.makeCallSingleReturn(
|
data, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.CallContractAtHash(ctx, msg, blockHash) },
|
func() (any, error) { return c.main.CallContractAtHash(ctx, msg, blockHash) },
|
||||||
func() (any, error) { return c.fallback.CallContractAtHash(ctx, msg, blockHash) },
|
func() (any, error) { return c.fallback.CallContractAtHash(ctx, msg, blockHash) },
|
||||||
true,
|
true,
|
||||||
|
@ -748,6 +827,7 @@ func (c *ClientWithFallback) PendingCallContract(ctx context.Context, msg ethere
|
||||||
rpcstats.CountCall("eth_PendingCallContract")
|
rpcstats.CountCall("eth_PendingCallContract")
|
||||||
|
|
||||||
data, err := c.makeCallSingleReturn(
|
data, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.PendingCallContract(ctx, msg) },
|
func() (any, error) { return c.main.PendingCallContract(ctx, msg) },
|
||||||
func() (any, error) { return c.fallback.PendingCallContract(ctx, msg) },
|
func() (any, error) { return c.fallback.PendingCallContract(ctx, msg) },
|
||||||
true,
|
true,
|
||||||
|
@ -764,6 +844,7 @@ func (c *ClientWithFallback) SuggestGasPrice(ctx context.Context) (*big.Int, err
|
||||||
rpcstats.CountCall("eth_SuggestGasPrice")
|
rpcstats.CountCall("eth_SuggestGasPrice")
|
||||||
|
|
||||||
gasPrice, err := c.makeCallSingleReturn(
|
gasPrice, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.SuggestGasPrice(ctx) },
|
func() (any, error) { return c.main.SuggestGasPrice(ctx) },
|
||||||
func() (any, error) { return c.fallback.SuggestGasPrice(ctx) },
|
func() (any, error) { return c.fallback.SuggestGasPrice(ctx) },
|
||||||
true,
|
true,
|
||||||
|
@ -780,6 +861,7 @@ func (c *ClientWithFallback) SuggestGasTipCap(ctx context.Context) (*big.Int, er
|
||||||
rpcstats.CountCall("eth_SuggestGasTipCap")
|
rpcstats.CountCall("eth_SuggestGasTipCap")
|
||||||
|
|
||||||
tip, err := c.makeCallSingleReturn(
|
tip, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.SuggestGasTipCap(ctx) },
|
func() (any, error) { return c.main.SuggestGasTipCap(ctx) },
|
||||||
func() (any, error) { return c.fallback.SuggestGasTipCap(ctx) },
|
func() (any, error) { return c.fallback.SuggestGasTipCap(ctx) },
|
||||||
true,
|
true,
|
||||||
|
@ -796,6 +878,7 @@ func (c *ClientWithFallback) FeeHistory(ctx context.Context, blockCount uint64,
|
||||||
rpcstats.CountCall("eth_FeeHistory")
|
rpcstats.CountCall("eth_FeeHistory")
|
||||||
|
|
||||||
feeHistory, err := c.makeCallSingleReturn(
|
feeHistory, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) },
|
func() (any, error) { return c.main.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) },
|
||||||
func() (any, error) { return c.fallback.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) },
|
func() (any, error) { return c.fallback.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) },
|
||||||
true,
|
true,
|
||||||
|
@ -812,6 +895,7 @@ func (c *ClientWithFallback) EstimateGas(ctx context.Context, msg ethereum.CallM
|
||||||
rpcstats.CountCall("eth_EstimateGas")
|
rpcstats.CountCall("eth_EstimateGas")
|
||||||
|
|
||||||
estimate, err := c.makeCallSingleReturn(
|
estimate, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) { return c.main.EstimateGas(ctx, msg) },
|
func() (any, error) { return c.main.EstimateGas(ctx, msg) },
|
||||||
func() (any, error) { return c.fallback.EstimateGas(ctx, msg) },
|
func() (any, error) { return c.fallback.EstimateGas(ctx, msg) },
|
||||||
true,
|
true,
|
||||||
|
@ -828,6 +912,7 @@ func (c *ClientWithFallback) SendTransaction(ctx context.Context, tx *types.Tran
|
||||||
rpcstats.CountCall("eth_SendTransaction")
|
rpcstats.CountCall("eth_SendTransaction")
|
||||||
|
|
||||||
return c.makeCallNoReturn(
|
return c.makeCallNoReturn(
|
||||||
|
ctx,
|
||||||
func() error { return c.main.SendTransaction(ctx, tx) },
|
func() error { return c.main.SendTransaction(ctx, tx) },
|
||||||
func() error { return c.fallback.SendTransaction(ctx, tx) },
|
func() error { return c.fallback.SendTransaction(ctx, tx) },
|
||||||
)
|
)
|
||||||
|
@ -837,6 +922,7 @@ func (c *ClientWithFallback) CallContext(ctx context.Context, result interface{}
|
||||||
rpcstats.CountCall("eth_CallContext")
|
rpcstats.CountCall("eth_CallContext")
|
||||||
|
|
||||||
return c.makeCallNoReturn(
|
return c.makeCallNoReturn(
|
||||||
|
ctx,
|
||||||
func() error { return c.mainRPC.CallContext(ctx, result, method, args...) },
|
func() error { return c.mainRPC.CallContext(ctx, result, method, args...) },
|
||||||
func() error { return c.fallbackRPC.CallContext(ctx, result, method, args...) },
|
func() error { return c.fallbackRPC.CallContext(ctx, result, method, args...) },
|
||||||
)
|
)
|
||||||
|
@ -846,6 +932,7 @@ func (c *ClientWithFallback) BatchCallContext(ctx context.Context, b []rpc.Batch
|
||||||
rpcstats.CountCall("eth_BatchCallContext")
|
rpcstats.CountCall("eth_BatchCallContext")
|
||||||
|
|
||||||
return c.makeCallNoReturn(
|
return c.makeCallNoReturn(
|
||||||
|
ctx,
|
||||||
func() error { return c.mainRPC.BatchCallContext(ctx, b) },
|
func() error { return c.mainRPC.BatchCallContext(ctx, b) },
|
||||||
func() error { return c.fallbackRPC.BatchCallContext(ctx, b) },
|
func() error { return c.fallbackRPC.BatchCallContext(ctx, b) },
|
||||||
)
|
)
|
||||||
|
@ -855,10 +942,11 @@ func (c *ClientWithFallback) ToBigInt() *big.Int {
|
||||||
return big.NewInt(int64(c.ChainID))
|
return big.NewInt(int64(c.ChainID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientWithFallback) GetBaseFeeFromBlock(blockNumber *big.Int) (string, error) {
|
func (c *ClientWithFallback) GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error) {
|
||||||
rpcstats.CountCall("eth_GetBaseFeeFromBlock")
|
rpcstats.CountCall("eth_GetBaseFeeFromBlock")
|
||||||
var feeHistory FeeHistory
|
|
||||||
err := c.mainRPC.Call(&feeHistory, "eth_feeHistory", "0x1", (*hexutil.Big)(blockNumber), nil)
|
feeHistory, err := c.FeeHistory(ctx, 1, blockNumber, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "the method eth_feeHistory does not exist/is not available" {
|
if err.Error() == "the method eth_feeHistory does not exist/is not available" {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -867,8 +955,8 @@ func (c *ClientWithFallback) GetBaseFeeFromBlock(blockNumber *big.Int) (string,
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseGasFee string = ""
|
var baseGasFee string = ""
|
||||||
if len(feeHistory.BaseFeePerGas) > 0 {
|
if len(feeHistory.BaseFee) > 0 {
|
||||||
baseGasFee = feeHistory.BaseFeePerGas[0]
|
baseGasFee = feeHistory.BaseFee[0].String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseGasFee, err
|
return baseGasFee, err
|
||||||
|
@ -881,6 +969,7 @@ func (c *ClientWithFallback) CallBlockHashByTransaction(ctx context.Context, blo
|
||||||
rpcstats.CountCall("eth_FullTransactionByBlockNumberAndIndex")
|
rpcstats.CountCall("eth_FullTransactionByBlockNumberAndIndex")
|
||||||
|
|
||||||
tx, err := c.makeCallSingleReturn(
|
tx, err := c.makeCallSingleReturn(
|
||||||
|
ctx,
|
||||||
func() (any, error) {
|
func() (any, error) {
|
||||||
return callBlockHashByTransaction(ctx, c.mainRPC, blockNumber, index)
|
return callBlockHashByTransaction(ctx, c.mainRPC, blockNumber, index)
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMaxRequestsPerSecond = 100
|
||||||
|
minRequestsPerSecond = 20
|
||||||
|
requestsPerSecondStep = 10
|
||||||
|
|
||||||
|
tickerInterval = 1 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRequestsOverLimit = fmt.Errorf("number of requests over limit")
|
||||||
|
)
|
||||||
|
|
||||||
|
type callerOnWait struct {
|
||||||
|
requests int
|
||||||
|
ch chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCLimiter struct {
|
||||||
|
uuid uuid.UUID
|
||||||
|
|
||||||
|
maxRequestsPerSecond int
|
||||||
|
maxRequestsPerSecondMutex sync.RWMutex
|
||||||
|
|
||||||
|
requestsMadeWithinSecond int
|
||||||
|
requestsMadeWithinSecondMutex sync.RWMutex
|
||||||
|
|
||||||
|
callersOnWaitForRequests []callerOnWait
|
||||||
|
callersOnWaitForRequestsMutex sync.RWMutex
|
||||||
|
|
||||||
|
quit chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRPCLimiter() *RPCLimiter {
|
||||||
|
|
||||||
|
limiter := RPCLimiter{
|
||||||
|
uuid: uuid.New(),
|
||||||
|
maxRequestsPerSecond: defaultMaxRequestsPerSecond,
|
||||||
|
quit: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
limiter.start()
|
||||||
|
|
||||||
|
return &limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RPCLimiter) ReduceLimit() {
|
||||||
|
rl.maxRequestsPerSecondMutex.Lock()
|
||||||
|
defer rl.maxRequestsPerSecondMutex.Unlock()
|
||||||
|
if rl.maxRequestsPerSecond <= minRequestsPerSecond {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rl.maxRequestsPerSecond = rl.maxRequestsPerSecond - requestsPerSecondStep
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RPCLimiter) start() {
|
||||||
|
ticker := time.NewTicker(tickerInterval)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
{
|
||||||
|
rl.requestsMadeWithinSecondMutex.Lock()
|
||||||
|
oldrequestsMadeWithinSecond := rl.requestsMadeWithinSecond
|
||||||
|
if rl.requestsMadeWithinSecond != 0 {
|
||||||
|
rl.requestsMadeWithinSecond = 0
|
||||||
|
}
|
||||||
|
rl.requestsMadeWithinSecondMutex.Unlock()
|
||||||
|
if oldrequestsMadeWithinSecond == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.callersOnWaitForRequestsMutex.Lock()
|
||||||
|
numOfRequestsToMakeAvailable := rl.maxRequestsPerSecond
|
||||||
|
for {
|
||||||
|
if numOfRequestsToMakeAvailable == 0 || len(rl.callersOnWaitForRequests) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = -1
|
||||||
|
for i := 0; i < len(rl.callersOnWaitForRequests); i++ {
|
||||||
|
if rl.callersOnWaitForRequests[i].requests <= numOfRequestsToMakeAvailable {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
callerOnWait := rl.callersOnWaitForRequests[index]
|
||||||
|
numOfRequestsToMakeAvailable -= callerOnWait.requests
|
||||||
|
rl.callersOnWaitForRequests = append(rl.callersOnWaitForRequests[:index], rl.callersOnWaitForRequests[index+1:]...)
|
||||||
|
|
||||||
|
callerOnWait.ch <- true
|
||||||
|
}
|
||||||
|
rl.callersOnWaitForRequestsMutex.Unlock()
|
||||||
|
|
||||||
|
case <-rl.quit:
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RPCLimiter) Stop() {
|
||||||
|
rl.quit <- true
|
||||||
|
close(rl.quit)
|
||||||
|
for _, callerOnWait := range rl.callersOnWaitForRequests {
|
||||||
|
close(callerOnWait.ch)
|
||||||
|
}
|
||||||
|
rl.callersOnWaitForRequests = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RPCLimiter) WaitForRequestsAvailability(requests int) error {
|
||||||
|
if requests > rl.maxRequestsPerSecond {
|
||||||
|
return ErrRequestsOverLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
rl.requestsMadeWithinSecondMutex.Lock()
|
||||||
|
if rl.requestsMadeWithinSecond+requests <= rl.maxRequestsPerSecond {
|
||||||
|
rl.requestsMadeWithinSecond += requests
|
||||||
|
rl.requestsMadeWithinSecondMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rl.requestsMadeWithinSecondMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
callerOnWait := callerOnWait{
|
||||||
|
requests: requests,
|
||||||
|
ch: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
rl.callersOnWaitForRequestsMutex.Lock()
|
||||||
|
rl.callersOnWaitForRequests = append(rl.callersOnWaitForRequests, callerOnWait)
|
||||||
|
rl.callersOnWaitForRequestsMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
<-callerOnWait.ch
|
||||||
|
|
||||||
|
close(callerOnWait.ch)
|
||||||
|
|
||||||
|
rl.requestsMadeWithinSecondMutex.Lock()
|
||||||
|
rl.requestsMadeWithinSecond += requests
|
||||||
|
rl.requestsMadeWithinSecondMutex.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,7 +6,9 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -51,6 +53,8 @@ type Client struct {
|
||||||
upstream chain.ClientInterface
|
upstream chain.ClientInterface
|
||||||
rpcClientsMutex sync.RWMutex
|
rpcClientsMutex sync.RWMutex
|
||||||
rpcClients map[uint64]chain.ClientInterface
|
rpcClients map[uint64]chain.ClientInterface
|
||||||
|
rpcLimiterMutex sync.RWMutex
|
||||||
|
limiterPerProvider map[string]*chain.RPCLimiter
|
||||||
|
|
||||||
router *router
|
router *router
|
||||||
NetworkManager *network.Manager
|
NetworkManager *network.Manager
|
||||||
|
@ -85,6 +89,7 @@ func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.U
|
||||||
NetworkManager: networkManager,
|
NetworkManager: networkManager,
|
||||||
handlers: make(map[string]Handler),
|
handlers: make(map[string]Handler),
|
||||||
rpcClients: make(map[uint64]chain.ClientInterface),
|
rpcClients: make(map[uint64]chain.ClientInterface),
|
||||||
|
limiterPerProvider: make(map[string]*chain.RPCLimiter),
|
||||||
log: log,
|
log: log,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +101,11 @@ func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.U
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dial upstream server: %s", err)
|
return nil, fmt.Errorf("dial upstream server: %s", err)
|
||||||
}
|
}
|
||||||
c.upstream = chain.NewSimpleClient(upstreamClient, upstreamChainID)
|
limiter, err := c.getRPCLimiter(c.upstreamURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get RPC limiter: %s", err)
|
||||||
|
}
|
||||||
|
c.upstream = chain.NewSimpleClient(limiter, upstreamClient, upstreamChainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.router = newRouter(c.upstreamEnabled)
|
c.router = newRouter(c.upstreamEnabled)
|
||||||
|
@ -112,6 +121,33 @@ func (c *Client) SetWalletNotifier(notifier func(chainID uint64, message string)
|
||||||
c.walletNotifier = notifier
|
c.walletNotifier = notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractLastParamFromURL(inputURL string) (string, error) {
|
||||||
|
parsedURL, err := url.Parse(inputURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pathSegments := strings.Split(parsedURL.Path, "/")
|
||||||
|
lastSegment := pathSegments[len(pathSegments)-1]
|
||||||
|
|
||||||
|
return lastSegment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getRPCLimiter(URL string) (*chain.RPCLimiter, error) {
|
||||||
|
apiKey, err := extractLastParamFromURL(URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.rpcLimiterMutex.Lock()
|
||||||
|
defer c.rpcLimiterMutex.Unlock()
|
||||||
|
if limiter, ok := c.limiterPerProvider[apiKey]; ok {
|
||||||
|
return limiter, nil
|
||||||
|
}
|
||||||
|
limiter := chain.NewRPCLimiter()
|
||||||
|
c.limiterPerProvider[apiKey] = limiter
|
||||||
|
return limiter, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, error) {
|
func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, error) {
|
||||||
c.rpcClientsMutex.Lock()
|
c.rpcClientsMutex.Lock()
|
||||||
defer c.rpcClientsMutex.Unlock()
|
defer c.rpcClientsMutex.Unlock()
|
||||||
|
@ -135,15 +171,28 @@ func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, err
|
||||||
return nil, fmt.Errorf("dial upstream server: %s", err)
|
return nil, fmt.Errorf("dial upstream server: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcFallbackClient *gethrpc.Client
|
rpcLimiter, err := c.getRPCLimiter(network.RPCURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get RPC limiter: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rpcFallbackClient *gethrpc.Client
|
||||||
|
rpcFallbackLimiter *chain.RPCLimiter
|
||||||
|
)
|
||||||
if len(network.FallbackURL) > 0 {
|
if len(network.FallbackURL) > 0 {
|
||||||
rpcFallbackClient, err = gethrpc.Dial(network.FallbackURL)
|
rpcFallbackClient, err = gethrpc.Dial(network.FallbackURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dial upstream server: %s", err)
|
return nil, fmt.Errorf("dial upstream server: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpcFallbackLimiter, err = c.getRPCLimiter(network.FallbackURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get RPC fallback limiter: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client := chain.NewClient(rpcClient, rpcFallbackClient, chainID)
|
client := chain.NewClient(rpcLimiter, rpcClient, rpcFallbackLimiter, rpcFallbackClient, chainID)
|
||||||
client.WalletNotifier = c.walletNotifier
|
client.WalletNotifier = c.walletNotifier
|
||||||
c.rpcClients[chainID] = client
|
c.rpcClients[chainID] = client
|
||||||
return client, nil
|
return client, nil
|
||||||
|
@ -199,8 +248,12 @@ func (c *Client) UpdateUpstreamURL(url string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
rpcLimiter, err := c.getRPCLimiter(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
c.Lock()
|
c.Lock()
|
||||||
c.upstream = chain.NewSimpleClient(rpcClient, c.UpstreamChainID)
|
c.upstream = chain.NewSimpleClient(rpcLimiter, rpcClient, c.UpstreamChainID)
|
||||||
c.upstreamURL = url
|
c.upstreamURL = url
|
||||||
c.Unlock()
|
c.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -288,7 +288,7 @@ func (tc *TestClient) CallBlockHashByTransaction(ctx context.Context, blockNumbe
|
||||||
return common.BigToHash(blockNumber), nil
|
return common.BigToHash(blockNumber), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TestClient) GetBaseFeeFromBlock(blockNumber *big.Int) (string, error) {
|
func (tc *TestClient) GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error) {
|
||||||
tc.incCounter("GetBaseFeeFromBlock")
|
tc.incCounter("GetBaseFeeFromBlock")
|
||||||
if tc.traceAPICalls {
|
if tc.traceAPICalls {
|
||||||
tc.t.Log("GetBaseFeeFromBlock")
|
tc.t.Log("GetBaseFeeFromBlock")
|
||||||
|
|
|
@ -109,7 +109,7 @@ func getTransferByHash(ctx context.Context, client chain.ClientInterface, signer
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseGasFee, err := client.GetBaseFeeFromBlock(big.NewInt(int64(transactionLog.BlockNumber)))
|
baseGasFee, err := client.GetBaseFeeFromBlock(ctx, big.NewInt(int64(transactionLog.BlockNumber)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue