From 71e9c9b8a732b33f50b4565627463fb252b4aeff Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 25 Mar 2025 05:08:53 +1100 Subject: [PATCH] internal/ethapi: support for beacon root and withdrawals in simulate api (#31304) Adds block override fields for beacon block root and withdrawals to the eth_simulateV1. Addresses https://github.com/ethereum/go-ethereum/issues/31264 --- eth/gasestimator/gasestimator.go | 4 +- eth/tracers/api.go | 4 +- internal/ethapi/api.go | 4 +- internal/ethapi/api_test.go | 18 +++++++ internal/ethapi/override/override.go | 14 ++++- internal/ethapi/simulate.go | 79 +++++++++++++++++++++++----- 6 files changed, 106 insertions(+), 17 deletions(-) diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index a6c4718cf4..fc8e3a2e42 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -223,7 +223,9 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio dirtyState = opts.State.Copy() ) if opts.BlockOverrides != nil { - opts.BlockOverrides.Apply(&evmContext) + if err := opts.BlockOverrides.Apply(&evmContext); err != nil { + return nil, err + } } // Lower the basefee to 0 to avoid breaking EVM // invariants (basefee < feecap). diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 627dd487fc..3cb86c80e2 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -950,7 +950,9 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) // Apply the customization rules if required. if config != nil { - config.BlockOverrides.Apply(&vmctx) + if overrideErr := config.BlockOverrides.Apply(&vmctx); overrideErr != nil { + return nil, overrideErr + } rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time) precompiles = vm.ActivePrecompiledContracts(rules) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 979a7e822a..e0d5b32622 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -660,7 +660,9 @@ func (context *ChainContext) Config() *params.ChainConfig { func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) if blockOverrides != nil { - blockOverrides.Apply(&blockCtx) + if err := blockOverrides.Apply(&blockCtx); err != nil { + return nil, err + } } rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time) precompiles := vm.ActivePrecompiledContracts(rules) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 88ff7b8af3..b086d1a6d5 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1134,6 +1134,24 @@ func TestCall(t *testing.T) { }, want: "0x0000000000000000000000000000000000000000000000000000000000000000", }, + { + name: "unsupported block override beaconRoot", + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{}, + blockOverrides: override.BlockOverrides{ + BeaconRoot: &common.Hash{0, 1, 2}, + }, + expectErr: errors.New(`block override "beaconRoot" is not supported for this RPC method`), + }, + { + name: "unsupported block override withdrawals", + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{}, + blockOverrides: override.BlockOverrides{ + Withdrawals: &types.Withdrawals{}, + }, + expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`), + }, } for _, tc := range testSuite { result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) diff --git a/internal/ethapi/override/override.go b/internal/ethapi/override/override.go index f6a8a94ffd..0bcf3c444d 100644 --- a/internal/ethapi/override/override.go +++ b/internal/ethapi/override/override.go @@ -17,6 +17,7 @@ package override import ( + "errors" "fmt" "math/big" @@ -128,12 +129,20 @@ type BlockOverrides struct { PrevRandao *common.Hash BaseFeePerGas *hexutil.Big BlobBaseFee *hexutil.Big + BeaconRoot *common.Hash + Withdrawals *types.Withdrawals } // Apply overrides the given header fields into the given block context. -func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { +func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) error { if o == nil { - return + return nil + } + if o.BeaconRoot != nil { + return errors.New(`block override "beaconRoot" is not supported for this RPC method`) + } + if o.Withdrawals != nil { + return errors.New(`block override "withdrawals" is not supported for this RPC method`) } if o.Number != nil { blockCtx.BlockNumber = o.Number.ToInt() @@ -159,6 +168,7 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) { if o.BlobBaseFee != nil { blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt() } + return nil } // MakeHeader returns a new header object with the overridden diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 097de8b0b0..ba346b132f 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -36,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi/override" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" ) const ( @@ -95,6 +94,47 @@ type simOpts struct { ReturnFullTransactions bool } +// simChainHeadReader implements ChainHeaderReader which is needed as input for FinalizeAndAssemble. +type simChainHeadReader struct { + context.Context + Backend +} + +func (m *simChainHeadReader) Config() *params.ChainConfig { + return m.Backend.ChainConfig() +} + +func (m *simChainHeadReader) CurrentHeader() *types.Header { + return m.Backend.CurrentHeader() +} + +func (m *simChainHeadReader) GetHeader(hash common.Hash, number uint64) *types.Header { + header, err := m.Backend.HeaderByNumber(m.Context, rpc.BlockNumber(number)) + if err != nil || header == nil { + return nil + } + if header.Hash() != hash { + return nil + } + return header +} + +func (m *simChainHeadReader) GetHeaderByNumber(number uint64) *types.Header { + header, err := m.Backend.HeaderByNumber(m.Context, rpc.BlockNumber(number)) + if err != nil { + return nil + } + return header +} + +func (m *simChainHeadReader) GetHeaderByHash(hash common.Hash) *types.Header { + header, err := m.Backend.HeaderByHash(m.Context, hash) + if err != nil { + return nil + } + return header +} + // simulator is a stateful object that simulates a series of blocks. // it is not safe for concurrent use. type simulator struct { @@ -209,6 +249,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsVerkle(header.Number, header.Time) { core.ProcessParentBlockHash(header.ParentHash, evm) } + if header.ParentBeaconRoot != nil { + core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, evm) + } var allLogs []*types.Log for i, call := range block.Calls { if err := ctx.Err(); err != nil { @@ -258,6 +301,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } callResults[i] = callRes } + header.GasUsed = gasUsed + if sim.chainConfig.IsCancun(header.Number, header.Time) { + header.BlobGasUsed = &blobGasUsed + } var requests [][]byte // Process EIP-7685 requests if sim.chainConfig.IsPrague(header.Number, header.Time) { @@ -271,20 +318,16 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, // EIP-7251 core.ProcessConsolidationQueue(&requests, evm) } - header.Root = sim.state.IntermediateRoot(true) - header.GasUsed = gasUsed - if sim.chainConfig.IsCancun(header.Number, header.Time) { - header.BlobGasUsed = &blobGasUsed - } - var withdrawals types.Withdrawals - if sim.chainConfig.IsShanghai(header.Number, header.Time) { - withdrawals = make([]*types.Withdrawal, 0) - } if requests != nil { reqHash := types.CalcRequestsHash(requests) header.RequestsHash = &reqHash } - b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil)) + blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals} + chainHeadReader := &simChainHeadReader{ctx, sim.b} + b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts) + if err != nil { + return nil, nil, err + } repairLogs(callResults, b.Hash()) return b, callResults, nil } @@ -346,6 +389,9 @@ func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) { n := new(big.Int).Add(prevNumber, big.NewInt(1)) block.BlockOverrides.Number = (*hexutil.Big)(n) } + if block.BlockOverrides.Withdrawals == nil { + block.BlockOverrides.Withdrawals = &types.Withdrawals{} + } diff := new(big.Int).Sub(block.BlockOverrides.Number.ToInt(), prevNumber) if diff.Cmp(common.Big0) <= 0 { return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", block.BlockOverrides.Number.ToInt().Uint64(), prevNumber)} @@ -360,7 +406,13 @@ func (sim *simulator) sanitizeChain(blocks []simBlock) ([]simBlock, error) { for i := uint64(0); i < gap.Uint64(); i++ { n := new(big.Int).Add(prevNumber, big.NewInt(int64(i+1))) t := prevTimestamp + timestampIncrement - b := simBlock{BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(n), Time: (*hexutil.Uint64)(&t)}} + b := simBlock{ + BlockOverrides: &override.BlockOverrides{ + Number: (*hexutil.Big)(n), + Time: (*hexutil.Uint64)(&t), + Withdrawals: &types.Withdrawals{}, + }, + } prevTimestamp = t res = append(res, b) } @@ -405,6 +457,9 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) { var parentBeaconRoot *common.Hash if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { parentBeaconRoot = &common.Hash{} + if overrides.BeaconRoot != nil { + parentBeaconRoot = overrides.BeaconRoot + } } header = overrides.MakeHeader(&types.Header{ UncleHash: types.EmptyUncleHash,