diff --git a/eth/api_backend.go b/eth/api_backend.go index d0dafc9ac0..73eda5beef 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -257,14 +257,19 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(hash) } -func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error, error) { vmError := func() error { return nil } if vmConfig == nil { vmConfig = b.eth.blockchain.GetVMConfig() } state.SetBalance(msg.From, math.MaxBig256) txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + var context vm.BlockContext + if blockCtx != nil { + context = *blockCtx + } else { + context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + } return vm.NewEVM(context, txContext, state, XDCxState, b.eth.chainConfig, *vmConfig), vmError, nil } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 6a1fedaccf..95878f0de6 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -101,34 +101,10 @@ func NewAPI(backend Backend) *API { return &API{backend: backend} } -type chainContext struct { - api *API - ctx context.Context -} - -func (context *chainContext) Engine() consensus.Engine { - return context.api.backend.Engine() -} - -func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { - header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) - if err != nil { - return nil - } - if header.Hash() == hash { - return header - } - header, err = context.api.backend.HeaderByHash(context.ctx, hash) - if err != nil { - return nil - } - return header -} - // chainContext represents the context reader which is used by the evm for reading // the necessary chain context. func (api *API) chainContext(ctx context.Context) core.ChainContext { - return &chainContext{api: api, ctx: ctx} + return ethapi.NewChainContext(ctx, api.backend) } // blockByNumber is the wrapper of the chain access function offered by the backend. diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go new file mode 100644 index 0000000000..89cfa7bf1b --- /dev/null +++ b/ethclient/gethclient/gethclient_test.go @@ -0,0 +1,111 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package gethclient + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/XinFinOrg/XDPoSChain/common" +) + +func TestOverrideAccountMarshal(t *testing.T) { + om := map[common.Address]OverrideAccount{ + {0x11}: { + // Zero-valued nonce is not overridden, but simply dropped by the encoder. + Nonce: 0, + }, + {0xaa}: { + Nonce: 5, + }, + {0xbb}: { + Code: []byte{1}, + }, + {0xcc}: { + // 'code', 'balance', 'state' should be set when input is + // a non-nil but empty value. + Code: []byte{}, + Balance: big.NewInt(0), + State: map[common.Hash]common.Hash{}, + // For 'stateDiff' the behavior is different, empty map + // is ignored because it makes no difference. + StateDiff: map[common.Hash]common.Hash{}, + }, + } + + marshalled, err := json.MarshalIndent(&om, "", " ") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expected := `{ + "0x1100000000000000000000000000000000000000": {}, + "0xaa00000000000000000000000000000000000000": { + "nonce": "0x5" + }, + "0xbb00000000000000000000000000000000000000": { + "code": "0x01" + }, + "0xcc00000000000000000000000000000000000000": { + "code": "0x", + "balance": "0x0", + "state": {} + } +}` + + if string(marshalled) != expected { + t.Error("wrong output:", string(marshalled)) + t.Error("want:", expected) + } +} + +func TestBlockOverridesMarshal(t *testing.T) { + for i, tt := range []struct { + bo BlockOverrides + want string + }{ + { + bo: BlockOverrides{}, + want: `{}`, + }, + { + bo: BlockOverrides{ + Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"), + }, + want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`, + }, + { + bo: BlockOverrides{ + Number: big.NewInt(1), + Difficulty: big.NewInt(2), + Time: 3, + GasLimit: 4, + BaseFee: big.NewInt(5), + }, + want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFee":"0x5"}`, + }, + } { + marshalled, err := json.Marshal(&tt.bo) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(marshalled) != tt.want { + t.Errorf("Testcase #%d failed. expected\n%s\ngot\n%s", i, tt.want, string(marshalled)) + } + } +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index feefdc0a33..e11758326b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1079,7 +1079,39 @@ func (s *BlockChainAPI) getCandidatesFromSmartContract() ([]utils.Masternode, er return candidatesWithStakeInfo, nil } -func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { +// ChainContextBackend provides methods required to implement ChainContext. +type ChainContextBackend interface { + Engine() consensus.Engine + HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error) +} + +// ChainContext is an implementation of core.ChainContext. It's main use-case +// is instantiating a vm.BlockContext without having access to the BlockChain object. +type ChainContext struct { + b ChainContextBackend + ctx context.Context +} + +// NewChainContext creates a new ChainContext object. +func NewChainContext(ctx context.Context, backend ChainContextBackend) *ChainContext { + return &ChainContext{ctx: ctx, b: backend} +} + +func (context *ChainContext) Engine() consensus.Engine { + return context.b.Engine() +} + +func (context *ChainContext) GetHeader(hash common.Hash, number uint64) *types.Header { + // This method is called to get the hash for a block number when executing the BLOCKHASH + // opcode. Hence no need to search for non-canonical blocks. + header, err := context.b.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) + if err != nil || header.Hash() != hash { + return nil + } + return header +} + +func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) statedb, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) @@ -1131,7 +1163,11 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash msg.BalanceTokenFee.Mul(msg.BalanceTokenFee, msg.GasPrice) // Get a new instance of the EVM. - evm, vmError, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &vm.Config{NoBaseFee: true}) + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) + if blockOverrides != nil { + blockOverrides.Apply(&blockCtx) + } + evm, vmError, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &vm.Config{NoBaseFee: true}, &blockCtx) if err != nil { return nil, err } @@ -1192,7 +1228,7 @@ func (e *revertError) ErrorData() interface{} { // Call executes the given transaction on the state for the given block number. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. -func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { +func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) { if blockNrOrHash == nil { latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) blockNrOrHash = &latest @@ -1201,7 +1237,7 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO if args.To != nil && *args.To == common.MasternodeVotingSMCBinary { timeout = 0 } - result, err := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, timeout, s.b.RPCGasCap()) + result, err := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, blockOverrides, timeout, s.b.RPCGasCap()) if err != nil { return nil, err } @@ -1273,7 +1309,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap) + result, err := DoCall(ctx, b, args, blockNrOrHash, nil, nil, 0, gasCap) if err != nil { if errors.Is(err, vm.ErrOutOfGas) || errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit @@ -1782,7 +1818,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) config := vm.Config{Tracer: tracer, NoBaseFee: true} - vmenv, _, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &config) + vmenv, _, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &config, nil) if err != nil { return nil, 0, nil, err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index f3ed5ae65c..f19c0fd9e3 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -73,7 +73,7 @@ type Backend interface { PendingBlockAndReceipts() (*types.Block, types.Receipts) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetTd(ctx context.Context, blockHash common.Hash) *big.Int - GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) + GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error, error) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 15ac410d1a..edb22b69a8 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -349,7 +349,7 @@ func (b *backendMock) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *backendMock) GetEVM(context.Context, *core.Message, *state.StateDB, *tradingstate.TradingStateDB, *types.Header, *vm.Config) (*vm.EVM, func() error, error) { +func (b *backendMock) GetEVM(context.Context, *core.Message, *state.StateDB, *tradingstate.TradingStateDB, *types.Header, *vm.Config, *vm.BlockContext) (*vm.EVM, func() error, error) { return nil, nil, nil } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index ad4dca97d7..03301663ee 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -570,8 +570,8 @@ web3._extend({ new web3._extend.Method({ name: 'call', call: 'eth_call', - params: 3, - inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null], + params: 4, + inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null], }), new web3._extend.Method({ name: 'getBlockReceipts',