From f170b7d89a526792be1769334677d1cf12aae0d5 Mon Sep 17 00:00:00 2001 From: LeoZhang Date: Tue, 12 Mar 2024 10:29:52 +0800 Subject: [PATCH] Revert "[R4R]-{estimateGas}feature: EstimateGas performance optimization (#38)" This reverts commit bb4117ba83a5ab6dbccf8d78c0555d8d4cf51ad2. --- common/types.go | 7 +- core/state_transition.go | 66 ++--- eth/tracers/api.go | 2 +- graphql/graphql.go | 10 +- internal/ethapi/api.go | 203 ++++----------- internal/ethapi/api_test.go | 368 ---------------------------- internal/ethapi/transaction_args.go | 52 ++-- params/config.go | 29 --- 8 files changed, 100 insertions(+), 637 deletions(-) diff --git a/common/types.go b/common/types.go index 9ed46aa0e0..218ca0be4c 100644 --- a/common/types.go +++ b/common/types.go @@ -195,7 +195,7 @@ func (h UnprefixedHash) MarshalText() ([]byte, error) { return []byte(hex.EncodeToString(h[:])), nil } -// ///////// Address +/////////// Address // Address represents the 20 byte address of an Ethereum account. type Address [AddressLength]byte @@ -309,11 +309,6 @@ func (a *Address) SetBytes(b []byte) { copy(a[AddressLength-len(b):], b) } -// Cmp compares two addresses. -func (a Address) Cmp(other Address) int { - return bytes.Compare(a[:], other[:]) -} - // MarshalText returns the hex representation of a. func (a Address) MarshalText() ([]byte, error) { return hexutil.Bytes(a[:]).MarshalText() diff --git a/core/state_transition.go b/core/state_transition.go index 28ab6f6d1c..6524fcc2fb 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -39,10 +39,9 @@ var ( // ExecutionResult includes all output after executing given evm // message no matter the execution itself is successful or not. type ExecutionResult struct { - UsedGas uint64 // Total used gas but include the refunded gas - RefundedGas uint64 // Total gas refunded after execution - Err error // Any error encountered during the execution(listed in core/vm/errors.go) - ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) + UsedGas uint64 // Total used gas but include the refunded gas + Err error // Any error encountered during the execution(listed in core/vm/errors.go) + ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) } // Unwrap returns the internal evm error which allows us for further @@ -237,11 +236,6 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err return NewStateTransition(evm, msg, gp).TransitionDb() } -// CalculateL1Cost calculates the L1 cost for a transaction without modifying the state. -func CalculateL1Cost(evm *vm.EVM, msg *Message, gp *GasPool) (*big.Int, error) { - return NewStateTransition(evm, msg, gp).CalculateL1Cost() -} - // StateTransition represents a state transition. // // == The State Transitioning Model @@ -291,23 +285,6 @@ func (st *StateTransition) to() common.Address { return *st.msg.To } -// CalculateL1Cost calculates the L1 cost for a transaction without modifying the state. -func (st *StateTransition) CalculateL1Cost() (*big.Int, error) { - var l1Cost *big.Int - - // Calculate rollup gas data from the message if necessary - if st.msg.RunMode == GasEstimationMode || st.msg.RunMode == GasEstimationWithSkipCheckBalanceMode { - st.CalculateRollupGasDataFromMessage() - } - - // Calculate L1 cost if L1CostFunc is defined and not in EthcallMode - if st.evm.Context.L1CostFunc != nil && st.msg.RunMode != EthcallMode { - l1Cost = st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg.RollupDataGas, st.msg.IsDepositTx, st.msg.To) - } - - return l1Cost, nil -} - func (st *StateTransition) buyGas() (*big.Int, error) { if err := st.applyMetaTransaction(); err != nil { return nil, err @@ -468,9 +445,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.AddBalance(st.msg.From, mint) } - // Mint BVM_ETH + //Mint BVM_ETH rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time) - // add eth value + //add eth value if ethValue := st.msg.ETHValue; ethValue != nil && ethValue.Cmp(big.NewInt(0)) != 0 { st.mintBVMETH(ethValue, rules) } @@ -616,24 +593,22 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { // Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice // is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually // changing the sender's balance - var gasRefund uint64 if !st.msg.IsDepositTx && !st.msg.IsSystemTx { if !rules.IsLondon { // Before EIP-3529: refunds were capped to gasUsed / 2 - gasRefund = st.refundGas(params.RefundQuotient, tokenRatio) + st.refundGas(params.RefundQuotient, tokenRatio) } else { // After EIP-3529: refunds are capped to gasUsed / 5 - gasRefund = st.refundGas(params.RefundQuotientEIP3529, tokenRatio) + st.refundGas(params.RefundQuotientEIP3529, tokenRatio) } } if st.msg.IsDepositTx && rules.IsOptimismRegolith { // Skip coinbase payments for deposit tx in Regolith return &ExecutionResult{ - UsedGas: st.gasUsed(), - RefundedGas: gasRefund, - Err: vmerr, - ReturnData: ret, + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, }, nil } effectiveTip := msg.GasPrice @@ -656,24 +631,23 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && rules.IsOptimismBedrock { st.state.AddBalance(params.OptimismBaseFeeRecipient, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)) // Can not collect l1 fee here again, all l1 fee has been collected by CoinBase & OptimismBaseFeeRecipient - // if cost := st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg.RollupDataGas, st.msg.IsDepositTx); cost != nil { + //if cost := st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg.RollupDataGas, st.msg.IsDepositTx); cost != nil { // st.state.AddBalance(params.OptimismL1FeeRecipient, cost) - // } + //} } return &ExecutionResult{ - UsedGas: st.gasUsed(), - RefundedGas: gasRefund, - Err: vmerr, - ReturnData: ret, + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, }, nil } -func (st *StateTransition) refundGas(refundQuotient, tokenRatio uint64) uint64 { +func (st *StateTransition) refundGas(refundQuotient, tokenRatio uint64) { if st.msg.RunMode == GasEstimationWithSkipCheckBalanceMode || st.msg.RunMode == EthcallMode { st.gasRemaining = st.gasRemaining * tokenRatio st.gp.AddGas(st.gasRemaining) - return 0 + return } // Apply refund counter, capped to a refund quotient refund := st.gasUsed() / refundQuotient @@ -699,8 +673,6 @@ func (st *StateTransition) refundGas(refundQuotient, tokenRatio uint64) uint64 { // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gasRemaining) - - return refund } // gasUsed returns the amount of gas used up by the state transition. @@ -801,7 +773,7 @@ func (st *StateTransition) generateBVMETHMintEvent(mintAddress common.Address, m topics := make([]common.Hash, 2) topics[0] = methodHash topics[1] = mintAddress.Hash() - // data means the mint amount in MINT EVENT. + //data means the mint amount in MINT EVENT. d := common.HexToHash(common.Bytes2Hex(mintValue.Bytes())).Bytes() st.evm.StateDB.AddLog(&types.Log{ Address: BVM_ETH_ADDR, @@ -820,7 +792,7 @@ func (st *StateTransition) generateBVMETHTransferEvent(from, to common.Address, topics[0] = methodHash topics[1] = from.Hash() topics[2] = to.Hash() - // data means the transfer amount in Transfer EVENT. + //data means the transfer amount in Transfer EVENT. data := common.HexToHash(common.Bytes2Hex(amount.Bytes())).Bytes() st.evm.StateDB.AddLog(&types.Log{ Address: BVM_ETH_ADDR, diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5b32e474d4..28c4c2ca5c 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -985,7 +985,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc config.BlockOverrides.Apply(&vmctx) } // Execute the trace - msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee(), core.EthcallMode) + msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee(), core.EthcallMode, args.GasPrice) if err != nil { return nil, err } diff --git a/graphql/graphql.go b/graphql/graphql.go index 43f140d2d3..65d2b627f4 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -54,16 +54,16 @@ func (b *Long) UnmarshalGraphQL(input interface{}) error { switch input := input.(type) { case string: // uncomment to support hex values - // if strings.HasPrefix(input, "0x") { + //if strings.HasPrefix(input, "0x") { // // apply leniency and support hex representations of longs. // value, err := hexutil.DecodeUint64(input) // *b = Long(value) // return err - // } else { + //} else { value, err := strconv.ParseInt(input, 10, 64) *b = Long(value) return err - // } + //} case int32: *b = Long(input) case int64: @@ -1070,7 +1070,7 @@ func (b *Block) Call(ctx context.Context, args struct { return nil, err } } - result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap(), core.EthcallMode) + result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap(), core.EthcallMode, args.Data.GasPrice) if err != nil { return nil, err } @@ -1140,7 +1140,7 @@ func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap(), core.EthcallMode) + result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap(), core.EthcallMode, args.Data.GasPrice) if err != nil { return nil, err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 6621ca55d6..f1ae3e8735 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -25,8 +25,11 @@ import ( "strings" "time" - "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum" + + "github.com/davecgh/go-spew/spew" + "github.com/tyler-smith/go-bip39" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -47,15 +50,10 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/tyler-smith/go-bip39" ) -// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is -// allowed to produce in order to speed up calculations. -// gasBuffer is used to enlarge a buffer for Estimation -const ( - estimateGasErrorRatio = 0.015 - gasBuffer = uint64(120) +var ( + gasBuffer = uint64(120) ) // EthereumAPI provides an API to access Ethereum related information. @@ -1081,7 +1079,7 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) { } } -func DoCalculateL1Cost(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, runMode core.RunMode) (*big.Int, error) { +func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, runMode core.RunMode, gasPriceForEstimate *hexutil.Big) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) @@ -1104,54 +1102,7 @@ func DoCalculateL1Cost(ctx context.Context, b Backend, args TransactionArgs, blo defer cancel() // Get a new instance of the EVM. - msg, err := args.ToMessage(globalGasCap, header.BaseFee, runMode) - if err != nil { - return nil, err - } - evm, _, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}) - if err != nil { - return nil, err - } - // Wait for the context to be done and cancel the evm. Even if the - // EVM has finished, cancelling may be done (repeatedly) - go func() { - <-ctx.Done() - evm.Cancel() - }() - - // Execute the message. - gp := new(core.GasPool).AddGas(core.DefaultMantleBlockGasLimit) - l1Cost, err := core.CalculateL1Cost(evm, msg, gp) - if err != nil { - return nil, err - } - return l1Cost, nil -} - -func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, runMode core.RunMode) (*core.ExecutionResult, error) { - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - - state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if state == nil || err != nil { - return nil, err - } - if err := overrides.Apply(state); err != nil { - return nil, err - } - // Setup context so it may be cancelled the call has completed - // or, in case of unmetered gas, setup a context with a timeout. - var cancel context.CancelFunc - if timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, timeout) - } else { - ctx, cancel = context.WithCancel(ctx) - } - // Make sure the context is cancelled when the call has completed - // this makes sure resources are cleaned up. - defer cancel() - - // Get a new instance of the EVM. - msg, err := args.ToMessage(globalGasCap, header.BaseFee, runMode) + msg, err := args.ToMessage(globalGasCap, header.BaseFee, runMode, gasPriceForEstimate) if err != nil { return nil, err } @@ -1238,7 +1189,7 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO } } - result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), core.EthcallMode) + result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), core.EthcallMode, args.GasPrice) if err != nil { return nil, err } @@ -1252,25 +1203,27 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( - lo = params.TxGas - 1 - hi uint64 + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 ) // Use zero address if sender unspecified. if args.From == nil { args.From = new(common.Address) } - - data := hexutil.Bytes(args.data()) - args.Data = &data - - state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if err != nil { - return 0, err - } // Determine the highest gas limit can be used during the estimation. - hi = header.GasLimit if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { hi = uint64(*args.Gas) + } else { + // Retrieve the block to act as the gas ceiling + block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return 0, err + } + if block == nil { + return 0, errors.New("block not found") + } + hi = block.GasLimit() } // Normalize the gasPrice used for estimateGas @@ -1301,6 +1254,11 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr // Recap the highest gas limit with account's available balance. if feeCap.BitLen() != 0 { + state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return 0, err + } + balance := state.GetBalance(*args.From) // from can't be nil metaTxParams, err := types.DecodeMetaTxParams(args.data()) if err != nil { @@ -1325,13 +1283,6 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr } } - l1Cost, err := DoCalculateL1Cost(ctx, b, args, blockNrOrHash, nil, 0, gasCap, runMode) - if err != nil { - return 0, fmt.Errorf("failed to calculate L1 cost: %w", err) - } - available.Sub(available, l1Cost) - - // Calculate gas limit based on buffer. allowance := new(big.Int).Div(available, feeCap) // If the allowance is larger than maximum uint64, skip checking @@ -1350,19 +1301,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) hi = gasCap } - - revertErr := func(result *core.ExecutionResult) error { - if len(result.Revert()) > 0 { - return newRevertError(result) - } - return result.Err - } + cap = hi // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap, runMode) + result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap, runMode, (*hexutil.Big)(gasPriceForEstimateGas)) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit @@ -1371,84 +1316,15 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr } return result.Failed(), result, nil } - - // If the transaction is a plain value transfer, short circuit estimation and - // directly try 21000. Returning 21000 without any execution is dangerous as - // some tx field combos might bump the price up even for plain transfers (e.g. - // unused access list items). Ever so slightly wasteful, but safer overall. - if args.Data == nil { - if args.To != nil && state.GetCodeSize(*args.To) == 0 { - failed, _, err := executable(params.TxGas) - if !failed && err == nil { - return hexutil.Uint64(params.TxGas), nil - } - } - } - - // We first execute the transaction at the highest allowable gas limit, since if this fails we - // can return error immediately. - failed, result, err := executable(hi) - if err != nil { - return 0, err - } - if failed { - if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) { - return 0, revertErr(result) - } - return 0, fmt.Errorf("gas required exceeds allowance (%d)", hi) - } - - // For almost any transaction, the gas consumed by the unconstrained execution - // above lower-bounds the gas limit required for it to succeed. One exception - // is those that explicitly check gas remaining in order to execute within a - // given limit, but we probably don't want to return the lowest possible gas - // limit for these cases anyway. - lo = result.UsedGas - 1 - - // There's a fairly high chance for the transaction to execute successfully - // with gasLimit set to the first execution's usedGas + gasRefund. Explicitly - // check that gas amount and use as a limit for the binary search. - optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63 - if optimisticGasLimit < hi { - failed, _, err = executable(optimisticGasLimit) - if err != nil { - // This should not happen under normal conditions since if we make it this far the - // transaction had run without error at least once before. - log.Error("Execution error in estimate gas", "err", err) - return 0, err - } - if failed { - lo = optimisticGasLimit - } else { - hi = optimisticGasLimit - } - } - // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { - // It is a bit pointless to return a perfect estimation, as changing - // network conditions require the caller to bump it up anyway. Since - // wallets tend to use 20-25% bump, allowing a small approximation - // error is fine (as long as it's upwards). - if float64(hi-lo)/float64(hi) < estimateGasErrorRatio { - break - } - mid := (hi + lo) / 2 - if mid > lo*2 { - // Most txs don't need much higher gas limit than their gas used, and most txs don't - // require near the full block limit of gas, so the selection of where to bisect the - // range here is skewed to favor the low side. - mid = lo * 2 - } - - failed, _, err = executable(mid) + failed, _, err := executable(mid) // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is // assigned. Return the error directly, don't struggle any more. if err != nil { - log.Error("Execution error in estimate gas", "err", err) return 0, err } if failed { @@ -1457,6 +1333,23 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr hi = mid } } + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == cap { + failed, result, err := executable(hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfGas { + if len(result.Revert()) > 0 { + return 0, newRevertError(result) + } + return 0, result.Err + } + // Otherwise, the specified gas cap is too low + return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) + } + } return hexutil.Uint64(hi * gasBuffer / 100), nil } @@ -1849,7 +1742,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH statedb := db.Copy() // Set the accesslist to the last al args.AccessList = &accessList - msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee, core.EthcallMode) + msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee, core.EthcallMode, args.GasPrice) if err != nil { return nil, 0, nil, err } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 524dda438b..f950ce4011 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -17,36 +17,16 @@ package ethapi import ( - "context" - "crypto/ecdsa" "encoding/json" - "errors" "math/big" "testing" - "time" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/beacon" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/bloombits" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/require" - "golang.org/x/exp/slices" ) func TestNewRPCTransactionDepositTx(t *testing.T) { @@ -294,351 +274,3 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []type }, } } - -func newTestAccountManager(t *testing.T) (*accounts.Manager, accounts.Account) { - var ( - dir = t.TempDir() - am = accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: true}) - b = keystore.NewKeyStore(dir, 2, 1) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - ) - acc, err := b.ImportECDSA(testKey, "") - if err != nil { - t.Fatalf("failed to create test account: %v", err) - } - if err := b.Unlock(acc, ""); err != nil { - t.Fatalf("failed to unlock account: %v\n", err) - } - am.AddBackend(b) - return am, acc -} - -type testBackend struct { - db ethdb.Database - chain *core.BlockChain - pending *types.Block - accman *accounts.Manager - acc accounts.Account -} - -func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { - var ( - cacheConfig = &core.CacheConfig{ - TrieCleanLimit: 256, - TrieDirtyLimit: 256, - TrieTimeLimit: 5 * time.Minute, - SnapshotLimit: 0, - TrieDirtyDisabled: true, // Archive mode - } - ) - accman, acc := newTestAccountManager(t) - gspec.Alloc[acc.Address] = core.GenesisAccount{Balance: big.NewInt(params.Ether)} - // Generate blocks for testing - db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator) - txlookupLimit := uint64(0) - chain, err := core.NewBlockChain(db, cacheConfig, gspec, nil, engine, vm.Config{}, nil, &txlookupLimit) - if err != nil { - t.Fatalf("failed to create tester chain: %v", err) - } - if n, err := chain.InsertChain(blocks); err != nil { - t.Fatalf("block %d: failed to insert into chain: %v", n, err) - } - - backend := &testBackend{db: db, chain: chain, accman: accman, acc: acc} - return backend -} - -func (b *testBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { - // TODO implement me - panic("implement me") -} - -func (b *testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) { - if vmConfig == nil { - defaultVMConfig := b.chain.GetVMConfig() - vmConfig = defaultVMConfig - } - - // Build Transaction Context - txContext := core.NewEVMTxContext(msg) - - // Build Block Context - blockContext := core.NewEVMBlockContext(header, b.chain, nil, b.chain.Config(), state) - - // Initialize EVM with context and configuration. - evm := vm.NewEVM(blockContext, txContext, state, b.chain.Config(), *vmConfig) - - // Return the EVM instance, an anonymous function that always returns nil as the error handling function, and nil as the error return value. - return evm, func() error { return nil }, nil -} - -func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { - tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) - return tx, blockHash, blockNumber, index, nil -} - -func (b *testBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { - // TODO implement me - panic("implement me") -} - -func (b *testBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { - // TODO implement me - panic("implement me") -} - -func (b *testBackend) HistoricalRPCService() *rpc.Client { - // TODO implement me - panic("implement me") -} - -func (b *testBackend) Genesis() *types.Block { - // TODO implement me - panic("implement me") -} - -func (b *testBackend) setPendingBlock(block *types.Block) { - b.pending = block -} - -func (b testBackend) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} } -func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return big.NewInt(0), nil -} - -// func (b testBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { -// return nil, nil, nil, nil, nil -// } -func (b testBackend) ChainDb() ethdb.Database { return b.db } -func (b testBackend) AccountManager() *accounts.Manager { return b.accman } -func (b testBackend) ExtRPCEnabled() bool { return false } -func (b testBackend) RPCGasCap() uint64 { return 10000000 } -func (b testBackend) RPCEVMTimeout() time.Duration { return time.Second } -func (b testBackend) RPCTxFeeCap() float64 { return 0 } -func (b testBackend) UnprotectedAllowed() bool { return false } -func (b testBackend) SetHead(number uint64) {} -func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { - if number == rpc.LatestBlockNumber { - return b.chain.CurrentBlock(), nil - } - if number == rpc.PendingBlockNumber && b.pending != nil { - return b.pending.Header(), nil - } - return b.chain.GetHeaderByNumber(uint64(number)), nil -} -func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - return b.chain.GetHeaderByHash(hash), nil -} -func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { - if blockNr, ok := blockNrOrHash.Number(); ok { - return b.HeaderByNumber(ctx, blockNr) - } - if blockHash, ok := blockNrOrHash.Hash(); ok { - return b.HeaderByHash(ctx, blockHash) - } - panic("unknown type rpc.BlockNumberOrHash") -} -func (b testBackend) CurrentHeader() *types.Header { return b.chain.CurrentBlock() } -func (b testBackend) CurrentBlock() *types.Header { return b.chain.CurrentBlock() } -func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { - if number == rpc.LatestBlockNumber { - head := b.chain.CurrentBlock() - return b.chain.GetBlock(head.Hash(), head.Number.Uint64()), nil - } - if number == rpc.PendingBlockNumber { - return b.pending, nil - } - return b.chain.GetBlockByNumber(uint64(number)), nil -} -func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return b.chain.GetBlockByHash(hash), nil -} -func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { - if blockNr, ok := blockNrOrHash.Number(); ok { - return b.BlockByNumber(ctx, blockNr) - } - if blockHash, ok := blockNrOrHash.Hash(); ok { - return b.BlockByHash(ctx, blockHash) - } - panic("unknown type rpc.BlockNumberOrHash") -} -func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { - return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil -} -func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { - if number == rpc.PendingBlockNumber { - panic("pending state not implemented") - } - header, err := b.HeaderByNumber(ctx, number) - if err != nil { - return nil, nil, err - } - if header == nil { - return nil, nil, errors.New("header not found") - } - stateDb, err := b.chain.StateAt(header.Root) - return stateDb, header, err -} -func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { - if blockNr, ok := blockNrOrHash.Number(); ok { - return b.StateAndHeaderByNumber(ctx, blockNr) - } - panic("only implemented for number") -} -func (b testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { panic("implement me") } -func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - header, err := b.HeaderByHash(ctx, hash) - if header == nil || err != nil { - return nil, err - } - receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), b.chain.Config()) - return receipts, nil -} -func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { - if b.pending != nil && hash == b.pending.Hash() { - return nil - } - return big.NewInt(1) -} - -func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - panic("implement me") -} -func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - panic("implement me") -} -func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { - panic("implement me") -} -func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - panic("implement me") -} -func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") } -func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") } -func (b testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { - return 0, nil -} -func (b testBackend) Stats() (pending int, queued int) { panic("implement me") } -func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription { - panic("implement me") -} -func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } -func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() } -func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { - panic("implement me") -} -func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - panic("implement me") -} - -func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - panic("implement me") -} -func (b testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { - panic("implement me") -} -func (b testBackend) BloomStatus() (uint64, uint64) { panic("implement me") } -func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { - panic("implement me") -} - -type account struct { - key *ecdsa.PrivateKey - addr common.Address -} - -func newAccounts(n int) (accounts []account) { - for i := 0; i < n; i++ { - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - accounts = append(accounts, account{key: key, addr: addr}) - } - slices.SortFunc(accounts, func(a, b account) bool { return a.addr.Cmp(b.addr) < 0 }) - return accounts -} - -func newRPCBalance(balance *big.Int) **hexutil.Big { - rpcBalance := (*hexutil.Big)(balance) - return &rpcBalance -} - -func hex2Bytes(str string) *hexutil.Bytes { - rpcBytes := hexutil.Bytes(common.Hex2Bytes(str)) - return &rpcBytes -} - -func TestEstimateGas(t *testing.T) { - t.Parallel() - // Initialize test accounts - var ( - accounts = newAccounts(2) - genesis = &core.Genesis{ - Config: params.MergedTestChainConfig, - Alloc: core.GenesisAlloc{ - accounts[0].addr: {Balance: big.NewInt(params.Ether)}, - accounts[1].addr: {Balance: big.NewInt(params.Ether)}, - }, - } - genBlocks = 10 - signer = types.HomesteadSigner{} - randomAccounts = newAccounts(2) - _ = randomAccounts - ) - api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) { - // Transfer from account[0] to account[1] - // value: 1000 wei - // fee: 0 wei - tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &accounts[1].addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, accounts[0].key) - b.AddTx(tx) - b.SetPoS() - })) - var testSuite = []struct { - blockNumber rpc.BlockNumber - call TransactionArgs - overrides StateOverride - expectErr error - want uint64 - }{ - // simple transfer on latest block - { - blockNumber: rpc.LatestBlockNumber, - call: TransactionArgs{ - From: &accounts[0].addr, - To: &accounts[1].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), - }, - expectErr: nil, - want: 21000, - }, - { - blockNumber: rpc.LatestBlockNumber, - call: TransactionArgs{ - From: &accounts[0].addr, - Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), - GasPrice: (*hexutil.Big)(big.NewInt(1_000_000_000)), // Legacy as pricing - }, - expectErr: nil, - want: 67617, - }, - } - for i, tc := range testSuite { - _, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}) - if tc.expectErr != nil { - if err == nil { - t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) - continue - } - if !errors.Is(err, tc.expectErr) { - t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) - } - continue - } - if err != nil { - t.Errorf("test %d: want no error, have %v", i, err) - continue - } - // if float64(result) > float64(tc.want)*(1+estimateGasErrorRatio) { - // t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want) - // } - } -} diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 38c2f910de..3421304543 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -200,7 +200,7 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ // ToMessage converts the transaction arguments to the Message type used by the // core evm. This method is used in calls and traces that do not require a real // live transaction. -func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, runMode core.RunMode) (*core.Message, error) { +func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, runMode core.RunMode, gasPriceForEstimate *hexutil.Big) (*core.Message, error) { // Reject invalid combinations of pre- and post-1559 fee styles if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") @@ -256,31 +256,31 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, ru } } - // // use suggested gasPrice for estimateGas to calculate gasUsed - // if runMode == core.GasEstimationMode || runMode == core.GasEstimationWithSkipCheckBalanceMode { - // // use default gasPrice if user does not set gasPrice or gasPrice is 0 - // if args.GasPrice == nil && gasPrice.Cmp(common.Big0) == 0 { - // gasPrice = gasPriceForEstimate.ToInt() - // } - // // use gasTipCap to set gasFeeCap - // if args.MaxFeePerGas == nil && args.MaxPriorityFeePerGas != nil { - // gasFeeCap = args.MaxPriorityFeePerGas.ToInt() - // } - // // use gasFeeCap to set gasTipCap - // if args.MaxPriorityFeePerGas == nil && args.MaxFeePerGas != nil { - // gasTipCap = args.MaxFeePerGas.ToInt() - // } - // // use default gasPrice to set gasFeeCap & gasTipCap if user set gasPrice - // if args.GasPrice != nil { - // gasFeeCap = gasPrice - // gasTipCap = gasPrice - // } - // // use default gasPrice to set gasFeeCap & gasTipCap if user does not set any value - // if args.MaxFeePerGas == nil && args.MaxPriorityFeePerGas == nil && args.GasPrice == nil { - // gasFeeCap = gasPriceForEstimate.ToInt() - // gasTipCap = gasPriceForEstimate.ToInt() - // } - // } + // use suggested gasPrice for estimateGas to calculate gasUsed + if runMode == core.GasEstimationMode || runMode == core.GasEstimationWithSkipCheckBalanceMode { + // use default gasPrice if user does not set gasPrice or gasPrice is 0 + if args.GasPrice == nil && gasPrice.Cmp(common.Big0) == 0 { + gasPrice = gasPriceForEstimate.ToInt() + } + // use gasTipCap to set gasFeeCap + if args.MaxFeePerGas == nil && args.MaxPriorityFeePerGas != nil { + gasFeeCap = args.MaxPriorityFeePerGas.ToInt() + } + // use gasFeeCap to set gasTipCap + if args.MaxPriorityFeePerGas == nil && args.MaxFeePerGas != nil { + gasTipCap = args.MaxFeePerGas.ToInt() + } + // use default gasPrice to set gasFeeCap & gasTipCap if user set gasPrice + if args.GasPrice != nil { + gasFeeCap = gasPrice + gasTipCap = gasPrice + } + // use default gasPrice to set gasFeeCap & gasTipCap if user does not set any value + if args.MaxFeePerGas == nil && args.MaxPriorityFeePerGas == nil && args.GasPrice == nil { + gasFeeCap = gasPriceForEstimate.ToInt() + gasTipCap = gasPriceForEstimate.ToInt() + } + } value := new(big.Int) if args.Value != nil { diff --git a/params/config.go b/params/config.go index e3e8262fe1..924ffced7d 100644 --- a/params/config.go +++ b/params/config.go @@ -218,35 +218,6 @@ var ( }, } - // MergedTestChainConfig contains every protocol change (EIPs) introduced - // and accepted by the Ethereum core developers for testing purposes. - MergedTestChainConfig = &ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: nil, - DAOForkSupport: false, - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - ArrowGlacierBlock: big.NewInt(0), - GrayGlacierBlock: big.NewInt(0), - MergeNetsplitBlock: big.NewInt(0), - ShanghaiTime: newUint64(0), - CancunTime: newUint64(0), - PragueTime: nil, - TerminalTotalDifficulty: big.NewInt(0), - TerminalTotalDifficultyPassed: true, - Ethash: new(EthashConfig), - Clique: nil, - } - // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ SectionIndex: 229,