From df0726567931c7774ff55786326c787dc5787ab3 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Wed, 1 Jun 2022 15:48:24 -0700 Subject: [PATCH] feat: ethclient gas estimation blocknumber param This commit adds the blocknumber param to the `ethclient`'s gas estimation function. This lets users choose the block number to estimate gas against. If `nil` is passed, then `pending` will be used to preserve existing behavior. Technically, a block tag can be used here but a blocknumber is used for simplicity. Gas estimation takes similar params as `eth_call`, and there are two different functions on the `ethclient` for doing `eth_call` - one by block number and one by block hash. The simulated backend is not updated to take into account the block number parameter, it will still use the pending state. That functionality can be added in the future if desired. Closes https://github.com/ethereum/go-ethereum/issues/25001 --- accounts/abi/bind/backend.go | 2 +- accounts/abi/bind/backends/simulated.go | 2 +- accounts/abi/bind/backends/simulated_test.go | 4 ++-- accounts/abi/bind/base.go | 8 ++++---- accounts/abi/bind/base_test.go | 2 +- ethclient/ethclient.go | 7 +++++-- ethclient/ethclient_test.go | 4 ++-- interfaces.go | 9 +++++---- mobile/ethclient.go | 4 ++-- 9 files changed, 23 insertions(+), 19 deletions(-) diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index c16990f395c4..81bbac30823b 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -92,7 +92,7 @@ type ContractTransactor interface { // 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. - EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) + EstimateGas(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) (gas uint64, err error) // SendTransaction injects the transaction into the pending pool for execution. SendTransaction(ctx context.Context, tx *types.Transaction) error diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index ac696f446be6..1d80a7133110 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -482,7 +482,7 @@ func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, erro // EstimateGas executes the requested code against the currently pending block/state and // returns the used amount of gas. -func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { +func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) (uint64, error) { b.mu.Lock() defer b.mu.Unlock() diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 8a0cbe335778..8d85b695a7a7 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -508,7 +508,7 @@ func TestEstimateGas(t *testing.T) { }, 21275, nil, nil}, } for _, c := range cases { - got, err := sim.EstimateGas(context.Background(), c.message) + got, err := sim.EstimateGas(context.Background(), c.message, nil) if c.expectError != nil { if err == nil { t.Fatalf("Expect error, got nil") @@ -602,7 +602,7 @@ func TestEstimateGasWithPrice(t *testing.T) { }, params.TxGas, errors.New("gas required exceeds allowance (20999)")}, // 20999=(2.2ether-0.1ether-1wei)/(1e14) } for i, c := range cases { - got, err := sim.EstimateGas(context.Background(), c.message) + got, err := sim.EstimateGas(context.Background(), c.message, nil) if c.expectError != nil { if err == nil { t.Fatalf("test %d: expect error, got nil", i) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index fe330014d35a..49636009b0c0 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -264,7 +264,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add gasLimit := opts.GasLimit if opts.GasLimit == 0 { var err error - gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value) + gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value, nil) if err != nil { return nil, err } @@ -308,7 +308,7 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr gasLimit := opts.GasLimit if opts.GasLimit == 0 { var err error - gasLimit, err = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value) + gasLimit, err = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value, nil) if err != nil { return nil, err } @@ -329,7 +329,7 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr return types.NewTx(baseTx), nil } -func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) { +func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value, blockNumber *big.Int) (uint64, error) { if contract != nil { // Gas estimation cannot succeed without code for method invocations. if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil { @@ -347,7 +347,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad Value: value, Data: input, } - return c.transactor.EstimateGas(ensureContext(opts.Context), msg) + return c.transactor.EstimateGas(ensureContext(opts.Context), msg, nil) } func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) { diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 25b2f8a865f2..7de66c397375 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -67,7 +67,7 @@ func (mt *mockTransactor) SuggestGasTipCap(ctx context.Context) (*big.Int, error return mt.gasTipCap, nil } -func (mt *mockTransactor) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { +func (mt *mockTransactor) EstimateGas(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) (gas uint64, err error) { return 0, nil } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 24edd8648ef3..7704c067328a 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -509,9 +509,12 @@ func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { // 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 (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { +func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (uint64, error) { var hex hexutil.Uint64 - err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg)) + if blockNumber == nil { + blockNumber = big.NewInt(-1) + } + err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg), toBlockNumArg(blockNumber)) if err != nil { return 0, err } diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 4a8727b37478..eed240521e1c 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -520,7 +520,7 @@ func testCallContractAtHash(t *testing.T, client *rpc.Client) { Gas: 21000, Value: big.NewInt(1), } - gas, err := ec.EstimateGas(context.Background(), msg) + gas, err := ec.EstimateGas(context.Background(), msg, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -547,7 +547,7 @@ func testCallContract(t *testing.T, client *rpc.Client) { Gas: 21000, Value: big.NewInt(1), } - gas, err := ec.EstimateGas(context.Background(), msg) + gas, err := ec.EstimateGas(context.Background(), msg, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/interfaces.go b/interfaces.go index 76c1ef6908f2..75ec2365cd60 100644 --- a/interfaces.go +++ b/interfaces.go @@ -220,11 +220,12 @@ type PendingContractCaller interface { } // GasEstimator wraps EstimateGas, which tries to estimate the gas needed to execute a -// specific transaction based on the pending state. 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. +// specific transaction based on the block number. Passing `nil` will operate on +// pending state. 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. type GasEstimator interface { - EstimateGas(ctx context.Context, call CallMsg) (uint64, error) + EstimateGas(ctx context.Context, call CallMsg, blockNumber *big.Int) (uint64, error) } // A PendingStateEventer provides access to real time notifications about changes to the diff --git a/mobile/ethclient.go b/mobile/ethclient.go index 662125c4adeb..f6acb0743aab 100644 --- a/mobile/ethclient.go +++ b/mobile/ethclient.go @@ -302,8 +302,8 @@ func (ec *EthereumClient) SuggestGasPrice(ctx *Context) (price *BigInt, _ error) // 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 (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg) (gas int64, _ error) { - rawGas, err := ec.client.EstimateGas(ctx.context, msg.msg) +func (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg, blockNumber *big.Int) (gas int64, _ error) { + rawGas, err := ec.client.EstimateGas(ctx.context, msg.msg, blockNumber) return int64(rawGas), err }