• Inphi's avatar
    op-program: Generic precompile oracle (#9699) · 44201560
    Inphi authored
    * op-program: Generic precompile oracle
    
    The generic precompile oracle replaces the point evaluation precompile
    oracle. The new oracle can be used to retrieve the return data and
    status of any precompile, including bn256Pairing and ecrecover.
    
    * op-challenger: Use generic precompile oracle
    
    Replace the KZG point evaluation oracle with a generic precompile oracle
    
    * op-program: Simplify required gas logic.
    Use FromHex instead of Hex2Bytes
    
    * op-program: Set beacon URL when capturing and verifying sepolia chain data
    
    * op-program: Actually use the beacon URL
    
    * op-program: Use default l1-rpckind if not set rather than overriding to debug_geth
    
    * Use freshly generated sepolia test data
    
    * Use sepolia compatibility data
    
    * Fix spelling
    
    * update PreimageOracle.sol snapshot
    
    ---------
    Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
    44201560
engine_backend.go 6.43 KB
package l2

import (
	"fmt"
	"math/big"

	"github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi"
	"github.com/ethereum-optimism/optimism/op-service/eth"
	"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
	oracleHead *types.Header
	head       *types.Header
	safe       *types.Header
	finalized  *types.Header
	vmCfg      vm.Config

	// Block by number cache
	hashByNum            map[uint64]common.Hash
	earliestIndexedBlock *types.Header

	// Inserted blocks
	blocks map[common.Hash]*types.Block
	db     ethdb.KeyValueStore
}

var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil)

func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, precompileOracle engineapi.PrecompileOracle, chainCfg *params.ChainConfig, l2OutputRoot common.Hash) (*OracleBackedL2Chain, error) {
	output := oracle.OutputByRoot(l2OutputRoot)
	outputV0, ok := output.(*eth.OutputV0)
	if !ok {
		return nil, fmt.Errorf("unsupported L2 output version: %d", output.Version())
	}
	head := oracle.BlockByHash(outputV0.BlockHash)
	logger.Info("Loaded L2 head", "hash", head.Hash(), "number", head.Number())
	return &OracleBackedL2Chain{
		log:      logger,
		oracle:   oracle,
		chainCfg: chainCfg,
		engine:   beacon.New(nil),

		hashByNum: map[uint64]common.Hash{
			head.NumberU64(): head.Hash(),
		},
		earliestIndexedBlock: head.Header(),

		// Treat the agreed starting head as finalized - nothing before it can be disputed
		head:       head.Header(),
		safe:       head.Header(),
		finalized:  head.Header(),
		oracleHead: head.Header(),
		blocks:     make(map[common.Hash]*types.Block),
		db:         NewOracleBackedDB(oracle),
		vmCfg: vm.Config{
			OptimismPrecompileOverrides: engineapi.CreatePrecompileOverrides(precompileOracle),
		},
	}, nil
}

func (o *OracleBackedL2Chain) CurrentHeader() *types.Header {
	return o.head
}

func (o *OracleBackedL2Chain) GetHeaderByNumber(n uint64) *types.Header {
	if o.head.Number.Uint64() < n {
		return nil
	}
	hash, ok := o.hashByNum[n]
	if ok {
		return o.GetHeaderByHash(hash)
	}
	// Walk back from current head to the requested block number
	h := o.head
	for h.Number.Uint64() > n {
		h = o.GetHeaderByHash(h.ParentHash)
		o.hashByNum[h.Number.Uint64()] = h.Hash()
	}
	o.earliestIndexedBlock = h
	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 {
	return o.GetBlockByHash(hash).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
	return o.oracle.BlockByHash(hash)
}

func (o *OracleBackedL2Chain) GetBlock(hash common.Hash, number uint64) *types.Block {
	var block *types.Block
	if o.oracleHead.Number.Uint64() < number {
		// For blocks above the chain head, only consider newly built blocks
		// Avoids requesting an unknown block from the oracle which would panic.
		block = o.blocks[hash]
	} else {
		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)
	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) {
	oldHead := o.head
	o.head = head.Header()

	// Remove canonical hashes after the new header
	for n := head.NumberU64() + 1; n <= oldHead.Number.Uint64(); n++ {
		delete(o.hashByNum, n)
	}

	// Add new canonical blocks to the block by number cache
	// Since the original head is added to the block number cache and acts as the finalized block,
	// at some point we must reach the existing canonical chain and can stop updating.
	h := o.head
	for {
		newHash := h.Hash()
		prevHash, ok := o.hashByNum[h.Number.Uint64()]
		if ok && prevHash == newHash {
			// Connected with the existing canonical chain so stop updating
			break
		}
		o.hashByNum[h.Number.Uint64()] = newHash
		h = o.GetHeaderByHash(h.ParentHash)
	}
	return head.Hash(), nil
}

func (o *OracleBackedL2Chain) SetFinalized(header *types.Header) {
	o.finalized = header
}

func (o *OracleBackedL2Chain) SetSafe(header *types.Header) {
	o.safe = header
}