Skip to content

Commit

Permalink
consensus: handle legacy pre-bedrock header verification
Browse files Browse the repository at this point in the history
  • Loading branch information
protolambda authored and sebastianst committed Jan 3, 2024
1 parent 8e15470 commit 07b934f
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 0 deletions.
24 changes: 24 additions & 0 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down
69 changes: 69 additions & 0 deletions consensus/beacon/oplegacy.go
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions fork.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 07b934f

Please sign in to comment.