From 07b934f4ec563c9fd36e351c9faae9c4b54046bb Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 8 Nov 2023 22:28:30 +0100 Subject: [PATCH] consensus: handle legacy pre-bedrock header verification --- consensus/beacon/consensus.go | 24 ++++++++++++ consensus/beacon/oplegacy.go | 69 +++++++++++++++++++++++++++++++++++ eth/ethconfig/config.go | 3 ++ fork.yaml | 10 +++++ 4 files changed, 106 insertions(+) create mode 100644 consensus/beacon/oplegacy.go diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index a7d0e58161..619f3c6b69 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -105,12 +105,27 @@ func errOut(n int, err error) chan error { return errs } +// OP-Stack Bedrock variant of splitHeaders: the total-terminal difficulty is terminated at bedrock transition, but also reset to 0. +// So just use the bedrock fork check to split the headers, to simplify the splitting. +// The returned slices are slices over the input. The input must be sorted. +func (beacon *Beacon) splitBedrockHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) ([]*types.Header, []*types.Header, error) { + for i, h := range headers { + if chain.Config().IsBedrock(h.Number) { + return headers[:i], headers[i:], nil + } + } + return headers, nil, nil +} + // splitHeaders splits the provided header batch into two parts according to // the configured ttd. It requires the parent of header batch along with its // td are stored correctly in chain. If ttd is not configured yet, all headers // will be treated legacy PoW headers. // Note, this function will not verify the header validity but just split them. func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) ([]*types.Header, []*types.Header, error) { + if chain.Config().Optimism != nil { + return beacon.splitBedrockHeaders(chain, headers) + } // TTD is not defined yet, all headers should be in legacy format. ttd := chain.Config().TerminalTotalDifficulty if ttd == nil { @@ -461,6 +476,15 @@ func (beacon *Beacon) SetThreads(threads int) { // It depends on the parentHash already being stored in the database. // If the parentHash is not stored in the database a UnknownAncestor error is returned. func IsTTDReached(chain consensus.ChainHeaderReader, parentHash common.Hash, parentNumber uint64) (bool, error) { + if cfg := chain.Config(); cfg.Optimism != nil { + num := parentNumber + if num == ^(uint64(0)) { // caller can (intentionally?!) underflow on parent-of-block 0 case. + num = 0 + } + if cfg.IsBedrock(new(big.Int).SetUint64(num)) { + return true, nil + } + } if chain.Config().TerminalTotalDifficulty == nil { return false, nil } diff --git a/consensus/beacon/oplegacy.go b/consensus/beacon/oplegacy.go new file mode 100644 index 0000000000..41fa3373d7 --- /dev/null +++ b/consensus/beacon/oplegacy.go @@ -0,0 +1,69 @@ +package beacon + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "math/big" +) + +type OpLegacy struct { +} + +func (o *OpLegacy) Author(header *types.Header) (common.Address, error) { + return header.Coinbase, nil +} + +func (o *OpLegacy) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { + return nil // legacy chain is verified by block-hash reverse sync +} + +func (o *OpLegacy) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { + quit := make(chan struct{}, 1) + result := make(chan error, len(headers)) + for range headers { + result <- nil + } + return quit, result +} + +func (o *OpLegacy) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + return nil +} + +func (o *OpLegacy) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { + return fmt.Errorf("cannot prepare for legacy block header: %s (num %d)", header.Hash(), header.Number) +} + +func (o *OpLegacy) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { + panic(fmt.Errorf("cannot finalize legacy block header: %s (num %d)", header.Hash(), header.Number)) +} + +func (o *OpLegacy) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) { + return nil, fmt.Errorf("cannot finalize and assemble for legacy block header: %s (num %d)", header.Hash(), header.Number) +} + +func (o *OpLegacy) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + return fmt.Errorf("cannot seal legacy block header: %s (num %d)", block.Hash(), block.Number()) +} + +func (o *OpLegacy) SealHash(header *types.Header) common.Hash { + panic(fmt.Errorf("cannot compute pow/poa seal-hash for legacy block header: %s (num %d)", header.Hash(), header.Number)) +} + +func (o *OpLegacy) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + return big.NewInt(0) +} + +func (o *OpLegacy) APIs(chain consensus.ChainHeaderReader) []rpc.API { + return nil +} + +func (o *OpLegacy) Close() error { + return nil +} + +var _ consensus.Engine = (*OpLegacy)(nil) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index ff164ed841..9b2edec62e 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -190,6 +190,9 @@ type Config struct { // Clique is allowed for now to live standalone, but ethash is forbidden and can // only exist on already merged networks. func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) { + if config.Optimism != nil { + return beacon.New(&beacon.OpLegacy{}), nil + } // If proof-of-authority is requested, set it up if config.Clique != nil { return beacon.New(clique.New(config.Clique, db)), nil diff --git a/fork.yaml b/fork.yaml index eb697a7ae8..a60ae411a0 100644 --- a/fork.yaml +++ b/fork.yaml @@ -80,8 +80,18 @@ def: description: | The Engine API is activated at the Merge transition, with a Total Terminal Difficulty (TTD). The rollup starts post-merge, and thus sets the TTD to 0. + The TTD is always "reached" starting at the bedrock block. globs: - "consensus/beacon/consensus.go" + - title: "Legacy OP-mainnet / OP-goerli header-verification support" + description: | + Pre-Bedrock OP-mainnet and OP-Goerli had differently formatted block-headers, loosely compatible with the geth types (since it was based on Clique). + However, due to differences like the extra-data length (97+ bytes), these legacy block-headers need special verification. + The pre-merge "consensus" fallback is set to this custom but basic verifier, to accept these headers when syncing a pre-bedrock part of the chain, + independent of any clique code or configuration (which may be removed from geth at a later point). + All the custom verifier has to do is accept the headers, as the headers are already verified by block-hash through the reverse-header-sync. + globs: + - "consensus/beacon/oplegacy.go" - title: "Engine API modifications" description: | The Engine API is extended to insert transactions into the block and optionally exclude the tx-pool,