Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Holocene: withdrawals root in block header #383

Draft
wants to merge 1 commit into
base: optimism
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 40 additions & 34 deletions beacon/engine/gen_ed.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion beacon/engine/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ type ExecutableData struct {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`

// OP-Stack Holocene specific field:
// instead of computing the root from a withdrawals list, set it directly.
// The "withdrawals" list attribute must be non-nil but empty.
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
}

// JSON type overrides for executableData.
Expand Down Expand Up @@ -240,7 +245,13 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
// ExecutableData before withdrawals are enabled by marshaling
// Withdrawals as the json null value.
var withdrawalsRoot *common.Hash
if data.Withdrawals != nil {
if data.WithdrawalsRoot != nil {
if data.Withdrawals == nil || len(data.Withdrawals) != 0 {
return nil, fmt.Errorf("attribute WithdrawalsRoot was set. Expecting non-nil empty withdrawals list, but got %v", data.Withdrawals)
}
h := *data.WithdrawalsRoot // copy, avoid any sharing of memory
withdrawalsRoot = &h
} else if data.Withdrawals != nil {
h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil))
withdrawalsRoot = &h
}
Expand Down Expand Up @@ -293,6 +304,8 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(),
// OP-Stack addition: withdrawals list alone does not express the withdrawals storage-root.
WithdrawalsRoot: block.WithdrawalsRoot(),
}
bundle := BlobsBundleV1{
Commitments: make([]hexutil.Bytes, 0),
Expand Down
9 changes: 9 additions & 0 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,15 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
// Assign the final state root to header.
header.Root = state.IntermediateRoot(true)

if chain.Config().IsOptimismHolocene(header.Time) {
if body.Withdrawals == nil || len(body.Withdrawals) > 0 { // We verify nil/empty withdrawals in the CL pre-holocene
return nil, fmt.Errorf("expected non-nil empty withdrawals operation list in Holocene, but got: %v", body.Withdrawals)
}
// State-root has just been computed, we can get an accurate storage-root now.
h := state.GetStorageRoot(params.OptimismL2ToL1MessagePasser)
header.WithdrawalsHash = &h
}

// Assemble and return the final block.
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
}
Expand Down
16 changes: 15 additions & 1 deletion core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
if block.Withdrawals() == nil {
return errors.New("missing withdrawals in block body")
}
if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash {
if v.config.IsOptimismHolocene(header.Time) {
if len(block.Withdrawals()) > 0 {
return errors.New("no withdrawal block-operations allowed, withdrawalsRoot is set to storage root")
}
// The withdrawalsHash is verified in ValidateState, like the state root, as verification requires state merkleization.
} else if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash {
return fmt.Errorf("withdrawals root hash mismatch (header value %x, calculated %x)", *header.WithdrawalsHash, hash)
}
} else if block.Withdrawals() != nil {
Expand Down Expand Up @@ -147,6 +152,15 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
}
if v.config.IsOptimismHolocene(block.Time()) {
if header.WithdrawalsHash == nil {
return errors.New("expected withdrawals root in OP-Stack post-Holocene block header")
}
// Validate the withdrawals root against the L2 withdrawals storage, similar to how the StateRoot is verified.
if root := statedb.GetStorageRoot(params.OptimismL2ToL1MessagePasser); *header.WithdrawalsHash != root {
return fmt.Errorf("invalid withdrawals hash (remote: %s local: %s) dberr: %w", *header.WithdrawalsHash, root, statedb.Error())
}
}
return nil
}

Expand Down
8 changes: 8 additions & 0 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,14 @@ func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash }
func (b *Block) UncleHash() common.Hash { return b.header.UncleHash }
func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) }

func (b *Block) WithdrawalsRoot() *common.Hash {
if b.header.WithdrawalsHash == nil {
return nil
}
h := *b.header.WithdrawalsHash
return &h
}

func (b *Block) BaseFee() *big.Int {
if b.header.BaseFee == nil {
return nil
Expand Down
1 change: 1 addition & 0 deletions eth/catalyst/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
"params.ExcessBlobGas", ebg,
"len(params.Transactions)", len(params.Transactions),
"len(params.Withdrawals)", len(params.Withdrawals),
"params.WithdrawalsRoot", params.WithdrawalsRoot,
"beaconRoot", beaconRoot,
"error", err)
return api.invalid(err, nil), nil
Expand Down
7 changes: 6 additions & 1 deletion eth/downloader/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ type Downloader struct {

// BlockChain encapsulates functions required to sync a (full or snap) blockchain.
type BlockChain interface {
// Config returns the chain configuration.
// OP-Stack diff, to adjust withdrawal-hash verification.
// Usage of ths in the Downloader is discouraged.
Config() *params.ChainConfig

// HasHeader verifies a header's presence in the local chain.
HasHeader(common.Hash, uint64) bool

Expand Down Expand Up @@ -201,7 +206,7 @@ func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer
dl := &Downloader{
stateDB: stateDb,
mux: mux,
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
queue: newQueue(chain.Config(), blockCacheMaxItems, blockCacheInitialItems),
peers: newPeerSet(),
blockchain: chain,
dropPeer: dropPeer,
Expand Down
20 changes: 18 additions & 2 deletions eth/downloader/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ func (f *fetchResult) Done(kind uint) bool {
return v&(1<<kind) == 0
}

type OPStackChainConfig interface {
IsOptimismHolocene(time uint64) bool
}

// queue represents hashes that are either need fetching or are being fetched
type queue struct {
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
Expand Down Expand Up @@ -156,10 +160,15 @@ type queue struct {
closed bool

logTime time.Time // Time instance when status was last reported

// opConfig is used for OP-Stack chain configuration checks.
// This may be nil if not an OP-Stack chain.
opConfig OPStackChainConfig
}

// newQueue creates a new download queue for scheduling block retrieval.
func newQueue(blockCacheLimit int, thresholdInitialSize int) *queue {
// The
func newQueue(opConfig OPStackChainConfig, blockCacheLimit int, thresholdInitialSize int) *queue {
lock := new(sync.RWMutex)
q := &queue{
headerContCh: make(chan bool, 1),
Expand All @@ -169,6 +178,7 @@ func newQueue(blockCacheLimit int, thresholdInitialSize int) *queue {
receiptWakeCh: make(chan bool, 1),
active: sync.NewCond(lock),
lock: lock,
opConfig: opConfig,
}
q.Reset(blockCacheLimit, thresholdInitialSize)
return q
Expand Down Expand Up @@ -804,7 +814,13 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH
if withdrawalLists[index] == nil {
return errInvalidBody
}
if withdrawalListHashes[index] != *header.WithdrawalsHash {
if q.opConfig != nil && q.opConfig.IsOptimismHolocene(header.Time) {
// If Holocene, we expect an empty list of withdrawal operations,
// but the WithdrawalsHash in the header is used for the withdrawals state storage-root.
if withdrawalListHashes[index] != types.EmptyWithdrawalsHash {
return errInvalidBody
}
} else if withdrawalListHashes[index] != *header.WithdrawalsHash {
return errInvalidBody
}
}
Expand Down
6 changes: 3 additions & 3 deletions eth/downloader/queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestBasics(t *testing.T) {
numOfBlocks := len(emptyChain.blocks)
numOfReceipts := len(emptyChain.blocks) / 2

q := newQueue(10, 10)
q := newQueue(nil, 10, 10)
if !q.Idle() {
t.Errorf("new queue should be idle")
}
Expand Down Expand Up @@ -196,7 +196,7 @@ func TestBasics(t *testing.T) {
func TestEmptyBlocks(t *testing.T) {
numOfBlocks := len(emptyChain.blocks)

q := newQueue(10, 10)
q := newQueue(nil, 10, 10)

q.Prepare(1, SnapSync)

Expand Down Expand Up @@ -275,7 +275,7 @@ func XTestDelivery(t *testing.T) {
if false {
log.SetDefault(log.NewLogger(slog.NewTextHandler(os.Stdout, nil)))
}
q := newQueue(10, 10)
q := newQueue(nil, 10, 10)
var wg sync.WaitGroup
q.Prepare(1, SnapSync)
wg.Add(1)
Expand Down
2 changes: 2 additions & 0 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ var (
OptimismBaseFeeRecipient = common.HexToAddress("0x4200000000000000000000000000000000000019")
// The L1 portion of the transaction fee accumulates at this predeploy
OptimismL1FeeRecipient = common.HexToAddress("0x420000000000000000000000000000000000001A")
// The L2 withdrawals contract predeploy address
OptimismL2ToL1MessagePasser = common.HexToAddress("0x4200000000000000000000000000000000000016")
)

const (
Expand Down