From 23fc61420c904ae9d7b8e5978b39bedd469cd283 Mon Sep 17 00:00:00 2001 From: Denis Pingin <2085934+denis-pingin@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:30:24 +0100 Subject: [PATCH 01/19] miner: fix config unmarshalling (#433) --- miner/miner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miner/miner.go b/miner/miner.go index 7cbfebf56410..447cf4ccbd2f 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -64,8 +64,8 @@ type Config struct { RollupTransactionConditionalRateLimit int // Total number of conditional cost units allowed in a second EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value - MaxDATxSize *big.Int // if non-nil, don't include any txs with data availability size larger than this in any built block - MaxDABlockSize *big.Int // if non-nil, then don't build a block requiring more than this amount of total data availability + MaxDATxSize *big.Int `toml:",omitempty"` // if non-nil, don't include any txs with data availability size larger than this in any built block + MaxDABlockSize *big.Int `toml:",omitempty"` // if non-nil, then don't build a block requiring more than this amount of total data availability } // DefaultConfig contains default settings for miner. From 717daa16e204bf9c7e50994284c1d09970d8ee73 Mon Sep 17 00:00:00 2001 From: George Knee Date: Tue, 26 Nov 2024 11:24:33 +0000 Subject: [PATCH 02/19] update superchain-registry dependency (#436) brings in the unichain sepolia holocene_time --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3c152bd5bdbb..f9aa6cba52ba 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/deckarep/golang-set/v2 v2.6.0 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 - github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241119111730-bee358f6d6e6 + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048 github.com/ethereum/c-kzg-4844 v1.0.0 github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 github.com/fatih/color v1.16.0 diff --git a/go.sum b/go.sum index a63fc834cd02..b45225e86f0e 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241119111730-bee358f6d6e6 h1:+AIYWDX7FeWRLnBVqPiwireTacLLGGww1slGyv+YN0o= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241119111730-bee358f6d6e6/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048 h1:kb220NeqVRRt/XP5JHt3i4zpLsYNCdWMM/0tDnOFk3o= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A= From 550ddf9f132687e8efe773aaa70fe1aa714a3845 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 26 Nov 2024 16:23:16 +0100 Subject: [PATCH 03/19] core: fix call to post-validation evm message hook --- core/state_transition.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/state_transition.go b/core/state_transition.go index 2de1cad7626c..a3b05705c9e4 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -155,6 +155,8 @@ type Message struct { Mint *big.Int // Mint is the amount to mint before EVM processing, or nil if there is no minting. RollupCostData types.RollupCostData // RollupCostData caches data to compute the fee we charge for data availability + // PostValidation is an optional check of the resulting post-state, if and when the message is + // applied fully to the EVM. This function may return an error to deny inclusion of the message. PostValidation func(evm *vm.EVM, result *ExecutionResult) error } @@ -450,7 +452,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { err = nil } - if st.msg.PostValidation != nil { + if err == nil && st.msg.PostValidation != nil { if err := st.msg.PostValidation(st.evm, result); err != nil { return nil, err } From da595e374515e4b93430e0c5fd8d1616aa1ab229 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 26 Nov 2024 16:46:29 +0100 Subject: [PATCH 04/19] miner: fix TestRejectedConditionalTx flake --- miner/miner_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miner/miner_test.go b/miner/miner_test.go index d365ee18ac12..e038b02f61b9 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -188,8 +188,8 @@ func TestRejectedConditionalTx(t *testing.T) { }) tx.SetConditional(&types.TransactionConditional{TimestampMax: uint64Ptr(timestamp - 1)}) - // 1 pending tx - miner.txpool.Add(types.Transactions{tx}, true, false) + // 1 pending tx (synchronously, it has to be there before it can be rejected) + miner.txpool.Add(types.Transactions{tx}, true, true) if !miner.txpool.Has(tx.Hash()) { t.Fatalf("conditional tx is not in the mempool") } From 0a46245ccc5c801e7b18c258aceb5327d8ad69ad Mon Sep 17 00:00:00 2001 From: Roberto Bayardo Date: Mon, 2 Dec 2024 08:33:37 -0800 Subject: [PATCH 05/19] exit building loop early if there's no more room for min-sized transaction in the block (#439) --- miner/worker.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/miner/worker.go b/miner/worker.go index a4f297c6c6b4..c3bd100897b2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -545,6 +545,12 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran log.Debug("adding tx would exceed block DA size limit", "hash", ltx.Hash, "txda", ltx.DABytes, "blockda", blockDABytes, "dalimit", miner.config.MaxDABlockSize) txs.Pop() + // If the number of remaining bytes is too few to hold even the minimum possible transaction size, + // then we can stop early. + daBytesRemaining := new(big.Int).Sub(miner.config.MaxDABlockSize, daBytesAfter) + if daBytesRemaining.Cmp(types.MinTransactionSize) < 0 { + break + } continue } } From 812f258754fa7682244773afa9827f0bf6c60941 Mon Sep 17 00:00:00 2001 From: mountcount <166301065+mountcount@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:03:01 +0800 Subject: [PATCH 06/19] chore: fix some problematic function names in comment (#438) Signed-off-by: mountcount --- beacon/light/request/scheduler.go | 2 +- consensus/misc/eip1559/eip1559.go | 2 +- consensus/misc/eip1559/eip1559_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon/light/request/scheduler.go b/beacon/light/request/scheduler.go index e80daf805e86..242ed56d2840 100644 --- a/beacon/light/request/scheduler.go +++ b/beacon/light/request/scheduler.go @@ -269,7 +269,7 @@ func (s *Scheduler) addEvent(event Event) { s.Trigger() } -// filterEvent sorts each Event either as a request event or a server event, +// filterEvents sorts each Event either as a request event or a server event, // depending on its type. Request events are also sorted in a map based on the // module that originally initiated the request. It also ensures that no events // related to a server are returned before EvRegistered or after EvUnregistered. diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index a970bdcd31b3..98456a1653b1 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -57,7 +57,7 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade return nil } -// DecodeHolocene1599Params extracts the Holcene 1599 parameters from the encoded form defined here: +// DecodeHolocene1559Params extracts the Holcene 1599 parameters from the encoded form defined here: // https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md#eip-1559-parameters-in-payloadattributesv3 // // Returns 0,0 if the format is invalid, though ValidateHolocene1559Params should be used instead of this function for diff --git a/consensus/misc/eip1559/eip1559_test.go b/consensus/misc/eip1559/eip1559_test.go index c69dc80f93ac..c48b540b74e6 100644 --- a/consensus/misc/eip1559/eip1559_test.go +++ b/consensus/misc/eip1559/eip1559_test.go @@ -187,7 +187,7 @@ func TestCalcBaseFeeOptimism(t *testing.T) { } } -// TestCalcBaseFeeHolocene assumes all blocks are Optimism blocks post-Holocene upgrade +// TestCalcBaseFeeOptimismHolocene assumes all blocks are Optimism blocks post-Holocene upgrade func TestCalcBaseFeeOptimismHolocene(t *testing.T) { parentBaseFee := int64(10_000_000) parentGasLimit := uint64(30_000_000) From 73d5279b95c13f6503b0aeeeaea96307aec5f3a4 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 9 Dec 2024 18:36:04 +0000 Subject: [PATCH 07/19] add test case for a hf moving from before genesis to after --- params/config_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/params/config_test.go b/params/config_test.go index 4e5f3e5af416..dd9549fce2a2 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -144,6 +144,18 @@ func TestCheckCompatible(t *testing.T) { genesisTimestamp: newUint64(24), wantErr: nil, }, + { + stored: &ChainConfig{HoloceneTime: newUint64(10)}, + new: &ChainConfig{HoloceneTime: newUint64(20)}, + headTimestamp: 25, + genesisTimestamp: newUint64(15), + wantErr: &ConfigCompatError{ + What: "Holocene fork timestamp", + StoredTime: newUint64(10), + NewTime: newUint64(20), + RewindToTime: 9, + }, + }, } for i, test := range tests { From bb6ab01f6ed4a34d4c6b5b14240f871a2144d8c8 Mon Sep 17 00:00:00 2001 From: geoknee Date: Mon, 9 Dec 2024 18:37:36 +0000 Subject: [PATCH 08/19] fix isForkTimestampIncompatible to return false when either fork is post genesis --- params/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index 04cc43a84037..db3c04393680 100644 --- a/params/config.go +++ b/params/config.go @@ -955,7 +955,7 @@ func configBlockEqual(x, y *big.Int) bool { // isForkTimestampIncompatible returns true if a fork scheduled at timestamp s1 // cannot be rescheduled to timestamp s2 because head is already past the fork. func isForkTimestampIncompatible(s1, s2 *uint64, head uint64, genesis *uint64) bool { - return (isTimestampForked(s1, head) || isTimestampForked(s2, head)) && !configTimestampEqual(s1, s2) && !isTimestampPreGenesis(s1, genesis) && !isTimestampPreGenesis(s2, genesis) + return (isTimestampForked(s1, head) || isTimestampForked(s2, head)) && !configTimestampEqual(s1, s2) && (!isTimestampPreGenesis(s1, genesis) || !isTimestampPreGenesis(s2, genesis)) } func isTimestampPreGenesis(s, genesis *uint64) bool { From bd8b61ff51fba460a2b12685d20da154cc30ee69 Mon Sep 17 00:00:00 2001 From: George Knee Date: Tue, 10 Dec 2024 09:58:47 +0000 Subject: [PATCH 09/19] Update params/config.go Co-authored-by: Sebastian Stammler --- params/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index db3c04393680..40a71bea0b73 100644 --- a/params/config.go +++ b/params/config.go @@ -955,7 +955,7 @@ func configBlockEqual(x, y *big.Int) bool { // isForkTimestampIncompatible returns true if a fork scheduled at timestamp s1 // cannot be rescheduled to timestamp s2 because head is already past the fork. func isForkTimestampIncompatible(s1, s2 *uint64, head uint64, genesis *uint64) bool { - return (isTimestampForked(s1, head) || isTimestampForked(s2, head)) && !configTimestampEqual(s1, s2) && (!isTimestampPreGenesis(s1, genesis) || !isTimestampPreGenesis(s2, genesis)) + return (isTimestampForked(s1, head) || isTimestampForked(s2, head)) && !configTimestampEqual(s1, s2) && !(isTimestampPreGenesis(s1, genesis) && isTimestampPreGenesis(s2, genesis)) } func isTimestampPreGenesis(s, genesis *uint64) bool { From d11e6d648d1aa413bb80e7f86478746bf3acbe6e Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 21 Sep 2024 16:21:09 -0600 Subject: [PATCH 10/19] all: withdrawals root in header is reused to commit to OP-Stack L2 withdrawals storage root --- beacon/engine/gen_ed.go | 6 ++++++ beacon/engine/types.go | 15 ++++++++++++++- consensus/beacon/consensus.go | 9 +++++++++ core/block_validator.go | 16 +++++++++++++++- core/types/block.go | 8 ++++++++ eth/catalyst/api.go | 2 ++ eth/downloader/downloader.go | 7 ++++++- eth/downloader/queue.go | 20 ++++++++++++++++++-- eth/downloader/queue_test.go | 6 +++--- params/protocol_params.go | 2 ++ 10 files changed, 83 insertions(+), 8 deletions(-) diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index b2eb1dc9822f..90a02c395e5a 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -36,6 +36,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` Deposits types.Deposits `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -62,6 +63,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.Deposits = e.Deposits enc.ExecutionWitness = e.ExecutionWitness + enc.WithdrawalsRoot = e.WithdrawalsRoot return json.Marshal(&enc) } @@ -87,6 +89,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` Deposits *types.Deposits `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -166,5 +169,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.ExecutionWitness != nil { e.ExecutionWitness = dec.ExecutionWitness } + if dec.WithdrawalsRoot != nil { + e.WithdrawalsRoot = dec.WithdrawalsRoot + } return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 41c475ed57d0..f956c799f0b7 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -94,6 +94,11 @@ type ExecutableData struct { ExcessBlobGas *uint64 `json:"excessBlobGas"` Deposits types.Deposits `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + + // 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. @@ -270,7 +275,13 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H // 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 } @@ -337,6 +348,8 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. BlobGasUsed: block.BlobGasUsed(), ExcessBlobGas: block.ExcessBlobGas(), ExecutionWitness: block.ExecutionWitness(), + // OP-Stack addition: withdrawals list alone does not express the withdrawals storage-root. + WithdrawalsRoot: block.WithdrawalsRoot(), } bundle := BlobsBundleV1{ Commitments: make([]hexutil.Bytes, 0), diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index d03d01e7d41d..f79505f3d3a3 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -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 the final block. block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) diff --git a/core/block_validator.go b/core/block_validator.go index 4f51f5dc1788..4b75fd276ae8 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -73,7 +73,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 { @@ -155,6 +160,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 } diff --git a/core/types/block.go b/core/types/block.go index 621891e4b485..1881d0025f5d 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -429,6 +429,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 diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 5200dae2fc0d..e1a09a40210f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -881,6 +881,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe "len(params.Transactions)", len(params.Transactions), "len(params.Withdrawals)", len(params.Withdrawals), "len(params.Deposits)", len(params.Deposits), + "params.WithdrawalsRoot", params.WithdrawalsRoot, "beaconRoot", beaconRoot, "error", err) return api.invalid(err, nil), nil @@ -996,6 +997,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v "len(params.Transactions)", len(params.Transactions), "len(params.Withdrawals)", len(params.Withdrawals), "len(params.Deposits)", len(params.Deposits), + "params.WithdrawalsRoot", params.WithdrawalsRoot, "beaconRoot", beaconRoot, "error", err) errorMsg := err.Error() diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index f5313715b649..fc79401dc1e9 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -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 @@ -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, diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index adad45020040..d732bc9f70fa 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -121,6 +121,10 @@ func (f *fetchResult) Done(kind uint) bool { return v&(1< Date: Fri, 6 Dec 2024 10:23:13 -0500 Subject: [PATCH 11/19] config: add check for Isthmus hardfork --- core/block_validator.go | 6 +++--- eth/downloader/queue.go | 6 +++--- params/config.go | 9 +++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 4b75fd276ae8..c0e734972737 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -73,7 +73,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if block.Withdrawals() == nil { return errors.New("missing withdrawals in block body") } - if v.config.IsOptimismHolocene(header.Time) { + if v.config.IsOptimismIsthmus(header.Time) { if len(block.Withdrawals()) > 0 { return errors.New("no withdrawal block-operations allowed, withdrawalsRoot is set to storage root") } @@ -160,9 +160,9 @@ 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 v.config.IsOptimismIsthmus(block.Time()) { if header.WithdrawalsHash == nil { - return errors.New("expected withdrawals root in OP-Stack post-Holocene block header") + return errors.New("expected withdrawals root in OP-Stack post-Isthmus 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 { diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index d732bc9f70fa..582a9f87b22f 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -122,7 +122,7 @@ func (f *fetchResult) Done(kind uint) bool { } type OPStackChainConfig interface { - IsOptimismHolocene(time uint64) bool + IsOptimismIsthmus(time uint64) bool } // queue represents hashes that are either need fetching or are being fetched @@ -815,8 +815,8 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if withdrawalLists[index] == nil { return errInvalidBody } - if q.opConfig != nil && q.opConfig.IsOptimismHolocene(header.Time) { - // If Holocene, we expect an empty list of withdrawal operations, + if q.opConfig != nil && q.opConfig.IsOptimismIsthmus(header.Time) { + // If Isthmus, 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 diff --git a/params/config.go b/params/config.go index 40a71bea0b73..a0994fe54679 100644 --- a/params/config.go +++ b/params/config.go @@ -351,6 +351,7 @@ type ChainConfig struct { FjordTime *uint64 `json:"fjordTime,omitempty"` // Fjord switch time (nil = no fork, 0 = already on Optimism Fjord) GraniteTime *uint64 `json:"graniteTime,omitempty"` // Granite switch time (nil = no fork, 0 = already on Optimism Granite) HoloceneTime *uint64 `json:"holoceneTime,omitempty"` // Holocene switch time (nil = no fork, 0 = already on Optimism Holocene) + IsthmusTime *uint64 `json:"isthmusTime,omitempty"` // Isthmus switch time (nil = no fork, 0 = already on Optimism Isthmus) InteropTime *uint64 `json:"interopTime,omitempty"` // Interop switch time (nil = no fork, 0 = already on optimism interop) @@ -655,6 +656,10 @@ func (c *ChainConfig) IsHolocene(time uint64) bool { return isTimestampForked(c.HoloceneTime, time) } +func (c *ChainConfig) IsIsthmus(time uint64) bool { + return isTimestampForked(c.IsthmusTime, time) +} + func (c *ChainConfig) IsInterop(time uint64) bool { return isTimestampForked(c.InteropTime, time) } @@ -693,6 +698,10 @@ func (c *ChainConfig) IsOptimismHolocene(time uint64) bool { return c.IsOptimism() && c.IsHolocene(time) } +func (c *ChainConfig) IsOptimismIsthmus(time uint64) bool { + return c.IsOptimism() && c.IsIsthmus(time) +} + // IsOptimismPreBedrock returns true iff this is an optimism node & bedrock is not yet active func (c *ChainConfig) IsOptimismPreBedrock(num *big.Int) bool { return c.IsOptimism() && !c.IsBedrock(num) From 87534bd1f775fef99126fd9392277c2150b10e69 Mon Sep 17 00:00:00 2001 From: Vinod Damle <5338861+vdamle@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:24:23 -0500 Subject: [PATCH 12/19] all: more changes for usage of hdr withdrawals root/l2 withdrawals storage root * update checks for l2 withdrawal root to be gated on Isthmus * core: pass chainConfig to NewBlock for withdrawalRoot checks and fix usage this is so that we can check whether Isthmus is active within NewBlock() * genesis: read and set withdrawalsRoot to storageRoot of L2toL1MP contract * prioritize supplied withdrawalsRoot in beacon engine header check --- beacon/engine/types.go | 14 ++-- consensus/beacon/consensus.go | 8 +-- consensus/clique/clique.go | 2 +- consensus/ethash/consensus.go | 2 +- core/chain_makers.go | 4 ++ core/genesis.go | 86 ++++++++++++++++------- core/genesis_test.go | 9 ++- core/rawdb/accessors_indexes_test.go | 2 +- core/rawdb/chain_iterator_test.go | 9 +-- core/rlp_test.go | 12 ++-- core/state_processor_test.go | 2 +- core/txpool/legacypool/legacypool_test.go | 2 +- core/types/block.go | 12 +++- core/types/block_test.go | 3 +- eth/catalyst/api.go | 4 +- eth/catalyst/api_test.go | 10 +-- eth/gasprice/optimism-gasprice_test.go | 2 +- internal/ethapi/api_test.go | 4 +- internal/ethapi/simulate.go | 2 +- miner/miner_test.go | 2 +- 20 files changed, 123 insertions(+), 68 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index f956c799f0b7..0bc1404a53da 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -95,7 +95,7 @@ type ExecutableData struct { Deposits types.Deposits `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` - // OP-Stack Holocene specific field: + // OP-Stack Isthmus 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"` @@ -230,8 +230,8 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { // and that the blockhash of the constructed block matches the parameters. Nil // Withdrawals value will propagate through the returned block. Empty // Withdrawals value must be passed via non-nil, length 0 value in data. -func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { - block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot) +func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, config *params.ChainConfig) (*types.Block, error) { + block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, config) if err != nil { return nil, err } @@ -244,7 +244,7 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b // ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used // for stateless execution, so it skips checking if the executable data hashes to // the requested hash (stateless has to *compute* the root hash, it's not given). -func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { +func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, config *params.ChainConfig) (*types.Block, error) { txs, err := decodeTransactions(data.Transactions) if err != nil { return nil, err @@ -275,10 +275,10 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H // ExecutableData before withdrawals are enabled by marshaling // Withdrawals as the json null value. var withdrawalsRoot *common.Hash + if config.IsOptimismIsthmus(data.Timestamp) && data.WithdrawalsRoot == nil { + return nil, fmt.Errorf("attribute WithdrawalsRoot is required for Isthmus blocks") + } 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 { diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index f79505f3d3a3..8b39cdd4c19d 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -403,9 +403,9 @@ 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) + if chain.Config().IsOptimismIsthmus(header.Time) { + if body.Withdrawals == nil || len(body.Withdrawals) > 0 { // We verify nil/empty withdrawals in the CL pre-Isthmus + return nil, fmt.Errorf("expected non-nil empty withdrawals operation list in Isthmus, but got: %v", body.Withdrawals) } // State-root has just been computed, we can get an accurate storage-root now. h := state.GetStorageRoot(params.OptimismL2ToL1MessagePasser) @@ -413,7 +413,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea } // Assemble the final block. - block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) + block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil), chain.Config()) // Create the block witness and attach to block. // This step needs to happen as late as possible to catch all access events. diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index c9e94840020a..89d9fb7abbb5 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -597,7 +597,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Assemble and return the final block for sealing. - return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil + return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil), chain.Config()), nil } // Authorize injects a private key into the consensus engine to mint new blocks diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 0bd1a56bce19..475be5476f7c 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -520,7 +520,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Header seems complete, assemble into a block and return - return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil + return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles, Withdrawals: body.Withdrawals}, receipts, trie.NewStackTrie(nil), chain.Config()), nil } // SealHash returns the hash of a block prior to it being sealed. diff --git a/core/chain_makers.go b/core/chain_makers.go index bc5ac8417389..aca4feccec33 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -329,6 +329,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse b.header.Difficulty = big.NewInt(0) } } + if config.IsIsthmus(b.header.Time) { + b.withdrawals = make([]*types.Withdrawal, 0) + b.header.WithdrawalsHash = &types.EmptyWithdrawalsHash + } // Mutate the state and block according to any hard-fork specs if daoBlock := config.DAOForkBlock; daoBlock != nil { limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) diff --git a/core/genesis.go b/core/genesis.go index f8032507337e..2c324e6bb0fb 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -126,8 +126,11 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) { return &genesis, nil } -// hashAlloc computes the state root according to the genesis specification. -func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) { +// hashAlloc returns the following: +// * computed state root according to the genesis specification. +// * storage root of the L2ToL1MessagePasser contract. +// * error if any, when committing the genesis state (if so, state root and storage root will be empty). +func hashAlloc(ga *types.GenesisAlloc, isVerkle, isIsthmus bool) (common.Hash, common.Hash, error) { // If a genesis-time verkle trie is requested, create a trie config // with the verkle trie enabled so that the tree can be initialized // as such. @@ -143,7 +146,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) { db := rawdb.NewMemoryDatabase() statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb.NewDatabase(db, config), nil)) if err != nil { - return common.Hash{}, err + return common.Hash{}, common.Hash{}, err } for addr, account := range *ga { if account.Balance != nil { @@ -155,15 +158,27 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) { statedb.SetState(addr, key, value) } } - return statedb.Commit(0, false) + + stateRoot, err := statedb.Commit(0, false) + if err != nil { + return common.Hash{}, common.Hash{}, err + } + // get the storage root of the L2ToL1MessagePasser contract + var storageRootMessagePasser common.Hash + if isIsthmus { + storageRootMessagePasser = statedb.GetStorageRoot(params.OptimismL2ToL1MessagePasser) + } + + return stateRoot, storageRootMessagePasser, nil } // flushAlloc is very similar with hash, but the main difference is all the -// generated states will be persisted into the given database. -func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) { +// generated states will be persisted into the given database. Returns the +// same values as hashAlloc. +func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, isIsthmus bool) (common.Hash, common.Hash, error) { statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb, nil)) if err != nil { - return common.Hash{}, err + return common.Hash{}, common.Hash{}, err } for addr, account := range *ga { if account.Balance != nil { @@ -177,17 +192,22 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e statedb.SetState(addr, key, value) } } - root, err := statedb.Commit(0, false) + stateRoot, err := statedb.Commit(0, false) if err != nil { - return common.Hash{}, err + return common.Hash{}, common.Hash{}, err + } + // get the storage root of the L2ToL1MessagePasser contract + var storageRootMessagePasser common.Hash + if isIsthmus { + storageRootMessagePasser = statedb.GetStorageRoot(params.OptimismL2ToL1MessagePasser) } // Commit newly generated states into disk if it's not empty. - if root != types.EmptyRootHash { - if err := triedb.Commit(root, true); err != nil { - return common.Hash{}, err + if stateRoot != types.EmptyRootHash { + if err := triedb.Commit(stateRoot, true); err != nil { + return common.Hash{}, common.Hash{}, err } } - return root, nil + return stateRoot, storageRootMessagePasser, nil } func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc types.GenesisAlloc, err error) { @@ -477,22 +497,27 @@ func (g *Genesis) IsVerkle() bool { // ToBlock returns the genesis block according to genesis specification. func (g *Genesis) ToBlock() *types.Block { - var root common.Hash + var stateRoot, storageRootMessagePasser common.Hash var err error if g.StateHash != nil { if len(g.Alloc) > 0 { panic(fmt.Errorf("cannot both have genesis hash %s "+ "and non-empty state-allocation", *g.StateHash)) } - root = *g.StateHash - } else if root, err = hashAlloc(&g.Alloc, g.IsVerkle()); err != nil { + // stateHash is only relevant for pre-bedrock (and hence pre-isthmus) chains. + // we bail here since this is not a valid usage of StateHash + if g.Config.IsIsthmus(g.Timestamp) { + panic(fmt.Errorf("stateHash usage disallowed in chain with isthmus active at genesis")) + } + stateRoot = *g.StateHash + } else if stateRoot, storageRootMessagePasser, err = hashAlloc(&g.Alloc, g.IsVerkle(), g.Config.IsIsthmus(g.Timestamp)); err != nil { panic(err) } - return g.toBlockWithRoot(root) + return g.toBlockWithRoot(stateRoot, storageRootMessagePasser) } // toBlockWithRoot constructs the genesis block with the given genesis state root. -func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { +func (g *Genesis) toBlockWithRoot(stateRoot, storageRootMessagePasser common.Hash) *types.Block { head := &types.Header{ Number: new(big.Int).SetUint64(g.Number), Nonce: types.EncodeNonce(g.Nonce), @@ -505,7 +530,7 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { Difficulty: g.Difficulty, MixDigest: g.Mixhash, Coinbase: g.Coinbase, - Root: root, + Root: stateRoot, } if g.GasLimit == 0 { head.GasLimit = params.GenesisGasLimit @@ -549,8 +574,17 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { head.RequestsHash = &types.EmptyRequestsHash requests = make(types.Requests, 0) } + // If Isthmus is active at genesis, set the WithdrawalRoot to the storage root of the L2ToL1MessagePasser contract. + if g.Config.IsIsthmus(g.Timestamp) { + if storageRootMessagePasser == (common.Hash{}) { + // if there was no MessagePasser contract storage, set the WithdrawalsHash to the empty hash + head.WithdrawalsHash = &types.EmptyWithdrawalsHash + } else { + head.WithdrawalsHash = &storageRootMessagePasser + } + } } - return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil)) + return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil), g.Config) } // Commit writes the block and state of a genesis specification to the database. @@ -569,23 +603,23 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo if config.Clique != nil && len(g.ExtraData) < 32+crypto.SignatureLength { return nil, errors.New("can't start clique chain without signers") } - var stateHash common.Hash + var stateRoot, storageRootMessagePasser common.Hash + var err error if len(g.Alloc) == 0 { if g.StateHash == nil { log.Warn("Empty genesis alloc, and no 'stateHash' override was set") - stateHash = types.EmptyRootHash // default to the hash of the empty state. Some unit-tests rely on this. + stateRoot = types.EmptyRootHash // default to the hash of the empty state. Some unit-tests rely on this. } else { - stateHash = *g.StateHash + stateRoot = *g.StateHash } } else { // flush the data to disk and compute the state root - root, err := flushAlloc(&g.Alloc, triedb) + stateRoot, storageRootMessagePasser, err = flushAlloc(&g.Alloc, triedb, g.Config.IsIsthmus(g.Timestamp)) if err != nil { return nil, err } - stateHash = root } - block := g.toBlockWithRoot(stateHash) + block := g.toBlockWithRoot(stateRoot, storageRootMessagePasser) // Marshal the genesis state specification and persist. blob, err := json.Marshal(g.Alloc) diff --git a/core/genesis_test.go b/core/genesis_test.go index 0fee8741386a..93c65a822ee7 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -223,13 +223,16 @@ func TestReadWriteGenesisAlloc(t *testing.T) { {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, {2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}}, } - hash, _ = hashAlloc(alloc, false) + stateRoot, storageRootMessagePasser, _ = hashAlloc(alloc, false, false) ) + if storageRootMessagePasser != (common.Hash{}) { + t.Fatalf("unexpected storage root") + } blob, _ := json.Marshal(alloc) - rawdb.WriteGenesisStateSpec(db, hash, blob) + rawdb.WriteGenesisStateSpec(db, stateRoot, blob) var reload types.GenesisAlloc - err := reload.UnmarshalJSON(rawdb.ReadGenesisStateSpec(db, hash)) + err := reload.UnmarshalJSON(rawdb.ReadGenesisStateSpec(db, stateRoot)) if err != nil { t.Fatalf("Failed to load genesis state %v", err) } diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index 78dba000fcef..bb2e197f83b7 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -76,7 +76,7 @@ func TestLookupStorage(t *testing.T) { tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) txs := []*types.Transaction{tx1, tx2, tx3} - block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher()) + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher(), params.TestChainConfig) // Check that no transactions entries are in a pristine database for i, tx := range txs { diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index 390424f673fc..3795f2fc7503 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) func TestChainIterator(t *testing.T) { @@ -34,7 +35,7 @@ func TestChainIterator(t *testing.T) { var block *types.Block var txs []*types.Transaction to := common.BytesToAddress([]byte{0x11}) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) // Empty genesis block + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher(), params.TestChainConfig) // Empty genesis block WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) for i := uint64(1); i <= 10; i++ { @@ -60,7 +61,7 @@ func TestChainIterator(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher(), params.TestChainConfig) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } @@ -111,7 +112,7 @@ func TestIndexTransactions(t *testing.T) { to := common.BytesToAddress([]byte{0x11}) // Write empty genesis block - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher(), params.TestChainConfig) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) @@ -138,7 +139,7 @@ func TestIndexTransactions(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher(), params.TestChainConfig) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } diff --git a/core/rlp_test.go b/core/rlp_test.go index bc37408537a3..1815bb6bd859 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -27,10 +27,14 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" "golang.org/x/crypto/sha3" ) -func getBlock(transactions int, uncles int, dataSize int) *types.Block { +func getBlock(config *params.ChainConfig, transactions int, uncles int, dataSize int) *types.Block { + if config == nil { + config = params.TestChainConfig + } var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") engine = ethash.NewFaker() @@ -40,7 +44,7 @@ func getBlock(transactions int, uncles int, dataSize int) *types.Block { address = crypto.PubkeyToAddress(key.PublicKey) funds = big.NewInt(1_000_000_000_000_000_000) gspec = &Genesis{ - Config: params.TestChainConfig, + Config: config, Alloc: types.GenesisAlloc{address: {Balance: funds}}, } ) @@ -83,7 +87,7 @@ func TestRlpIterator(t *testing.T) { func testRlpIterator(t *testing.T, txs, uncles, datasize int) { desc := fmt.Sprintf("%d txs [%d datasize] and %d uncles", txs, datasize, uncles) - bodyRlp, _ := rlp.EncodeToBytes(getBlock(txs, uncles, datasize).Body()) + bodyRlp, _ := rlp.EncodeToBytes(getBlock(nil, txs, uncles, datasize).Body()) it, err := rlp.NewListIterator(bodyRlp) if err != nil { t.Fatal(err) @@ -142,7 +146,7 @@ func BenchmarkHashing(b *testing.B) { blockRlp []byte ) { - block := getBlock(200, 2, 50) + block := getBlock(nil, 200, 2, 50) bodyRlp, _ = rlp.EncodeToBytes(block.Body()) blockRlp, _ = rlp.EncodeToBytes(block) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 2ee74f02ea23..07318d8f56cc 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -425,7 +425,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr if config.IsShanghai(header.Number, header.Time) { body.Withdrawals = []*types.Withdrawal{} } - return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) + return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil), config) } var ( diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 2aecb3f2e7dd..4a5c07b60c44 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -86,7 +86,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { } func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { - return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil)) + return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil), bc.config) } func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { diff --git a/core/types/block.go b/core/types/block.go index 1881d0025f5d..0eb5095c982a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-verkle" ) @@ -257,7 +258,7 @@ type extblock struct { // // The body elements and the receipts are used to recompute and overwrite the // relevant portions of the header. -func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { +func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher, config *params.ChainConfig) *Block { if body == nil { body = &Body{} } @@ -294,7 +295,14 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher } } - if withdrawals == nil { + if config.IsIsthmus(header.Time) { + if withdrawals == nil || len(withdrawals) > 0 { + panic(fmt.Sprintf("expected non-nil empty withdrawals operation list in Isthmus, but got: %v", body.Withdrawals)) + } + b.header.WithdrawalsHash = header.WithdrawalsHash + b.withdrawals = make(Withdrawals, 0) + } else if withdrawals == nil { + // pre-Canyon b.header.WithdrawalsHash = nil } else if len(withdrawals) == 0 { b.header.WithdrawalsHash = &EmptyWithdrawalsHash diff --git a/core/types/block_test.go b/core/types/block_test.go index 16cb64fa783b..a4cf08da1a0d 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -254,7 +254,8 @@ func makeBenchBlock() *Block { Extra: []byte("benchmark uncle"), } } - return NewBlock(header, &Body{Transactions: txs, Uncles: uncles}, receipts, blocktest.NewHasher()) + withdrawals := make([]*Withdrawal, 0) + return NewBlock(header, &Body{Transactions: txs, Uncles: uncles, Withdrawals: withdrawals}, receipts, blocktest.NewHasher(), params.TestChainConfig) } func TestRlpDecodeParentHash(t *testing.T) { diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index e1a09a40210f..d776044a33a2 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -853,7 +853,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe defer api.newPayloadLock.Unlock() log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) - block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot) + block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, api.eth.BlockChain().Config()) if err != nil { bgu := "nil" if params.BlobGasUsed != nil { @@ -969,7 +969,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { log.Trace("Engine API request received", "method", "ExecuteStatelessPayload", "number", params.Number, "hash", params.BlockHash) - block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot) + block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot, api.eth.BlockChain().Config()) if err != nil { bgu := "nil" if params.BlobGasUsed != nil { diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 665661cf14e1..f9126f253f4c 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -326,7 +326,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data, block %d: %v", i, err) } - block, err := engine.ExecutableDataToBlock(*execData, nil, nil) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil, ethservice.BlockChain().Config()) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -368,7 +368,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data %v", err) } - block, err := engine.ExecutableDataToBlock(*execData, nil, nil) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil, ethservice.BlockChain().Config()) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -1012,7 +1012,7 @@ func TestSimultaneousNewBlock(t *testing.T) { t.Fatal(testErr) } } - block, err := engine.ExecutableDataToBlock(*execData, nil, nil) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil, ethservice.BlockChain().Config()) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -1614,7 +1614,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) { }, } - block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil)) + block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil), params.OptimismTestConfig) envelope := engine.BlockToExecutableData(block, nil, sidecars) var want int for _, tx := range txs { @@ -1629,7 +1629,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) { if got := len(envelope.BlobsBundle.Blobs); got != want { t.Fatalf("invalid number of blobs: got %v, want %v", got, want) } - _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil) + _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, params.OptimismTestConfig) if err != nil { t.Error(err) } diff --git a/eth/gasprice/optimism-gasprice_test.go b/eth/gasprice/optimism-gasprice_test.go index ae183d4a9840..87291d02dcb2 100644 --- a/eth/gasprice/optimism-gasprice_test.go +++ b/eth/gasprice/optimism-gasprice_test.go @@ -103,7 +103,7 @@ func newOpTestBackend(t *testing.T, txs []testTxData) *opTestBackend { nonce++ } hasher := trie.NewStackTrie(nil) - b := types.NewBlock(&header, &types.Body{Transactions: ts}, nil, hasher) + b := types.NewBlock(&header, &types.Body{Transactions: ts}, nil, hasher, params.OptimismTestConfig) return &opTestBackend{block: b, receipts: rs} } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 91b03b7ed017..6bdbc092c9df 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -2767,7 +2767,7 @@ func TestRPCMarshalBlock(t *testing.T) { } txs = append(txs, tx) } - block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, &types.Body{Transactions: txs}, nil, blocktest.NewHasher()) + block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, &types.Body{Transactions: txs}, nil, blocktest.NewHasher(), params.MainnetChainConfig) var testSuite = []struct { inclTx bool @@ -2982,7 +2982,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) { Address: common.Address{0x12, 0x34}, Amount: 10, } - pending = types.NewBlock(&types.Header{Number: big.NewInt(11), Time: 42}, &types.Body{Transactions: types.Transactions{tx}, Withdrawals: types.Withdrawals{withdrawal}}, nil, blocktest.NewHasher()) + pending = types.NewBlock(&types.Header{Number: big.NewInt(11), Time: 42}, &types.Body{Transactions: types.Transactions{tx}, Withdrawals: types.Withdrawals{withdrawal}}, nil, blocktest.NewHasher(), params.TestChainConfig) ) backend := newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index c42ede29db1c..d17be0c4950a 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -249,7 +249,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if sim.chainConfig.IsShanghai(header.Number, header.Time) { withdrawals = make([]*types.Withdrawal, 0) } - b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil)) + b := types.NewBlock(header, &types.Body{Transactions: txes, Withdrawals: withdrawals}, receipts, trie.NewStackTrie(nil), sim.chainConfig) repairLogs(callResults, b.Hash()) return b, callResults, nil } diff --git a/miner/miner_test.go b/miner/miner_test.go index e038b02f61b9..67e2f182a66f 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -80,7 +80,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { } func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { - return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil)) + return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil), bc.Config()) } func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { From 7b60d97a086edd395c3eafa6e7c9609e3df000ce Mon Sep 17 00:00:00 2001 From: Vinod Damle <5338861+vdamle@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:25:55 -0500 Subject: [PATCH 13/19] core: add rlp roundtrip test for Header w/ withdrawalHash --- core/rlp_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/rlp_test.go b/core/rlp_test.go index 1815bb6bd859..3e338d14a2c7 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -199,3 +199,23 @@ func BenchmarkHashing(b *testing.B) { b.Fatalf("hash wrong, got %x exp %x", got, exp) } } + +func TestBlockRlpEncodeDecode(t *testing.T) { + zeroTime := uint64(0) + + // create a config where Isthmus upgrade is active + config := *params.OptimismTestConfig + config.ShanghaiTime = &zeroTime + config.IsthmusTime = &zeroTime + + block := getBlock(&config, 10, 2, 50) + + blockRlp, err := rlp.EncodeToBytes(block) + assert.Nil(t, err) + + var decoded types.Block + err = rlp.DecodeBytes(blockRlp, &decoded) + assert.Nil(t, err) + + assert.Equal(t, decoded.Hash(), block.Hash()) +} From d2027d66121da0516eca5f3f65d7194976069b82 Mon Sep 17 00:00:00 2001 From: Vinod Damle <5338861+vdamle@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:29:11 -0500 Subject: [PATCH 14/19] t8ntool: update fork.yaml with t8ntool non-update caveat --- fork.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fork.yaml b/fork.yaml index cf31c2431645..eefeb277fb68 100644 --- a/fork.yaml +++ b/fork.yaml @@ -329,6 +329,14 @@ def: similar to a withdrawal of the Beacon-chain into the Ethereum L1 execution chain. globs: - "eth/tracers/live/supply.go" + - title: EVM t8ntool + description: | + The EVM `t8ntool` has not been updated with most op-stack features and does not + use the same sealer logic as used in Geth consensus. Isthumus hard fork adds + a `withdrawalsRoot` field in the block header. We note that the `t8ntool` is + not updated to handle the newly added `withdrawalsRoot` field in the block header. + globs: + - "cmd/evm/internal/t8ntool/block.go" - title: "Hardware wallet support" description: Extend Ledger wallet support for newer devices on Macos sub: From 1750edce4fd76ce14a4ca7ff112153fdeb676b3a Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 6 Dec 2024 00:46:12 +0100 Subject: [PATCH 15/19] all: minor OP-Stack Isthmus withdrawals-root fixes --- beacon/engine/types.go | 9 +++++++-- consensus/beacon/consensus.go | 1 + consensus/clique/clique.go | 2 +- consensus/ethash/consensus.go | 2 +- core/block_validator.go | 2 +- core/chain_makers.go | 5 +++-- core/genesis.go | 13 ++++++------- core/rlp_test.go | 9 +++++---- core/types/block.go | 2 +- eth/api_debug.go | 3 +++ eth/downloader/queue.go | 2 +- 11 files changed, 30 insertions(+), 20 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 0bc1404a53da..c50b1bcdd030 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -275,8 +275,13 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H // ExecutableData before withdrawals are enabled by marshaling // Withdrawals as the json null value. var withdrawalsRoot *common.Hash - if config.IsOptimismIsthmus(data.Timestamp) && data.WithdrawalsRoot == nil { - return nil, fmt.Errorf("attribute WithdrawalsRoot is required for Isthmus blocks") + if config.IsOptimismIsthmus(data.Timestamp) { + if data.WithdrawalsRoot == nil { + return nil, fmt.Errorf("attribute WithdrawalsRoot is required for Isthmus blocks") + } + if data.Withdrawals == nil || len(data.Withdrawals) > 0 { + return nil, fmt.Errorf("expected non-nil empty withdrawals operation list in Isthmus, but got: %v", data.Withdrawals) + } } if data.WithdrawalsRoot != nil { h := *data.WithdrawalsRoot // copy, avoid any sharing of memory diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 8b39cdd4c19d..5200289a5c1f 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -410,6 +410,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // State-root has just been computed, we can get an accurate storage-root now. h := state.GetStorageRoot(params.OptimismL2ToL1MessagePasser) header.WithdrawalsHash = &h + state.AccessEvents().AddAccount(params.OptimismL2ToL1MessagePasser, false) // include in execution witness } // Assemble the final block. diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 89d9fb7abbb5..e19feaac6090 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -304,7 +304,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H } // Verify the non-existence of withdrawalsHash. if header.WithdrawalsHash != nil { - return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) + return fmt.Errorf("invalid withdrawalsHash: have %s, expected nil", header.WithdrawalsHash) } if chain.Config().IsCancun(header.Number, header.Time) { return errors.New("clique does not support cancun fork") diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 475be5476f7c..9ee6b6c102dd 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -270,7 +270,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa } // Verify the non-existence of withdrawalsHash. if header.WithdrawalsHash != nil { - return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) + return fmt.Errorf("invalid withdrawalsHash: have %s, expected nil", header.WithdrawalsHash) } if chain.Config().IsCancun(header.Number, header.Time) { return errors.New("ethash does not support cancun fork") diff --git a/core/block_validator.go b/core/block_validator.go index c0e734972737..bc3076ed08ec 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -79,7 +79,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } // 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) + return fmt.Errorf("withdrawals root hash mismatch (header value %s, calculated %s)", *header.WithdrawalsHash, hash) } } else if block.Withdrawals() != nil { // Withdrawals are not allowed prior to Shanghai fork diff --git a/core/chain_makers.go b/core/chain_makers.go index aca4feccec33..46e78b58f387 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -329,9 +329,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse b.header.Difficulty = big.NewInt(0) } } - if config.IsIsthmus(b.header.Time) { + if config.IsOptimismIsthmus(b.header.Time) { b.withdrawals = make([]*types.Withdrawal, 0) - b.header.WithdrawalsHash = &types.EmptyWithdrawalsHash + h := types.EmptyWithdrawalsHash + b.header.WithdrawalsHash = &h } // Mutate the state and block according to any hard-fork specs if daoBlock := config.DAOForkBlock; daoBlock != nil { diff --git a/core/genesis.go b/core/genesis.go index 2c324e6bb0fb..ae376cd1b7a8 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -504,13 +504,13 @@ func (g *Genesis) ToBlock() *types.Block { panic(fmt.Errorf("cannot both have genesis hash %s "+ "and non-empty state-allocation", *g.StateHash)) } - // stateHash is only relevant for pre-bedrock (and hence pre-isthmus) chains. + // g.StateHash is only relevant for pre-bedrock (and hence pre-isthmus) chains. // we bail here since this is not a valid usage of StateHash - if g.Config.IsIsthmus(g.Timestamp) { + if g.Config.IsOptimismIsthmus(g.Timestamp) { panic(fmt.Errorf("stateHash usage disallowed in chain with isthmus active at genesis")) } stateRoot = *g.StateHash - } else if stateRoot, storageRootMessagePasser, err = hashAlloc(&g.Alloc, g.IsVerkle(), g.Config.IsIsthmus(g.Timestamp)); err != nil { + } else if stateRoot, storageRootMessagePasser, err = hashAlloc(&g.Alloc, g.IsVerkle(), g.Config.IsOptimismIsthmus(g.Timestamp)); err != nil { panic(err) } return g.toBlockWithRoot(stateRoot, storageRootMessagePasser) @@ -575,13 +575,12 @@ func (g *Genesis) toBlockWithRoot(stateRoot, storageRootMessagePasser common.Has requests = make(types.Requests, 0) } // If Isthmus is active at genesis, set the WithdrawalRoot to the storage root of the L2ToL1MessagePasser contract. - if g.Config.IsIsthmus(g.Timestamp) { + if g.Config.IsOptimismIsthmus(g.Timestamp) { if storageRootMessagePasser == (common.Hash{}) { // if there was no MessagePasser contract storage, set the WithdrawalsHash to the empty hash - head.WithdrawalsHash = &types.EmptyWithdrawalsHash - } else { - head.WithdrawalsHash = &storageRootMessagePasser + storageRootMessagePasser = types.EmptyWithdrawalsHash } + head.WithdrawalsHash = &storageRootMessagePasser } } return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil), g.Config) diff --git a/core/rlp_test.go b/core/rlp_test.go index 3e338d14a2c7..c2db2b8a91b1 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" ) @@ -207,15 +207,16 @@ func TestBlockRlpEncodeDecode(t *testing.T) { config := *params.OptimismTestConfig config.ShanghaiTime = &zeroTime config.IsthmusTime = &zeroTime + require.True(t, config.IsOptimismIsthmus(zeroTime)) block := getBlock(&config, 10, 2, 50) blockRlp, err := rlp.EncodeToBytes(block) - assert.Nil(t, err) + require.NoError(t, err) var decoded types.Block err = rlp.DecodeBytes(blockRlp, &decoded) - assert.Nil(t, err) + require.NoError(t, err) - assert.Equal(t, decoded.Hash(), block.Hash()) + require.Equal(t, decoded.Hash(), block.Hash()) } diff --git a/core/types/block.go b/core/types/block.go index 0eb5095c982a..372332520df4 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -295,7 +295,7 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher } } - if config.IsIsthmus(header.Time) { + if config.IsOptimismIsthmus(header.Time) { if withdrawals == nil || len(withdrawals) > 0 { panic(fmt.Sprintf("expected non-nil empty withdrawals operation list in Isthmus, but got: %v", body.Withdrawals)) } diff --git a/eth/api_debug.go b/eth/api_debug.go index ab8dc7420fbb..203764a00eb3 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -482,6 +482,9 @@ func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateles return nil, fmt.Errorf("failed to process block %d: %w", block.Number(), err) } + // OP-Stack warning: below has the side-effect of including the withdrawals storage-root + // into the execution witness through the storage lookup by ValidateState, triggering the pre-fetcher. + // The Process function only runs through Finalize steps, not through FinalizeAndAssemble, missing merkleization. if err := blockchain.Validator().ValidateState(block, statedb, res, false); err != nil { return nil, fmt.Errorf("failed to validate block %d: %w", block.Number(), err) } diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 582a9f87b22f..1e3f1a8a9771 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -167,7 +167,7 @@ type queue struct { } // newQueue creates a new download queue for scheduling block retrieval. -// The +// The opConfig argument may be nil, if not an OP-Stack chain. func newQueue(opConfig OPStackChainConfig, blockCacheLimit int, thresholdInitialSize int) *queue { lock := new(sync.RWMutex) q := &queue{ From 77217299a6e08f84f80969f61d3889be53695f09 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Tue, 10 Dec 2024 19:11:03 +0100 Subject: [PATCH 16/19] Prepare Holocene mainnet release (#448) --- go.mod | 2 +- go.sum | 4 ++-- params/superchain.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index f9aa6cba52ba..2341bff2cfcb 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/deckarep/golang-set/v2 v2.6.0 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 - github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048 + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241210175229-327d7af91761 github.com/ethereum/c-kzg-4844 v1.0.0 github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 github.com/fatih/color v1.16.0 diff --git a/go.sum b/go.sum index b45225e86f0e..a40ce87fce40 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048 h1:kb220NeqVRRt/XP5JHt3i4zpLsYNCdWMM/0tDnOFk3o= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241210175229-327d7af91761 h1:sk8fDY7QJOhP/mOnEjMj9K8G+3dmPp5MVZsJu4lhZ2E= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241210175229-327d7af91761/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A= diff --git a/params/superchain.go b/params/superchain.go index bab6bec17e0b..110d7d98f5c7 100644 --- a/params/superchain.go +++ b/params/superchain.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -var OPStackSupport = ProtocolVersionV0{Build: [8]byte{}, Major: 9, Minor: 0, Patch: 0, PreRelease: 1}.Encode() +var OPStackSupport = ProtocolVersionV0{Build: [8]byte{}, Major: 9, Minor: 0, Patch: 0, PreRelease: 0}.Encode() func init() { for id, ch := range superchain.OPChains { From a7e2bc5942e953a82f59e3af429f25830d4b4b82 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 10 Dec 2024 20:06:49 +0100 Subject: [PATCH 17/19] Revert "Isthmus: withdrawals root in block header" (#449) --- beacon/engine/gen_ed.go | 6 -- beacon/engine/types.go | 26 ++----- consensus/beacon/consensus.go | 12 +--- consensus/clique/clique.go | 4 +- consensus/ethash/consensus.go | 4 +- core/block_validator.go | 18 +---- core/chain_makers.go | 5 -- core/genesis.go | 85 +++++++---------------- core/genesis_test.go | 9 +-- core/rawdb/accessors_indexes_test.go | 2 +- core/rawdb/chain_iterator_test.go | 9 ++- core/rlp_test.go | 33 ++------- core/state_processor_test.go | 2 +- core/txpool/legacypool/legacypool_test.go | 2 +- core/types/block.go | 20 +----- core/types/block_test.go | 3 +- eth/api_debug.go | 3 - eth/catalyst/api.go | 6 +- eth/catalyst/api_test.go | 10 +-- eth/downloader/downloader.go | 7 +- eth/downloader/queue.go | 20 +----- eth/downloader/queue_test.go | 6 +- eth/gasprice/optimism-gasprice_test.go | 2 +- fork.yaml | 8 --- internal/ethapi/api_test.go | 4 +- internal/ethapi/simulate.go | 2 +- miner/miner_test.go | 2 +- params/config.go | 9 --- params/protocol_params.go | 2 - 29 files changed, 72 insertions(+), 249 deletions(-) diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index 90a02c395e5a..b2eb1dc9822f 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -36,7 +36,6 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` Deposits types.Deposits `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` - WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -63,7 +62,6 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.Deposits = e.Deposits enc.ExecutionWitness = e.ExecutionWitness - enc.WithdrawalsRoot = e.WithdrawalsRoot return json.Marshal(&enc) } @@ -89,7 +87,6 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` Deposits *types.Deposits `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` - WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -169,8 +166,5 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.ExecutionWitness != nil { e.ExecutionWitness = dec.ExecutionWitness } - if dec.WithdrawalsRoot != nil { - e.WithdrawalsRoot = dec.WithdrawalsRoot - } return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index c50b1bcdd030..41c475ed57d0 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -94,11 +94,6 @@ type ExecutableData struct { ExcessBlobGas *uint64 `json:"excessBlobGas"` Deposits types.Deposits `json:"depositRequests"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` - - // OP-Stack Isthmus 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. @@ -230,8 +225,8 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { // and that the blockhash of the constructed block matches the parameters. Nil // Withdrawals value will propagate through the returned block. Empty // Withdrawals value must be passed via non-nil, length 0 value in data. -func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, config *params.ChainConfig) (*types.Block, error) { - block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, config) +func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { + block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot) if err != nil { return nil, err } @@ -244,7 +239,7 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b // ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used // for stateless execution, so it skips checking if the executable data hashes to // the requested hash (stateless has to *compute* the root hash, it's not given). -func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, config *params.ChainConfig) (*types.Block, error) { +func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { txs, err := decodeTransactions(data.Transactions) if err != nil { return nil, err @@ -275,18 +270,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H // ExecutableData before withdrawals are enabled by marshaling // Withdrawals as the json null value. var withdrawalsRoot *common.Hash - if config.IsOptimismIsthmus(data.Timestamp) { - if data.WithdrawalsRoot == nil { - return nil, fmt.Errorf("attribute WithdrawalsRoot is required for Isthmus blocks") - } - if data.Withdrawals == nil || len(data.Withdrawals) > 0 { - return nil, fmt.Errorf("expected non-nil empty withdrawals operation list in Isthmus, but got: %v", data.Withdrawals) - } - } - if data.WithdrawalsRoot != nil { - h := *data.WithdrawalsRoot // copy, avoid any sharing of memory - withdrawalsRoot = &h - } else if data.Withdrawals != nil { + if data.Withdrawals != nil { h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil)) withdrawalsRoot = &h } @@ -353,8 +337,6 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. BlobGasUsed: block.BlobGasUsed(), ExcessBlobGas: block.ExcessBlobGas(), ExecutionWitness: block.ExecutionWitness(), - // OP-Stack addition: withdrawals list alone does not express the withdrawals storage-root. - WithdrawalsRoot: block.WithdrawalsRoot(), } bundle := BlobsBundleV1{ Commitments: make([]hexutil.Bytes, 0), diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 5200289a5c1f..d03d01e7d41d 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -403,18 +403,8 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Assign the final state root to header. header.Root = state.IntermediateRoot(true) - if chain.Config().IsOptimismIsthmus(header.Time) { - if body.Withdrawals == nil || len(body.Withdrawals) > 0 { // We verify nil/empty withdrawals in the CL pre-Isthmus - return nil, fmt.Errorf("expected non-nil empty withdrawals operation list in Isthmus, 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 - state.AccessEvents().AddAccount(params.OptimismL2ToL1MessagePasser, false) // include in execution witness - } - // Assemble the final block. - block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil), chain.Config()) + block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) // Create the block witness and attach to block. // This step needs to happen as late as possible to catch all access events. diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index e19feaac6090..c9e94840020a 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -304,7 +304,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H } // Verify the non-existence of withdrawalsHash. if header.WithdrawalsHash != nil { - return fmt.Errorf("invalid withdrawalsHash: have %s, expected nil", header.WithdrawalsHash) + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } if chain.Config().IsCancun(header.Number, header.Time) { return errors.New("clique does not support cancun fork") @@ -597,7 +597,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Assemble and return the final block for sealing. - return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil), chain.Config()), nil + return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil } // Authorize injects a private key into the consensus engine to mint new blocks diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 9ee6b6c102dd..0bd1a56bce19 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -270,7 +270,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa } // Verify the non-existence of withdrawalsHash. if header.WithdrawalsHash != nil { - return fmt.Errorf("invalid withdrawalsHash: have %s, expected nil", header.WithdrawalsHash) + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } if chain.Config().IsCancun(header.Number, header.Time) { return errors.New("ethash does not support cancun fork") @@ -520,7 +520,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Header seems complete, assemble into a block and return - return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles, Withdrawals: body.Withdrawals}, receipts, trie.NewStackTrie(nil), chain.Config()), nil + return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil } // SealHash returns the hash of a block prior to it being sealed. diff --git a/core/block_validator.go b/core/block_validator.go index bc3076ed08ec..4f51f5dc1788 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -73,13 +73,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if block.Withdrawals() == nil { return errors.New("missing withdrawals in block body") } - if v.config.IsOptimismIsthmus(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 %s, calculated %s)", *header.WithdrawalsHash, hash) + 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 { // Withdrawals are not allowed prior to Shanghai fork @@ -160,15 +155,6 @@ 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.IsOptimismIsthmus(block.Time()) { - if header.WithdrawalsHash == nil { - return errors.New("expected withdrawals root in OP-Stack post-Isthmus 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 } diff --git a/core/chain_makers.go b/core/chain_makers.go index 46e78b58f387..bc5ac8417389 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -329,11 +329,6 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse b.header.Difficulty = big.NewInt(0) } } - if config.IsOptimismIsthmus(b.header.Time) { - b.withdrawals = make([]*types.Withdrawal, 0) - h := types.EmptyWithdrawalsHash - b.header.WithdrawalsHash = &h - } // Mutate the state and block according to any hard-fork specs if daoBlock := config.DAOForkBlock; daoBlock != nil { limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) diff --git a/core/genesis.go b/core/genesis.go index ae376cd1b7a8..f8032507337e 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -126,11 +126,8 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) { return &genesis, nil } -// hashAlloc returns the following: -// * computed state root according to the genesis specification. -// * storage root of the L2ToL1MessagePasser contract. -// * error if any, when committing the genesis state (if so, state root and storage root will be empty). -func hashAlloc(ga *types.GenesisAlloc, isVerkle, isIsthmus bool) (common.Hash, common.Hash, error) { +// hashAlloc computes the state root according to the genesis specification. +func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) { // If a genesis-time verkle trie is requested, create a trie config // with the verkle trie enabled so that the tree can be initialized // as such. @@ -146,7 +143,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle, isIsthmus bool) (common.Hash, c db := rawdb.NewMemoryDatabase() statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb.NewDatabase(db, config), nil)) if err != nil { - return common.Hash{}, common.Hash{}, err + return common.Hash{}, err } for addr, account := range *ga { if account.Balance != nil { @@ -158,27 +155,15 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle, isIsthmus bool) (common.Hash, c statedb.SetState(addr, key, value) } } - - stateRoot, err := statedb.Commit(0, false) - if err != nil { - return common.Hash{}, common.Hash{}, err - } - // get the storage root of the L2ToL1MessagePasser contract - var storageRootMessagePasser common.Hash - if isIsthmus { - storageRootMessagePasser = statedb.GetStorageRoot(params.OptimismL2ToL1MessagePasser) - } - - return stateRoot, storageRootMessagePasser, nil + return statedb.Commit(0, false) } // flushAlloc is very similar with hash, but the main difference is all the -// generated states will be persisted into the given database. Returns the -// same values as hashAlloc. -func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, isIsthmus bool) (common.Hash, common.Hash, error) { +// generated states will be persisted into the given database. +func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) { statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb, nil)) if err != nil { - return common.Hash{}, common.Hash{}, err + return common.Hash{}, err } for addr, account := range *ga { if account.Balance != nil { @@ -192,22 +177,17 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, isIsthmus bool) statedb.SetState(addr, key, value) } } - stateRoot, err := statedb.Commit(0, false) + root, err := statedb.Commit(0, false) if err != nil { - return common.Hash{}, common.Hash{}, err - } - // get the storage root of the L2ToL1MessagePasser contract - var storageRootMessagePasser common.Hash - if isIsthmus { - storageRootMessagePasser = statedb.GetStorageRoot(params.OptimismL2ToL1MessagePasser) + return common.Hash{}, err } // Commit newly generated states into disk if it's not empty. - if stateRoot != types.EmptyRootHash { - if err := triedb.Commit(stateRoot, true); err != nil { - return common.Hash{}, common.Hash{}, err + if root != types.EmptyRootHash { + if err := triedb.Commit(root, true); err != nil { + return common.Hash{}, err } } - return stateRoot, storageRootMessagePasser, nil + return root, nil } func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc types.GenesisAlloc, err error) { @@ -497,27 +477,22 @@ func (g *Genesis) IsVerkle() bool { // ToBlock returns the genesis block according to genesis specification. func (g *Genesis) ToBlock() *types.Block { - var stateRoot, storageRootMessagePasser common.Hash + var root common.Hash var err error if g.StateHash != nil { if len(g.Alloc) > 0 { panic(fmt.Errorf("cannot both have genesis hash %s "+ "and non-empty state-allocation", *g.StateHash)) } - // g.StateHash is only relevant for pre-bedrock (and hence pre-isthmus) chains. - // we bail here since this is not a valid usage of StateHash - if g.Config.IsOptimismIsthmus(g.Timestamp) { - panic(fmt.Errorf("stateHash usage disallowed in chain with isthmus active at genesis")) - } - stateRoot = *g.StateHash - } else if stateRoot, storageRootMessagePasser, err = hashAlloc(&g.Alloc, g.IsVerkle(), g.Config.IsOptimismIsthmus(g.Timestamp)); err != nil { + root = *g.StateHash + } else if root, err = hashAlloc(&g.Alloc, g.IsVerkle()); err != nil { panic(err) } - return g.toBlockWithRoot(stateRoot, storageRootMessagePasser) + return g.toBlockWithRoot(root) } // toBlockWithRoot constructs the genesis block with the given genesis state root. -func (g *Genesis) toBlockWithRoot(stateRoot, storageRootMessagePasser common.Hash) *types.Block { +func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { head := &types.Header{ Number: new(big.Int).SetUint64(g.Number), Nonce: types.EncodeNonce(g.Nonce), @@ -530,7 +505,7 @@ func (g *Genesis) toBlockWithRoot(stateRoot, storageRootMessagePasser common.Has Difficulty: g.Difficulty, MixDigest: g.Mixhash, Coinbase: g.Coinbase, - Root: stateRoot, + Root: root, } if g.GasLimit == 0 { head.GasLimit = params.GenesisGasLimit @@ -574,16 +549,8 @@ func (g *Genesis) toBlockWithRoot(stateRoot, storageRootMessagePasser common.Has head.RequestsHash = &types.EmptyRequestsHash requests = make(types.Requests, 0) } - // If Isthmus is active at genesis, set the WithdrawalRoot to the storage root of the L2ToL1MessagePasser contract. - if g.Config.IsOptimismIsthmus(g.Timestamp) { - if storageRootMessagePasser == (common.Hash{}) { - // if there was no MessagePasser contract storage, set the WithdrawalsHash to the empty hash - storageRootMessagePasser = types.EmptyWithdrawalsHash - } - head.WithdrawalsHash = &storageRootMessagePasser - } } - return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil), g.Config) + return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil)) } // Commit writes the block and state of a genesis specification to the database. @@ -602,23 +569,23 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo if config.Clique != nil && len(g.ExtraData) < 32+crypto.SignatureLength { return nil, errors.New("can't start clique chain without signers") } - var stateRoot, storageRootMessagePasser common.Hash - var err error + var stateHash common.Hash if len(g.Alloc) == 0 { if g.StateHash == nil { log.Warn("Empty genesis alloc, and no 'stateHash' override was set") - stateRoot = types.EmptyRootHash // default to the hash of the empty state. Some unit-tests rely on this. + stateHash = types.EmptyRootHash // default to the hash of the empty state. Some unit-tests rely on this. } else { - stateRoot = *g.StateHash + stateHash = *g.StateHash } } else { // flush the data to disk and compute the state root - stateRoot, storageRootMessagePasser, err = flushAlloc(&g.Alloc, triedb, g.Config.IsIsthmus(g.Timestamp)) + root, err := flushAlloc(&g.Alloc, triedb) if err != nil { return nil, err } + stateHash = root } - block := g.toBlockWithRoot(stateRoot, storageRootMessagePasser) + block := g.toBlockWithRoot(stateHash) // Marshal the genesis state specification and persist. blob, err := json.Marshal(g.Alloc) diff --git a/core/genesis_test.go b/core/genesis_test.go index 93c65a822ee7..0fee8741386a 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -223,16 +223,13 @@ func TestReadWriteGenesisAlloc(t *testing.T) { {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, {2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}}, } - stateRoot, storageRootMessagePasser, _ = hashAlloc(alloc, false, false) + hash, _ = hashAlloc(alloc, false) ) - if storageRootMessagePasser != (common.Hash{}) { - t.Fatalf("unexpected storage root") - } blob, _ := json.Marshal(alloc) - rawdb.WriteGenesisStateSpec(db, stateRoot, blob) + rawdb.WriteGenesisStateSpec(db, hash, blob) var reload types.GenesisAlloc - err := reload.UnmarshalJSON(rawdb.ReadGenesisStateSpec(db, stateRoot)) + err := reload.UnmarshalJSON(rawdb.ReadGenesisStateSpec(db, hash)) if err != nil { t.Fatalf("Failed to load genesis state %v", err) } diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index bb2e197f83b7..78dba000fcef 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -76,7 +76,7 @@ func TestLookupStorage(t *testing.T) { tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) txs := []*types.Transaction{tx1, tx2, tx3} - block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher(), params.TestChainConfig) + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher()) // Check that no transactions entries are in a pristine database for i, tx := range txs { diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index 3795f2fc7503..390424f673fc 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -25,7 +25,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" ) func TestChainIterator(t *testing.T) { @@ -35,7 +34,7 @@ func TestChainIterator(t *testing.T) { var block *types.Block var txs []*types.Transaction to := common.BytesToAddress([]byte{0x11}) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher(), params.TestChainConfig) // Empty genesis block + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) // Empty genesis block WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) for i := uint64(1); i <= 10; i++ { @@ -61,7 +60,7 @@ func TestChainIterator(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher(), params.TestChainConfig) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } @@ -112,7 +111,7 @@ func TestIndexTransactions(t *testing.T) { to := common.BytesToAddress([]byte{0x11}) // Write empty genesis block - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher(), params.TestChainConfig) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) @@ -139,7 +138,7 @@ func TestIndexTransactions(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher(), params.TestChainConfig) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } diff --git a/core/rlp_test.go b/core/rlp_test.go index c2db2b8a91b1..bc37408537a3 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -27,14 +27,10 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" ) -func getBlock(config *params.ChainConfig, transactions int, uncles int, dataSize int) *types.Block { - if config == nil { - config = params.TestChainConfig - } +func getBlock(transactions int, uncles int, dataSize int) *types.Block { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") engine = ethash.NewFaker() @@ -44,7 +40,7 @@ func getBlock(config *params.ChainConfig, transactions int, uncles int, dataSize address = crypto.PubkeyToAddress(key.PublicKey) funds = big.NewInt(1_000_000_000_000_000_000) gspec = &Genesis{ - Config: config, + Config: params.TestChainConfig, Alloc: types.GenesisAlloc{address: {Balance: funds}}, } ) @@ -87,7 +83,7 @@ func TestRlpIterator(t *testing.T) { func testRlpIterator(t *testing.T, txs, uncles, datasize int) { desc := fmt.Sprintf("%d txs [%d datasize] and %d uncles", txs, datasize, uncles) - bodyRlp, _ := rlp.EncodeToBytes(getBlock(nil, txs, uncles, datasize).Body()) + bodyRlp, _ := rlp.EncodeToBytes(getBlock(txs, uncles, datasize).Body()) it, err := rlp.NewListIterator(bodyRlp) if err != nil { t.Fatal(err) @@ -146,7 +142,7 @@ func BenchmarkHashing(b *testing.B) { blockRlp []byte ) { - block := getBlock(nil, 200, 2, 50) + block := getBlock(200, 2, 50) bodyRlp, _ = rlp.EncodeToBytes(block.Body()) blockRlp, _ = rlp.EncodeToBytes(block) } @@ -199,24 +195,3 @@ func BenchmarkHashing(b *testing.B) { b.Fatalf("hash wrong, got %x exp %x", got, exp) } } - -func TestBlockRlpEncodeDecode(t *testing.T) { - zeroTime := uint64(0) - - // create a config where Isthmus upgrade is active - config := *params.OptimismTestConfig - config.ShanghaiTime = &zeroTime - config.IsthmusTime = &zeroTime - require.True(t, config.IsOptimismIsthmus(zeroTime)) - - block := getBlock(&config, 10, 2, 50) - - blockRlp, err := rlp.EncodeToBytes(block) - require.NoError(t, err) - - var decoded types.Block - err = rlp.DecodeBytes(blockRlp, &decoded) - require.NoError(t, err) - - require.Equal(t, decoded.Hash(), block.Hash()) -} diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 07318d8f56cc..2ee74f02ea23 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -425,7 +425,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr if config.IsShanghai(header.Number, header.Time) { body.Withdrawals = []*types.Withdrawal{} } - return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil), config) + return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) } var ( diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 4a5c07b60c44..2aecb3f2e7dd 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -86,7 +86,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { } func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { - return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil), bc.config) + return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil)) } func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { diff --git a/core/types/block.go b/core/types/block.go index 372332520df4..621891e4b485 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-verkle" ) @@ -258,7 +257,7 @@ type extblock struct { // // The body elements and the receipts are used to recompute and overwrite the // relevant portions of the header. -func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher, config *params.ChainConfig) *Block { +func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { if body == nil { body = &Body{} } @@ -295,14 +294,7 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher } } - if config.IsOptimismIsthmus(header.Time) { - if withdrawals == nil || len(withdrawals) > 0 { - panic(fmt.Sprintf("expected non-nil empty withdrawals operation list in Isthmus, but got: %v", body.Withdrawals)) - } - b.header.WithdrawalsHash = header.WithdrawalsHash - b.withdrawals = make(Withdrawals, 0) - } else if withdrawals == nil { - // pre-Canyon + if withdrawals == nil { b.header.WithdrawalsHash = nil } else if len(withdrawals) == 0 { b.header.WithdrawalsHash = &EmptyWithdrawalsHash @@ -437,14 +429,6 @@ 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 diff --git a/core/types/block_test.go b/core/types/block_test.go index a4cf08da1a0d..16cb64fa783b 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -254,8 +254,7 @@ func makeBenchBlock() *Block { Extra: []byte("benchmark uncle"), } } - withdrawals := make([]*Withdrawal, 0) - return NewBlock(header, &Body{Transactions: txs, Uncles: uncles, Withdrawals: withdrawals}, receipts, blocktest.NewHasher(), params.TestChainConfig) + return NewBlock(header, &Body{Transactions: txs, Uncles: uncles}, receipts, blocktest.NewHasher()) } func TestRlpDecodeParentHash(t *testing.T) { diff --git a/eth/api_debug.go b/eth/api_debug.go index 203764a00eb3..ab8dc7420fbb 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -482,9 +482,6 @@ func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateles return nil, fmt.Errorf("failed to process block %d: %w", block.Number(), err) } - // OP-Stack warning: below has the side-effect of including the withdrawals storage-root - // into the execution witness through the storage lookup by ValidateState, triggering the pre-fetcher. - // The Process function only runs through Finalize steps, not through FinalizeAndAssemble, missing merkleization. if err := blockchain.Validator().ValidateState(block, statedb, res, false); err != nil { return nil, fmt.Errorf("failed to validate block %d: %w", block.Number(), err) } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index d776044a33a2..5200dae2fc0d 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -853,7 +853,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe defer api.newPayloadLock.Unlock() log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) - block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, api.eth.BlockChain().Config()) + block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot) if err != nil { bgu := "nil" if params.BlobGasUsed != nil { @@ -881,7 +881,6 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe "len(params.Transactions)", len(params.Transactions), "len(params.Withdrawals)", len(params.Withdrawals), "len(params.Deposits)", len(params.Deposits), - "params.WithdrawalsRoot", params.WithdrawalsRoot, "beaconRoot", beaconRoot, "error", err) return api.invalid(err, nil), nil @@ -969,7 +968,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { log.Trace("Engine API request received", "method", "ExecuteStatelessPayload", "number", params.Number, "hash", params.BlockHash) - block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot, api.eth.BlockChain().Config()) + block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot) if err != nil { bgu := "nil" if params.BlobGasUsed != nil { @@ -997,7 +996,6 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v "len(params.Transactions)", len(params.Transactions), "len(params.Withdrawals)", len(params.Withdrawals), "len(params.Deposits)", len(params.Deposits), - "params.WithdrawalsRoot", params.WithdrawalsRoot, "beaconRoot", beaconRoot, "error", err) errorMsg := err.Error() diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index f9126f253f4c..665661cf14e1 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -326,7 +326,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data, block %d: %v", i, err) } - block, err := engine.ExecutableDataToBlock(*execData, nil, nil, ethservice.BlockChain().Config()) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -368,7 +368,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data %v", err) } - block, err := engine.ExecutableDataToBlock(*execData, nil, nil, ethservice.BlockChain().Config()) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -1012,7 +1012,7 @@ func TestSimultaneousNewBlock(t *testing.T) { t.Fatal(testErr) } } - block, err := engine.ExecutableDataToBlock(*execData, nil, nil, ethservice.BlockChain().Config()) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -1614,7 +1614,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) { }, } - block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil), params.OptimismTestConfig) + block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil)) envelope := engine.BlockToExecutableData(block, nil, sidecars) var want int for _, tx := range txs { @@ -1629,7 +1629,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) { if got := len(envelope.BlobsBundle.Blobs); got != want { t.Fatalf("invalid number of blobs: got %v, want %v", got, want) } - _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, params.OptimismTestConfig) + _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil) if err != nil { t.Error(err) } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index fc79401dc1e9..f5313715b649 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -146,11 +146,6 @@ 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 @@ -206,7 +201,7 @@ func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer dl := &Downloader{ stateDB: stateDb, mux: mux, - queue: newQueue(chain.Config(), blockCacheMaxItems, blockCacheInitialItems), + queue: newQueue(blockCacheMaxItems, blockCacheInitialItems), peers: newPeerSet(), blockchain: chain, dropPeer: dropPeer, diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 1e3f1a8a9771..adad45020040 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -121,10 +121,6 @@ func (f *fetchResult) Done(kind uint) bool { return v&(1< Date: Wed, 11 Dec 2024 17:58:00 +0100 Subject: [PATCH 18/19] core: Move blockchain head verification after possible rewind (op-geth #446) * core: Move blockchain head verification after possible rewind If the blockchain needs a rewind due to a config change, it makes more sense to first perform that rewind and only then check the chain head's validity. The change in config might cause the loaded chain's head to be invalid before the rewind. * log configs at startup * fix logging --- core/blockchain.go | 9 +++++---- core/genesis.go | 12 +++++++++++- params/config.go | 2 ++ params/config_test.go | 12 ++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index bbf57c3ccb7a..b9fd783e625a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -410,10 +410,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } } } - // The first thing the node will do is reconstruct the verification data for - // the head block (ethash cache or clique voting snapshot). Might as well do - // it in advance. - bc.engine.VerifyHeader(bc, bc.CurrentHeader()) if bc.logger != nil && bc.logger.OnBlockchainInit != nil { bc.logger.OnBlockchainInit(chainConfig) @@ -467,6 +463,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis rawdb.WriteChainConfig(db, genesisHash, chainConfig) } + // The first thing the node will do is reconstruct the verification data for + // the head block (ethash cache or clique voting snapshot). Might as well do + // it in advance. + bc.engine.VerifyHeader(bc, bc.CurrentHeader()) + // Start tx indexer if it's enabled. if txLookupLimit != nil { bc.txIndexer = newTxIndexer(*txLookupLimit, bc) diff --git a/core/genesis.go b/core/genesis.go index f8032507337e..f841f94f5751 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -381,6 +381,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) applyOverrides(newcfg) + if err := newcfg.CheckConfigForkOrder(); err != nil { return newcfg, common.Hash{}, err } @@ -390,7 +391,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g rawdb.WriteChainConfig(db, stored, newcfg) return newcfg, stored, nil } + storedData, _ := json.Marshal(storedcfg) + log.Info("Stored config", "json", string(storedData)) // Special case: if a private network is being used (no genesis and also no // mainnet hash in the database), we must not apply the `configOrDefault` // chain config as that would be AllProtocolChanges (applying any new fork @@ -400,6 +403,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g newcfg = storedcfg applyOverrides(newcfg) } + newData, _ := json.Marshal(newcfg) + log.Info("New config", "json", string(newData), "genesis-nil", genesis == nil) + // Check config compatibility and write the config. Compatibility errors // are returned to the caller unless we're already at block zero. head := rawdb.ReadHeadHeader(db) @@ -414,9 +420,13 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) { return newcfg, stored, compatErr } + // Don't overwrite if the old is identical to the new - if newData, _ := json.Marshal(newcfg); !bytes.Equal(storedData, newData) { + if !bytes.Equal(storedData, newData) { + log.Info("Configs differ") rawdb.WriteChainConfig(db, stored, newcfg) + } else { + log.Info("Configs equal") } return newcfg, stored, nil } diff --git a/params/config.go b/params/config.go index 40a71bea0b73..05c7c93bbaff 100644 --- a/params/config.go +++ b/params/config.go @@ -21,6 +21,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params/forks" ) @@ -709,6 +710,7 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height, time uint64, var lasterr *ConfigCompatError for { err := c.checkCompatible(newcfg, bhead, btime, genesisTimestamp) + log.Info("Checking compatibility", "height", bhead, "time", btime, "error", err) if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) { break } diff --git a/params/config_test.go b/params/config_test.go index dd9549fce2a2..a12cd6333748 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -156,6 +156,18 @@ func TestCheckCompatible(t *testing.T) { RewindToTime: 9, }, }, + { + stored: &ChainConfig{HoloceneTime: newUint64(10)}, + new: &ChainConfig{HoloceneTime: newUint64(20)}, + headTimestamp: 15, + genesisTimestamp: newUint64(5), + wantErr: &ConfigCompatError{ + What: "Holocene fork timestamp", + StoredTime: newUint64(10), + NewTime: newUint64(20), + RewindToTime: 9, + }, + }, } for i, test := range tests { From efa05b1bf5c22a60745e638ad9d4adadfe3daba9 Mon Sep 17 00:00:00 2001 From: George Knee Date: Fri, 13 Dec 2024 11:00:27 +0000 Subject: [PATCH 19/19] update superchain dependency (#450) Brings in minato sepolia Holocene activation time --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2341bff2cfcb..2753cbc702ce 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/deckarep/golang-set/v2 v2.6.0 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 - github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241210175229-327d7af91761 + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241213092551-33a63fce8214 github.com/ethereum/c-kzg-4844 v1.0.0 github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 github.com/fatih/color v1.16.0 diff --git a/go.sum b/go.sum index a40ce87fce40..b8799493464f 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241210175229-327d7af91761 h1:sk8fDY7QJOhP/mOnEjMj9K8G+3dmPp5MVZsJu4lhZ2E= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241210175229-327d7af91761/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241213092551-33a63fce8214 h1:94dIMFDCafAQ3FCC1pryuhgfZc1jPoDwK4xSMOPshN8= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241213092551-33a63fce8214/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A=