Commit d470c776 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-program: Store created blocks to avoid needing to re-execute them (#12382)

* op-program: Store created blocks to avoid needing to re-execute them.

Since op-program always creates a new block via engine_forkChoiceUpdated/engine_getPayload and then immediately sends the same block back to engine_newPayload to be imported, store the created block to the database without updating the chain head, so the engine_newPayload method is a no-op instead of having to execute the transactions again.

* op-program: Add test that created block is not reprocessed on import (#12402)

Also fixes differences in block hash for created payloads when using non-op stack chains.
parent 9e1b6a94
...@@ -112,6 +112,7 @@ func NewOpGeth(t testing.TB, ctx context.Context, cfg *e2esys.SystemConfig) (*Op ...@@ -112,6 +112,7 @@ func NewOpGeth(t testing.TB, ctx context.Context, cfg *e2esys.SystemConfig) (*Op
l2Client, err := ethclient.Dial(node.UserRPC().RPC()) l2Client, err := ethclient.Dial(node.UserRPC().RPC())
require.NoError(t, err) require.NoError(t, err)
// Note: Using CanyonTime here because for OP Stack chains, Shanghai must be activated at the same time as Canyon.
genesisPayload, err := eth.BlockAsPayload(l2GenesisBlock, cfg.DeployConfig.CanyonTime(l2GenesisBlock.Time())) genesisPayload, err := eth.BlockAsPayload(l2GenesisBlock, cfg.DeployConfig.CanyonTime(l2GenesisBlock.Time()))
require.NoError(t, err) require.NoError(t, err)
......
...@@ -18,12 +18,16 @@ import ( ...@@ -18,12 +18,16 @@ import (
var ErrNotFound = errors.New("not found") var ErrNotFound = errors.New("not found")
type OracleEngine struct { type OracleEngine struct {
api *engineapi.L2EngineAPI api *engineapi.L2EngineAPI
backend engineapi.EngineBackend
// backend is the actual implementation used to create and process blocks. It is specifically a
// engineapi.CachingEngineBackend to ensure that blocks are stored when they are created and don't need to be
// re-executed when sent back via execution_newPayload.
backend engineapi.CachingEngineBackend
rollupCfg *rollup.Config rollupCfg *rollup.Config
} }
func NewOracleEngine(rollupCfg *rollup.Config, logger log.Logger, backend engineapi.EngineBackend) *OracleEngine { func NewOracleEngine(rollupCfg *rollup.Config, logger log.Logger, backend engineapi.CachingEngineBackend) *OracleEngine {
engineAPI := engineapi.NewL2EngineAPI(logger, backend, nil) engineAPI := engineapi.NewL2EngineAPI(logger, backend, nil)
return &OracleEngine{ return &OracleEngine{
api: engineAPI, api: engineAPI,
...@@ -94,7 +98,7 @@ func (o *OracleEngine) PayloadByHash(ctx context.Context, hash common.Hash) (*et ...@@ -94,7 +98,7 @@ func (o *OracleEngine) PayloadByHash(ctx context.Context, hash common.Hash) (*et
if block == nil { if block == nil {
return nil, ErrNotFound return nil, ErrNotFound
} }
return eth.BlockAsPayloadEnv(block, o.rollupCfg.CanyonTime) return eth.BlockAsPayloadEnv(block, o.backend.Config().ShanghaiTime)
} }
func (o *OracleEngine) PayloadByNumber(ctx context.Context, n uint64) (*eth.ExecutionPayloadEnvelope, error) { func (o *OracleEngine) PayloadByNumber(ctx context.Context, n uint64) (*eth.ExecutionPayloadEnvelope, error) {
......
...@@ -40,7 +40,9 @@ type OracleBackedL2Chain struct { ...@@ -40,7 +40,9 @@ type OracleBackedL2Chain struct {
db ethdb.KeyValueStore db ethdb.KeyValueStore
} }
var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil) // Must implement CachingEngineBackend, not just EngineBackend to ensure that blocks are stored when they are created
// and don't need to be re-executed when sent back via execution_newPayload.
var _ engineapi.CachingEngineBackend = (*OracleBackedL2Chain)(nil)
func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, precompileOracle engineapi.PrecompileOracle, chainCfg *params.ChainConfig, l2OutputRoot common.Hash) (*OracleBackedL2Chain, error) { func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, precompileOracle engineapi.PrecompileOracle, chainCfg *params.ChainConfig, l2OutputRoot common.Hash) (*OracleBackedL2Chain, error) {
output := oracle.OutputByRoot(l2OutputRoot) output := oracle.OutputByRoot(l2OutputRoot)
...@@ -191,19 +193,27 @@ func (o *OracleBackedL2Chain) InsertBlockWithoutSetHead(block *types.Block, make ...@@ -191,19 +193,27 @@ func (o *OracleBackedL2Chain) InsertBlockWithoutSetHead(block *types.Block, make
return nil, fmt.Errorf("invalid transaction (%d): %w", i, err) return nil, fmt.Errorf("invalid transaction (%d): %w", i, err)
} }
} }
expected, err := processor.Assemble() expected, err := o.AssembleAndInsertBlockWithoutSetHead(processor)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid block: %w", err) return nil, fmt.Errorf("invalid block: %w", err)
} }
if expected.Hash() != block.Hash() { if expected.Hash() != block.Hash() {
return nil, fmt.Errorf("block root mismatch, expected: %v, actual: %v", expected.Hash(), block.Hash()) return nil, fmt.Errorf("block root mismatch, expected: %v, actual: %v", expected.Hash(), block.Hash())
} }
return nil, nil
}
func (o *OracleBackedL2Chain) AssembleAndInsertBlockWithoutSetHead(processor *engineapi.BlockProcessor) (*types.Block, error) {
block, err := processor.Assemble()
if err != nil {
return nil, fmt.Errorf("invalid block: %w", err)
}
err = processor.Commit() err = processor.Commit()
if err != nil { if err != nil {
return nil, fmt.Errorf("commit block: %w", err) return nil, fmt.Errorf("commit block: %w", err)
} }
o.blocks[block.Hash()] = block o.blocks[block.Hash()] = block
return nil, nil return block, nil
} }
func (o *OracleBackedL2Chain) SetCanonical(head *types.Block) (common.Hash, error) { func (o *OracleBackedL2Chain) SetCanonical(head *types.Block) (common.Hash, error) {
......
...@@ -271,9 +271,9 @@ func TestPrecompileOracle(t *testing.T) { ...@@ -271,9 +271,9 @@ func TestPrecompileOracle(t *testing.T) {
} }
func assertBlockDataAvailable(t *testing.T, chain *OracleBackedL2Chain, block *types.Block, blockNumber uint64) { func assertBlockDataAvailable(t *testing.T, chain *OracleBackedL2Chain, block *types.Block, blockNumber uint64) {
require.Equal(t, block, chain.GetBlockByHash(block.Hash()), "get block %v by hash", blockNumber) require.Equal(t, block.Hash(), chain.GetBlockByHash(block.Hash()).Hash(), "get block %v by hash", blockNumber)
require.Equal(t, block.Header(), chain.GetHeaderByHash(block.Hash()), "get header %v by hash", blockNumber) require.Equal(t, block.Header(), chain.GetHeaderByHash(block.Hash()), "get header %v by hash", blockNumber)
require.Equal(t, block, chain.GetBlock(block.Hash(), blockNumber), "get block %v by hash and number", blockNumber) require.Equal(t, block.Hash(), chain.GetBlock(block.Hash(), blockNumber).Hash(), "get block %v by hash and number", blockNumber)
require.Equal(t, block.Header(), chain.GetHeader(block.Hash(), blockNumber), "get header %v by hash and number", blockNumber) require.Equal(t, block.Header(), chain.GetHeader(block.Hash(), blockNumber), "get header %v by hash and number", blockNumber)
require.True(t, chain.HasBlockAndState(block.Hash(), blockNumber), "has block and state for block %v", blockNumber) require.True(t, chain.HasBlockAndState(block.Hash(), blockNumber), "has block and state for block %v", blockNumber)
} }
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -33,7 +35,7 @@ func TestPayloadByHash(t *testing.T) { ...@@ -33,7 +35,7 @@ func TestPayloadByHash(t *testing.T) {
block := stub.head block := stub.head
payload, err := engine.PayloadByHash(ctx, block.Hash()) payload, err := engine.PayloadByHash(ctx, block.Hash())
require.NoError(t, err) require.NoError(t, err)
expected, err := eth.BlockAsPayload(block, engine.rollupCfg.CanyonTime) expected, err := eth.BlockAsPayload(block, engine.backend.Config().ShanghaiTime)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, &eth.ExecutionPayloadEnvelope{ExecutionPayload: expected}, payload) require.Equal(t, &eth.ExecutionPayloadEnvelope{ExecutionPayload: expected}, payload)
}) })
...@@ -55,7 +57,7 @@ func TestPayloadByNumber(t *testing.T) { ...@@ -55,7 +57,7 @@ func TestPayloadByNumber(t *testing.T) {
block := stub.head block := stub.head
payload, err := engine.PayloadByNumber(ctx, block.NumberU64()) payload, err := engine.PayloadByNumber(ctx, block.NumberU64())
require.NoError(t, err) require.NoError(t, err)
expected, err := eth.BlockAsPayload(block, engine.rollupCfg.CanyonTime) expected, err := eth.BlockAsPayload(block, engine.backend.Config().ShanghaiTime)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, &eth.ExecutionPayloadEnvelope{ExecutionPayload: expected}, payload) require.Equal(t, &eth.ExecutionPayloadEnvelope{ExecutionPayload: expected}, payload)
}) })
...@@ -128,7 +130,7 @@ func TestSystemConfigByL2Hash(t *testing.T) { ...@@ -128,7 +130,7 @@ func TestSystemConfigByL2Hash(t *testing.T) {
engine, stub := createOracleEngine(t) engine, stub := createOracleEngine(t)
t.Run("KnownBlock", func(t *testing.T) { t.Run("KnownBlock", func(t *testing.T) {
payload, err := eth.BlockAsPayload(stub.safe, engine.rollupCfg.CanyonTime) payload, err := eth.BlockAsPayload(stub.safe, engine.backend.Config().ShanghaiTime)
require.NoError(t, err) require.NoError(t, err)
expected, err := derive.PayloadToSystemConfig(engine.rollupCfg, payload) expected, err := derive.PayloadToSystemConfig(engine.rollupCfg, payload)
require.NoError(t, err) require.NoError(t, err)
...@@ -148,6 +150,7 @@ func createOracleEngine(t *testing.T) (*OracleEngine, *stubEngineBackend) { ...@@ -148,6 +150,7 @@ func createOracleEngine(t *testing.T) (*OracleEngine, *stubEngineBackend) {
head := createL2Block(t, 4) head := createL2Block(t, 4)
safe := createL2Block(t, 3) safe := createL2Block(t, 3)
finalized := createL2Block(t, 2) finalized := createL2Block(t, 2)
rollupCfg := chaincfg.OPSepolia()
backend := &stubEngineBackend{ backend := &stubEngineBackend{
head: head, head: head,
safe: safe, safe: safe,
...@@ -162,10 +165,11 @@ func createOracleEngine(t *testing.T) (*OracleEngine, *stubEngineBackend) { ...@@ -162,10 +165,11 @@ func createOracleEngine(t *testing.T) (*OracleEngine, *stubEngineBackend) {
safe.NumberU64(): safe.Hash(), safe.NumberU64(): safe.Hash(),
finalized.NumberU64(): finalized.Hash(), finalized.NumberU64(): finalized.Hash(),
}, },
rollupCfg: rollupCfg,
} }
engine := OracleEngine{ engine := OracleEngine{
backend: backend, backend: backend,
rollupCfg: chaincfg.OPSepolia(), rollupCfg: rollupCfg,
} }
return &engine, backend return &engine, backend
} }
...@@ -192,80 +196,87 @@ type stubEngineBackend struct { ...@@ -192,80 +196,87 @@ type stubEngineBackend struct {
finalized *types.Block finalized *types.Block
blocks map[common.Hash]*types.Block blocks map[common.Hash]*types.Block
canonical map[uint64]common.Hash canonical map[uint64]common.Hash
rollupCfg *rollup.Config
} }
func (s stubEngineBackend) CurrentHeader() *types.Header { func (s *stubEngineBackend) CurrentHeader() *types.Header {
return s.head.Header() return s.head.Header()
} }
func (s stubEngineBackend) CurrentSafeBlock() *types.Header { func (s *stubEngineBackend) CurrentSafeBlock() *types.Header {
return s.safe.Header() return s.safe.Header()
} }
func (s stubEngineBackend) CurrentFinalBlock() *types.Header { func (s *stubEngineBackend) CurrentFinalBlock() *types.Header {
return s.finalized.Header() return s.finalized.Header()
} }
func (s stubEngineBackend) GetBlockByHash(hash common.Hash) *types.Block { func (s *stubEngineBackend) GetBlockByHash(hash common.Hash) *types.Block {
return s.blocks[hash] return s.blocks[hash]
} }
func (s stubEngineBackend) GetCanonicalHash(n uint64) common.Hash { func (s *stubEngineBackend) GetCanonicalHash(n uint64) common.Hash {
return s.canonical[n] return s.canonical[n]
} }
func (s stubEngineBackend) GetBlock(hash common.Hash, number uint64) *types.Block { func (s *stubEngineBackend) GetBlock(hash common.Hash, number uint64) *types.Block {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) HasBlockAndState(hash common.Hash, number uint64) bool { func (s *stubEngineBackend) HasBlockAndState(hash common.Hash, number uint64) bool {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) GetVMConfig() *vm.Config { func (s *stubEngineBackend) GetVMConfig() *vm.Config {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) Config() *params.ChainConfig { func (s *stubEngineBackend) Config() *params.ChainConfig {
return &params.ChainConfig{
ShanghaiTime: s.rollupCfg.CanyonTime,
}
}
func (s *stubEngineBackend) Engine() consensus.Engine {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) Engine() consensus.Engine { func (s *stubEngineBackend) StateAt(root common.Hash) (*state.StateDB, error) {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) StateAt(root common.Hash) (*state.StateDB, error) { func (s *stubEngineBackend) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) { func (s stubEngineBackend) AssembleAndInsertBlockWithoutSetHead(_ *engineapi.BlockProcessor) (*types.Block, error) {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) SetCanonical(head *types.Block) (common.Hash, error) { func (s *stubEngineBackend) SetCanonical(head *types.Block) (common.Hash, error) {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) SetFinalized(header *types.Header) { func (s *stubEngineBackend) SetFinalized(header *types.Header) {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) SetSafe(header *types.Header) { func (s *stubEngineBackend) SetSafe(header *types.Header) {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) GetHeader(hash common.Hash, number uint64) *types.Header { func (s *stubEngineBackend) GetHeader(hash common.Hash, number uint64) *types.Header {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) GetHeaderByNumber(number uint64) *types.Header { func (s *stubEngineBackend) GetHeaderByNumber(number uint64) *types.Header {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) GetHeaderByHash(hash common.Hash) *types.Header { func (s *stubEngineBackend) GetHeaderByHash(hash common.Hash) *types.Header {
panic("unsupported") panic("unsupported")
} }
func (s stubEngineBackend) GetTd(hash common.Hash, number uint64) *big.Int { func (s *stubEngineBackend) GetTd(hash common.Hash, number uint64) *big.Int {
panic("unsupported") panic("unsupported")
} }
...@@ -52,13 +52,6 @@ func NewBlockProcessorFromPayloadAttributes(provider BlockDataProvider, parent c ...@@ -52,13 +52,6 @@ func NewBlockProcessorFromPayloadAttributes(provider BlockDataProvider, parent c
ParentBeaconRoot: attrs.ParentBeaconBlockRoot, ParentBeaconRoot: attrs.ParentBeaconBlockRoot,
} }
// Ecotone
if attrs.ParentBeaconBlockRoot != nil {
zero := uint64(0)
header.BlobGasUsed = &zero
header.ExcessBlobGas = &zero
}
return NewBlockProcessorFromHeader(provider, header) return NewBlockProcessorFromHeader(provider, header)
} }
...@@ -80,7 +73,7 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (* ...@@ -80,7 +73,7 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*
header.BaseFee = eip1559.CalcBaseFee(provider.Config(), parentHeader, header.Time) header.BaseFee = eip1559.CalcBaseFee(provider.Config(), parentHeader, header.Time)
header.GasUsed = 0 header.GasUsed = 0
gasPool := new(core.GasPool).AddGas(header.GasLimit) gasPool := new(core.GasPool).AddGas(header.GasLimit)
if h.ParentBeaconRoot != nil { mkEVM := func() *vm.EVM {
// Unfortunately this is not part of any Geth environment setup, // Unfortunately this is not part of any Geth environment setup,
// we just have to apply it, like how the Geth block-builder worker does. // we just have to apply it, like how the Geth block-builder worker does.
context := core.NewEVMBlockContext(header, provider, nil, provider.Config(), statedb) context := core.NewEVMBlockContext(header, provider, nil, provider.Config(), statedb)
...@@ -90,8 +83,22 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (* ...@@ -90,8 +83,22 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*
precompileOverrides = vmConfig.PrecompileOverrides precompileOverrides = vmConfig.PrecompileOverrides
} }
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, provider.Config(), vm.Config{PrecompileOverrides: precompileOverrides}) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, provider.Config(), vm.Config{PrecompileOverrides: precompileOverrides})
return vmenv
}
if h.ParentBeaconRoot != nil {
if provider.Config().IsCancun(header.Number, header.Time) {
// Blob tx not supported on optimism chains but fields must be set when Cancun is active.
zero := uint64(0)
header.BlobGasUsed = &zero
header.ExcessBlobGas = &zero
}
vmenv := mkEVM()
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, statedb) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, statedb)
} }
if provider.Config().IsPrague(header.Number, header.Time) {
vmenv := mkEVM()
core.ProcessParentBlockHash(header.ParentHash, vmenv, statedb)
}
return &BlockProcessor{ return &BlockProcessor{
header: header, header: header,
state: statedb, state: statedb,
......
...@@ -46,6 +46,11 @@ type EngineBackend interface { ...@@ -46,6 +46,11 @@ type EngineBackend interface {
consensus.ChainHeaderReader consensus.ChainHeaderReader
} }
type CachingEngineBackend interface {
EngineBackend
AssembleAndInsertBlockWithoutSetHead(processor *BlockProcessor) (*types.Block, error)
}
// L2EngineAPI wraps an engine actor, and implements the RPC backend required to serve the engine API. // L2EngineAPI wraps an engine actor, and implements the RPC backend required to serve the engine API.
// This re-implements some of the Geth API work, but changes the API backend so we can deterministically // This re-implements some of the Geth API work, but changes the API backend so we can deterministically
// build and control the L2 block contents to reach very specific edge cases as desired for testing. // build and control the L2 block contents to reach very specific edge cases as desired for testing.
...@@ -177,7 +182,18 @@ func (ea *L2EngineAPI) endBlock() (*types.Block, error) { ...@@ -177,7 +182,18 @@ func (ea *L2EngineAPI) endBlock() (*types.Block, error) {
processor := ea.blockProcessor processor := ea.blockProcessor
ea.blockProcessor = nil ea.blockProcessor = nil
block, err := processor.Assemble() var block *types.Block
var err error
// If the backend supports it, write the newly created block to the database without making it canonical.
// This avoids needing to reprocess the block if it is sent back via newPayload.
// The block is not made canonical so if it is never sent back via newPayload worst case it just wastes some storage
// In the context of the OP Stack derivation, the created block is always immediately imported so it makes sense to
// optimise.
if cachingBackend, ok := ea.backend.(CachingEngineBackend); ok {
block, err = cachingBackend.AssembleAndInsertBlockWithoutSetHead(processor)
} else {
block, err = processor.Assemble()
}
if err != nil { if err != nil {
return nil, fmt.Errorf("assemble block: %w", err) return nil, fmt.Errorf("assemble block: %w", err)
} }
...@@ -317,7 +333,7 @@ func (ea *L2EngineAPI) getPayload(_ context.Context, payloadId eth.PayloadID) (* ...@@ -317,7 +333,7 @@ func (ea *L2EngineAPI) getPayload(_ context.Context, payloadId eth.PayloadID) (*
return nil, engine.UnknownPayload return nil, engine.UnknownPayload
} }
return eth.BlockAsPayloadEnv(bl, ea.config().CanyonTime) return eth.BlockAsPayloadEnv(bl, ea.config().ShanghaiTime)
} }
func (ea *L2EngineAPI) forkchoiceUpdated(_ context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) { func (ea *L2EngineAPI) forkchoiceUpdated(_ context.Context, state *eth.ForkchoiceState, attr *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) {
...@@ -471,17 +487,17 @@ func (ea *L2EngineAPI) newPayload(_ context.Context, payload *eth.ExecutionPaylo ...@@ -471,17 +487,17 @@ func (ea *L2EngineAPI) newPayload(_ context.Context, payload *eth.ExecutionPaylo
// If we already have the block locally, ignore the entire execution and just // If we already have the block locally, ignore the entire execution and just
// return a fake success. // return a fake success.
if block := ea.backend.GetBlock(payload.BlockHash, uint64(payload.BlockNumber)); block != nil { if block := ea.backend.GetBlock(payload.BlockHash, uint64(payload.BlockNumber)); block != nil {
ea.log.Warn("Ignoring already known beacon payload", "number", payload.BlockNumber, "hash", payload.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) ea.log.Info("Using existing beacon payload", "number", payload.BlockNumber, "hash", payload.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
hash := block.Hash() hash := block.Hash()
return &eth.PayloadStatusV1{Status: eth.ExecutionValid, LatestValidHash: &hash}, nil return &eth.PayloadStatusV1{Status: eth.ExecutionValid, LatestValidHash: &hash}, nil
} }
// TODO: skipping invalid ancestor check (i.e. not remembering previously failed blocks) // Skip invalid ancestor check (i.e. not remembering previously failed blocks)
parent := ea.backend.GetBlock(block.ParentHash(), block.NumberU64()-1) parent := ea.backend.GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil { if parent == nil {
ea.remotes[block.Hash()] = block ea.remotes[block.Hash()] = block
// TODO: hack, saying we accepted if we don't know the parent block. Might want to return critical error if we can't actually sync. // Return accepted if we don't know the parent block. Note that there's no actual sync to activate.
return &eth.PayloadStatusV1{Status: eth.ExecutionAccepted, LatestValidHash: nil}, nil return &eth.PayloadStatusV1{Status: eth.ExecutionAccepted, LatestValidHash: nil}, nil
} }
...@@ -497,7 +513,7 @@ func (ea *L2EngineAPI) newPayload(_ context.Context, payload *eth.ExecutionPaylo ...@@ -497,7 +513,7 @@ func (ea *L2EngineAPI) newPayload(_ context.Context, payload *eth.ExecutionPaylo
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number) log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
if _, err := ea.backend.InsertBlockWithoutSetHead(block, false); err != nil { if _, err := ea.backend.InsertBlockWithoutSetHead(block, false); err != nil {
ea.log.Warn("NewPayloadV1: inserting block failed", "error", err) ea.log.Warn("NewPayloadV1: inserting block failed", "error", err)
// TODO not remembering the payload as invalid // Skip remembering the block was invalid, but do return the invalid response.
return ea.invalid(err, parent.Header()), nil return ea.invalid(err, parent.Header()), nil
} }
hash := block.Hash() hash := block.Hash()
......
package engineapi
import (
"context"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
geth "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
func TestCreatedBlocksAreCached(t *testing.T) {
logger, logs := testlog.CaptureLogger(t, log.LvlInfo)
backend := newStubBackend(t)
engineAPI := NewL2EngineAPI(logger, backend, nil)
require.NotNil(t, engineAPI)
genesis := backend.GetHeaderByNumber(0)
genesisHash := genesis.Hash()
result, err := engineAPI.ForkchoiceUpdatedV3(context.Background(), &eth.ForkchoiceState{
HeadBlockHash: genesisHash,
SafeBlockHash: genesisHash,
FinalizedBlockHash: genesisHash,
}, &eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(genesis.Time + 1),
PrevRandao: eth.Bytes32{0x11},
SuggestedFeeRecipient: common.Address{0x33},
Withdrawals: &types.Withdrawals{},
ParentBeaconBlockRoot: &common.Hash{0x22},
NoTxPool: false,
GasLimit: (*eth.Uint64Quantity)(&genesis.GasLimit),
})
require.NoError(t, err)
require.EqualValues(t, engine.VALID, result.PayloadStatus.Status)
require.NotNil(t, result.PayloadID)
envelope, err := engineAPI.GetPayloadV3(context.Background(), *result.PayloadID)
require.NoError(t, err)
require.NotNil(t, envelope)
newPayloadResult, err := engineAPI.NewPayloadV3(context.Background(), envelope.ExecutionPayload, []common.Hash{}, envelope.ParentBeaconBlockRoot)
require.NoError(t, err)
require.EqualValues(t, engine.VALID, newPayloadResult.Status)
foundLog := logs.FindLog(testlog.NewMessageFilter("Using existing beacon payload"))
require.NotNil(t, foundLog)
require.Equal(t, envelope.ExecutionPayload.BlockHash, foundLog.AttrValue("hash"))
}
func newStubBackend(t *testing.T) *stubCachingBackend {
genesis := createGenesis()
ethCfg := &ethconfig.Config{
NetworkId: genesis.Config.ChainID.Uint64(),
Genesis: genesis,
StateScheme: rawdb.HashScheme,
NoPruning: true,
}
nodeCfg := &node.Config{
Name: "l2-geth",
}
n, err := node.New(nodeCfg)
require.NoError(t, err)
t.Cleanup(func() {
_ = n.Close()
})
backend, err := geth.New(n, ethCfg)
require.NoError(t, err)
chain := backend.BlockChain()
return &stubCachingBackend{EngineBackend: chain}
}
func createGenesis() *core.Genesis {
l2Genesis := &core.Genesis{
Config: params.MergedTestChainConfig, // Arbitrary post-merge config
Difficulty: common.Big0,
ParentHash: common.Hash{},
BaseFee: big.NewInt(7),
Alloc: map[common.Address]types.Account{},
}
return l2Genesis
}
type stubCachingBackend struct {
EngineBackend
}
func (s *stubCachingBackend) AssembleAndInsertBlockWithoutSetHead(processor *BlockProcessor) (*types.Block, error) {
block, err := processor.Assemble()
if err != nil {
return nil, err
}
if _, err := s.EngineBackend.InsertBlockWithoutSetHead(block, false); err != nil {
return nil, err
}
return block, nil
}
var _ CachingEngineBackend = (*stubCachingBackend)(nil)
...@@ -258,7 +258,7 @@ func (envelope *ExecutionPayloadEnvelope) CheckBlockHash() (actual common.Hash, ...@@ -258,7 +258,7 @@ func (envelope *ExecutionPayloadEnvelope) CheckBlockHash() (actual common.Hash,
return blockHash, blockHash == payload.BlockHash return blockHash, blockHash == payload.BlockHash
} }
func BlockAsPayload(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayload, error) { func BlockAsPayload(bl *types.Block, shanghaiTime *uint64) (*ExecutionPayload, error) {
baseFee, overflow := uint256.FromBig(bl.BaseFee()) baseFee, overflow := uint256.FromBig(bl.BaseFee())
if overflow { if overflow {
return nil, fmt.Errorf("invalid base fee in block: %s", bl.BaseFee()) return nil, fmt.Errorf("invalid base fee in block: %s", bl.BaseFee())
...@@ -291,15 +291,15 @@ func BlockAsPayload(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayload, ...@@ -291,15 +291,15 @@ func BlockAsPayload(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayload,
BlobGasUsed: (*Uint64Quantity)(bl.BlobGasUsed()), BlobGasUsed: (*Uint64Quantity)(bl.BlobGasUsed()),
} }
if canyonForkTime != nil && uint64(payload.Timestamp) >= *canyonForkTime { if shanghaiTime != nil && uint64(payload.Timestamp) >= *shanghaiTime {
payload.Withdrawals = &types.Withdrawals{} payload.Withdrawals = &types.Withdrawals{}
} }
return payload, nil return payload, nil
} }
func BlockAsPayloadEnv(bl *types.Block, canyonForkTime *uint64) (*ExecutionPayloadEnvelope, error) { func BlockAsPayloadEnv(bl *types.Block, shanghaiTime *uint64) (*ExecutionPayloadEnvelope, error) {
payload, err := BlockAsPayload(bl, canyonForkTime) payload, err := BlockAsPayload(bl, shanghaiTime)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment