From 93c782a83016ee7de83c0c4613dbf03fde8cd9f1 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Thu, 4 Apr 2019 22:49:14 -0700 Subject: [PATCH 01/11] add multiple gas estimation --- graphql/graphql.go | 14 ++++++++++++++ graphql/schema.go | 1 + 2 files changed, 15 insertions(+) diff --git a/graphql/graphql.go b/graphql/graphql.go index b3bcbd8a43e2..5569a52b8044 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -945,6 +945,20 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct { return ethapi.DoEstimateGas(ctx, p.backend, args.Data, rpc.PendingBlockNumber) } +func (p *Pending) EstimateGases(ctx context.Context, args struct { + Data *[]ethapi.CallArgs +}) (*[]hexutil.Uint64, error) { + ret := make([]hexutil.Uint64, 0, len(*args.Data)) + for _, call := range *args.Data { + estimatedGas, err := ethapi.DoEstimateGas(ctx, p.backend, call, rpc.PendingBlockNumber) + if err != nil { + return &ret, nil + } + ret = append(ret, estimatedGas) + } + return &ret, nil +} + // Resolver is the top-level object in the GraphQL hierarchy. type Resolver struct { backend *eth.EthAPIBackend diff --git a/graphql/schema.go b/graphql/schema.go index bd913d9aa654..a7c7d791edf2 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -289,6 +289,7 @@ const schema string = ` # EstimateGas estimates the amount of gas that will be required for # successful execution of a transaction for the pending state. estimateGas(data: CallData!): Long! + estimateGases(data: [CallData!]): [Long!] } type Query { From 8d39257fc7cfdaae6d02c48d85e56e71f4f8c136 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Mon, 11 May 2020 18:03:51 -0700 Subject: [PATCH 02/11] add support for sequential gas estimation --- graphql/graphql.go | 9 ++-- internal/ethapi/api.go | 103 +++++++++++++++++++++++++++-------------- 2 files changed, 73 insertions(+), 39 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 9555d8cce254..f72085dbe134 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -803,7 +803,7 @@ func (b *Block) Call(ctx context.Context, args struct { return nil, err } } - result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) + result, _, err := ethapi.DoCall(ctx, b.backend, args.Data, nil, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) if err != nil { return nil, err } @@ -827,7 +827,7 @@ func (b *Block) EstimateGas(ctx context.Context, args struct { return hexutil.Uint64(0), err } } - gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap()) + gas, _, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, nil, *b.numberOrHash, b.backend.RPCGasCap()) return gas, err } @@ -872,7 +872,7 @@ func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.CallArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) + result, _, err := ethapi.DoCall(ctx, p.backend, args.Data, nil, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) if err != nil { return nil, err } @@ -891,7 +891,8 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.CallArgs }) (hexutil.Uint64, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - return ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap()) + gas, _, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, nil, pendingBlockNr, p.backend.RPCGasCap()) + return gas, err } // Resolver is the top-level object in the GraphQL hierarchy. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 45240aa2cfb7..7db062cdb991 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "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" @@ -790,38 +791,48 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, error) { - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) +type PreviousState struct { + state *state.StateDB + header *types.Header +} - state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if state == nil || err != nil { - return nil, err +func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, *PreviousState, error) { + defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + if (prevState.header != nil && prevState.header == nil) || (prevState.header == nil && prevState.header != nil) { + return nil, nil, fmt.Errorf("both header and state must be set to use previous staate") + } + if prevState.header == nil && prevState.state == nil { + var err error + prevState.state, prevState.header, err = b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if prevState.state == nil || err != nil { + return nil, nil, err + } } // Override the fields of specified contracts before execution. for addr, account := range overrides { // Override account nonce. if account.Nonce != nil { - state.SetNonce(addr, uint64(*account.Nonce)) + prevState.state.SetNonce(addr, uint64(*account.Nonce)) } // Override account(contract) code. if account.Code != nil { - state.SetCode(addr, *account.Code) + prevState.state.SetCode(addr, *account.Code) } // Override account balance. if account.Balance != nil { - state.SetBalance(addr, (*big.Int)(*account.Balance)) + prevState.state.SetBalance(addr, (*big.Int)(*account.Balance)) } if account.State != nil && account.StateDiff != nil { - return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) + return nil, nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) } // Replace entire state if caller requires. if account.State != nil { - state.SetStorage(addr, *account.State) + prevState.state.SetStorage(addr, *account.State) } // Apply state diff into specified accounts. if account.StateDiff != nil { for key, value := range *account.StateDiff { - state.SetState(addr, key, value) + prevState.state.SetState(addr, key, value) } } } @@ -839,9 +850,9 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo // Get a new instance of the EVM. msg := args.ToMessage(globalGasCap) - evm, vmError, err := b.GetEVM(ctx, msg, state, header) + evm, vmError, err := b.GetEVM(ctx, msg, prevState.state, prevState.header) if err != nil { - return nil, err + return nil, nil, err } // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -855,13 +866,14 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo gp := new(core.GasPool).AddGas(math.MaxUint64) result, err := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { - return nil, err + return nil, nil, err } // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { - return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) + return nil, nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } - return result, err + prevState.header.Root = prevState.state.IntermediateRoot(b.ChainConfig().IsEIP158(prevState.header.Number)) + return result, prevState, err } // Call executes the given transaction on the state for the given block number. @@ -875,7 +887,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr if overrides != nil { accounts = *overrides } - result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) + result, _, err := DoCall(ctx, s.b, args, nil, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) if err != nil { return nil, err } @@ -899,12 +911,13 @@ func (e estimateGasError) Error() string { return errMsg } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, *PreviousState, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 + stateData *PreviousState ) // Use zero address if sender unspecified. if args.From == nil { @@ -917,7 +930,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash // Retrieve the block to act as the gas ceiling block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash) if err != nil { - return 0, err + return 0, nil, err } hi = block.GasLimit() } @@ -925,13 +938,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 0 { state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if err != nil { - return 0, err + return 0, nil, err } balance := state.GetBalance(*args.From) // from can't be nil available := new(big.Int).Set(balance) if args.Value != nil { if args.Value.ToInt().Cmp(available) >= 0 { - return 0, errors.New("insufficient funds for transfer") + return 0, nil, errors.New("insufficient funds for transfer") } available.Sub(available, args.Value.ToInt()) } @@ -957,7 +970,8 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) + result, prevS, err := DoCall(ctx, b, args, prevState, blockNrOrHash, nil, vm.Config{}, 0, gasCap) + stateData = prevS if err != nil { if err == core.ErrIntrinsicGas { return true, nil, nil // Special case, raise gas limit @@ -975,7 +989,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash // call or transaction will never be accepted no matter how much gas it is // assigened. Return the error directly, don't struggle any more. if err != nil { - return 0, err + return 0, nil, err } if failed { lo = mid @@ -987,7 +1001,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash if hi == cap { failed, result, err := executable(hi) if err != nil { - return 0, err + return 0, nil, err } if failed { if result != nil && result.Err != vm.ErrOutOfGas { @@ -1000,24 +1014,43 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash revert = ret } } - return 0, estimateGasError{ + return 0, nil, estimateGasError{ error: "always failing transaction", vmerr: result.Err, revert: revert, } } // Otherwise, the specified gas cap is too low - return 0, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)} + return 0, nil, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)} } } - return hexutil.Uint64(hi), nil + return hexutil.Uint64(hi), stateData, nil } -// EstimateGas returns an estimate of the amount of gas needed to execute the -// given transaction against the current pending block. -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { +func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface interface{}) (interface{}, error) { blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - return DoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap()) + var ( + gas hexutil.Uint64 + err error + stateData *PreviousState + ) + switch args := argsInterface.(type) { + case CallArgs: + gas, _, err = DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, s.b.RPCGasCap()) + return gas, err + case []CallArgs: + returnVals := make([]interface{}, len(args)) + for _, argData := range args { + gas, stateData, err = DoEstimateGas(ctx, s.b, argData, stateData, blockNrOrHash, s.b.RPCGasCap()) + returnVals = append(returnVals, map[string]interface{}{ + "result": gas, + "error": err, + }) + } + return returnVals, nil + default: + return nil, estimateGasError{error: fmt.Sprintf("unknown input")} + } } // ExecutionResult groups all structured logs emitted by the EVM @@ -1488,7 +1521,7 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { Data: input, } pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) + estimated, _, err := DoEstimateGas(ctx, b, callArgs, nil, pendingBlockNr, b.RPCGasCap()) if err != nil { return err } From e280d93c95efa5241090bb56249af646b02b59da Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Mon, 11 May 2020 18:06:20 -0700 Subject: [PATCH 03/11] merge --- graphql/graphql.go | 14 -------------- graphql/schema.go | 1 - 2 files changed, 15 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 948e1d08ce5c..f72085dbe134 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -895,20 +895,6 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct { return gas, err } -func (p *Pending) EstimateGases(ctx context.Context, args struct { - Data *[]ethapi.CallArgs -}) (*[]hexutil.Uint64, error) { - ret := make([]hexutil.Uint64, 0, len(*args.Data)) - for _, call := range *args.Data { - estimatedGas, err := ethapi.DoEstimateGas(ctx, p.backend, call, rpc.PendingBlockNumber) - if err != nil { - return &ret, nil - } - ret = append(ret, estimatedGas) - } - return &ret, nil -} - // Resolver is the top-level object in the GraphQL hierarchy. type Resolver struct { backend ethapi.Backend diff --git a/graphql/schema.go b/graphql/schema.go index 73ef68e469c6..5dec10db208a 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -292,7 +292,6 @@ const schema string = ` # EstimateGas estimates the amount of gas that will be required for # successful execution of a transaction for the pending state. estimateGas(data: CallData!): Long! - estimateGases(data: [CallData!]): [Long!] } type Query { From 2d2482b1d19c225d7814e9ecfab509f022d24514 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Mon, 11 May 2020 18:16:10 -0700 Subject: [PATCH 04/11] fix test errors https://github.com/ethereum/go-ethereum/pull/21062 --- core/blockchain_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 96b96425a9c4..755f29beae09 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -994,7 +994,7 @@ func TestLogRebirth(t *testing.T) { genesis = gspec.MustCommit(db) signer = types.NewEIP155Signer(gspec.Config.ChainID) engine = ethash.NewFaker() - blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) ) defer blockchain.Stop() @@ -1057,7 +1057,7 @@ func TestSideLogRebirth(t *testing.T) { gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} genesis = gspec.MustCommit(db) signer = types.NewEIP155Signer(gspec.Config.ChainID) - blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) ) defer blockchain.Stop() @@ -2615,7 +2615,7 @@ func TestDeleteRecreateSlots(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ Debug: true, Tracer: vm.NewJSONLogger(nil, os.Stdout), - }, nil) + }, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2695,7 +2695,7 @@ func TestDeleteRecreateAccount(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ Debug: true, Tracer: vm.NewJSONLogger(nil, os.Stdout), - }, nil) + }, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2868,7 +2868,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ //Debug: true, //Tracer: vm.NewJSONLogger(nil, os.Stdout), - }, nil) + }, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3002,7 +3002,7 @@ func TestInitThenFailCreateContract(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ //Debug: true, //Tracer: vm.NewJSONLogger(nil, os.Stdout), - }, nil) + }, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } From b87dd834aee13ee885d461898496e9167eb2da0e Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Tue, 19 May 2020 23:38:37 -0700 Subject: [PATCH 05/11] fix previous state error --- internal/ethapi/api.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 7db062cdb991..2ed1c7eb6905 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -19,6 +19,7 @@ package ethapi import ( "bytes" "context" + "encoding/json" "errors" "fmt" "math/big" @@ -1034,18 +1035,28 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface int err error stateData *PreviousState ) + if stateData == nil { + stateData = &PreviousState{} + } + getCallArgs := func(inter map[string]interface{}) CallArgs { + marshalled, _ := json.Marshal(inter) + callArgs := CallArgs{} + json.Unmarshal(marshalled, &callArgs) + return callArgs + } + log.Info(fmt.Sprintf("%T", argsInterface)) switch args := argsInterface.(type) { - case CallArgs: - gas, _, err = DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, s.b.RPCGasCap()) + case map[string]interface{}: + gas, _, err = DoEstimateGas(ctx, s.b, getCallArgs(args), stateData, blockNrOrHash, s.b.RPCGasCap()) return gas, err - case []CallArgs: - returnVals := make([]interface{}, len(args)) - for _, argData := range args { - gas, stateData, err = DoEstimateGas(ctx, s.b, argData, stateData, blockNrOrHash, s.b.RPCGasCap()) - returnVals = append(returnVals, map[string]interface{}{ - "result": gas, - "error": err, - }) + case []interface{}: + returnVals := make([]hexutil.Uint64, len(args)) + for idx, argData := range args { + gas, stateData, err = DoEstimateGas(ctx, s.b, getCallArgs(argData.(map[string]interface{})), stateData, blockNrOrHash, s.b.RPCGasCap()) + if err != nil { + return nil, err + } + returnVals[idx] = gas } return returnVals, nil default: From de52df7410c9150424779c3ff881485bc6927f66 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Wed, 20 May 2020 00:15:06 -0700 Subject: [PATCH 06/11] lint --- internal/ethapi/api.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2ed1c7eb6905..282211808e70 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1044,7 +1044,6 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface int json.Unmarshal(marshalled, &callArgs) return callArgs } - log.Info(fmt.Sprintf("%T", argsInterface)) switch args := argsInterface.(type) { case map[string]interface{}: gas, _, err = DoEstimateGas(ctx, s.b, getCallArgs(args), stateData, blockNrOrHash, s.b.RPCGasCap()) @@ -1060,7 +1059,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface int } return returnVals, nil default: - return nil, estimateGasError{error: fmt.Sprintf("unknown input")} + return nil, estimateGasError{error: "unknown input"} } } From 94fe32917386ed34c7174e24880f048bcbeb9614 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Wed, 20 May 2020 02:33:47 -0700 Subject: [PATCH 07/11] set empty previousState if not set --- internal/ethapi/api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 282211808e70..7ab9a2a4a9f8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -799,6 +799,9 @@ type PreviousState struct { func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, *PreviousState, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + if prevState == nil { + prevState = &PreviousState{} + } if (prevState.header != nil && prevState.header == nil) || (prevState.header == nil && prevState.header != nil) { return nil, nil, fmt.Errorf("both header and state must be set to use previous staate") } @@ -1035,9 +1038,6 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface int err error stateData *PreviousState ) - if stateData == nil { - stateData = &PreviousState{} - } getCallArgs := func(inter map[string]interface{}) CallArgs { marshalled, _ := json.Marshal(inter) callArgs := CallArgs{} From 3a4d3f019c7d88fde7b23bac2d1ec66bbde7aa41 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra Date: Fri, 22 May 2020 04:50:10 -0700 Subject: [PATCH 08/11] cleanup and create state copy during DoCall --- internal/ethapi/api.go | 73 ++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 7ab9a2a4a9f8..e2bf6d077ab3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -792,51 +792,41 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -type PreviousState struct { - state *state.StateDB - header *types.Header -} - -func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, *PreviousState, error) { +func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *state.StateDB, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, *state.StateDB, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - if prevState == nil { - prevState = &PreviousState{} - } - if (prevState.header != nil && prevState.header == nil) || (prevState.header == nil && prevState.header != nil) { - return nil, nil, fmt.Errorf("both header and state must be set to use previous staate") + state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, nil, err } - if prevState.header == nil && prevState.state == nil { - var err error - prevState.state, prevState.header, err = b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if prevState.state == nil || err != nil { - return nil, nil, err - } + if prevState != nil { + state = prevState.Copy() + header.Root = state.IntermediateRoot(b.ChainConfig().IsEIP158(header.Number)) } // Override the fields of specified contracts before execution. for addr, account := range overrides { // Override account nonce. if account.Nonce != nil { - prevState.state.SetNonce(addr, uint64(*account.Nonce)) + state.SetNonce(addr, uint64(*account.Nonce)) } // Override account(contract) code. if account.Code != nil { - prevState.state.SetCode(addr, *account.Code) + state.SetCode(addr, *account.Code) } // Override account balance. if account.Balance != nil { - prevState.state.SetBalance(addr, (*big.Int)(*account.Balance)) + state.SetBalance(addr, (*big.Int)(*account.Balance)) } if account.State != nil && account.StateDiff != nil { return nil, nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) } // Replace entire state if caller requires. if account.State != nil { - prevState.state.SetStorage(addr, *account.State) + state.SetStorage(addr, *account.State) } // Apply state diff into specified accounts. if account.StateDiff != nil { for key, value := range *account.StateDiff { - prevState.state.SetState(addr, key, value) + state.SetState(addr, key, value) } } } @@ -854,7 +844,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *PreviousSt // Get a new instance of the EVM. msg := args.ToMessage(globalGasCap) - evm, vmError, err := b.GetEVM(ctx, msg, prevState.state, prevState.header) + evm, vmError, err := b.GetEVM(ctx, msg, state, header) if err != nil { return nil, nil, err } @@ -876,8 +866,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *PreviousSt if evm.Cancelled() { return nil, nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } - prevState.header.Root = prevState.state.IntermediateRoot(b.ChainConfig().IsEIP158(prevState.header.Number)) - return result, prevState, err + return result, state, err } // Call executes the given transaction on the state for the given block number. @@ -915,13 +904,13 @@ func (e estimateGasError) Error() string { return errMsg } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, *PreviousState, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *state.StateDB, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, *state.StateDB, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 - stateData *PreviousState + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 + validState *state.StateDB ) // Use zero address if sender unspecified. if args.From == nil { @@ -971,23 +960,21 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *Pre cap = hi // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, error) { + executable := func(gas uint64) (bool, *core.ExecutionResult, *state.StateDB, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, prevS, err := DoCall(ctx, b, args, prevState, blockNrOrHash, nil, vm.Config{}, 0, gasCap) - stateData = prevS if err != nil { if err == core.ErrIntrinsicGas { - return true, nil, nil // Special case, raise gas limit + return true, nil, nil, nil // Special case, raise gas limit } - return true, nil, err // Bail out + return true, nil, nil, err // Bail out } - return result.Failed(), result, nil + return result.Failed(), result, prevS, nil } // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { mid := (hi + lo) / 2 - failed, _, err := executable(mid) + failed, _, prevS, 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 @@ -998,12 +985,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *Pre if failed { lo = mid } else { + validState = prevS hi = mid } } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - failed, result, err := executable(hi) + failed, result, prevS, err := executable(hi) if err != nil { return 0, nil, err } @@ -1027,8 +1015,9 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *Pre // Otherwise, the specified gas cap is too low return 0, nil, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)} } + validState = prevS } - return hexutil.Uint64(hi), stateData, nil + return hexutil.Uint64(hi), validState, nil } func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface interface{}) (interface{}, error) { @@ -1036,7 +1025,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface int var ( gas hexutil.Uint64 err error - stateData *PreviousState + prevState *state.StateDB ) getCallArgs := func(inter map[string]interface{}) CallArgs { marshalled, _ := json.Marshal(inter) @@ -1046,12 +1035,12 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface int } switch args := argsInterface.(type) { case map[string]interface{}: - gas, _, err = DoEstimateGas(ctx, s.b, getCallArgs(args), stateData, blockNrOrHash, s.b.RPCGasCap()) + gas, _, err = DoEstimateGas(ctx, s.b, getCallArgs(args), nil, blockNrOrHash, s.b.RPCGasCap()) return gas, err case []interface{}: returnVals := make([]hexutil.Uint64, len(args)) for idx, argData := range args { - gas, stateData, err = DoEstimateGas(ctx, s.b, getCallArgs(argData.(map[string]interface{})), stateData, blockNrOrHash, s.b.RPCGasCap()) + gas, prevState, err = DoEstimateGas(ctx, s.b, getCallArgs(argData.(map[string]interface{})), prevState, blockNrOrHash, s.b.RPCGasCap()) if err != nil { return nil, err } From d3dfc5fe8992aa61eb55b6fa77bec16c3ef93588 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 2 Jun 2020 10:40:33 -0700 Subject: [PATCH 09/11] create eth_estimateGasList method --- internal/ethapi/api.go | 108 +++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e2bf6d077ab3..5cc5bd7250c6 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -19,7 +19,6 @@ package ethapi import ( "bytes" "context" - "encoding/json" "errors" "fmt" "math/big" @@ -792,41 +791,51 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *state.StateDB, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, *state.StateDB, error) { +type PreviousState struct { + state *state.StateDB + header *types.Header +} + +func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, *PreviousState, 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, nil, err + if prevState == nil { + prevState = &PreviousState{} } - if prevState != nil { - state = prevState.Copy() - header.Root = state.IntermediateRoot(b.ChainConfig().IsEIP158(header.Number)) + if (prevState.header != nil && prevState.header == nil) || (prevState.header == nil && prevState.header != nil) { + return nil, nil, fmt.Errorf("both header and state must be set to use previous staate") + } + if prevState.header == nil && prevState.state == nil { + var err error + prevState.state, prevState.header, err = b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if prevState.state == nil || err != nil { + return nil, nil, err + } } // Override the fields of specified contracts before execution. for addr, account := range overrides { // Override account nonce. if account.Nonce != nil { - state.SetNonce(addr, uint64(*account.Nonce)) + prevState.state.SetNonce(addr, uint64(*account.Nonce)) } // Override account(contract) code. if account.Code != nil { - state.SetCode(addr, *account.Code) + prevState.state.SetCode(addr, *account.Code) } // Override account balance. if account.Balance != nil { - state.SetBalance(addr, (*big.Int)(*account.Balance)) + prevState.state.SetBalance(addr, (*big.Int)(*account.Balance)) } if account.State != nil && account.StateDiff != nil { return nil, nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) } // Replace entire state if caller requires. if account.State != nil { - state.SetStorage(addr, *account.State) + prevState.state.SetStorage(addr, *account.State) } // Apply state diff into specified accounts. if account.StateDiff != nil { for key, value := range *account.StateDiff { - state.SetState(addr, key, value) + prevState.state.SetState(addr, key, value) } } } @@ -844,7 +853,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *state.Stat // Get a new instance of the EVM. msg := args.ToMessage(globalGasCap) - evm, vmError, err := b.GetEVM(ctx, msg, state, header) + evm, vmError, err := b.GetEVM(ctx, msg, prevState.state, prevState.header) if err != nil { return nil, nil, err } @@ -866,7 +875,8 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *state.Stat if evm.Cancelled() { return nil, nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } - return result, state, err + prevState.header.Root = prevState.state.IntermediateRoot(b.ChainConfig().IsEIP158(prevState.header.Number)) + return result, prevState, err } // Call executes the given transaction on the state for the given block number. @@ -904,13 +914,13 @@ func (e estimateGasError) Error() string { return errMsg } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *state.StateDB, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, *state.StateDB, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, *PreviousState, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 - validState *state.StateDB + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 + stateData *PreviousState ) // Use zero address if sender unspecified. if args.From == nil { @@ -960,21 +970,23 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *sta cap = hi // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, *state.StateDB, error) { + executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) + result, prevS, err := DoCall(ctx, b, args, prevState, blockNrOrHash, nil, vm.Config{}, 0, gasCap) + stateData = prevS if err != nil { if err == core.ErrIntrinsicGas { - return true, nil, nil, nil // Special case, raise gas limit + return true, nil, nil // Special case, raise gas limit } - return true, nil, nil, err // Bail out + return true, nil, err // Bail out } - return result.Failed(), result, prevS, nil + return result.Failed(), result, nil } // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { mid := (hi + lo) / 2 - failed, _, prevS, 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 @@ -985,13 +997,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *sta if failed { lo = mid } else { - validState = prevS hi = mid } } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - failed, result, prevS, err := executable(hi) + failed, result, err := executable(hi) if err != nil { return 0, nil, err } @@ -1015,41 +1026,34 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *sta // Otherwise, the specified gas cap is too low return 0, nil, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)} } - validState = prevS } - return hexutil.Uint64(hi), validState, nil + return hexutil.Uint64(hi), stateData, nil } -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface interface{}) (interface{}, error) { +// EstimateGas returns an estimate of the amount of gas needed to execute the +// given transaction against the current pending block. +func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { + blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + gas, _, err := DoEstimateGas(ctx, s.b, args, nil, blockNrOrHash, s.b.RPCGasCap()) + return gas, err +} + +func (s *PublicBlockChainAPI) EstimateGasList(ctx context.Context, argsList []CallArgs) ([]hexutil.Uint64, error) { blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) var ( gas hexutil.Uint64 err error - prevState *state.StateDB + stateData *PreviousState ) - getCallArgs := func(inter map[string]interface{}) CallArgs { - marshalled, _ := json.Marshal(inter) - callArgs := CallArgs{} - json.Unmarshal(marshalled, &callArgs) - return callArgs - } - switch args := argsInterface.(type) { - case map[string]interface{}: - gas, _, err = DoEstimateGas(ctx, s.b, getCallArgs(args), nil, blockNrOrHash, s.b.RPCGasCap()) - return gas, err - case []interface{}: - returnVals := make([]hexutil.Uint64, len(args)) - for idx, argData := range args { - gas, prevState, err = DoEstimateGas(ctx, s.b, getCallArgs(argData.(map[string]interface{})), prevState, blockNrOrHash, s.b.RPCGasCap()) - if err != nil { - return nil, err - } - returnVals[idx] = gas + returnVals := make([]hexutil.Uint64, len(argsList)) + for idx, args := range argsList { + gas, stateData, err = DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, s.b.RPCGasCap()) + if err != nil { + return nil, err } - return returnVals, nil - default: - return nil, estimateGasError{error: "unknown input"} + returnVals[idx] = gas } + return returnVals, nil } // ExecutionResult groups all structured logs emitted by the EVM From 1d6f0d629b24fc21480bcb1ee4840b45182ff241 Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 2 Jun 2020 10:44:04 -0700 Subject: [PATCH 10/11] add comments for eth_estimateGasList --- internal/ethapi/api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 5cc5bd7250c6..b93ed45b5e56 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1038,6 +1038,8 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h return gas, err } +// EstimateGasList returns an estimate of the amount of gas needed to execute list of +// given transactions against the current pending block. func (s *PublicBlockChainAPI) EstimateGasList(ctx context.Context, argsList []CallArgs) ([]hexutil.Uint64, error) { blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) var ( From 0331a146b77a20462fbe671bc72ebf1cd816d54c Mon Sep 17 00:00:00 2001 From: Kosala Hemachandra <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 2 Jun 2020 11:16:14 -0700 Subject: [PATCH 11/11] enfore gasCap on eth_estimateGasList --- internal/ethapi/api.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b93ed45b5e56..217b442b4dab 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1046,13 +1046,15 @@ func (s *PublicBlockChainAPI) EstimateGasList(ctx context.Context, argsList []Ca gas hexutil.Uint64 err error stateData *PreviousState + gasCap = s.b.RPCGasCap() ) returnVals := make([]hexutil.Uint64, len(argsList)) for idx, args := range argsList { - gas, stateData, err = DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, s.b.RPCGasCap()) + gas, stateData, err = DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, gasCap) if err != nil { return nil, err } + gasCap.Sub(gasCap, new(big.Int).SetUint64(uint64(gas))) returnVals[idx] = gas } return returnVals, nil