internal/ethapi: add block overrides to eth_call (#26414)
Adds an optional config parameter to eth_call which allows users to override block context fields (same functionality that was added to traceCall in #24871) --------- Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
parent
52c246fac3
commit
ae66009640
@ -246,12 +246,17 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
|
||||
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error, error) {
|
||||
if vmConfig == nil {
|
||||
vmConfig = b.eth.blockchain.GetVMConfig()
|
||||
}
|
||||
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, b.eth.blockchain.Config(), *vmConfig), state.Error, nil
|
||||
}
|
||||
|
||||
|
@ -100,34 +100,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 constructs 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.
|
||||
|
@ -143,6 +143,28 @@ func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockN
|
||||
return hex, err
|
||||
}
|
||||
|
||||
// CallContractWithBlockOverrides executes a message call transaction, which is directly executed
|
||||
// in the VM of the node, but never mined into the blockchain.
|
||||
//
|
||||
// blockNumber selects the block height at which the call runs. It can be nil, in which
|
||||
// case the code is taken from the latest known block. Note that state from very old
|
||||
// blocks might not be available.
|
||||
//
|
||||
// overrides specifies a map of contract states that should be overwritten before executing
|
||||
// the message call.
|
||||
//
|
||||
// blockOverrides specifies block fields exposed to the EVM that can be overridden for the call.
|
||||
//
|
||||
// Please use ethclient.CallContract instead if you don't need the override functionality.
|
||||
func (ec *Client) CallContractWithBlockOverrides(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount, blockOverrides BlockOverrides) ([]byte, error) {
|
||||
var hex hexutil.Bytes
|
||||
err := ec.c.CallContext(
|
||||
ctx, &hex, "eth_call", toCallArg(msg),
|
||||
toBlockNumArg(blockNumber), overrides, blockOverrides,
|
||||
)
|
||||
return hex, err
|
||||
}
|
||||
|
||||
// GCStats retrieves the current garbage collection stats from a geth node.
|
||||
func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) {
|
||||
var result debug.GCStats
|
||||
@ -265,3 +287,52 @@ func (a OverrideAccount) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
return json.Marshal(output)
|
||||
}
|
||||
|
||||
// BlockOverrides specifies the set of header fields to override.
|
||||
type BlockOverrides struct {
|
||||
// Number overrides the block number.
|
||||
Number *big.Int
|
||||
// Difficulty overrides the block difficulty.
|
||||
Difficulty *big.Int
|
||||
// Time overrides the block timestamp. Time is applied only when
|
||||
// it is non-zero.
|
||||
Time uint64
|
||||
// GasLimit overrides the block gas limit. GasLimit is applied only when
|
||||
// it is non-zero.
|
||||
GasLimit uint64
|
||||
// Coinbase overrides the block coinbase. Coinbase is applied only when
|
||||
// it is different from the zero address.
|
||||
Coinbase common.Address
|
||||
// Random overrides the block extra data which feeds into the RANDOM opcode.
|
||||
// Random is applied only when it is a non-zero hash.
|
||||
Random common.Hash
|
||||
// BaseFee overrides the block base fee.
|
||||
BaseFee *big.Int
|
||||
}
|
||||
|
||||
func (o BlockOverrides) MarshalJSON() ([]byte, error) {
|
||||
type override struct {
|
||||
Number *hexutil.Big `json:"number,omitempty"`
|
||||
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
|
||||
Time hexutil.Uint64 `json:"time,omitempty"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
|
||||
Coinbase *common.Address `json:"coinbase,omitempty"`
|
||||
Random *common.Hash `json:"random,omitempty"`
|
||||
BaseFee *hexutil.Big `json:"baseFee,omitempty"`
|
||||
}
|
||||
|
||||
output := override{
|
||||
Number: (*hexutil.Big)(o.Number),
|
||||
Difficulty: (*hexutil.Big)(o.Difficulty),
|
||||
Time: hexutil.Uint64(o.Time),
|
||||
GasLimit: hexutil.Uint64(o.GasLimit),
|
||||
BaseFee: (*hexutil.Big)(o.BaseFee),
|
||||
}
|
||||
if o.Coinbase != (common.Address{}) {
|
||||
output.Coinbase = &o.Coinbase
|
||||
}
|
||||
if o.Random != (common.Hash{}) {
|
||||
output.Random = &o.Random
|
||||
}
|
||||
return json.Marshal(output)
|
||||
}
|
||||
|
@ -127,6 +127,9 @@ func TestGethClient(t *testing.T) {
|
||||
}, {
|
||||
"TestCallContract",
|
||||
func(t *testing.T) { testCallContract(t, client) },
|
||||
}, {
|
||||
"TestCallContractWithBlockOverrides",
|
||||
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
|
||||
},
|
||||
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
|
||||
// one block. The `testAcessList` fails if the miner has not yet created a
|
||||
@ -413,3 +416,75 @@ func TestOverrideAccountMarshal(t *testing.T) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
msg := ethereum.CallMsg{
|
||||
From: testAddr,
|
||||
To: &common.Address{},
|
||||
Gas: 50000,
|
||||
GasPrice: big.NewInt(1000000000),
|
||||
Value: big.NewInt(1),
|
||||
}
|
||||
override := OverrideAccount{
|
||||
// Returns coinbase address.
|
||||
Code: common.FromHex("0x41806000526014600cf3"),
|
||||
}
|
||||
mapAcc := make(map[common.Address]OverrideAccount)
|
||||
mapAcc[common.Address{}] = override
|
||||
res, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(res, common.FromHex("0x0000000000000000000000000000000000000000")) {
|
||||
t.Fatalf("unexpected result: %x", res)
|
||||
}
|
||||
|
||||
// Now test with block overrides
|
||||
bo := BlockOverrides{
|
||||
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
||||
}
|
||||
res, err = ec.CallContractWithBlockOverrides(context.Background(), msg, big.NewInt(0), &mapAcc, bo)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(res, common.FromHex("0x1111111111111111111111111111111111111111")) {
|
||||
t.Fatalf("unexpected result: %x", res)
|
||||
}
|
||||
}
|
||||
|
@ -1068,7 +1068,7 @@ func (c *CallResult) Status() hexutil.Uint64 {
|
||||
func (b *Block) Call(ctx context.Context, args struct {
|
||||
Data ethapi.TransactionArgs
|
||||
}) (*CallResult, error) {
|
||||
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
|
||||
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1131,7 +1131,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())
|
||||
result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
@ -955,7 +956,39 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||
@ -982,7 +1015,11 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
evm, vmError, err := b.GetEVM(ctx, msg, state, 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, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1046,8 +1083,8 @@ func (e *revertError) ErrorData() interface{} {
|
||||
//
|
||||
// Note, this function 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) {
|
||||
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
|
||||
func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) {
|
||||
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, blockOverrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1132,7 +1169,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, core.ErrIntrinsicGas) {
|
||||
return true, nil, nil // Special case, raise gas limit
|
||||
@ -1478,7 +1515,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, header, &config)
|
||||
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config, nil)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
|
@ -17,14 +17,34 @@
|
||||
package ethapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"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/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"
|
||||
)
|
||||
|
||||
func TestTransaction_RoundTripRpcJSON(t *testing.T) {
|
||||
@ -157,3 +177,450 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []type
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type testBackend struct {
|
||||
db ethdb.Database
|
||||
chain *core.BlockChain
|
||||
}
|
||||
|
||||
func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend {
|
||||
var (
|
||||
engine = ethash.NewFaker()
|
||||
backend = &testBackend{
|
||||
db: rawdb.NewMemoryDatabase(),
|
||||
}
|
||||
cacheConfig = &core.CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
SnapshotLimit: 0,
|
||||
TrieDirtyDisabled: true, // Archive mode
|
||||
}
|
||||
)
|
||||
// Generate blocks for testing
|
||||
_, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator)
|
||||
chain, err := core.NewBlockChain(backend.db, cacheConfig, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
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.chain = chain
|
||||
return backend
|
||||
}
|
||||
|
||||
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 nil }
|
||||
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
|
||||
}
|
||||
return b.chain.GetHeaderByNumber(uint64(number)), nil
|
||||
}
|
||||
func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) CurrentHeader() *types.Header { panic("implement me") }
|
||||
func (b testBackend) CurrentBlock() *types.Header { panic("implement me") }
|
||||
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
|
||||
}
|
||||
return b.chain.GetBlockByNumber(uint64(number)), nil
|
||||
}
|
||||
func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
|
||||
if blockNr, ok := blockNrOrHash.Number(); ok {
|
||||
return b.BlockByNumber(ctx, blockNr)
|
||||
}
|
||||
panic("implement me")
|
||||
}
|
||||
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) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { panic("implement me") }
|
||||
func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) (*vm.EVM, func() error, error) {
|
||||
vmError := func() error { return nil }
|
||||
if vmConfig == nil {
|
||||
vmConfig = b.chain.GetVMConfig()
|
||||
}
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
context := core.NewEVMBlockContext(header, b.chain, nil)
|
||||
if blockContext != nil {
|
||||
context = *blockContext
|
||||
}
|
||||
return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig), vmError, nil
|
||||
}
|
||||
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) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, 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) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) Stats() (pending int, queued int) { panic("implement me") }
|
||||
func (b testBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
|
||||
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")
|
||||
}
|
||||
|
||||
func TestEstimateGas(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
var (
|
||||
accounts = newAccounts(2)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
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)
|
||||
)
|
||||
api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, 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)
|
||||
}))
|
||||
var testSuite = []struct {
|
||||
blockNumber rpc.BlockNumber
|
||||
call TransactionArgs
|
||||
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,
|
||||
},
|
||||
// simple transfer with insufficient funds on latest block
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{
|
||||
From: &randomAccounts[0].addr,
|
||||
To: &accounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
expectErr: core.ErrInsufficientFunds,
|
||||
want: 21000,
|
||||
},
|
||||
// empty create
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{},
|
||||
expectErr: nil,
|
||||
want: 53000,
|
||||
},
|
||||
}
|
||||
for i, tc := range testSuite {
|
||||
result, 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 uint64(result) != tc.want {
|
||||
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCall(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
var (
|
||||
accounts = newAccounts(3)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{
|
||||
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
|
||||
},
|
||||
}
|
||||
genBlocks = 10
|
||||
signer = types.HomesteadSigner{}
|
||||
)
|
||||
api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, 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)
|
||||
}))
|
||||
randomAccounts := newAccounts(3)
|
||||
var testSuite = []struct {
|
||||
blockNumber rpc.BlockNumber
|
||||
overrides StateOverride
|
||||
call TransactionArgs
|
||||
blockOverrides BlockOverrides
|
||||
expectErr error
|
||||
want string
|
||||
}{
|
||||
// transfer on genesis
|
||||
{
|
||||
blockNumber: rpc.BlockNumber(0),
|
||||
call: TransactionArgs{
|
||||
From: &accounts[0].addr,
|
||||
To: &accounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
expectErr: nil,
|
||||
want: "0x",
|
||||
},
|
||||
// transfer on the head
|
||||
{
|
||||
blockNumber: rpc.BlockNumber(genBlocks),
|
||||
call: TransactionArgs{
|
||||
From: &accounts[0].addr,
|
||||
To: &accounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
expectErr: nil,
|
||||
want: "0x",
|
||||
},
|
||||
// transfer on a non-existent block, error expects
|
||||
{
|
||||
blockNumber: rpc.BlockNumber(genBlocks + 1),
|
||||
call: TransactionArgs{
|
||||
From: &accounts[0].addr,
|
||||
To: &accounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
expectErr: errors.New("header not found"),
|
||||
},
|
||||
// transfer on the latest block
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{
|
||||
From: &accounts[0].addr,
|
||||
To: &accounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
expectErr: nil,
|
||||
want: "0x",
|
||||
},
|
||||
// Call which can only succeed if state is state overridden
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{
|
||||
From: &randomAccounts[0].addr,
|
||||
To: &randomAccounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
overrides: StateOverride{
|
||||
randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
|
||||
},
|
||||
want: "0x",
|
||||
},
|
||||
// Invalid call without state overriding
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{
|
||||
From: &randomAccounts[0].addr,
|
||||
To: &randomAccounts[1].addr,
|
||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||
},
|
||||
expectErr: core.ErrInsufficientFunds,
|
||||
},
|
||||
// Successful simple contract call
|
||||
//
|
||||
// // SPDX-License-Identifier: GPL-3.0
|
||||
//
|
||||
// pragma solidity >=0.7.0 <0.8.0;
|
||||
//
|
||||
// /**
|
||||
// * @title Storage
|
||||
// * @dev Store & retrieve value in a variable
|
||||
// */
|
||||
// contract Storage {
|
||||
// uint256 public number;
|
||||
// constructor() {
|
||||
// number = block.number;
|
||||
// }
|
||||
// }
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{
|
||||
From: &randomAccounts[0].addr,
|
||||
To: &randomAccounts[2].addr,
|
||||
Data: hex2Bytes("8381f58a"), // call number()
|
||||
},
|
||||
overrides: StateOverride{
|
||||
randomAccounts[2].addr: OverrideAccount{
|
||||
Code: hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033"),
|
||||
StateDiff: &map[common.Hash]common.Hash{common.Hash{}: common.BigToHash(big.NewInt(123))},
|
||||
},
|
||||
},
|
||||
want: "0x000000000000000000000000000000000000000000000000000000000000007b",
|
||||
},
|
||||
// Block overrides should work
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{
|
||||
From: &accounts[1].addr,
|
||||
Input: &hexutil.Bytes{
|
||||
0x43, // NUMBER
|
||||
0x60, 0x00, 0x52, // MSTORE offset 0
|
||||
0x60, 0x20, 0x60, 0x00, 0xf3,
|
||||
},
|
||||
},
|
||||
blockOverrides: BlockOverrides{Number: (*hexutil.Big)(big.NewInt(11))},
|
||||
want: "0x000000000000000000000000000000000000000000000000000000000000000b",
|
||||
},
|
||||
}
|
||||
for i, tc := range testSuite {
|
||||
result, err := api.Call(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
|
||||
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) {
|
||||
// Second try
|
||||
if !reflect.DeepEqual(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 !reflect.DeepEqual(result.String(), tc.want) {
|
||||
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, result.String(), tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
key *ecdsa.PrivateKey
|
||||
addr common.Address
|
||||
}
|
||||
|
||||
type Accounts []Account
|
||||
|
||||
func (a Accounts) Len() int { return len(a) }
|
||||
func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 }
|
||||
|
||||
func newAccounts(n int) (accounts Accounts) {
|
||||
for i := 0; i < n; i++ {
|
||||
key, _ := crypto.GenerateKey()
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
accounts = append(accounts, Account{key: key, addr: addr})
|
||||
}
|
||||
sort.Sort(accounts)
|
||||
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
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ type Backend interface {
|
||||
PendingBlockAndReceipts() (*types.Block, types.Receipts)
|
||||
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
|
||||
GetTd(ctx context.Context, hash common.Hash) *big.Int
|
||||
GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error)
|
||||
GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, 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
|
||||
|
@ -305,7 +305,7 @@ func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number
|
||||
return nil, nil
|
||||
}
|
||||
func (b *backendMock) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil }
|
||||
func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
|
||||
func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (b *backendMock) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { return nil }
|
||||
|
@ -608,8 +608,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],
|
||||
}),
|
||||
],
|
||||
properties: [
|
||||
|
@ -184,12 +184,15 @@ func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
|
||||
func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error, error) {
|
||||
if vmConfig == nil {
|
||||
vmConfig = new(vm.Config)
|
||||
}
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
context := core.NewEVMBlockContext(header, b.eth.blockchain, nil)
|
||||
if blockCtx != nil {
|
||||
context = *blockCtx
|
||||
}
|
||||
return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error, nil
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user