diff --git a/bindings/.githead b/bindings/.githead index e2887dcc3..a3457a40d 100644 --- a/bindings/.githead +++ b/bindings/.githead @@ -1 +1 @@ -4b23d14239a22ef8583845089ecf312bb75029ff +b9f3bdad77614657c7978f341c4d4b14ca295b61 diff --git a/cmd/flags/common.go b/cmd/flags/common.go index a253ec99f..5b0eafe45 100644 --- a/cmd/flags/common.go +++ b/cmd/flags/common.go @@ -95,6 +95,11 @@ var ( Category: commonCategory, Value: 12, } + RPCTimeout = &cli.Uint64Flag{ + Name: "rpc.timeout", + Usage: "Timeout in seconds for RPC calls", + Category: commonCategory, + } ) // All common flags. @@ -111,6 +116,7 @@ var CommonFlags = []cli.Flag{ MetricsPort, BackOffMaxRetrys, BackOffRetryInterval, + RPCTimeout, } // MergeFlags merges the given flag slices. diff --git a/driver/chain_syncer/beaconsync/progress_tracker.go b/driver/chain_syncer/beaconsync/progress_tracker.go index 5b15a0cc7..11dd9dad0 100644 --- a/driver/chain_syncer/beaconsync/progress_tracker.go +++ b/driver/chain_syncer/beaconsync/progress_tracker.go @@ -8,8 +8,8 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/taikoxyz/taiko-client/pkg/rpc" ) var ( @@ -21,7 +21,7 @@ var ( // connected peer or some other reasons). type SyncProgressTracker struct { // RPC client - client *ethclient.Client + client *rpc.EthClient // Meta data triggered bool @@ -41,7 +41,7 @@ type SyncProgressTracker struct { } // NewSyncProgressTracker creates a new SyncProgressTracker instance. -func NewSyncProgressTracker(c *ethclient.Client, timeout time.Duration) *SyncProgressTracker { +func NewSyncProgressTracker(c *rpc.EthClient, timeout time.Duration) *SyncProgressTracker { return &SyncProgressTracker{client: c, timeout: timeout, ticker: time.NewTicker(syncProgressCheckInterval)} } diff --git a/driver/config.go b/driver/config.go index 363dd8300..37135c095 100644 --- a/driver/config.go +++ b/driver/config.go @@ -23,6 +23,7 @@ type Config struct { P2PSyncVerifiedBlocks bool P2PSyncTimeout time.Duration BackOffRetryInterval time.Duration + RPCTimeout *time.Duration } // NewConfigFromCliContext creates a new config instance from @@ -42,6 +43,13 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { return nil, errors.New("empty L2 check point URL") } + var timeout *time.Duration + + if c.IsSet(flags.RPCTimeout.Name) { + duration := time.Duration(c.Uint64(flags.RPCTimeout.Name)) * time.Second + timeout = &duration + } + return &Config{ L1Endpoint: c.String(flags.L1WSEndpoint.Name), L2Endpoint: c.String(flags.L2WSEndpoint.Name), @@ -53,5 +61,6 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { P2PSyncVerifiedBlocks: p2pSyncVerifiedBlocks, P2PSyncTimeout: time.Duration(int64(time.Second) * int64(c.Uint(flags.P2PSyncTimeout.Name))), BackOffRetryInterval: time.Duration(c.Uint64(flags.BackOffRetryInterval.Name)) * time.Second, + RPCTimeout: timeout, }, nil } diff --git a/driver/config_test.go b/driver/config_test.go index 8ec0781e5..0d3aa2ae8 100644 --- a/driver/config_test.go +++ b/driver/config_test.go @@ -15,6 +15,7 @@ func (s *DriverTestSuite) TestNewConfigFromCliContext() { l2EngineEndpoint := os.Getenv("L2_EXECUTION_ENGINE_AUTH_ENDPOINT") taikoL1 := os.Getenv("TAIKO_L1_ADDRESS") taikoL2 := os.Getenv("TAIKO_L2_ADDRESS") + rpcTimeout := 5 * time.Second app := cli.NewApp() app.Flags = []cli.Flag{ @@ -25,6 +26,7 @@ func (s *DriverTestSuite) TestNewConfigFromCliContext() { &cli.StringFlag{Name: flags.TaikoL2Address.Name}, &cli.StringFlag{Name: flags.JWTSecret.Name}, &cli.UintFlag{Name: flags.P2PSyncTimeout.Name}, + &cli.UintFlag{Name: flags.RPCTimeout.Name}, } app.Action = func(ctx *cli.Context) error { c, err := NewConfigFromCliContext(ctx) @@ -35,6 +37,7 @@ func (s *DriverTestSuite) TestNewConfigFromCliContext() { s.Equal(taikoL1, c.TaikoL1Address.String()) s.Equal(taikoL2, c.TaikoL2Address.String()) s.Equal(120*time.Second, c.P2PSyncTimeout) + s.Equal(rpcTimeout, *c.RPCTimeout) s.NotEmpty(c.JwtSecret) s.Nil(new(Driver).InitFromCli(context.Background(), ctx)) @@ -50,5 +53,6 @@ func (s *DriverTestSuite) TestNewConfigFromCliContext() { "-" + flags.TaikoL2Address.Name, taikoL2, "-" + flags.JWTSecret.Name, os.Getenv("JWT_SECRET"), "-" + flags.P2PSyncTimeout.Name, "120", + "-" + flags.RPCTimeout.Name, "5", })) } diff --git a/driver/driver.go b/driver/driver.go index ecb00e538..b85f4db5d 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -21,7 +21,6 @@ import ( const ( protocolStatusReportInterval = 30 * time.Second - exchangeTransitionConfigTimeout = 30 * time.Second exchangeTransitionConfigInterval = 1 * time.Minute ) @@ -68,6 +67,7 @@ func InitFromConfig(ctx context.Context, d *Driver, cfg *Config) (err error) { L2EngineEndpoint: cfg.L2EngineEndpoint, JwtSecret: cfg.JwtSecret, RetryInterval: cfg.BackOffRetryInterval, + Timeout: cfg.RPCTimeout, }); err != nil { return err } @@ -250,10 +250,7 @@ func (d *Driver) exchangeTransitionConfigLoop() { return case <-ticker.C: func() { - ctx, cancel := context.WithTimeout(d.ctx, exchangeTransitionConfigTimeout) - defer cancel() - - tc, err := d.rpc.L2Engine.ExchangeTransitionConfiguration(ctx, &engine.TransitionConfigurationV1{ + tc, err := d.rpc.L2Engine.ExchangeTransitionConfiguration(d.ctx, &engine.TransitionConfigurationV1{ TerminalTotalDifficulty: (*hexutil.Big)(common.Big0), TerminalBlockHash: common.Hash{}, TerminalBlockNumber: 0, diff --git a/pkg/chain_iterator/block_batch_iterator.go b/pkg/chain_iterator/block_batch_iterator.go index 0203f2853..14a89ff39 100644 --- a/pkg/chain_iterator/block_batch_iterator.go +++ b/pkg/chain_iterator/block_batch_iterator.go @@ -11,8 +11,8 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/taikoxyz/taiko-client/pkg/rpc" ) const ( @@ -43,7 +43,7 @@ type EndIterFunc func() // with the awareness of reorganization. type BlockBatchIterator struct { ctx context.Context - client *ethclient.Client + client *rpc.EthClient chainID *big.Int blocksReadPerEpoch uint64 startHeight uint64 @@ -58,7 +58,7 @@ type BlockBatchIterator struct { // BlockBatchIteratorConfig represents the configs of a block batch iterator. type BlockBatchIteratorConfig struct { - Client *ethclient.Client + Client *rpc.EthClient MaxBlocksReadPerEpoch *uint64 StartHeight *big.Int EndHeight *big.Int diff --git a/pkg/chain_iterator/event_iterator/block_proposed_iterator.go b/pkg/chain_iterator/event_iterator/block_proposed_iterator.go index 5a6043731..c7774907e 100644 --- a/pkg/chain_iterator/event_iterator/block_proposed_iterator.go +++ b/pkg/chain_iterator/event_iterator/block_proposed_iterator.go @@ -7,9 +7,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/taikoxyz/taiko-client/bindings" chainIterator "github.com/taikoxyz/taiko-client/pkg/chain_iterator" + "github.com/taikoxyz/taiko-client/pkg/rpc" ) // EndBlockProposedEventIterFunc ends the current iteration. @@ -35,7 +35,7 @@ type BlockProposedIterator struct { // BlockProposedIteratorConfig represents the configs of a BlockProposed event iterator. type BlockProposedIteratorConfig struct { - Client *ethclient.Client + Client *rpc.EthClient TaikoL1 *bindings.TaikoL1Client MaxBlocksReadPerEpoch *uint64 StartHeight *big.Int @@ -95,7 +95,7 @@ func (i *BlockProposedIterator) end() { // assembleBlockProposedIteratorCallback assembles the callback which will be used // by a event iterator's inner block iterator. func assembleBlockProposedIteratorCallback( - client *ethclient.Client, + client *rpc.EthClient, taikoL1Client *bindings.TaikoL1Client, filterQuery []*big.Int, callback OnBlockProposedEvent, diff --git a/pkg/chain_iterator/event_iterator/block_proven_iterator.go b/pkg/chain_iterator/event_iterator/block_proven_iterator.go index e8e44dbe3..71aa5be4e 100644 --- a/pkg/chain_iterator/event_iterator/block_proven_iterator.go +++ b/pkg/chain_iterator/event_iterator/block_proven_iterator.go @@ -7,9 +7,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/taikoxyz/taiko-client/bindings" chainIterator "github.com/taikoxyz/taiko-client/pkg/chain_iterator" + "github.com/taikoxyz/taiko-client/pkg/rpc" ) // EndBlockProvenEventIterFunc ends the current iteration. @@ -31,7 +31,7 @@ type BlockProvenIterator struct { // BlockProvenIteratorConfig represents the configs of a BlockProven event iterator. type BlockProvenIteratorConfig struct { - Client *ethclient.Client + Client *rpc.EthClient TaikoL1 *bindings.TaikoL1Client MaxBlocksReadPerEpoch *uint64 StartHeight *big.Int @@ -91,7 +91,7 @@ func (i *BlockProvenIterator) end() { // assembleBlockProvenIteratorCallback assembles the callback which will be used // by a event iterator's inner block iterator. func assembleBlockProvenIteratorCallback( - client *ethclient.Client, + client *rpc.EthClient, taikoL1Client *bindings.TaikoL1Client, filterQuery []*big.Int, callback OnBlockProvenEvent, diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 87bb01892..e477bab33 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -8,18 +8,21 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/taikoxyz/taiko-client/bindings" ) +const ( + defaultTimeout = 1 * time.Minute +) + // Client contains all L1/L2 RPC clients that a driver needs. type Client struct { // Geth ethclient clients - L1 *ethclient.Client - L2 *ethclient.Client - L2CheckPoint *ethclient.Client + L1 *EthClient + L2 *EthClient + L2CheckPoint *EthClient // Geth gethclient clients L1GethClient *gethclient.Client L2GethClient *gethclient.Client @@ -50,15 +53,32 @@ type ClientConfig struct { L2EngineEndpoint string JwtSecret string RetryInterval time.Duration + Timeout *time.Duration } // NewClient initializes all RPC clients used by Taiko client softwares. func NewClient(ctx context.Context, cfg *ClientConfig) (*Client, error) { - l1RPC, err := DialClientWithBackoff(ctx, cfg.L1Endpoint, cfg.RetryInterval) + l1EthClient, err := DialClientWithBackoff(ctx, cfg.L1Endpoint, cfg.RetryInterval) if err != nil { return nil, err } + l2EthClient, err := DialClientWithBackoff(ctx, cfg.L2Endpoint, cfg.RetryInterval) + if err != nil { + return nil, err + } + + var l1RPC *EthClient + var l2RPC *EthClient + + if cfg.Timeout != nil { + l1RPC = NewEthClientWithTimeout(l1EthClient, *cfg.Timeout) + l2RPC = NewEthClientWithTimeout(l2EthClient, *cfg.Timeout) + } else { + l1RPC = NewEthClientWithDefaultTimeout(l1EthClient) + l2RPC = NewEthClientWithDefaultTimeout(l2EthClient) + } + taikoL1, err := bindings.NewTaikoL1Client(cfg.TaikoL1Address, l1RPC) if err != nil { return nil, err @@ -72,11 +92,6 @@ func NewClient(ctx context.Context, cfg *ClientConfig) (*Client, error) { } } - l2RPC, err := DialClientWithBackoff(ctx, cfg.L2Endpoint, cfg.RetryInterval) - if err != nil { - return nil, err - } - taikoL2, err := bindings.NewTaikoL2Client(cfg.TaikoL2Address, l2RPC) if err != nil { return nil, err @@ -130,11 +145,19 @@ func NewClient(ctx context.Context, cfg *ClientConfig) (*Client, error) { } } - var l2CheckPoint *ethclient.Client + var l2CheckPoint *EthClient if len(cfg.L2CheckPoint) != 0 { - if l2CheckPoint, err = DialClientWithBackoff(ctx, cfg.L2CheckPoint, cfg.RetryInterval); err != nil { + l2CheckPointEthClient, err := DialClientWithBackoff(ctx, cfg.L2CheckPoint, cfg.RetryInterval) + + if err != nil { return nil, err } + + if cfg.Timeout != nil { + l2CheckPoint = NewEthClientWithTimeout(l2CheckPointEthClient, *cfg.Timeout) + } else { + l2CheckPoint = NewEthClientWithDefaultTimeout(l2CheckPointEthClient) + } } client := &Client{ diff --git a/pkg/rpc/engine.go b/pkg/rpc/engine.go index 1516e3b25..293e37c4b 100644 --- a/pkg/rpc/engine.go +++ b/pkg/rpc/engine.go @@ -2,7 +2,6 @@ package rpc import ( "context" - "time" "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/rpc" @@ -21,7 +20,7 @@ func (c *EngineClient) ForkchoiceUpdate( fc *engine.ForkchoiceStateV1, attributes *engine.PayloadAttributes, ) (*engine.ForkChoiceResponse, error) { - timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + timeoutCtx, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() var result *engine.ForkChoiceResponse @@ -37,7 +36,7 @@ func (c *EngineClient) NewPayload( ctx context.Context, payload *engine.ExecutableData, ) (*engine.PayloadStatusV1, error) { - timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + timeoutCtx, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() var result *engine.PayloadStatusV1 @@ -53,7 +52,7 @@ func (c *EngineClient) GetPayload( ctx context.Context, payloadID *engine.PayloadID, ) (*engine.ExecutableData, error) { - timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + timeoutCtx, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() var result *engine.ExecutionPayloadEnvelope @@ -69,8 +68,11 @@ func (c *EngineClient) ExchangeTransitionConfiguration( ctx context.Context, cfg *engine.TransitionConfigurationV1, ) (*engine.TransitionConfigurationV1, error) { + timeoutCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + var result *engine.TransitionConfigurationV1 - if err := c.Client.CallContext(ctx, &result, "engine_exchangeTransitionConfigurationV1", cfg); err != nil { + if err := c.Client.CallContext(timeoutCtx, &result, "engine_exchangeTransitionConfigurationV1", cfg); err != nil { return nil, err } diff --git a/pkg/rpc/ethclient.go b/pkg/rpc/ethclient.go new file mode 100644 index 000000000..fb6073be7 --- /dev/null +++ b/pkg/rpc/ethclient.go @@ -0,0 +1,378 @@ +package rpc + +import ( + "context" + "math/big" + "time" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" +) + +// EthClient is a wrapper for go-ethereum ethclient with a timeout attached. +type EthClient struct { + *ethclient.Client + timeout time.Duration +} + +// NewEthClientWithTimeout creates a new EthClient instance with the given +// request timeout. +func NewEthClientWithTimeout( + ethclient *ethclient.Client, + timeout time.Duration, +) *EthClient { + if ethclient == nil { + return nil + } + + return &EthClient{Client: ethclient, timeout: timeout} +} + +// NewEthClientWithDefaultTimeout creates a new EthClient instance with the default +// timeout. +func NewEthClientWithDefaultTimeout( + ethclient *ethclient.Client, +) *EthClient { + if ethclient == nil { + return nil + } + + return &EthClient{Client: ethclient, timeout: defaultTimeout} +} + +// ctxWithTimeoutOrDefault sets a context timeout if the deadline has not passed or is not set, +// and otherwise returns the context as passed in. cancel func is always set to an empty function +// so is safe to defer the cancel. +func (c *EthClient) ctxWithTimeoutOrDefault(ctx context.Context) (context.Context, context.CancelFunc) { + var ( + ctxWithTimeout = ctx + cancel context.CancelFunc = func() {} + ) + if _, ok := ctx.Deadline(); !ok { + ctxWithTimeout, cancel = context.WithTimeout(ctx, c.timeout) + } + + return ctxWithTimeout, cancel +} + +// ChainID retrieves the current chain ID for transaction replay protection. +func (c *EthClient) ChainID(ctx context.Context) (*big.Int, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.ChainID(ctxWithTimeout) +} + +// BlockByHash returns the given full block. +// +// Note that loading full blocks requires two requests. Use HeaderByHash +// if you don't need all transactions or uncle headers. +func (c *EthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.BlockByHash(ctxWithTimeout, hash) +} + +// BlockByNumber returns a block from the current canonical chain. If number is nil, the +// latest known block is returned. +// +// Note that loading full blocks requires two requests. Use HeaderByNumber +// if you don't need all transactions or uncle headers. +func (c *EthClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.BlockByNumber(ctxWithTimeout, number) +} + +// BlockNumber returns the most recent block number +func (c *EthClient) BlockNumber(ctx context.Context) (uint64, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.BlockNumber(ctxWithTimeout) +} + +// PeerCount returns the number of p2p peers as reported by the net_peerCount method. +func (c *EthClient) PeerCount(ctx context.Context) (uint64, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.PeerCount(ctxWithTimeout) +} + +// HeaderByHash returns the block header with the given hash. +func (c *EthClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.HeaderByHash(ctxWithTimeout, hash) +} + +// HeaderByNumber returns a block header from the current canonical chain. If number is +// nil, the latest known header is returned. +func (c *EthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.HeaderByNumber(ctxWithTimeout, number) +} + +// TransactionByHash returns the transaction with the given hash. +func (c *EthClient) TransactionByHash( + ctx context.Context, + hash common.Hash, +) (tx *types.Transaction, isPending bool, err error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.TransactionByHash(ctxWithTimeout, hash) +} + +// TransactionSender returns the sender address of the given transaction. The transaction +// must be known to the remote node and included in the blockchain at the given block and +// index. The sender is the one derived by the protocol at the time of inclusion. +// +// There is a fast-path for transactions retrieved by TransactionByHash and +// TransactionInBlock. Getting their sender address can be done without an RPC interaction. +func (c *EthClient) TransactionSender( + ctx context.Context, + tx *types.Transaction, + block common.Hash, + index uint, +) (common.Address, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.TransactionSender(ctxWithTimeout, tx, block, index) +} + +// TransactionCount returns the total number of transactions in the given block. +func (c *EthClient) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.TransactionCount(ctxWithTimeout, blockHash) +} + +// TransactionInBlock returns a single transaction at index in the given block. +func (c *EthClient) TransactionInBlock( + ctx context.Context, + blockHash common.Hash, + index uint, +) (*types.Transaction, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.TransactionInBlock(ctxWithTimeout, blockHash, index) +} + +// SyncProgress retrieves the current progress of the sync algorithm. If there's +// no sync currently running, it returns nil. +func (c *EthClient) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.SyncProgress(ctxWithTimeout) +} + +// NetworkID returns the network ID for this client. +func (c *EthClient) NetworkID(ctx context.Context) (*big.Int, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.NetworkID(ctxWithTimeout) +} + +// BalanceAt returns the wei balance of the given account. +// The block number can be nil, in which case the balance is taken from the latest known block. +func (c *EthClient) BalanceAt( + ctx context.Context, + account common.Address, + blockNumber *big.Int, +) (*big.Int, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.BalanceAt(ctxWithTimeout, account, blockNumber) +} + +// StorageAt returns the value of key in the contract storage of the given account. +// The block number can be nil, in which case the value is taken from the latest known block. +func (c *EthClient) StorageAt( + ctx context.Context, + account common.Address, + key common.Hash, + blockNumber *big.Int, +) ([]byte, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.StorageAt(ctxWithTimeout, account, key, blockNumber) +} + +// CodeAt returns the contract code of the given account. +// The block number can be nil, in which case the code is taken from the latest known block. +func (c *EthClient) CodeAt( + ctx context.Context, + account common.Address, + blockNumber *big.Int, +) ([]byte, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.CodeAt(ctxWithTimeout, account, blockNumber) +} + +// NonceAt returns the account nonce of the given account. +// The block number can be nil, in which case the nonce is taken from the latest known block. +func (c *EthClient) NonceAt( + ctx context.Context, + account common.Address, + blockNumber *big.Int, +) (uint64, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.NonceAt(ctxWithTimeout, account, blockNumber) +} + +// PendingBalanceAt returns the wei balance of the given account in the pending state. +func (c *EthClient) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.PendingBalanceAt(ctxWithTimeout, account) +} + +// PendingStorageAt returns the value of key in the contract storage of the given account in the pending state. +func (c *EthClient) PendingStorageAt( + ctx context.Context, + account common.Address, + key common.Hash, +) ([]byte, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.PendingStorageAt(ctxWithTimeout, account, key) +} + +// PendingCodeAt returns the contract code of the given account in the pending state. +func (c *EthClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.PendingCodeAt(ctxWithTimeout, account) +} + +// PendingNonceAt returns the account nonce of the given account in the pending state. +// This is the nonce that should be used for the next transaction. +func (c *EthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.PendingNonceAt(ctxWithTimeout, account) +} + +// PendingTransactionCount returns the total number of transactions in the pending state. +func (c *EthClient) PendingTransactionCount(ctx context.Context) (uint, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.PendingTransactionCount(ctxWithTimeout) +} + +// CallContract executes a message call transaction, which is directly executed in the VM +// of the node, but never mined into the blockchain. +// +// blockNumber selects the block height at which the call runs. It can be nil, in which +// case the code is taken from the latest known block. Note that state from very old +// blocks might not be available. +func (c *EthClient) CallContract( + ctx context.Context, + msg ethereum.CallMsg, + blockNumber *big.Int, +) ([]byte, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.CallContract(ctxWithTimeout, msg, blockNumber) +} + +// CallContractAtHash is almost the same as CallContract except that it selects +// the block by block hash instead of block height. +func (c *EthClient) CallContractAtHash( + ctx context.Context, + msg ethereum.CallMsg, + blockHash common.Hash, +) ([]byte, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.CallContractAtHash(ctxWithTimeout, msg, blockHash) +} + +// PendingCallContract executes a message call transaction using the EVM. +// The state seen by the contract call is the pending state. +func (c *EthClient) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.PendingCallContract(ctxWithTimeout, msg) +} + +// SuggestGasPrice retrieves the currently suggested gas price to allow a timely +// execution of a transaction. +func (c *EthClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.SuggestGasPrice(ctxWithTimeout) +} + +// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to +// allow a timely execution of a transaction. +func (c *EthClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.SuggestGasTipCap(ctxWithTimeout) +} + +// FeeHistory retrieves the fee market history. +func (c *EthClient) FeeHistory( + ctx context.Context, + blockCount uint64, + lastBlock *big.Int, + rewardPercentiles []float64, +) (*ethereum.FeeHistory, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.FeeHistory(ctxWithTimeout, blockCount, lastBlock, rewardPercentiles) +} + +// EstimateGas tries to estimate the gas needed to execute a specific transaction based on +// the current pending state of the backend blockchain. There is no guarantee that this is +// the true gas limit requirement as other transactions may be added or removed by miners, +// but it should provide a basis for setting a reasonable default. +func (c *EthClient) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.EstimateGas(ctxWithTimeout, msg) +} + +// SendTransaction injects a signed transaction into the pending pool for execution. +// +// If the transaction was a contract creation use the TransactionReceipt method to get the +// contract address after the transaction has been mined. +func (c *EthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ctxWithTimeout, cancel := c.ctxWithTimeoutOrDefault(ctx) + defer cancel() + + return c.Client.SendTransaction(ctxWithTimeout, tx) +} diff --git a/pkg/rpc/subscription.go b/pkg/rpc/subscription.go index 4ce6489a0..817987446 100644 --- a/pkg/rpc/subscription.go +++ b/pkg/rpc/subscription.go @@ -5,7 +5,6 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/taikoxyz/taiko-client/bindings" @@ -120,7 +119,7 @@ func SubscribeSlashed( // SubscribeChainHead subscribes the new chain heads. func SubscribeChainHead( - client *ethclient.Client, + client *EthClient, ch chan *types.Header, ) event.Subscription { return SubscribeEvent("ChainHead", func(ctx context.Context) (event.Subscription, error) { diff --git a/pkg/rpc/utils.go b/pkg/rpc/utils.go index 63d01a86e..06e03c9ba 100644 --- a/pkg/rpc/utils.go +++ b/pkg/rpc/utils.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" @@ -39,7 +38,11 @@ func GetProtocolStateVariables( // WaitReceipt keeps waiting until the given transaction has an execution // receipt to know whether it was reverted or not. -func WaitReceipt(ctx context.Context, client *ethclient.Client, tx *types.Transaction) (*types.Receipt, error) { +func WaitReceipt( + ctx context.Context, + client *EthClient, + tx *types.Transaction, +) (*types.Receipt, error) { ticker := time.NewTicker(waitReceiptPollingInterval) defer ticker.Stop() @@ -176,7 +179,7 @@ func StringToBytes32(str string) [32]byte { } // IsArchiveNode checks if the given node is an archive node. -func IsArchiveNode(ctx context.Context, client *ethclient.Client, l2GenesisHeight uint64) (bool, error) { +func IsArchiveNode(ctx context.Context, client *EthClient, l2GenesisHeight uint64) (bool, error) { if _, err := client.BalanceAt(ctx, zeroAddress, new(big.Int).SetUint64(l2GenesisHeight)); err != nil { if strings.Contains(err.Error(), "missing trie node") { return false, nil diff --git a/proposer/config.go b/proposer/config.go index 84eb8ba74..5cd153af5 100644 --- a/proposer/config.go +++ b/proposer/config.go @@ -30,6 +30,7 @@ type Config struct { ProposeBlockTxGasLimit *uint64 BackOffRetryInterval time.Duration ProposeBlockTxReplacementMultiplier uint64 + RPCTimeout *time.Duration } // NewConfigFromCliContext initializes a Config instance from @@ -90,6 +91,13 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { ) } + var timeout *time.Duration + + if c.IsSet(flags.RPCTimeout.Name) { + duration := time.Duration(c.Uint64(flags.RPCTimeout.Name)) * time.Second + timeout = &duration + } + return &Config{ L1Endpoint: c.String(flags.L1WSEndpoint.Name), L2Endpoint: c.String(flags.L2HTTPEndpoint.Name), @@ -107,5 +115,6 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { ProposeBlockTxGasLimit: proposeBlockTxGasLimit, BackOffRetryInterval: time.Duration(c.Uint64(flags.BackOffRetryInterval.Name)) * time.Second, ProposeBlockTxReplacementMultiplier: proposeBlockTxReplacementMultiplier, + RPCTimeout: timeout, }, nil } diff --git a/proposer/config_test.go b/proposer/config_test.go index 0a2fd4aea..ecd7ca6e7 100644 --- a/proposer/config_test.go +++ b/proposer/config_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "strconv" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -18,6 +19,7 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() { taikoL2 := os.Getenv("TAIKO_L2_ADDRESS") proposeInterval := "10s" commitSlot := 1024 + rpcTimeout := 5 * time.Second goldenTouchAddress, err := s.RpcClient.TaikoL2.GOLDENTOUCHADDRESS(nil) s.Nil(err) @@ -37,6 +39,7 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() { &cli.Uint64Flag{Name: flags.CommitSlot.Name}, &cli.StringFlag{Name: flags.TxPoolLocals.Name}, &cli.Uint64Flag{Name: flags.ProposeBlockTxReplacementMultiplier.Name}, + &cli.Uint64Flag{Name: flags.RPCTimeout.Name}, } app.Action = func(ctx *cli.Context) error { c, err := NewConfigFromCliContext(ctx) @@ -52,6 +55,7 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() { s.Equal(1, len(c.LocalAddresses)) s.Equal(goldenTouchAddress, c.LocalAddresses[0]) s.Equal(uint64(5), c.ProposeBlockTxReplacementMultiplier) + s.Equal(rpcTimeout, *c.RPCTimeout) s.Nil(new(Proposer).InitFromCli(context.Background(), ctx)) return err @@ -69,5 +73,6 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() { "-" + flags.CommitSlot.Name, strconv.Itoa(commitSlot), "-" + flags.TxPoolLocals.Name, goldenTouchAddress.Hex(), "-" + flags.ProposeBlockTxReplacementMultiplier.Name, "5", + "-" + flags.RPCTimeout.Name, "5", })) } diff --git a/proposer/proposer.go b/proposer/proposer.go index 0baa80420..01f80ce8a 100644 --- a/proposer/proposer.go +++ b/proposer/proposer.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/taikoxyz/taiko-client/bindings" @@ -100,6 +99,7 @@ func InitFromConfig(ctx context.Context, p *Proposer, cfg *Config) (err error) { TaikoL1Address: cfg.TaikoL1Address, TaikoL2Address: cfg.TaikoL2Address, RetryInterval: cfg.BackOffRetryInterval, + Timeout: cfg.RPCTimeout, }); err != nil { return fmt.Errorf("initialize rpc clients error: %w", err) } @@ -475,7 +475,7 @@ func sumTxsGasLimit(txs []*types.Transaction) uint64 { // getTxOpts creates a bind.TransactOpts instance using the given private key. func getTxOpts( ctx context.Context, - cli *ethclient.Client, + cli *rpc.EthClient, privKey *ecdsa.PrivateKey, chainID *big.Int, ) (*bind.TransactOpts, error) { diff --git a/prover/config.go b/prover/config.go index 7abccf338..0a6ee2567 100644 --- a/prover/config.go +++ b/prover/config.go @@ -39,6 +39,7 @@ type Config struct { BackOffRetryInterval time.Duration CheckProofWindowExpiredInterval time.Duration ProveUnassignedBlocks bool + RPCTimeout *time.Duration } // NewConfigFromCliContext creates a new config instance from command line flags. @@ -100,6 +101,13 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { startingBlockID = new(big.Int).SetUint64(c.Uint64(flags.StartingBlockID.Name)) } + var timeout *time.Duration + + if c.IsSet(flags.RPCTimeout.Name) { + duration := time.Duration(c.Uint64(flags.RPCTimeout.Name)) * time.Second + timeout = &duration + } + return &Config{ L1WsEndpoint: c.String(flags.L1WSEndpoint.Name), L1HttpEndpoint: c.String(flags.L1HTTPEndpoint.Name), @@ -127,5 +135,6 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { c.Uint64(flags.CheckProofWindowExpiredInterval.Name), ) * time.Second, ProveUnassignedBlocks: c.Bool(flags.ProveUnassignedBlocks.Name), + RPCTimeout: timeout, }, nil } diff --git a/prover/config_test.go b/prover/config_test.go index 8f6fd4217..386987730 100644 --- a/prover/config_test.go +++ b/prover/config_test.go @@ -26,6 +26,7 @@ var testFlags = []cli.Flag{ &cli.StringFlag{Name: flags.TaikoProverPoolL1Address.Name}, &cli.Uint64Flag{Name: flags.CheckProofWindowExpiredInterval.Name}, &cli.BoolFlag{Name: flags.ProveUnassignedBlocks.Name}, + &cli.Uint64Flag{Name: flags.RPCTimeout.Name}, } func (s *ProverTestSuite) TestNewConfigFromCliContext_OracleProver() { @@ -64,6 +65,7 @@ func (s *ProverTestSuite) TestNewConfigFromCliContext_OracleProver() { s.Equal("", c.Graffiti) s.Equal(30*time.Second, c.CheckProofWindowExpiredInterval) s.Equal(true, c.ProveUnassignedBlocks) + s.Nil(c.RPCTimeout) s.Nil(new(Prover).InitFromCli(context.Background(), ctx)) return err @@ -120,5 +122,6 @@ func (s *ProverTestSuite) TestNewConfigFromCliContext_OracleProverError() { "-" + flags.RandomDummyProofDelay.Name, "30m-1h", "-" + flags.OracleProver.Name, "-" + flags.Graffiti.Name, "", + "-" + flags.RPCTimeout.Name, "5", }), "oracleProver flag set without oracleProverPrivateKey set") } diff --git a/prover/proof_submitter/util.go b/prover/proof_submitter/util.go index fa6e54fd4..8648df37e 100644 --- a/prover/proof_submitter/util.go +++ b/prover/proof_submitter/util.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/taikoxyz/taiko-client/bindings" "github.com/taikoxyz/taiko-client/bindings/encoding" @@ -39,7 +38,7 @@ func isSubmitProofTxErrorRetryable(err error, blockID *big.Int) bool { // Used for creating TaikoL1.proveBlock and TaikoL1.proveBlockInvalid transactions. func getProveBlocksTxOpts( ctx context.Context, - cli *ethclient.Client, + cli *rpc.EthClient, chainID *big.Int, proverPrivKey *ecdsa.PrivateKey, ) (*bind.TransactOpts, error) { diff --git a/prover/prover.go b/prover/prover.go index 5d1ddbbd8..33607e96d 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -116,6 +116,7 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { TaikoL2Address: cfg.TaikoL2Address, TaikoProverPoolL1Address: cfg.TaikoProverPoolL1Address, RetryInterval: cfg.BackOffRetryInterval, + Timeout: cfg.RPCTimeout, }); err != nil { return err }