Commit 9ee1d5c2 authored by Adrian Sutton's avatar Adrian Sutton

op-program: Implement oracle backed engine API backend

parent 5eae8164
package l2
import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon"
"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/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
type OracleBackedL2Chain struct {
log log.Logger
oracle Oracle
chainCfg *params.ChainConfig
engine consensus.Engine
head *types.Header
safe *types.Header
finalized *types.Header
vmCfg vm.Config
// Inserted blocks
blocks map[common.Hash]*types.Block
db ethdb.KeyValueStore
}
var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil)
func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.ChainConfig, l2Head common.Hash) (*OracleBackedL2Chain, error) {
head, err := oracle.BlockByHash(l2Head)
if err != nil {
return nil, fmt.Errorf("loading l2 head: %w", err)
}
return &OracleBackedL2Chain{
log: logger,
oracle: oracle,
chainCfg: chainCfg,
engine: beacon.New(nil),
// Treat the agreed starting head as finalized - nothing before it can be disputed
head: head.Header(),
safe: head.Header(),
finalized: head.Header(),
blocks: make(map[common.Hash]*types.Block),
db: NewOracleBackedDB(oracle),
}, nil
}
func (o *OracleBackedL2Chain) CurrentHeader() *types.Header {
return o.head
}
func (o *OracleBackedL2Chain) GetHeaderByNumber(n uint64) *types.Header {
// Walk back from current head to the requested block number
h := o.head
if h.Number.Uint64() < n {
return nil
}
for h.Number.Uint64() > n {
h = o.GetHeaderByHash(h.ParentHash)
}
return h
}
func (o *OracleBackedL2Chain) GetTd(hash common.Hash, number uint64) *big.Int {
// Difficulty is always 0 post-merge and bedrock starts post-merge so total difficulty also always 0
return common.Big0
}
func (o *OracleBackedL2Chain) CurrentSafeBlock() *types.Header {
return o.safe
}
func (o *OracleBackedL2Chain) CurrentFinalBlock() *types.Header {
return o.finalized
}
func (o *OracleBackedL2Chain) GetHeaderByHash(hash common.Hash) *types.Header {
block := o.GetBlockByHash(hash)
if block == nil {
return nil
}
return block.Header()
}
func (o *OracleBackedL2Chain) GetBlockByHash(hash common.Hash) *types.Block {
// Check inserted blocks
block, ok := o.blocks[hash]
if ok {
return block
}
// Retrieve from the oracle
block, err := o.oracle.BlockByHash(hash)
if err != nil {
handleError(err)
}
if block == nil {
return nil
}
return block
}
func (o *OracleBackedL2Chain) GetBlock(hash common.Hash, number uint64) *types.Block {
block := o.GetBlockByHash(hash)
if block == nil {
return nil
}
if block.NumberU64() != number {
return nil
}
return block
}
func (o *OracleBackedL2Chain) GetHeader(hash common.Hash, u uint64) *types.Header {
block := o.GetBlock(hash, u)
if block == nil {
return nil
}
return block.Header()
}
func (o *OracleBackedL2Chain) HasBlockAndState(hash common.Hash, number uint64) bool {
block := o.GetBlock(hash, number)
return block != nil
}
func (o *OracleBackedL2Chain) GetCanonicalHash(n uint64) common.Hash {
header := o.GetHeaderByNumber(n)
if header == nil {
return common.Hash{}
}
return header.Hash()
}
func (o *OracleBackedL2Chain) GetVMConfig() *vm.Config {
return &o.vmCfg
}
func (o *OracleBackedL2Chain) Config() *params.ChainConfig {
return o.chainCfg
}
func (o *OracleBackedL2Chain) Engine() consensus.Engine {
return o.engine
}
func (o *OracleBackedL2Chain) StateAt(root common.Hash) (*state.StateDB, error) {
return state.New(root, state.NewDatabase(rawdb.NewDatabase(o.db)), nil)
}
func (o *OracleBackedL2Chain) InsertBlockWithoutSetHead(block *types.Block) error {
processor, err := engineapi.NewBlockProcessorFromHeader(o, block.Header())
if err != nil {
return err
}
for i, tx := range block.Transactions() {
err = processor.AddTx(tx)
if err != nil {
return fmt.Errorf("invalid transaction (%d): %w", i, err)
}
}
expected, err := processor.Assemble()
if err != nil {
return fmt.Errorf("invalid block: %w", err)
}
if expected.Hash() != block.Hash() {
return fmt.Errorf("block root mismatch, expected: %v, actual: %v", expected.Hash(), block.Hash())
}
err = processor.Commit()
if err != nil {
return fmt.Errorf("commit block: %w", err)
}
o.blocks[block.Hash()] = block
return nil
}
func (o *OracleBackedL2Chain) SetCanonical(head *types.Block) (common.Hash, error) {
o.head = head.Header()
return head.Hash(), nil
}
func (o *OracleBackedL2Chain) SetFinalized(header *types.Header) {
o.finalized = header
}
func (o *OracleBackedL2Chain) SetSafe(header *types.Header) {
o.safe = header
}
func handleError(err error) {
panic(err)
}
package l2
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi/test"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
var fundedKey, _ = crypto.GenerateKey()
var fundedAddress = crypto.PubkeyToAddress(fundedKey.PublicKey)
var targetAddress = common.HexToAddress("0x001122334455")
func TestInitialState(t *testing.T) {
blocks, chain := setupOracleBackedChain(t, 5)
head := blocks[5]
require.Equal(t, head.Header(), chain.CurrentHeader())
require.Equal(t, head.Header(), chain.CurrentSafeBlock())
require.Equal(t, head.Header(), chain.CurrentFinalBlock())
}
func TestGetBlocks(t *testing.T) {
blocks, chain := setupOracleBackedChain(t, 5)
for i, block := range blocks {
blockNumber := uint64(i)
assertBlockDataAvailable(t, chain, block, blockNumber)
require.Equal(t, block.Hash(), chain.GetCanonicalHash(blockNumber), "get canonical hash for block %v", blockNumber)
}
}
func TestUnknownBlock(t *testing.T) {
_, chain := setupOracleBackedChain(t, 1)
hash := common.HexToHash("0x556677881122")
blockNumber := uint64(1)
require.Nil(t, chain.GetBlockByHash(hash))
require.Nil(t, chain.GetHeaderByHash(hash))
require.Nil(t, chain.GetBlock(hash, blockNumber))
require.Nil(t, chain.GetHeader(hash, blockNumber))
require.False(t, chain.HasBlockAndState(hash, blockNumber))
}
func TestCanonicalHashNotFoundPastChainHead(t *testing.T) {
blocks, chain := setupOracleBackedChainWithLowerHead(t, 5, 3)
for i := 0; i <= 3; i++ {
require.Equal(t, blocks[i].Hash(), chain.GetCanonicalHash(uint64(i)))
require.Equal(t, blocks[i].Header(), chain.GetHeaderByNumber(uint64(i)))
}
for i := 4; i <= 5; i++ {
require.Equal(t, common.Hash{}, chain.GetCanonicalHash(uint64(i)))
require.Nil(t, chain.GetHeaderByNumber(uint64(i)))
}
}
func TestAppendToChain(t *testing.T) {
blocks, chain := setupOracleBackedChainWithLowerHead(t, 4, 3)
newBlock := blocks[4]
require.Nil(t, chain.GetBlockByHash(newBlock.Hash()), "block unknown before being added")
require.NoError(t, chain.InsertBlockWithoutSetHead(newBlock))
require.Equal(t, blocks[3].Header(), chain.CurrentHeader(), "should not update chain head yet")
require.Equal(t, common.Hash{}, chain.GetCanonicalHash(uint64(4)), "not yet a canonical hash")
require.Nil(t, chain.GetHeaderByNumber(uint64(4)), "not yet a canonical header")
assertBlockDataAvailable(t, chain, newBlock, 4)
canonical, err := chain.SetCanonical(newBlock)
require.NoError(t, err)
require.Equal(t, newBlock.Hash(), canonical)
require.Equal(t, newBlock.Hash(), chain.GetCanonicalHash(uint64(4)), "get canonical hash for new head")
require.Equal(t, newBlock.Header(), chain.GetHeaderByNumber(uint64(4)), "get canonical header for new head")
}
func TestSetFinalized(t *testing.T) {
blocks, chain := setupOracleBackedChainWithLowerHead(t, 5, 0)
for _, block := range blocks[1:] {
require.NoError(t, chain.InsertBlockWithoutSetHead(block))
}
chain.SetFinalized(blocks[2].Header())
require.Equal(t, blocks[2].Header(), chain.CurrentFinalBlock())
}
func TestSetSafe(t *testing.T) {
blocks, chain := setupOracleBackedChainWithLowerHead(t, 5, 0)
for _, block := range blocks[1:] {
require.NoError(t, chain.InsertBlockWithoutSetHead(block))
}
chain.SetSafe(blocks[2].Header())
require.Equal(t, blocks[2].Header(), chain.CurrentSafeBlock())
}
func TestUpdateStateDatabaseWhenImportingBlock(t *testing.T) {
blocks, chain := setupOracleBackedChain(t, 3)
newBlock := createBlock(t, chain)
db, err := chain.StateAt(blocks[1].Root())
require.NoError(t, err)
balance := db.GetBalance(fundedAddress)
require.NotEqual(t, big.NewInt(0), balance, "should have balance at imported block")
require.NotEqual(t, blocks[1].Root(), newBlock.Root(), "block should have modified world state")
_, err = chain.StateAt(newBlock.Root())
require.Error(t, err, "state from non-imported block should not be available")
err = chain.InsertBlockWithoutSetHead(newBlock)
require.NoError(t, err)
db, err = chain.StateAt(newBlock.Root())
require.NoError(t, err, "state should be available after importing")
balance = db.GetBalance(fundedAddress)
require.NotEqual(t, big.NewInt(0), balance, "should have balance from imported block")
}
func TestRejectBlockWithStateRootMismatch(t *testing.T) {
_, chain := setupOracleBackedChain(t, 1)
newBlock := createBlock(t, chain)
// Create invalid block by keeping the modified state root but exclude the transaction
invalidBlock := types.NewBlockWithHeader(newBlock.Header())
err := chain.InsertBlockWithoutSetHead(invalidBlock)
require.ErrorContains(t, err, "block root mismatch")
}
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.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.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)
}
func setupOracleBackedChain(t *testing.T, blockCount int) ([]*types.Block, *OracleBackedL2Chain) {
return setupOracleBackedChainWithLowerHead(t, blockCount, blockCount)
}
func setupOracleBackedChainWithLowerHead(t *testing.T, blockCount int, headBlockNumber int) ([]*types.Block, *OracleBackedL2Chain) {
logger := testlog.Logger(t, log.LvlDebug)
chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber)
head := blocks[headBlockNumber].Hash()
chain, err := NewOracleBackedL2Chain(logger, oracle, chainCfg, head)
require.NoError(t, err)
return blocks, chain
}
func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.ChainConfig, []*types.Block, *stubBlockOracle) {
deployConfig := &genesis.DeployConfig{
L1ChainID: 900,
L2ChainID: 901,
L2BlockTime: 2,
FundDevAccounts: true,
L2GenesisBlockGasLimit: 30_000_000,
}
l1Genesis, err := genesis.NewL1Genesis(deployConfig)
require.NoError(t, err)
l2Genesis, err := genesis.NewL2Genesis(deployConfig, l1Genesis.ToBlock())
require.NoError(t, err)
l2Genesis.Alloc[fundedAddress] = core.GenesisAccount{
Balance: big.NewInt(1_000_000_000_000_000_000),
Nonce: 0,
}
chainCfg := l2Genesis.Config
consensus := beacon.New(nil)
db := rawdb.NewMemoryDatabase()
// Set minimal amount of stuff to avoid nil references later
genesisBlock := l2Genesis.MustCommit(db)
blocks, _ := core.GenerateChain(chainCfg, genesisBlock, consensus, db, blockCount, func(i int, gen *core.BlockGen) {})
blocks = append([]*types.Block{genesisBlock}, blocks...)
oracle := newStubBlockOracle(blocks[:headBlockNumber+1], db)
return chainCfg, blocks, oracle
}
func createBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block {
parent := chain.GetBlockByHash(chain.CurrentHeader().Hash())
parentDB, err := chain.StateAt(parent.Root())
require.NoError(t, err)
nonce := parentDB.GetNonce(fundedAddress)
config := chain.Config()
db := rawdb.NewDatabase(NewOracleBackedDB(chain.oracle))
blocks, _ := core.GenerateChain(config, parent, chain.Engine(), db, 1, func(i int, gen *core.BlockGen) {
rawTx := &types.DynamicFeeTx{
ChainID: config.ChainID,
Nonce: nonce,
To: &targetAddress,
GasTipCap: big.NewInt(0),
GasFeeCap: parent.BaseFee(),
Gas: 21_000,
Value: big.NewInt(1),
}
tx := types.MustSignNewTx(fundedKey, types.NewLondonSigner(config.ChainID), rawTx)
gen.AddTx(tx)
})
return blocks[0]
}
type stubBlockOracle struct {
blocks map[common.Hash]*types.Block
kvStateOracle
}
func newStubBlockOracle(chain []*types.Block, db ethdb.Database) *stubBlockOracle {
blocks := make(map[common.Hash]*types.Block, len(chain))
for _, block := range chain {
blocks[block.Hash()] = block
}
return &stubBlockOracle{
blocks: blocks,
kvStateOracle: kvStateOracle{source: db},
}
}
func (o stubBlockOracle) BlockByHash(blockHash common.Hash) (*types.Block, error) {
return o.blocks[blockHash], nil
}
func TestEngineAPITests(t *testing.T) {
test.RunEngineAPITests(t, func() engineapi.EngineBackend {
_, chain := setupOracleBackedChain(t, 0)
return chain
})
}
......@@ -54,7 +54,7 @@ func NewBlockProcessorFromPayloadAttributes(provider BlockDataProvider, parent c
}
func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*BlockProcessor, error) {
header := *h // Copy to avoid mutating the original header
header := types.CopyHeader(h) // Copy to avoid mutating the original header
if header.GasLimit > params.MaxGasLimit {
return nil, fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
......@@ -72,7 +72,7 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*
header.GasUsed = 0
gasPool := new(core.GasPool).AddGas(header.GasLimit)
return &BlockProcessor{
header: &header,
header: header,
state: statedb,
gasPool: gasPool,
dataProvider: provider,
......
......@@ -20,7 +20,6 @@ import (
)
type EngineBackend interface {
CurrentBlock() *types.Header
CurrentSafeBlock() *types.Header
CurrentFinalBlock() *types.Header
GetBlockByHash(hash common.Hash) *types.Block
......@@ -222,7 +221,7 @@ func (ea *L2EngineAPI) ForkchoiceUpdatedV1(ctx context.Context, state *eth.Forkc
if latestValid, err := ea.backend.SetCanonical(block); err != nil {
return &eth.ForkchoiceUpdatedResult{PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionInvalid, LatestValidHash: &latestValid}}, err
}
} else if ea.backend.CurrentBlock().Hash() == state.HeadBlockHash {
} else if ea.backend.CurrentHeader().Hash() == state.HeadBlockHash {
// If the specified head matches with our local head, do nothing and keep
// generating the payload. It's a special corner case that a few slots are
// missing and we are requested to generate the payload in slot.
......@@ -336,7 +335,7 @@ func (ea *L2EngineAPI) NewPayloadV1(ctx context.Context, payload *eth.ExecutionP
}
func (ea *L2EngineAPI) invalid(err error, latestValid *types.Header) *eth.PayloadStatusV1 {
currentHash := ea.backend.CurrentBlock().Hash()
currentHash := ea.backend.CurrentHeader().Hash()
if latestValid != nil {
// Set latest valid hash to 0x0 if parent is PoW block
currentHash = common.Hash{}
......
......@@ -28,7 +28,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("IncludeRequiredTransactions", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
txData, err := derive.L1InfoDeposit(1, eth.HeaderBlockInfo(genesis), eth.SystemConfig{}, true)
api.assert.NoError(err)
......@@ -46,7 +46,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectCreatingBlockWithInvalidRequiredTransaction", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
txData, err := derive.L1InfoDeposit(1, eth.HeaderBlockInfo(genesis), eth.SystemConfig{}, true)
api.assert.NoError(err)
......@@ -84,7 +84,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("AllowBuildingOnOlderBlock", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
api.addBlock()
block := api.addBlock()
api.assert.Equal(block.BlockHash, api.headHash(), "should have extended chain")
......@@ -112,7 +112,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectBlockWithInvalidStateTransition", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
// Build a valid block
payloadID := api.startBlockBuilding(genesis, eth.Uint64Quantity(genesis.Time+2))
......@@ -129,7 +129,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectBlockWithSameTimeAsParent", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
// Start with a valid time
payloadID := api.startBlockBuilding(genesis, eth.Uint64Quantity(genesis.Time+1))
......@@ -146,7 +146,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectBlockWithTimeBeforeParent", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
// Start with a valid time
payloadID := api.startBlockBuilding(genesis, eth.Uint64Quantity(genesis.Time+1))
......@@ -163,7 +163,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectCreateBlockWithSameTimeAsParent", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
result, err := api.engine.ForkchoiceUpdatedV1(api.ctx, &eth.ForkchoiceState{
HeadBlockHash: genesis.Hash(),
......@@ -183,7 +183,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectCreateBlockWithTimeBeforeParent", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
result, err := api.engine.ForkchoiceUpdatedV1(api.ctx, &eth.ForkchoiceState{
HeadBlockHash: genesis.Hash(),
......@@ -203,7 +203,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectCreateBlockWithGasLimitAboveMax", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
gasLimit := eth.Uint64Quantity(params.MaxGasLimit + 1)
......@@ -238,7 +238,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectSafeHeadWhenNotAncestor", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
api.addBlock()
chainA2 := api.addBlock()
......@@ -258,7 +258,7 @@ func RunEngineAPITests(t *testing.T, createBackend func() engineapi.EngineBacken
t.Run("RejectFinalizedHeadWhenNotAncestor", func(t *testing.T) {
api := newTestHelper(t, createBackend)
genesis := api.backend.CurrentBlock()
genesis := api.backend.CurrentHeader()
api.addBlock()
chainA2 := api.addBlock()
......@@ -308,7 +308,7 @@ func newTestHelper(t *testing.T, createBackend func() engineapi.EngineBackend) *
}
func (h *testHelper) headHash() common.Hash {
return h.backend.CurrentBlock().Hash()
return h.backend.CurrentHeader().Hash()
}
func (h *testHelper) safeHash() common.Hash {
......@@ -324,12 +324,12 @@ func (h *testHelper) Log(args ...any) {
}
func (h *testHelper) addBlock(txs ...*types.Transaction) *eth.ExecutionPayload {
head := h.backend.CurrentBlock()
head := h.backend.CurrentHeader()
return h.addBlockWithParent(head, eth.Uint64Quantity(head.Time+2), txs...)
}
func (h *testHelper) addBlockWithParent(head *types.Header, timestamp eth.Uint64Quantity, txs ...*types.Transaction) *eth.ExecutionPayload {
prevHead := h.backend.CurrentBlock()
prevHead := h.backend.CurrentHeader()
id := h.startBlockBuilding(head, timestamp, txs...)
block := h.getPayload(id)
......@@ -340,10 +340,10 @@ func (h *testHelper) addBlockWithParent(head *types.Header, timestamp eth.Uint64
h.newPayload(block)
// Should not have changed the chain head yet
h.assert.Equal(prevHead, h.backend.CurrentBlock())
h.assert.Equal(prevHead, h.backend.CurrentHeader())
h.forkChoiceUpdated(block.BlockHash, head.Hash(), head.Hash())
h.assert.Equal(block.BlockHash, h.backend.CurrentBlock().Hash())
h.assert.Equal(block.BlockHash, h.backend.CurrentHeader().Hash())
return block
}
......
package l2
import "github.com/ethereum/go-ethereum/common"
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// StateOracle defines the high-level API used to retrieve L2 state data pre-images
// The returned data is always the preimage of the requested hash.
......@@ -16,3 +19,13 @@ type StateOracle interface {
// Returns an error if the pre-image is unavailable.
CodeByHash(codeHash common.Hash) ([]byte, error)
}
// Oracle defines the high-level API used to retrieve L2 data.
// The returned data is always the preimage of the requested hash.
type Oracle interface {
StateOracle
// BlockByHash retrieves the block with the given hash.
// Returns an error if the block is not available.
BlockByHash(blockHash common.Hash) (*types.Block, error)
}
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