From c689bdc261e96bbddd6afa3dca0b71e1bd985d95 Mon Sep 17 00:00:00 2001 From: panos-xyz Date: Wed, 23 Sep 2026 18:24:26 +0800 Subject: [PATCH 1/2] docs: correct stale and misleading code comments Many comments drifted from the code over successive refactors and upstream upgrades. This fixes the ones that were wrong against the current code: renamed or removed identifiers, behaviour descriptions that no longer match, incomplete step and method lists, version-pinned upstream notes, test comments that contradict their own assertions, and leftovers from vendored upstream code. Comment-only change; no code, strings or attributes touched. --- .config/nextest.toml | 5 +-- .github/workflows/release.yml | 5 +-- crates/chainspec/src/constants.rs | 3 +- crates/chainspec/src/hardfork.rs | 20 ++++++----- crates/chainspec/src/lib.rs | 4 +-- crates/consensus/src/error.rs | 4 +-- crates/consensus/src/lib.rs | 2 +- crates/consensus/src/validation.rs | 34 +++++++++++-------- crates/engine-api/src/api.rs | 8 +++-- crates/engine-api/src/builder.rs | 5 ++- crates/engine-api/src/lib.rs | 3 ++ crates/engine-api/src/metrics.rs | 10 +++--- crates/evm/src/block/mod.rs | 2 +- crates/evm/src/block/receipt.rs | 6 ++-- crates/evm/src/evm.rs | 2 +- crates/evm/src/lib.rs | 4 --- crates/node/src/args.rs | 3 +- crates/node/src/test_utils.rs | 5 +-- crates/node/src/validator.rs | 7 ++-- crates/node/src/version.rs | 4 +-- crates/node/tests/it/consensus.rs | 2 ++ crates/node/tests/it/engine.rs | 15 ++++---- crates/node/tests/it/helpers.rs | 4 +-- crates/node/tests/it/l1_messages.rs | 2 +- crates/node/tests/it/mixed_block_pressure.rs | 6 ++-- crates/node/tests/it/morph_tx.rs | 16 +++++---- crates/node/tests/it/rpc.rs | 6 ++-- crates/payload/builder/src/builder.rs | 12 +++---- crates/payload/builder/src/config.rs | 2 +- crates/payload/types/src/attributes.rs | 5 ++- crates/payload/types/src/lib.rs | 2 +- crates/primitives/src/lib.rs | 2 +- crates/primitives/src/receipt/mod.rs | 23 ++++++------- .../src/transaction/l1_transaction.rs | 16 ++++----- .../src/transaction/morph_transaction.rs | 17 +++++----- crates/proofs-exex/src/lib.rs | 17 +++++----- crates/proofs/src/api.rs | 2 +- crates/proofs/src/cursor_factory.rs | 2 +- crates/proofs/src/db/store.rs | 30 ++++++++-------- crates/proofs/src/live.rs | 8 ++--- crates/proofs/src/provider.rs | 2 +- crates/proofs/src/prune/error.rs | 2 +- crates/proofs/src/prune/pruner.rs | 4 +-- crates/proofs/src/prune/task.rs | 2 +- crates/reference-index/src/reader.rs | 11 +++--- crates/reference-index/src/runtime.rs | 3 +- crates/reference-index/src/types.rs | 4 +-- crates/reference-index/src/writer.rs | 10 +++--- crates/revm/src/handler.rs | 2 +- crates/revm/src/l1block.rs | 2 +- crates/revm/src/precompiles.rs | 14 ++++---- crates/revm/src/tx.rs | 2 +- crates/rpc/src/eth/call.rs | 16 +++++---- crates/rpc/src/eth/mod.rs | 8 ++--- crates/rpc/src/eth/receipt.rs | 12 +++---- crates/rpc/src/eth/transaction.rs | 13 ++++--- crates/rpc/src/morph/handler.rs | 2 +- crates/rpc/src/morph/rpc.rs | 2 +- crates/rpc/src/types/receipt.rs | 2 +- crates/txpool/src/error.rs | 7 ++-- crates/txpool/src/lib.rs | 14 +++++--- crates/txpool/src/morph_tx_validation.rs | 2 +- crates/txpool/src/validator.rs | 21 ++++++++---- 63 files changed, 251 insertions(+), 231 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index f652f4be..d418c5d0 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -13,8 +13,9 @@ fail-fast = false slow-timeout = { period = "30s", terminate-after = 4 } # E2E integration tests spawn full nodes — each needs exclusive MDBX resources. -# threads-required = 2 means nextest counts each as needing 2 of the test-threads -# slots, so only 1 runs at a time on CI (2 slots / 2 required = 1 concurrent). +# threads-required = 2 means nextest counts each as needing 2 of the `num-cpus` +# test-thread slots, halving e2e concurrency (2 at a time on the 4-vCPU +# `ubuntu-latest` runner). [[profile.ci.overrides]] filter = "package(morph-node) & binary(it)" threads-required = 2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77ebfc18..01b71f3e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,8 +113,9 @@ jobs: - name: Resolve build profile id: profile run: | - # Tag push -> profiling (matches Dockerfile + EC2 deploy defaults; - # see PR rationale: maxperf regresses ERC20 long tail). + # Tag push -> profiling (matches the Dockerfile default; maxperf + # regresses the ERC20 long tail, see #104). EC2 deploys build + # separately via MakefileEc2.mk with the `reproducible` profile. # workflow_dispatch -> user-selected profile (defaults to profiling, # `maxperf` available for eth-heavy reference builds). if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then diff --git a/crates/chainspec/src/constants.rs b/crates/chainspec/src/constants.rs index 4c30f023..eb8d7744 100644 --- a/crates/chainspec/src/constants.rs +++ b/crates/chainspec/src/constants.rs @@ -9,7 +9,8 @@ pub const MORPH_MAINNET_CHAIN_ID: u64 = 2818; pub const MORPH_HOODI_CHAIN_ID: u64 = 2910; /// The default L2 sequencer fee (0.001 Gwei = 1_000_000 wei). -/// The sequencer has the right to set any base fee below `MORPH_MAX_BASE_FEE`. +/// The sequencer has the right to set any base fee up to `MORPH_MAXIMUM_BASE_FEE` +/// (enforced by `morph-consensus` header validation). pub const MORPH_BASE_FEE: u64 = 1_000_000; /// Maximum L2 transaction payload bytes per block (L1 messages excluded). diff --git a/crates/chainspec/src/hardfork.rs b/crates/chainspec/src/hardfork.rs index e13a4b9f..aa024b6d 100644 --- a/crates/chainspec/src/hardfork.rs +++ b/crates/chainspec/src/hardfork.rs @@ -13,18 +13,20 @@ //! 4. Update `morph_hardfork_at()` to check for the new hardfork first (latest hardfork is checked first) //! 5. Add `MorphHardfork::Vivace => Self::OSAKA` (or appropriate SpecId) in `From for SpecId` //! 6. Update `From for MorphHardfork` to check for the new hardfork first -//! 7. Add test `test_is_vivace` and update existing `is_*` tests to include the new variant +//! 7. Add the new variant to the `SpecId` mapping tests and to the fork list in +//! `test_morph_hardforks_do_not_enable_amsterdam_state_gas` +//! +//! ### In `genesis.rs`: +//! 8. Add `vivace_time: Option` field to `MorphHardforkInfo`, named so its camelCase key +//! matches morph-geth's genesis JSON key (e.g. `jade_fork_time` for `jadeForkTime`) //! //! ### In `spec.rs`: -//! 8. Add `vivace_time: Option` field to `MorphGenesisInfo` -//! 9. Extract `vivace_time` in `From for MorphChainSpec` -//! 10. Add `(MorphHardfork::Vivace, vivace_time)` to `morph_forks` vec -//! 11. Update tests to include `"vivaceTime": ` in genesis JSON +//! 9. Add `(MorphHardfork::Vivace, hardfork_info.vivace_time)` to `time_forks` in +//! `build_hardforks` +//! 10. Update tests to include `"vivaceTime": ` in genesis JSON //! -//! ### In genesis files and generator: -//! 12. Add `"vivaceTime": 0` to `genesis/dev.json` -//! 13. Add `vivace_time: Option` arg to `xtask/src/genesis_args.rs` -//! 14. Add insertion of `"vivaceTime"` to chain_config.extra_fields +//! ### In genesis files: +//! 11. Add the activation key to `res/genesis/{mainnet,hoodi}.json` when the fork is scheduled //! //! ## Current State //! diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 581c3d78..843d062b 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -5,7 +5,7 @@ //! - [`MorphChainSpec`]: The main chain specification type that wraps reth's `ChainSpec` //! with Morph-specific configuration. //! - [`hardfork::MorphHardfork`]: Morph-specific hardfork definitions (Bernoulli, Curie, Morph203, etc.) -//! - [`MorphChainConfig`]: Morph L2-specific chain configuration (fee vault, max tx size, etc.) +//! - [`MorphChainConfig`]: Morph L2-specific chain configuration (fee vault address) //! //! # Supported Networks //! @@ -16,7 +16,7 @@ //! //! Morph hardforks use two activation mechanisms: //! - **Block-based**: Bernoulli, Curie (activated at specific block numbers) -//! - **Timestamp-based**: Morph203, Viridian, Emerald (activated at specific timestamps) +//! - **Timestamp-based**: Morph203, Viridian, Emerald, Jade (activated at specific timestamps) //! //! # Example //! diff --git a/crates/consensus/src/error.rs b/crates/consensus/src/error.rs index 42896bd8..90727965 100644 --- a/crates/consensus/src/error.rs +++ b/crates/consensus/src/error.rs @@ -14,8 +14,8 @@ use alloy_primitives::Address; /// in the standard reth `ConsensusError`. #[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] pub enum MorphConsensusError { - /// Invalid L1 message order - either L1 messages are not at the start of the block - /// or queue indices are not strictly sequential. + /// Invalid L1 message order - an L1 message appears after an L2 transaction + /// (L1 messages must be at the start of the block). #[error("Invalid L1 message order")] InvalidL1MessageOrder, diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs index f7b9bfcf..b9b2064e 100644 --- a/crates/consensus/src/lib.rs +++ b/crates/consensus/src/lib.rs @@ -14,7 +14,7 @@ //! //! 1. All L1 messages must be at the beginning of the block //! 2. L1 messages must be in ascending `queue_index` order -//! 3. No gaps in the `queue_index` sequence +//! 3. No gaps in the `queue_index` sequence within a block //! //! # Example //! diff --git a/crates/consensus/src/validation.rs b/crates/consensus/src/validation.rs index 063d7db5..5cb87f59 100644 --- a/crates/consensus/src/validation.rs +++ b/crates/consensus/src/validation.rs @@ -29,6 +29,8 @@ //! - Transaction root must be valid //! - L2 transaction payload (EIP-2718 encoded, L1 messages excluded) must not //! exceed [`morph_chainspec::MORPH_MAX_TX_PAYLOAD_BYTES_PER_BLOCK`] +//! - MorphTx (0x7F) must be active (Emerald), use an active version (V1 needs Jade), +//! and pass field validation //! //! ## Post-Execution Validation //! @@ -85,7 +87,7 @@ const GAS_LIMIT_BOUND_DIVISOR: u64 = 1024; /// L1 message ordering requires both body data (transactions) and parent header data. /// Since reth's `Consensus` trait methods provide these separately — `validate_block_pre_execution` /// has the block body but not the parent header, while `validate_header_against_parent` has -/// both headers but not the body — the validation is split into two independent checks: +/// both headers but not the body — the validation is split into three independent checks: /// /// 1. **Internal consistency** (`validate_block_pre_execution`): L1 messages are at the block /// start, have sequential queue indices, and are consistent with `header.next_l1_msg_index`. @@ -96,10 +98,10 @@ const GAS_LIMIT_BOUND_DIVISOR: u64 = 1024; /// from `parent.next_l1_msg_index` and the block's leading L1 messages. /// /// The consensus trait methods have no ordering dependency and share no mutable state. The strict -/// cross-block equality check (`header.next == parent.next + l1_count`) requires simultaneous -/// access to both parent header and block body, which reth's trait API does not provide in -/// any single method, so Morph performs that final check in the engine tree payload validator -/// before a block is accepted. +/// cross-block equality check (`header.next` equals `parent.next` advanced past the block's +/// leading L1 messages) requires simultaneous access to both parent header and block body, +/// which reth's trait API does not provide in any single method, so Morph performs that final +/// check in the engine tree payload validator before a block is accepted. #[derive(Debug, Clone)] pub struct MorphConsensus { /// Chain specification containing hardfork information and chain config. @@ -214,8 +216,9 @@ impl HeaderValidator for MorphConsensus { /// /// 1. **Parent Hash**: Header's parent_hash must match parent's hash /// 2. **Block Number**: Header's number must be parent's number + 1 - /// 3. **Timestamp**: Header's timestamp must be >= parent's timestamp + /// 3. **Timestamp**: Header's timestamp must be > parent's timestamp (>= from Emerald onward) /// 4. **Gas Limit**: Change must be within 1/1024 of parent's limit + /// 5. **L1 Message Index**: `next_l1_msg_index` must not decrease relative to the parent fn validate_header_against_parent( &self, header: &SealedHeader, @@ -275,7 +278,8 @@ impl Consensus for MorphConsensus { /// 4. **Withdrawals**: Must be empty (Morph L2 doesn't support withdrawals) /// 5. **L2 Payload Size**: Encoded L2 txs (L1 messages excluded) must not /// exceed [`MORPH_MAX_TX_PAYLOAD_BYTES_PER_BLOCK`] - /// 6. **L1 Messages**: Must be ordered correctly (sequential queue indices, L1 before L2) + /// 6. **MorphTx**: Type active (Emerald), version active (V1 needs Jade), fields valid + /// 7. **L1 Messages**: Must be ordered correctly (sequential queue indices, L1 before L2) fn validate_block_pre_execution( &self, block: &SealedBlock, @@ -689,12 +693,8 @@ fn validate_morph_txs( // Receipts Validation // ============================================================================ -/// Verifies the receipts root and logs bloom against the expected values. -/// -/// This function: -/// 1. Calculates the receipts root from the provided receipts -/// 2. Calculates the logs bloom by combining all receipt blooms -/// 3. Compares both against the expected values from the block header +/// Compares a receipts root and logs bloom pre-computed by the executor against the +/// expected values from the block header. #[inline] fn verify_receipts_precomputed( expected_receipts_root: B256, @@ -723,6 +723,12 @@ fn verify_receipts_precomputed( Ok(()) } +/// Verifies the receipts root and logs bloom against the expected values. +/// +/// This function: +/// 1. Calculates the receipts root from the provided receipts +/// 2. Calculates the logs bloom by combining all receipt blooms +/// 3. Compares both against the expected values from the block header fn verify_receipts( expected_receipts_root: B256, expected_logs_bloom: Bloom, @@ -1144,7 +1150,7 @@ mod tests { create_regular_tx(), ]; - // Header says 2 but should be 3 (last=2, 2+1=3). Value < min_expected triggers error. + // Header says 2 but should be 3 (last=2, 2+1=3). Value != expected triggers error. let result = validate_l1_messages_in_block(&txs, 2, true); assert!(result.is_err()); let err_str = result.unwrap_err().to_string(); diff --git a/crates/engine-api/src/api.rs b/crates/engine-api/src/api.rs index 42cff591..25ab8af8 100644 --- a/crates/engine-api/src/api.rs +++ b/crates/engine-api/src/api.rs @@ -20,8 +20,10 @@ use morph_primitives::MorphHeader; /// and provides the following methods: /// /// - `assemble_l2_block`: Build a new L2 block with the given transactions +/// - `assemble_l2_block_v2`: Build a new L2 block on an explicitly given parent hash /// - `validate_l2_block`: Validate an L2 block without importing it /// - `new_l2_block`: Import and finalize a new L2 block +/// - `new_l2_block_v2`: Import a new L2 block onto the parent selected by hash (may reorg) /// - `new_safe_l2_block`: Import a safe L2 block from derivation /// - `set_block_tags`: Update safe/finalized block tags without importing a block #[async_trait::async_trait] @@ -29,9 +31,9 @@ use morph_primitives::MorphHeader; pub trait MorphL2EngineApi: Send + Sync { /// Build a new L2 block with the given transactions. /// - /// This method is called by the sequencer to assemble a new block containing - /// the provided transactions. The transactions should include L1 messages - /// at the beginning, followed by L2 transactions. + /// This method is called by the sequencer to assemble a new block. The provided + /// transactions are the L1 messages to execute first; L2 transactions are then + /// packed from the local txpool. /// /// # Arguments /// diff --git a/crates/engine-api/src/builder.rs b/crates/engine-api/src/builder.rs index 79c76231..9f9f7334 100644 --- a/crates/engine-api/src/builder.rs +++ b/crates/engine-api/src/builder.rs @@ -826,8 +826,7 @@ impl RealMorphL2EngineApi { // FCU safe/finalized must be canonical ancestors. Unsafe imports pass safe zero; // new_safe_l2_block passes the imported block itself, never a cached old safe. // Forward only the L1-derived finalized tag; zero is a no-op when it is absent, - // and pinned reth v2.2.0 still cleans changesets/canonical memory without - // finalized. + // and reth still cleans changesets/canonical memory without finalized. let forkchoice = alloy_rpc_types_engine::ForkchoiceState { head_block_hash: data.hash, safe_block_hash, @@ -892,7 +891,7 @@ impl RealMorphL2EngineApi { let logs_bloom = alloy_primitives::Bloom::from_slice(data.logs_bloom.as_ref()); // Override coinbase to empty address when FeeVault is enabled, - // matching go-ethereum's executableDataToBlock (l2_api.go:292-293). + // matching go-ethereum's executableDataToBlock (l2_api.go). let beneficiary = if self.chain_spec.is_fee_vault_enabled() { Address::ZERO } else { diff --git a/crates/engine-api/src/lib.rs b/crates/engine-api/src/lib.rs index 8d740723..547669fa 100644 --- a/crates/engine-api/src/lib.rs +++ b/crates/engine-api/src/lib.rs @@ -12,9 +12,12 @@ //! standard Ethereum Engine API: //! //! - `engine_assembleL2Block`: Build a new block with given transactions +//! - `engine_assembleL2BlockV2`: Build a new block on an explicitly given parent hash //! - `engine_validateL2Block`: Validate a block without importing //! - `engine_newL2Block`: Import and finalize a block +//! - `engine_newL2BlockV2`: Import a block onto the parent selected by hash (may reorg) //! - `engine_newSafeL2Block`: Import a safe block from derivation +//! - `engine_setBlockTags`: Update safe/finalized block tags without importing a block #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] diff --git a/crates/engine-api/src/metrics.rs b/crates/engine-api/src/metrics.rs index 20b95e54..cd506641 100644 --- a/crates/engine-api/src/metrics.rs +++ b/crates/engine-api/src/metrics.rs @@ -24,17 +24,17 @@ pub(crate) struct MorphEngineApiMetrics { // ------------------------------------------------------------------------- // assembleL2Block // ------------------------------------------------------------------------- - /// Latency for `engine_assembleL2Block` calls. + /// Latency for `engine_assembleL2Block` and `engine_assembleL2BlockV2` calls. pub(crate) assemble_l2_block_duration_seconds: Histogram, - /// Number of `engine_assembleL2Block` calls that returned an error. + /// Number of `engine_assembleL2Block`/`engine_assembleL2BlockV2` calls that returned an error. pub(crate) assemble_l2_block_failures_total: Counter, // ------------------------------------------------------------------------- // newL2Block // ------------------------------------------------------------------------- - /// Latency for `engine_newL2Block` calls. + /// Latency for `engine_newL2Block` and `engine_newL2BlockV2` calls. pub(crate) new_l2_block_duration_seconds: Histogram, - /// Number of `engine_newL2Block` calls that returned an error. + /// Number of `engine_newL2Block`/`engine_newL2BlockV2` calls that returned an error. pub(crate) new_l2_block_failures_total: Counter, // ------------------------------------------------------------------------- @@ -42,7 +42,7 @@ pub(crate) struct MorphEngineApiMetrics { // ------------------------------------------------------------------------- /// Latency for `engine_validateL2Block` calls. pub(crate) validate_l2_block_duration_seconds: Histogram, - /// Number of `engine_validateL2Block` calls that returned `success: false`. + /// Number of `engine_validateL2Block` calls that returned an error or `success: false`. pub(crate) validate_l2_block_failures_total: Counter, // ------------------------------------------------------------------------- diff --git a/crates/evm/src/block/mod.rs b/crates/evm/src/block/mod.rs index fbdecf02..b3531d75 100644 --- a/crates/evm/src/block/mod.rs +++ b/crates/evm/src/block/mod.rs @@ -78,7 +78,7 @@ impl TxResult for MorphTxResult { /// ## Execution Flow /// 1. `apply_pre_execution_changes`: Set up state and load contracts /// 2. `execute_transaction_without_commit`: Execute transaction in EVM -/// 3. `commit_transaction`: Calculate fees, build receipt, commit state +/// 3. `commit_transaction`: Build receipt from the cached fee info, commit state /// 4. `finish`: Return final execution result with all receipts pub struct MorphBlockExecutor { /// The EVM used by executor (owned, not a reference) diff --git a/crates/evm/src/block/receipt.rs b/crates/evm/src/block/receipt.rs index 4d791b3d..8594f093 100644 --- a/crates/evm/src/block/receipt.rs +++ b/crates/evm/src/block/receipt.rs @@ -75,13 +75,13 @@ pub(crate) struct MorphReceiptBuilderCtx<'a, E: Evm> { /// /// # Token Fee Calculation Formula /// ```text -/// token_fee = eth_fee * fee_rate / token_scale +/// token_fee = eth_fee * token_scale / fee_rate (rounded up) /// ``` /// /// # Fields /// - `version`: The version of the Morph transaction format (0 = legacy, 1 = with reference/memo) /// - `fee_token_id`: ID of the ERC20 token registered in L2TokenRegistry -/// - `fee_rate`: Exchange rate from L2TokenRegistry (token per ETH) +/// - `fee_rate`: Price ratio from L2TokenRegistry (token price relative to ETH) /// - `token_scale`: Decimal scale factor for the token (e.g., 10^18) /// - `fee_limit`: Maximum tokens the user agreed to pay /// - `reference`: 32-byte key for transaction indexing by external systems @@ -203,7 +203,7 @@ impl MorphReceiptBuilder for DefaultMorphReceiptBuilder { // MorphTx transactions should always have MorphTx-specific fields. // If fields are missing, it indicates one of the following: // 1. The fee token is not registered in L2TokenRegistry - // 2. TokenFeeInfo::fetch returned None (token inactive or query failed) + // 2. The token registry lookup failed (logged by commit_transaction) // 3. A bug in get_morph_tx_fields logic // // We log a warning and fallback to L1-fee-only receipt to avoid diff --git a/crates/evm/src/evm.rs b/crates/evm/src/evm.rs index 368f605d..7d06d437 100644 --- a/crates/evm/src/evm.rs +++ b/crates/evm/src/evm.rs @@ -53,7 +53,7 @@ impl EvmFactory for MorphEvmFactory { /// /// This is a wrapper type around the `revm` ethereum evm with optional [`Inspector`] (tracing) /// support. [`Inspector`] support is configurable at runtime because it's part of the underlying -/// `RevmEvm` type. +/// `morph_revm::MorphEvm` type. #[expect(missing_debug_implementations)] pub struct MorphEvm { inner: morph_revm::MorphEvm, diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 62e90949..bbac1a06 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -31,7 +31,6 @@ //! │ │ - Calculates L1 data fee for all L2 transactions │ │ //! │ │ - Extracts token fee info for MorphTx (0x7F) │ │ //! │ │ - Builds receipts with full Morph-specific context │ │ -//! │ │ - Applies hardfork state changes (Curie, etc.) │ │ //! │ └─────────────────────────────────────────────────────────┘ │ //! └─────────────────────────────────────────────────────────────────┘ //! ``` @@ -100,9 +99,6 @@ pub use morph_revm::{MorphBlockEnv, MorphHaltReason}; /// - Block executor creation with Morph-specific execution logic /// - Block assembler for constructing `MorphHeader` blocks /// -/// # Usage -/// -/// Create with a chain specification: /// # Trait Implementations /// /// - `ConfigureEvm`: Provides EVM environment setup and block context creation diff --git a/crates/node/src/args.rs b/crates/node/src/args.rs index ab5e9260..aaf34f5c 100644 --- a/crates/node/src/args.rs +++ b/crates/node/src/args.rs @@ -23,7 +23,8 @@ pub const MORPH_DEFAULT_MAX_TX_PAYLOAD_BYTES: u64 = MORPH_MAX_TX_PAYLOAD_BYTES_P /// budget, and `--morph.max-tx-payload-bytes` (the uncompressed L2 payload /// that must fit in one 6-blob batch). /// -/// Note: Block building deadline is configured via reth's built-in `--builder.deadline` flag. +/// Note: reth's `--builder.deadline` only bounds the payload job as a whole; the per-build +/// packing time budget is `MorphBuilderConfig::time_limit` (1s), which has no CLI flag. #[derive(Debug, Clone, Args)] #[command(next_help_heading = "Morph")] pub struct MorphArgs { diff --git a/crates/node/src/test_utils.rs b/crates/node/src/test_utils.rs index 24d22560..196329bd 100644 --- a/crates/node/src/test_utils.rs +++ b/crates/node/src/test_utils.rs @@ -117,6 +117,7 @@ impl HardforkSchedule { /// /// - `AllActive`: no changes (test genesis already has all forks at 0) /// - `PreJade`: set `jadeForkTime` to `u64::MAX` + /// - `PreViridian`: set `viridianTime`, `emeraldTime` and `jadeForkTime` to `u64::MAX` /// - `Hoodi`/`Mainnet`: compare each `*Time` key against the reference network; /// forks active now → 0, forks not yet active → `u64::MAX`. /// Block-based forks (`*Block`) are always kept at 0. @@ -271,7 +272,7 @@ impl TestNodeBuilder { self } - /// Enable or disable dev mode (auto-sealing blocks every 100ms). + /// Enable or disable dev mode (reth's local miner auto-seals blocks from the pool). pub fn with_dev(mut self, is_dev: bool) -> Self { self.is_dev = is_dev; self @@ -960,7 +961,7 @@ impl MorphTxBuilder { /// Configure as MorphTx **v0** with ERC20 fee payment. /// /// - `fee_token_id` must be > 0 (v0 requires ERC20 fee) - /// - Sets a generous `fee_limit` (1e20 tokens) to avoid rejection + /// - Sets a generous `fee_limit` (1e20 wei, i.e. 100 tokens) to avoid rejection pub fn with_v0_token_fee(mut self, fee_token_id: u16) -> Self { assert!(fee_token_id > 0, "v0 MorphTx requires fee_token_id > 0"); self.version = 0; diff --git a/crates/node/src/validator.rs b/crates/node/src/validator.rs index bcbccc01..71a29dc7 100644 --- a/crates/node/src/validator.rs +++ b/crates/node/src/validator.rs @@ -69,8 +69,9 @@ where /// Builder for Morph tree engine validator. /// -/// This wires [`MorphEngineValidator`] into both payload validation and state-root -/// decision/validation hooks. +/// This wires [`MorphEngineValidator`] into upstream payload validation, installs the +/// pre-Jade-aware `MorphStateRootStrategy`, and wraps the result in +/// [`MorphTreeEngineValidator`] for the L1-queue and withdraw-trie-root checks. #[derive(Debug, Clone)] pub struct MorphTreeEngineValidatorBuilder { payload_validator_builder: PVB, @@ -945,7 +946,7 @@ mod tests { } /// Block-input path (P2P sync, pipeline backfill) reaches - /// `validate_block_post_execution_with_hashed_state` without calling + /// `validate_withdraw_trie_root_update` without calling /// `convert_payload_to_block`, so no expectation is registered. The /// validator must treat the missing entry as `SkipValidation` and /// return `Ok` — otherwise sync stalls. The upstream strict state-root diff --git a/crates/node/src/version.rs b/crates/node/src/version.rs index fef55f20..402b652b 100644 --- a/crates/node/src/version.rs +++ b/crates/node/src/version.rs @@ -1,8 +1,8 @@ //! Morph-Reth version metadata. //! //! Overrides reth's default version info so `--version` reports morph-reth's -//! own version, commit SHA, and build timestamp instead of the upstream reth -//! fork's values. +//! own version, commit SHA, and build timestamp instead of upstream reth's +//! values. use reth_node_core::version::{RethCliVersionConsts, try_init_version_metadata}; use std::{borrow::Cow, env}; diff --git a/crates/node/tests/it/consensus.rs b/crates/node/tests/it/consensus.rs index 2e373d38..21d6cc16 100644 --- a/crates/node/tests/it/consensus.rs +++ b/crates/node/tests/it/consensus.rs @@ -5,6 +5,8 @@ //! - L1 messages must precede all L2 transactions (ordering constraint) //! - L1 messages within a block must have strictly sequential queue indices //! - Post-Jade blocks with a wrong state root are rejected +//! - Header checks against the parent (number, parent hash, timestamp, gas used/limit) +//! - `next_l1_msg_index` rules, including where the Jade exact-index rule starts use alloy_primitives::B256; use morph_node::test_utils::{ diff --git a/crates/node/tests/it/engine.rs b/crates/node/tests/it/engine.rs index 4f749c53..2a1f84ef 100644 --- a/crates/node/tests/it/engine.rs +++ b/crates/node/tests/it/engine.rs @@ -258,8 +258,7 @@ async fn new_l2_block_v2_imports_block_on_current_head() -> eyre::Result<()> { /// second — which builds on the same parent, not on the new head — must reorg the head /// onto it. This is the core capability the centralized sequencer relies on /// (`NewL2BlockV2` + `SetCanonical`); the V1 path would reject the sibling with a -/// wrong-parent-hash error. Near-wall-clock timestamps keep the blocks out of the -/// historical-finalization fallback so the engine permits the reorg. +/// wrong-parent-hash error. #[tokio::test(flavor = "multi_thread")] async fn new_l2_block_v2_reorgs_onto_sibling_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); @@ -274,9 +273,8 @@ async fn new_l2_block_v2_reorgs_onto_sibling_block() -> eyre::Result<()> { .unwrap() .as_secs(); - // Block 1 on genesis. Timestamps sit a few seconds in the past: recent enough to - // stay out of the historical-finalization fallback (so the engine permits the - // reorg) but not in the future (which header validation would reject). + // Block 1 on genesis. Timestamps sit a few seconds in the past, not in the future + // (which header validation would reject). let mut params = AssembleL2BlockParams::empty(1); params.timestamp = Some(now - 6); let block1: ExecutableL2Data = client.request("engine_assembleL2Block", (params,)).await?; @@ -348,9 +346,8 @@ async fn new_safe_l2_block_with_parent_hash_reorgs_onto_non_head_parent() -> eyr .unwrap() .as_secs(); - // Block 1 on genesis (live import). Past-but-recent timestamps keep the blocks out - // of the historical-finalization fallback (so the reorg is permitted) without - // tripping the future-timestamp header check. + // Block 1 on genesis (live import). Past timestamps avoid tripping the + // future-timestamp header check. let mut p1 = AssembleL2BlockParams::empty(1); p1.timestamp = Some(now - 6); let block1: ExecutableL2Data = client.request("engine_assembleL2Block", (p1,)).await?; @@ -999,7 +996,7 @@ async fn new_safe_l2_block_rejects_transactions_over_gas_limit() -> eyre::Result import_l2_block(&node, block1).await?; let head_before = canonical_snapshot(&node)?; - // First transaction fits; the second one alone exceeds the whole block gas limit, so the + // First transaction fits; the second one's limit equals the whole block gas limit, so the // pair cannot be executed under it. let fits = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) .with_v1_token_fee(TEST_TOKEN_ID) diff --git a/crates/node/tests/it/helpers.rs b/crates/node/tests/it/helpers.rs index ceb66cd4..9b88a732 100644 --- a/crates/node/tests/it/helpers.rs +++ b/crates/node/tests/it/helpers.rs @@ -254,7 +254,7 @@ pub(crate) const fn reference_query(reference: B256) -> ReferenceQueryArgs { /// /// L2 transactions already in the pool will also be included after the L1 messages. /// -/// NOTE: Uses direct `resolve_kind` polling instead of the event stream to +/// NOTE: Uses direct `best_payload` polling instead of the event stream to /// avoid state leakage between sequential calls in multi-block tests. pub(crate) async fn advance_block_with_l1_messages( node: &mut MorphTestNode, @@ -398,7 +398,7 @@ pub(crate) async fn build_block_no_submit( /// Craft a block by modifying a valid payload, then try to import it via engine API. /// -/// Returns `true` if the block was accepted (VALID/SYNCING), `false` if rejected (INVALID). +/// Returns `true` only if the block was accepted as VALID, `false` otherwise (INVALID/SYNCING). /// The modification function receives a mutable reference to the unsealed block. /// /// After modification, `transactions_root` is recomputed and the block is re-sealed. diff --git a/crates/node/tests/it/l1_messages.rs b/crates/node/tests/it/l1_messages.rs index 94f63240..07a68ea4 100644 --- a/crates/node/tests/it/l1_messages.rs +++ b/crates/node/tests/it/l1_messages.rs @@ -44,7 +44,7 @@ async fn l1_messages_across_blocks_continuous() -> eyre::Result<()> { } /// When a block has no L1 messages, queue index tracking is unchanged. -/// L1 messages in a later block can continue from any higher index. +/// L1 messages in a later block continue from the parent's `next_l1_msg_index`. #[tokio::test(flavor = "multi_thread")] async fn l1_messages_resume_after_empty_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); diff --git a/crates/node/tests/it/mixed_block_pressure.rs b/crates/node/tests/it/mixed_block_pressure.rs index e1de9cee..83965ab2 100644 --- a/crates/node/tests/it/mixed_block_pressure.rs +++ b/crates/node/tests/it/mixed_block_pressure.rs @@ -64,9 +64,9 @@ fn assert_l1_prefix(block: &Block, expected_l1_messages: u64) { assert_eq!(block.header.next_l1_msg_index, expected_l1_messages); } -/// Data-availability bytes the builder attributes to a block, which is the RLP -/// network length of each pool transaction. L1 messages are excluded because -/// their data already lives on L1. +/// Data-availability bytes of a block's pool transactions, summed as RLP network +/// lengths (never below the EIP-2718 lengths the builder counts). L1 messages are +/// excluded because their data already lives on L1. fn l2_da_bytes(block: &Block) -> u64 { block .body diff --git a/crates/node/tests/it/morph_tx.rs b/crates/node/tests/it/morph_tx.rs index 7fe7d58d..7284e961 100644 --- a/crates/node/tests/it/morph_tx.rs +++ b/crates/node/tests/it/morph_tx.rs @@ -429,17 +429,19 @@ async fn morph_tx_v0_token_balance_decreases() -> eyre::Result<()> { /// - mainnet gas used: 59_335 /// /// The important shape is not the exact mainnet state, but that the MorphTx pays -/// fees in the same ERC20 contract it calls. Fee deduction touches the sender's -/// balance slot before the main ERC20 `transfer` SLOAD/SSTORE pair, so this -/// catches regressions in the `sload_morph`, `sstore_morph`, and reimburse -/// cold/warm-state handling. +/// fees in the same ERC20 contract it calls. Fee deduction writes the sender's +/// balance slot before the main ERC20 `transfer` SLOAD/SSTORE pair and leaves it +/// cold, with its DB-committed `original_value` and the deducted present value. +/// Morph relies on revm's native SLOAD/SSTORE to charge geth-equivalent gas for +/// that state (see `MorphEvm::new` in `crates/revm/src/evm.rs`), so this test +/// guards it together with the reimburse cold/warm-state handling. /// -/// `EXPECTED_GAS_USED = 48_128` is the sandbox golden, NOT the mainnet +/// The expected `48_128` is the sandbox golden, NOT the mainnet /// 59_335. The sandbox uses a minimal hand-written ERC20 with one /// storage slot per `transfer`, while the mainnet token's compiled /// bytecode does extra checks; initial balances and call data sizes also -/// differ. What's locked is the bug-vs-fix delta: a regression in -/// `sload_morph`/`sstore_morph` causes the main tx's SSTORE on +/// differ. What's locked is the bug-vs-fix delta: a regression that +/// re-baselines the slot's `original_value` causes the main tx's SSTORE on /// `sender.balanceOf` to be charged 2900 (SSTORE_RESET) instead of 100 /// (dirty), pushing `cumulative_gas_used` ~2800 above the golden and /// tripping this assertion before the change reaches mainnet. diff --git a/crates/node/tests/it/rpc.rs b/crates/node/tests/it/rpc.rs index a55232cb..9f770ff3 100644 --- a/crates/node/tests/it/rpc.rs +++ b/crates/node/tests/it/rpc.rs @@ -546,8 +546,8 @@ async fn build_standard_jade_block_for_debug_trace() -> eyre::Result<(MorphTestN /// Comprehensive test: debug + trace replay APIs on a standard Jade block with Cancun active. /// -/// Uses internal APIs (debug_api / trace_api) directly via `node.rpc.inner`, -/// matching the approach on `main`. This avoids HTTP serialization overhead +/// Uses internal APIs (debug_api / trace_api) directly via `node.rpc.inner`. +/// This avoids HTTP serialization overhead /// and the TaskManager lifetime pitfalls of the HTTP path. #[tokio::test(flavor = "multi_thread")] async fn debug_trace_replay_apis_work_for_standard_jade_block() -> eyre::Result<()> { @@ -827,7 +827,7 @@ async fn eth_call_token_fee_does_not_reject_zero_eth_sender() -> eyre::Result<() /// `eth_estimateGas` rejects a request whose sender cannot afford `tx.value`. /// /// Exercises the first balance check in `MorphEthApi::caller_gas_allowance` -/// — the one that fires before the L1 fee is even computed. +/// — the `value` check that fires before the L1 fee is compared to the balance. /// /// Setup: /// - An unfunded random sender (`balance = 0`). diff --git a/crates/payload/builder/src/builder.rs b/crates/payload/builder/src/builder.rs index b3da4b7f..b4e9c258 100644 --- a/crates/payload/builder/src/builder.rs +++ b/crates/payload/builder/src/builder.rs @@ -431,9 +431,9 @@ impl MorphPayloadBuilderCtx { // Execute the transaction and record EVM execution time. let apply_started = Instant::now(); - // `BlockBuilder::execute_transaction` returns `GasOutput` from - // alloy-evm 0.34; pre-Amsterdam morph treats regular and state gas - // as a single number, so collapse to `tx_gas_used()` immediately. + // `BlockBuilder::execute_transaction` returns alloy-evm's `GasOutput`; + // pre-Amsterdam morph treats regular and state gas as a single number, + // so collapse to `tx_gas_used()` immediately. let gas_used = match builder.execute_transaction(recovered_tx.clone()) { Ok(gas_output) => gas_output.tx_gas_used(), Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { @@ -600,7 +600,7 @@ impl MorphPayloadBuilderCtx { } let apply_started = Instant::now(); - // Same reasoning as the L1-message branch above: collapse `GasOutput` + // Same reasoning as the supplied-transaction branch above: collapse `GasOutput` // into a single u64 since we are still pre-Amsterdam. let gas_used = match builder.execute_transaction(tx.clone()) { Ok(gas_output) => gas_output.tx_gas_used(), @@ -886,8 +886,8 @@ where // 6. Finish building the block. // - // When `trie_handle` is provided, drop the state hook to signal FinishedStateUpdates - // to the background sparse trie task (via StateHookSender's Drop impl), then wait for + // When `state_root_handle` is provided, drop the state hook to signal FinishedStateUpdates + // to the background sparse trie task (via `StateRootUpdateHook`'s Drop impl), then wait for // the final root. Fall back to synchronous state root if the task fails. let BlockBuilderOutcome { execution_result, diff --git a/crates/payload/builder/src/config.rs b/crates/payload/builder/src/config.rs index 460c5472..a511e3d9 100644 --- a/crates/payload/builder/src/config.rs +++ b/crates/payload/builder/src/config.rs @@ -253,7 +253,7 @@ mod tests { fn test_breaker_should_break_on_gas_limit() { // Set gas_limit = 2 * MIN_TRANSACTION_GAS = 42000 // Threshold = 42000 - 21000 = 21000 - // should_break returns true when cumulative_gas_used > threshold + // should_break returns true when gas_pool_used > threshold let gas_limit = 2 * MIN_TRANSACTION_GAS; let breaker = PayloadBuildingBreaker::new(Duration::from_secs(10), gas_limit, None); diff --git a/crates/payload/types/src/attributes.rs b/crates/payload/types/src/attributes.rs index 6459c11b..6251ef66 100644 --- a/crates/payload/types/src/attributes.rs +++ b/crates/payload/types/src/attributes.rs @@ -120,9 +120,8 @@ impl From for MorphPayloadAttributes { /// This is the internal representation used by the payload builder, /// with decoded supplied transactions and a computed payload ID. /// -/// Implements `reth_payload_primitives::PayloadAttributes` so it can serve as the -/// `type Attributes` in `PayloadBuilder` (v2.0.0 requires the builder attributes to -/// implement PayloadAttributes). The serde impls are required by the trait bound. +/// Implements `reth_payload_primitives::PayloadAttributes` so it satisfies the bound on +/// reth's `PayloadConfig::new`. The serde impls are required by the trait bound. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct MorphPayloadBuilderAttributes { /// Computed payload ID. diff --git a/crates/payload/types/src/lib.rs b/crates/payload/types/src/lib.rs index f940589b..aa621bc3 100644 --- a/crates/payload/types/src/lib.rs +++ b/crates/payload/types/src/lib.rs @@ -52,7 +52,7 @@ pub use safe_l2_data::SafeL2Data; #[non_exhaustive] pub struct MorphPayloadTypes; -/// Execution data for Morph node. Simply wraps a sealed block. +/// Execution data for Morph node: a sealed block plus an optional expected withdraw trie root. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct MorphExecutionData { /// The built block. diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index d80d6215..35c64eef 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -20,7 +20,7 @@ //! //! - [`Block`]: Morph block type alias //! - [`BlockBody`]: Morph block body type alias -//! - [`MorphHeader`]: Morph header type alias +//! - [`MorphHeader`]: Morph block header (Ethereum header plus `next_l1_msg_index`) //! //! # Node Primitives //! diff --git a/crates/primitives/src/receipt/mod.rs b/crates/primitives/src/receipt/mod.rs index a74fd369..d0aa3988 100644 --- a/crates/primitives/src/receipt/mod.rs +++ b/crates/primitives/src/receipt/mod.rs @@ -123,8 +123,6 @@ impl MorphReceipt { } /// Returns RLP header for inner encoding without bloom. - /// - /// Used for DA (data availability) layer compression where bloom is omitted to save space. pub fn rlp_header_inner_without_bloom(&self) -> Header { Header { list: true, @@ -135,7 +133,6 @@ impl MorphReceipt { /// Returns length of RLP-encoded receipt fields without bloom and without an RLP header. /// /// The fields are: `[status, cumulative_gas_used, logs]` (no bloom). - /// Used for DA layer compression. pub fn rlp_encoded_fields_length_without_bloom(&self) -> usize { match self { Self::Legacy(r) @@ -154,7 +151,6 @@ impl MorphReceipt { /// RLP-encodes receipt fields without bloom and without an RLP header. /// /// Encodes: `[status, cumulative_gas_used, logs]` (no bloom). - /// Used for DA layer compression. pub fn rlp_encode_fields_without_bloom(&self, out: &mut dyn BufMut) { match self { Self::Legacy(r) @@ -177,7 +173,6 @@ impl MorphReceipt { /// RLP-decodes the receipt from the provided buffer without bloom. /// /// Expects format: `[status, cumulative_gas_used, logs]` (no bloom). - /// Used for DA layer decompression. pub fn rlp_decode_inner_without_bloom( buf: &mut &[u8], tx_type: MorphTxType, @@ -342,7 +337,7 @@ impl Encodable2718 for MorphReceipt { /// /// Format: `[type_byte] + RLP([status, cumulative_gas_used, logs])` /// - /// Bloom is omitted for DA layer compression - it can be recalculated from logs. + /// Bloom is omitted - it can be recalculated from logs. fn encode_2718_len(&self) -> usize { !self.tx_type().is_legacy() as usize + self.rlp_header_inner_without_bloom().length_with_payload() @@ -352,7 +347,7 @@ impl Encodable2718 for MorphReceipt { /// /// Format: `[type_byte] + RLP([status, cumulative_gas_used, logs])` /// - /// Bloom is omitted for DA layer compression - it can be recalculated from logs. + /// Bloom is omitted - it can be recalculated from logs. fn encode_2718(&self, out: &mut dyn BufMut) { if !self.tx_type().is_legacy() { out.put_u8(self.tx_type().into()); @@ -388,8 +383,9 @@ impl Decodable2718 for MorphReceipt { impl alloy_rlp::Encodable for MorphReceipt { /// Encodes the receipt for P2P network transmission. /// - /// Uses `network_encode` which wraps typed receipts in an additional RLP string header, - /// as required by the eth wire protocol (eth/66, eth/67). + /// Uses `network_encode` which wraps typed receipts in an additional RLP string header. + /// This bloom-less encoding is what reth's eth/69+ `Receipts69`/`Receipts70` messages + /// carry; eth/66-68 `Receipts` encode `ReceiptWithBloom` instead. fn encode(&self, out: &mut dyn BufMut) { self.network_encode(out); } @@ -450,7 +446,8 @@ mod compact { /// for the CompactZstd derive macro. /// /// Note: `fee_token_id` and `version` are stored as `u64` instead of `u16`/`u8` because - /// they don't implement `Compact` in reth_codecs. The conversion is lossless. + /// `u16` has no `Compact` impl in reth_codecs and the `u64` width is part of the stored + /// encoding. The conversion is lossless. #[derive(reth_codecs::CompactZstd)] #[reth_zstd( compressor = reth_zstd_compressors::with_receipt_compressor, @@ -462,7 +459,7 @@ mod compact { #[allow(clippy::owned_cow)] logs: Cow<'a, Vec>, l1_fee: Option, - /// Stored as u64 for Compact compatibility (u8 doesn't implement Compact) + /// Stored as u64 (part of the stored encoding; lossless for the u8 version) version: Option, /// Stored as u64 for Compact compatibility (u16 doesn't implement Compact) fee_token_id: Option, @@ -726,7 +723,7 @@ mod tests { /// Tests that EIP-2718 encoding and decoding roundtrips correctly for EIP-1559 receipt. /// - /// This tests the without-bloom encoding used for DA compression: + /// This tests the without-bloom encoding: /// - encode_2718: encodes [status, gas, logs] without bloom /// - decode_2718: decodes the same format #[test] @@ -900,7 +897,7 @@ mod tests { /// Tests that without-bloom encoding is smaller than with-bloom encoding. /// - /// This verifies the DA compression benefit. + /// The difference must be at least the 256-byte bloom. #[test] fn test_without_bloom_is_smaller() { let receipt = create_test_receipt(); diff --git a/crates/primitives/src/transaction/l1_transaction.rs b/crates/primitives/src/transaction/l1_transaction.rs index 6c6cba1b..a1580046 100644 --- a/crates/primitives/src/transaction/l1_transaction.rs +++ b/crates/primitives/src/transaction/l1_transaction.rs @@ -2,8 +2,6 @@ //! //! This module defines the TxL1Msg type which represents L1 message //! transactions that are processed on Morph L2. -//! -//! Reference: use alloy_consensus::{ SignableTransaction, Transaction, @@ -29,8 +27,6 @@ pub const L1_TX_TYPE_ID: u8 = 0x7E; /// a signature field. Gas for the transaction execution is already paid for on the L1. /// /// Note: Contract creation is NOT allowed via L1 message transactions. -/// -/// Reference: #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( @@ -238,7 +234,7 @@ impl RlpEcdsaEncodableTx for TxL1Msg { impl RlpEcdsaDecodableTx for TxL1Msg { const DEFAULT_TX_TYPE: u8 = { Self::tx_type() }; - /// Decodes the inner [TxEip1559] fields from RLP bytes. + /// Decodes the inner [`TxL1Msg`] fields from RLP bytes. fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { Self::decode_fields(buf) } @@ -338,8 +334,8 @@ mod msg_serde { //! standard EVM JSON-RPC client (ethers v5/v6, viem, web3.js — including the //! `@morph-network/*` SDK adapters that wrap them) nevertheless expects those keys //! on every transaction object. Following morph-geth's RPC contract, we render all - //! five as `"0x0"` placeholders during serialization and silently drop them on the - //! way back in. + //! five, plus `gasPrice`, as `"0x0"` placeholders during serialization and silently + //! drop them on the way back in. //! //! Because [`alloy_consensus::Sealed`] flattens its inner `T` into the surrounding //! object, these top-level fields naturally appear at the envelope's RPC root — @@ -651,9 +647,9 @@ mod tests { } /// JSON serialization must include the morph-geth RPC parity placeholders - /// `nonce`/`v`/`r`/`s`/`yParity` so downstream clients (ethers v5/v6, viem) + /// `nonce`/`gasPrice`/`v`/`r`/`s`/`yParity` so downstream clients (ethers v5/v6, viem) /// can parse L1 message tx objects returned by `eth_getBlockByNumber` etc. - /// All five live on the helper struct alongside the real fields, so they + /// All six live on the helper struct alongside the real fields, so they /// also appear when the type is serialized standalone (not just via the /// envelope). #[test] @@ -701,7 +697,7 @@ mod tests { /// End-to-end check: serializing a [`MorphTxEnvelope::L1Msg`] (the type /// actually emitted by RPC) must include all the morph-geth parity fields - /// at the top level: type, queueIndex, sender, nonce, v, r, s, yParity, hash. + /// at the top level: type, queueIndex, sender, nonce, gasPrice, v, r, s, yParity, hash. #[test] fn test_l1_envelope_rpc_json_includes_geth_parity_fields() { use crate::MorphTxEnvelope; diff --git a/crates/primitives/src/transaction/morph_transaction.rs b/crates/primitives/src/transaction/morph_transaction.rs index e60943df..1929bfe5 100644 --- a/crates/primitives/src/transaction/morph_transaction.rs +++ b/crates/primitives/src/transaction/morph_transaction.rs @@ -90,16 +90,16 @@ pub struct TxMorph { /// A scalar value equal to the maximum amount of gas that should be used /// in executing this transaction. This is paid up-front, before any /// computation is done and may not be increased later. - /// Matches go-ethereum's `AltFeeTx.Gas` (uint64). + /// Matches go-ethereum's `MorphTx.Gas` (uint64). #[cfg_attr( feature = "serde", serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit") )] pub gas_limit: u64, - /// A scalar value equal to the maximum amount of gas that should be used - /// in executing this transaction. This is paid up-front, before any - /// computation is done and may not be increased later. + /// A scalar value equal to the maximum total fee per unit of gas + /// the sender is willing to pay. The actual fee paid per gas is + /// the minimum of this and `base_fee + max_priority_fee_per_gas`. /// /// This is also known as `GasFeeCap`. #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] @@ -917,8 +917,9 @@ mod compact_txmorph { /// Follows the same pattern as reth's `TxEip1559` compact helper /// (see `reth-codecs/src/alloy/transaction/eip1559.rs`). /// - /// - `version` and `fee_token_id` are stored as `u64` because `u8`/`u16` don't - /// implement `Compact` in reth_codecs. The conversion is lossless. + /// - `version` and `fee_token_id` are stored as `u64` because the derive only accepts + /// a `u8` field in last position and `u16` has no `Compact` impl in reth_codecs. + /// The conversion is lossless. /// - `memo` and `input` are packed into a single `Bytes` field (`data`) because /// the derive macro only allows one `Bytes` field and it must be last. /// Format: `[memo_len: u8][memo_bytes][input_bytes]`. @@ -933,7 +934,7 @@ mod compact_txmorph { to: TxKind, value: U256, access_list: AccessList, - /// Stored as u64 for Compact compatibility (u8 doesn't implement Compact) + /// Stored as u64 for Compact compatibility (the derive rejects a non-last u8 field) version: u64, /// Stored as u64 for Compact compatibility (u16 doesn't implement Compact) fee_token_id: u64, @@ -1694,7 +1695,7 @@ mod tests { #[test] fn test_morph_transaction_memo_validation() { - // Valid memo (under 64 bytes) - use V1 since it doesn't require fee_token_id + // Valid memo (exactly 64 bytes) - use V1 since it doesn't require fee_token_id let valid_tx = TxMorph { version: MORPH_TX_VERSION_1, memo: Some(Bytes::from(vec![0u8; 64])), diff --git a/crates/proofs-exex/src/lib.rs b/crates/proofs-exex/src/lib.rs index 036b8853..7ebc2836 100644 --- a/crates/proofs-exex/src/lib.rs +++ b/crates/proofs-exex/src/lib.rs @@ -113,11 +113,11 @@ where } } -/// Proofs `ExEx` - processes blocks and tracks state changes within fault proof window. +/// Proofs `ExEx` - processes blocks and tracks state changes within the proof-history window. /// -/// Saves and serves trie nodes to make proofs faster. This handles the process of -/// saving the current state, new blocks as they're added, and serving proof RPCs -/// based on the saved data. +/// Saves trie nodes to make proofs faster. This handles the process of saving new +/// blocks as they're added on top of the state written by `morph-reth proofs init`; +/// the proof RPC overrides are served from the saved data. /// #[derive(Debug)] pub struct MorphProofsExEx @@ -307,8 +307,9 @@ where /// Drains pending sync states for the lifetime of the node. /// /// Proof history is a best-effort side index: no failure propagates out of this loop. A failed - /// turn is classified for observability and then abandoned; later notifications may supply - /// recovery work. Structural failures additionally clear the health gauge so + /// turn is classified for observability; a transient forward-sync failure is re-armed after a + /// backoff, and any other failure is abandoned until later notifications supply recovery + /// work. Structural failures additionally clear the health gauge so /// `debug_proofsSyncStatus` and the RPC canonical-anchor check remain the authority on whether /// the index can be served. async fn sync_loop( @@ -1165,8 +1166,8 @@ mod tests { /// /// Right after `proofs init` the store holds only its anchor, so `earliest == latest` and any /// reorg of the tip is already "beyond earliest" — the single most likely reorg to hit a freshly - /// initialized node. `handle_revert` therefore has to contain that failure: it runs inside a - /// critical task whose error path is a panic, so returning `Err` here would shut the node down. + /// initialized node. `run_revert` therefore has to contain that failure: it runs inside a + /// critical task whose error path is a panic, so propagating `Err` would shut the node down. #[tokio::test] async fn handle_revert_contains_unwind_failure() { let dir = tempdir_path(); diff --git a/crates/proofs/src/api.rs b/crates/proofs/src/api.rs index 0d014093..bf15d9ec 100644 --- a/crates/proofs/src/api.rs +++ b/crates/proofs/src/api.rs @@ -217,7 +217,7 @@ pub trait MorphProofsStore: Send + Sync + Debug { new_earliest_block_ref: BlockWithParent, ) -> MorphProofsStorageResult; - /// Remove account, storage and trie updates from historical storage for all blocks till + /// Remove account, storage and trie updates from historical storage for all blocks from /// the specified block (inclusive). fn unwind_history(&self, to: BlockWithParent) -> MorphProofsStorageResult<()>; diff --git a/crates/proofs/src/cursor_factory.rs b/crates/proofs/src/cursor_factory.rs index fa2dfcbb..059f256e 100644 --- a/crates/proofs/src/cursor_factory.rs +++ b/crates/proofs/src/cursor_factory.rs @@ -24,7 +24,7 @@ use crate::{ /// Request-scoped factory that opens trie cursors against a shared read-only transaction. /// /// Holds a borrow of the transaction so every cursor allocation reuses the same MDBX -/// reader slot. See [`MorphProofsStore::Tx`] for the underlying contention story. +/// reader slot. #[derive(Debug, Clone)] pub struct MorphProofsTrieCursorFactory<'tx, 'db, S: MorphProofsStore> { storage: &'db MorphProofsStorage, diff --git a/crates/proofs/src/db/store.rs b/crates/proofs/src/db/store.rs index 369ded65..02e293e1 100644 --- a/crates/proofs/src/db/store.rs +++ b/crates/proofs/src/db/store.rs @@ -551,9 +551,9 @@ impl MdbxProofsStorage { v } - /// Delete history versions for `items` that are strictly older than the provided block number. - /// `items` is a list of (Key, `SurvivorBlock`). Everything strictly older than `SurvivorBlock` - /// is deleted. Returns the number of entries deleted. + /// Delete history versions for `cutoff_items` that are strictly older than the provided block + /// number. `cutoff_items` is a list of (Key, `SurvivorBlock`). Everything strictly older than + /// `SurvivorBlock` is deleted. Returns the number of entries deleted. fn prune_history_preceding( &self, tx: &(impl DbTxMut + DbTx), @@ -1194,14 +1194,12 @@ impl MorphProofsStore for MdbxProofsStorage { })? } - /// Update the initial state with the provided diff. - /// Prune all historical trie data till `new_earliest_block_number` (inclusive) using + /// Prune all historical trie data till `new_earliest_block_ref` (inclusive) using /// the [`BlockChangeSet`] index. /// /// Arguments: - /// - `new_earliest_block_ref`: The new earliest block reference (with parent hash). - /// - `diff`: The state diff to apply to the initial state (block 0). This diff represents all - /// the changes from the old earliest block to the new earliest block (inclusive). + /// - `new_earliest_block_ref`: The new earliest block reference; only its block number and + /// hash are read. fn prune_earliest_state( &self, new_earliest_block_ref: BlockWithParent, @@ -1257,9 +1255,9 @@ impl MorphProofsStore for MdbxProofsStorage { }) } - /// Unwind the historical state to `unwind_upto_block` (inclusive), deleting all history + /// Unwind the historical state to `to` (inclusive), deleting all history /// starting from provided block. Also updates the `ProofWindow::LatestBlock` to parent of - /// `unwind_upto_block`. + /// `to`. fn unwind_history(&self, to: BlockWithParent) -> MorphProofsStorageResult<()> { self.commit_update(|tx| { let proof_window = match self.inner_get_proof_window(tx)? { @@ -3198,7 +3196,7 @@ mod tests { .unwrap(); // Now prune to the stored tip, with the new initial state: - // - path1 should be in removed_nodes (it was deleted in block 3) + // - path1 should be removed entirely (it was deleted in block 3) // - path2 should be included with its value (it still exists from block 2) store.prune_earliest_state(block_3).unwrap(); @@ -3783,9 +3781,9 @@ mod tests { } } - /// Mirror of `tests/lib.rs::test_store_trie_updates_with_wiped_storage_and_new_slots` for - /// the storage *trie* path. When `StorageTrieUpdates::is_deleted` is true AND - /// `storage_nodes` is non-empty (the shape revm/reth produce when a contract is destroyed + /// Wiped-storage-with-new-slots case for the storage *trie* path. When + /// `StorageTrieUpdates::is_deleted` is true AND `storage_nodes` is non-empty (the shape + /// revm/reth produce when a contract is destroyed /// and recreated with a fresh trie in the same block), the wipe branch must tombstone every /// pre-existing path for the address AND persist the new post-recreation nodes — with new /// nodes winning on path collision. @@ -4299,7 +4297,7 @@ mod tests { // Unwind to block 2 store.unwind_history(b2).expect("unwind"); - // Verify: blocks 1 and 2 remain, blocks 3 and 4 are removed + // Verify: block 1 remains, blocks 2, 3 and 4 are removed let tx = store.env.tx().expect("tx"); let mut cur = tx.new_cursor::().expect("cursor"); @@ -4812,7 +4810,7 @@ mod tests { .store_trie_updates(b3, make_diff(30)) .expect("store b3"); - // Unwind to block 1 + // Unwind to block 5, beyond the latest stored block (no-op) store.unwind_history(b5).expect("first unwind"); // Verify state is still correct diff --git a/crates/proofs/src/live.rs b/crates/proofs/src/live.rs index bd70a875..53eb84a1 100644 --- a/crates/proofs/src/live.rs +++ b/crates/proofs/src/live.rs @@ -193,13 +193,13 @@ where /// Handles chain reorganizations by replacing block updates after a common ancestor. /// /// This method removes all block updates after the latest common ancestor (the block before - /// the first block in `new_blocks`) and replaces them with the updates from the provided new - /// chain. + /// the first block in `block_updates`) and replaces them with the updates from the provided + /// new chain. /// /// # Arguments /// - /// * `new_blocks` - A vector of references to `RecoveredBlock` instances representing the new - /// blocks to be added to the trie storage. + /// * `block_updates` - The new chain's blocks, each with its sorted trie updates and hashed + /// post-state, to be added to the trie storage. pub fn unwind_and_store_block_updates( &self, block_updates: Vec<( diff --git a/crates/proofs/src/provider.rs b/crates/proofs/src/provider.rs index d28181d9..630d4802 100644 --- a/crates/proofs/src/provider.rs +++ b/crates/proofs/src/provider.rs @@ -36,7 +36,7 @@ use crate::{ /// State provider for external proofs storage. pub struct MorphProofsStateProviderRef<'a, Storage: MorphProofsStore> { - /// Historical state provider for non-state related tasks. + /// Latest state provider for non-state related tasks (block hashes and bytecode). latest: Box, /// Storage provider for state lookups. diff --git a/crates/proofs/src/prune/error.rs b/crates/proofs/src/prune/error.rs index ae31c595..75f33c48 100644 --- a/crates/proofs/src/prune/error.rs +++ b/crates/proofs/src/prune/error.rs @@ -63,7 +63,7 @@ impl PrunerOutput { /// Error returned by the pruner. #[derive(Debug, Error)] pub enum PrunerError { - /// Wrapped error from the underlying `MorphProofStorage` layer. + /// Wrapped error from the underlying `MorphProofsStorage` layer. #[error(transparent)] Storage(#[from] MorphProofsStorageError), diff --git a/crates/proofs/src/prune/pruner.rs b/crates/proofs/src/prune/pruner.rs index dc0e70f0..d63769eb 100644 --- a/crates/proofs/src/prune/pruner.rs +++ b/crates/proofs/src/prune/pruner.rs @@ -697,7 +697,7 @@ mod tests { ); } - // interval < min_block_interval -> "Nothing to prune" path; default output. + // interval < retention_blocks -> "Nothing to prune" path; default output. #[tokio::test] async fn run_inner_interval_too_small_real_db() { let dir = TempDir::new().unwrap(); @@ -723,7 +723,7 @@ mod tests { assert_eq!(e.0, 4); assert_eq!(l.0, 5); - // Require min_block_interval=2 (or greater) so interval < min + // Require retention_blocks=2 (or greater) so interval < retention_blocks let block_hash_reader = MockBlockHashReader::new(); let pruner = MorphProofStoragePruner::new(store, block_hash_reader, 2, 1000); let out = pruner.run_inner().expect("ok"); diff --git a/crates/proofs/src/prune/task.rs b/crates/proofs/src/prune/task.rs index cb490dd3..f6b1eb71 100644 --- a/crates/proofs/src/prune/task.rs +++ b/crates/proofs/src/prune/task.rs @@ -50,7 +50,7 @@ where } } - /// Run forever (until `cancel`), executing one prune pass per `task_run_interval`. + /// Run forever (until `signal` fires), executing one prune pass per `task_run_interval`. pub async fn run(self, mut signal: GracefulShutdown) { info!( target: "trie::pruner_task", diff --git a/crates/reference-index/src/reader.rs b/crates/reference-index/src/reader.rs index 644410d8..b5093a2b 100644 --- a/crates/reference-index/src/reader.rs +++ b/crates/reference-index/src/reader.rs @@ -14,9 +14,10 @@ use std::sync::{ atomic::{AtomicBool, AtomicU8, Ordering}, }; -/// Runtime phase, exported purely for metrics and logs. +/// Runtime phase, exported for metrics and logs, and read by the RPC layer only +/// to skip its bounded catch-up wait while `Deferred` or `PreJade`. /// -/// The read path no longer branches on this. Read correctness comes from the +/// Read correctness never depends on this. It comes from the /// MDBX read-transaction snapshot plus the `(indexed_to, indexed_hash) == tip` /// comparison in [`ReferenceIndexHandle::query_at`], and from the before/after /// `chain_info()` bracketing in the RPC layer. The only read-gate bit derived @@ -36,7 +37,8 @@ pub enum ReferenceIndexPhase { Live = 4, /// A non-canonical indexed suffix is being removed. Repairing = 5, - /// Queries cannot be served until retry or a manual rebuild succeeds. + /// Queries cannot be served: the runtime stopped on an error that needs a manual + /// rebuild (or its blocking task died), so only a node restart recovers. Unavailable = 6, } @@ -58,8 +60,7 @@ impl ReferenceIndexPhase { #[derive(Debug)] struct ReferenceIndexShared { db: RwLock>, - /// Latest runtime phase. Kept for metrics/logs only; not read on the query - /// path (see [`ReferenceIndexPhase`]). + /// Latest runtime phase. Not read by `query_at` (see [`ReferenceIndexPhase`]). phase: AtomicU8, /// Set while the runtime is in [`ReferenceIndexPhase::Unavailable`] (manual /// rebuild required / persistent failure). The read path returns diff --git a/crates/reference-index/src/runtime.rs b/crates/reference-index/src/runtime.rs index 84d7c448..cfc83d8a 100644 --- a/crates/reference-index/src/runtime.rs +++ b/crates/reference-index/src/runtime.rs @@ -314,7 +314,8 @@ impl ReferenceIndexRuntime { Ok(()) } - /// Run the non-critical background reconciler until node shutdown. + /// Run the non-critical background reconciler until node shutdown, or until it + /// stops in [`ReferenceIndexPhase::Unavailable`]. /// /// Notifications are wake-ups and rate signals only. Canonical data is /// always re-read through [`CanonicalChain`] before a durable commit. diff --git a/crates/reference-index/src/types.rs b/crates/reference-index/src/types.rs index 3bfa3a50..083e15a3 100644 --- a/crates/reference-index/src/types.rs +++ b/crates/reference-index/src/types.rs @@ -14,8 +14,8 @@ pub struct CanonicalTip { /// Current reference index database schema version. /// -/// Version 1 was used only by the pre-release ExEx prototype. There is no -/// migration path: operators remove that derived database manually. +/// Version 1 was written by the ExEx-based index shipped in v0.3.0 through v1.0.0. +/// There is no migration path: operators remove that derived database manually. pub const SCHEMA_VERSION: u32 = 2; /// Validated query parameters for reference lookups. diff --git a/crates/reference-index/src/writer.rs b/crates/reference-index/src/writer.rs index 5d9be658..4f231daa 100644 --- a/crates/reference-index/src/writer.rs +++ b/crates/reference-index/src/writer.rs @@ -1,8 +1,8 @@ //! Reference index write path helpers. //! //! All write functions take an already-open write transaction so that the -//! caller can batch multiple blocks (backfill) or delete+write (reorg) in a -//! single atomic commit. +//! caller can batch multiple block writes (backfill) or deletions (reorg +//! rollback) together with the cursor update in a single atomic commit. use crate::{ DEFAULT_BACKFILL_BATCH_BLOCKS, @@ -189,10 +189,8 @@ mod tests { Signature::new(U256::from(1u64), U256::from(1u64), false) } - /// A MorphTx wrapper that returns `Some(reference)` when queried. We - /// don't have a simple TxMorph factory in this crate, so we fabricate - /// a test envelope by using the Eip1559 variant and pairing the expected - /// hash via the trait impl. + /// An Eip1559 envelope, so `reference()` returns `None`. We don't have a + /// simple TxMorph factory in this crate to build a reference-carrying tx. /// /// For now we test write_block/delete_block indirectly by checking that /// `write_block` writes the `IndexedBlocks` row for blocks without any diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs index c2b04d81..6f4fd2c8 100644 --- a/crates/revm/src/handler.rs +++ b/crates/revm/src/handler.rs @@ -438,7 +438,7 @@ where // Attempt token refund. Matches go-ethereum's refundGas() which silently logs // and continues on failure: "Continue execution even if refund fails - refund - // should not cause transaction to fail" (state_transition.go:698). + // should not cause transaction to fail" (state_transition.go:711). let refund_result = if let Some(balance_slot) = token_fee_info.balance_slot { let journal = evm.ctx().journal_mut(); transfer_erc20_with_slot( diff --git a/crates/revm/src/l1block.rs b/crates/revm/src/l1block.rs index e99f2f6a..99481bd7 100644 --- a/crates/revm/src/l1block.rs +++ b/crates/revm/src/l1block.rs @@ -214,7 +214,7 @@ impl L1BlockInfo { } else { self.calculate_tx_l1_cost_curie(input, hardfork) }; - // Cap to u64::MAX for circuit compatibility (go-ethereum: rollup_fee.go:248-249) + // Cap to u64::MAX for circuit compatibility (go-ethereum: `CalculateL1DataFee`) fee.min(L1_FEE_CAP) } } diff --git a/crates/revm/src/precompiles.rs b/crates/revm/src/precompiles.rs index cce783fa..67c8fcc2 100644 --- a/crates/revm/src/precompiles.rs +++ b/crates/revm/src/precompiles.rs @@ -9,14 +9,14 @@ //! Berlin (base) //! └── Bernoulli/Curie = Berlin with ripemd160/blake2f replaced by disabled stubs //! └── Morph203/Viridian = Bernoulli with ripemd160/blake2f re-enabled (working) -//! └── Emerald = Morph203 + Osaka precompiles +//! └── Emerald/Jade = Morph203 + Osaka precompiles //! ``` //! //! | Hardfork | Base | Changes | Notes | //! |------------------|-----------|----------------------------------------------------------|-------------------------------| //! | Bernoulli/Curie | Berlin | ripemd160/blake2f as disabled stubs; modexp 32B limit | - | //! | Morph203/Viridian| Bernoulli | blake2f/ripemd160 re-enabled; BN256 pairing 4-pair limit | - | -//! | Emerald | Morph203 | BLS12-381, P256verify; modexp EIP-7823/7883 upgrade | NO KZG (0x0a) | +//! | Emerald/Jade | Morph203 | BLS12-381, P256verify; modexp EIP-7823/7883 upgrade | NO KZG (0x0a) | //! //! ## Why Disabled Stubs? //! @@ -27,7 +27,7 @@ //! via `StateDB.Prepare` (EIP-2929). CALL costs 100 gas (warm) instead of 2600 (cold). //! //! 2. When called, go-eth's CALL handler sets `gas = 0` for any non-revert error, consuming -//! all forwarded gas. revm's `PrecompileError` result also causes all forwarded gas to +//! all forwarded gas. revm's `PrecompileHalt` status also causes all forwarded gas to //! be consumed (parent does not reclaim gas when sub-call is not ok-or-revert). //! //! Omitting these stubs causes morph-reth to treat 0x03/0x09 as cold empty accounts (2600 @@ -196,7 +196,7 @@ fn modexp_len_exceeds_32(data: &[u8], offset: usize) -> bool { /// Without this limit, morph-reth would accept arbitrarily large modexp inputs /// that go-ethereum rejects, causing a consensus split. /// -/// Ref: +/// Ref: go-ethereum `bigModExp.Run` in `core/vm/contracts.go` fn modexp_with_32byte_limit(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult { // The first 96 bytes of modexp input are three 32-byte big-endian length fields: // [0..32] = base_len, [32..64] = exp_len, [64..96] = mod_len @@ -226,7 +226,7 @@ fn modexp_with_32byte_limit(input: &[u8], gas_limit: u64, reservoir: u64) -> Pre /// accounting differs (the underlying computation is the same, but block gas /// limits and metering become inconsistent). /// -/// Ref: +/// Ref: go-ethereum `bn256PairingIstanbul.Run` in `core/vm/contracts.go` fn bn256_pairing_with_4pair_limit( input: &[u8], gas_limit: u64, @@ -251,7 +251,7 @@ fn bn256_pairing_with_4pair_limit( /// All 9 Berlin addresses are present (so they get warmed via EIP-2929), but 0x03/0x09 /// consume all forwarded gas and return failure when called. /// -/// Matches: +/// Matches: go-ethereum `PrecompiledContractsBernoulli` in `core/vm/contracts.go` pub fn bernoulli() -> &'static Precompiles { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { @@ -328,7 +328,7 @@ pub fn morph203() -> &'static Precompiles { /// - Adds P256verify (0x100) from RIP-7212 /// - Does **NOT** include KZG Point Evaluation (0x0a) — go-ethereum omits it /// -/// Ref: +/// Ref: go-ethereum `PrecompiledContractsEmerald` in `core/vm/contracts.go` pub fn emerald() -> &'static Precompiles { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { diff --git a/crates/revm/src/tx.rs b/crates/revm/src/tx.rs index bddd2768..75466b69 100644 --- a/crates/revm/src/tx.rs +++ b/crates/revm/src/tx.rs @@ -318,7 +318,7 @@ impl MorphTxEnv { } } -/// Extracted MorphTx fields from RLP-encoded bytes. +/// MorphTx fields extracted from the typed envelope. struct DecodedMorphTxFields { version: u8, fee_token_id: u16, diff --git a/crates/rpc/src/eth/call.rs b/crates/rpc/src/eth/call.rs index a1718bbf..b1141e31 100644 --- a/crates/rpc/src/eth/call.rs +++ b/crates/rpc/src/eth/call.rs @@ -1,10 +1,11 @@ //! Morph `eth_call` / `eth_estimateGas` overrides. //! //! [`Call::caller_gas_allowance`] is overridden so `eth_estimateGas` caps -//! gas by `balance − value − l1_fee` (ETH path) or the fee token balance -//! (MorphTx `fee_token_id > 0`). `eth_call` and `eth_createAccessList` -//! are detected via `cfg_env.disable_block_gas_limit = true` and fall -//! through to the upstream allowance without the L1-fee extension. +//! gas by `balance − value − l1_fee` (ETH path) or a token-denominated limit +//! (MorphTx `fee_token_id > 0`, see `token_gas_allowance`). `eth_call` and +//! `eth_createAccessList` are detected via `cfg_env.disable_block_gas_limit = true` and fall +//! through to the upstream allowance without the L1-fee extension, except +//! that fee-token callers are left uncapped for the handler. use crate::MorphEthApiError; use crate::eth::{MorphEthApi, MorphNodeCore}; @@ -209,8 +210,9 @@ where /// logic can be unit-tested without an EVM/DB stack. /// /// The `gas_cap` argument is the per-call RPC ceiling (`EthApiNodeBackend::gas_cap()`), -/// only consumed by the EVM-call-mode + no-`fee_limit` fallback to avoid -/// returning `u64::MAX`. See the in-body comment for the security rationale. +/// only consumed in EVM-call mode: as the no-`fee_limit` fallback (instead of `u64::MAX`) +/// and as the clamp on a user-supplied `fee_limit`. See the in-body comment for the security +/// rationale. fn token_gas_allowance( eth_balance: U256, value: U256, @@ -238,7 +240,7 @@ fn token_gas_allowance( // trusted balance is the natural ceiling. // - EVM-call mode (`balance_slot.is_none()`): RPC cannot resolve the // balance without spinning up an EVM (the handler does that at real - // execution via `load_for_caller`). On the estimateGas path + // execution via `load_token_fee_info`). On the estimateGas path // `disable_fee_charge=true` short-circuits the handler's check, so // there is no natural balance ceiling — we MUST enforce `gas_cap` // here, matching `eth_call`'s effective ceiling. Trusting a diff --git a/crates/rpc/src/eth/mod.rs b/crates/rpc/src/eth/mod.rs index 6d57c11f..01ff9e00 100644 --- a/crates/rpc/src/eth/mod.rs +++ b/crates/rpc/src/eth/mod.rs @@ -49,7 +49,7 @@ impl RpcTypes for MorphRpcTypes { type TransactionRequest = MorphTransactionRequest; } -/// Morph RPC converter with custom receipt and header conversion. +/// Morph RPC converter with custom receipt conversion. pub type MorphRpcConverter = RpcConverter::Evm, MorphReceiptConverter>; @@ -400,9 +400,9 @@ where _block: &RecoveredBlock<::Block>, _db: &mut StateCacheDb, ) -> Result<(), Self::Error> { - // Morph must skip Ethereum's 4788-style pre-block system calls during replay. - // Standard Morph headers omit parentBeaconBlockRoot, so the default Ethereum - // SystemCaller prelude would fail with "EIP-4788 beacon root missing". + // Morph has no Ethereum-style (EIP-4788) pre-block system calls to replay. The + // upstream default would only run the Morph executor's pre-execution step, + // which just pre-warms the L1 gas oracle account. Ok(()) } } diff --git a/crates/rpc/src/eth/receipt.rs b/crates/rpc/src/eth/receipt.rs index bc07636f..34508ea6 100644 --- a/crates/rpc/src/eth/receipt.rs +++ b/crates/rpc/src/eth/receipt.rs @@ -321,10 +321,10 @@ mod tests { /// Regression test for the `transactionReceipts` subscription wiring. /// - /// reth v2.2.0 exposes a `transactionReceipts` pubsub topic that, in the - /// `SubscriptionKind::TransactionReceipts` arm of - /// `reth_rpc::eth::pubsub`, calls `converter.convert_receipts(inputs)` - /// against the same converter the RPC `eth_getBlockReceipts` / + /// reth v2.5.2 exposes a `transactionReceipts` pubsub topic whose stream + /// (`EthSubscriptions::transaction_receipts_stream`) calls + /// `converter.convert_receipts_with_block(..)`, which defaults to + /// `convert_receipts`, against the same converter the RPC `eth_getBlockReceipts` / /// `eth_getTransactionReceipt` endpoints use. For Morph that converter /// is [`MorphReceiptConverter`]. /// @@ -553,8 +553,8 @@ mod tests { } /// Companion test: L1 message receipts must come back from the - /// pubsub-style converter path with default Morph fields and the - /// L1Msg envelope variant, just like `eth_getBlockReceipts`. + /// pubsub-style converter path with default Morph fields, just like + /// `eth_getBlockReceipts`. #[test] fn transaction_receipts_subscription_l1_msg_carries_default_morph_fields() { use alloy_consensus::transaction::Recovered; diff --git a/crates/rpc/src/eth/transaction.rs b/crates/rpc/src/eth/transaction.rs index e0a19ad1..f97cc0c0 100644 --- a/crates/rpc/src/eth/transaction.rs +++ b/crates/rpc/src/eth/transaction.rs @@ -187,10 +187,11 @@ fn morph_envelope_from_ethereum( /// `Ok(None)` if this should be a standard Ethereum transaction, /// or `Err(...)` if there's a validation error. /// -/// A MorphTx is constructed when any of these conditions are met: +/// Unless the request sets legacy `gasPrice` (always a standard transaction), +/// a MorphTx is constructed when any of these conditions are met: /// - `version` is present /// - `feeTokenID > 0` (ERC20 gas payment) -/// - `reference` is present +/// - `reference` is present and non-zero /// - `memo` is present and non-empty fn try_build_morph_tx_from_request( req: &alloy_rpc_types_eth::TransactionRequest, @@ -392,8 +393,7 @@ mod tests { ); } - /// Test that eth_estimateGas (disable_fee_charge = false) generates RLP encoding for L1 fee - /// calculation. + /// Test that eth_estimateGas generates RLP encoding for L1 fee calculation. /// /// This ensures that eth_estimateGas correctly calculates L1 data fee, matching go-ethereum /// behavior where available balance is reduced by l1DataFee before checking sufficiency. @@ -409,7 +409,7 @@ mod tests { memo: None, }; - // eth_estimateGas scenario: disable_fee_charge = false (default) + // reth's eth_estimateGas sets disable_fee_charge = true; the conversion ignores it. let evm_env = create_evm_env(false); // Act: Convert to TxEnv @@ -478,7 +478,7 @@ mod tests { memo: Some(memo.clone()), }; - // eth_estimateGas scenario: should encode for L1 fee + // Should encode for L1 fee let evm_env = create_evm_env(false); // Act: Convert to TxEnv @@ -723,7 +723,6 @@ mod tests { memo: None, }; - // eth_estimateGas scenario let evm_env = create_evm_env(false); // Act: Convert to TxEnv diff --git a/crates/rpc/src/morph/handler.rs b/crates/rpc/src/morph/handler.rs index 14abb67f..acef6045 100644 --- a/crates/rpc/src/morph/handler.rs +++ b/crates/rpc/src/morph/handler.rs @@ -53,7 +53,6 @@ impl MorphRpc { // ── Handler ─────────────────────────────────────────────────────────────────── -/// Handler that wraps [`MorphRpc`] and implements the jsonrpsee server trait. #[cfg(test)] #[derive(Clone)] struct WaitObserver(std::sync::Arc); @@ -65,6 +64,7 @@ impl std::fmt::Debug for WaitObserver { } } +/// Handler that wraps [`MorphRpc`] and implements the jsonrpsee server trait. #[derive(Debug, Clone)] pub struct MorphRpcHandler { ctx: MorphRpc, diff --git a/crates/rpc/src/morph/rpc.rs b/crates/rpc/src/morph/rpc.rs index d1bc2f79..a888ab30 100644 --- a/crates/rpc/src/morph/rpc.rs +++ b/crates/rpc/src/morph/rpc.rs @@ -33,7 +33,7 @@ pub trait MorphRpc { /// opening the index. /// /// When the durable index cursor lags the canonical head, waits for up to - /// 100 ms for the index to catch up. Returns `-32000 "reference index is + /// 300 ms for the index to catch up. Returns `-32000 "reference index is /// behind"` if the cursor still does not exactly match the canonical head /// number and hash, or while indexing is intentionally deferred. /// diff --git a/crates/rpc/src/types/receipt.rs b/crates/rpc/src/types/receipt.rs index b631b41b..90ce1ce6 100644 --- a/crates/rpc/src/types/receipt.rs +++ b/crates/rpc/src/types/receipt.rs @@ -23,7 +23,7 @@ pub struct MorphRpcReceipt { #[serde(rename = "l1Fee")] pub l1_fee: U256, - /// MorphTx version (only for MorphTx type 0x7F). + /// MorphTx version (`0x0` for non-MorphTx receipts, as in morph-geth). /// 0 = legacy format, 1 = with reference/memo support. pub version: Option, diff --git a/crates/txpool/src/error.rs b/crates/txpool/src/error.rs index d676b0bb..d675ad45 100644 --- a/crates/txpool/src/error.rs +++ b/crates/txpool/src/error.rs @@ -39,18 +39,19 @@ pub enum MorphTxError { token_id: u16, /// The token address. token_address: Address, - /// The actual token balance. + /// The spendable token amount: the balance, capped by a non-zero `fee_limit`. balance: U256, /// The required token amount. required: U256, }, /// Insufficient ETH balance to pay for transaction value. - /// MorphTx still requires ETH for the `value` field. + /// MorphTx still requires ETH for the `value` field. On the `fee_token_id == 0` + /// ETH-fee path this also covers the full cost (gas + L1 data fee + value). InsufficientEthForValue { /// The ETH balance. balance: U256, - /// The transaction value. + /// The transaction value, or the full ETH cost on the ETH-fee path. value: U256, }, diff --git a/crates/txpool/src/lib.rs b/crates/txpool/src/lib.rs index 52ca426b..d15fc971 100644 --- a/crates/txpool/src/lib.rs +++ b/crates/txpool/src/lib.rs @@ -15,19 +15,23 @@ //! The validator performs the following Morph-specific checks: //! - Rejects EIP-4844 blob transactions (not supported on L2) //! - Rejects L1 message transactions (only included by sequencer) +//! - Rejects EIP-7702 transactions before Viridian and MorphTx before Emerald +//! - Enforces the EIP-3860 initcode size limit regardless of Shanghai activation //! - Validates L1 data fee affordability //! - Validates MorphTx (0x7F) ERC20 token balance and fee_limit //! //! # MorphTx (0x7F) Validation //! //! MorphTx allows users to pay gas fees using ERC20 tokens. The validator: -//! 1. Checks the token is registered and active in L2TokenRegistry -//! 2. Calculates required token amount: `eth_to_token(gas_fee + l1_data_fee)` -//! 3. Uses effective token limit semantics: +//! 1. Validates structural MorphTx rules (`version`, `fee_limit`, memo length, fee ordering) +//! 2. Verifies `eth_balance >= value` (and for `fee_token_id == 0`, ETH covers full cost) +//! 3. Checks the token is registered and active in L2TokenRegistry, with a non-zero price ratio +//! 4. Calculates required token amount: +//! `TokenFeeInfo::eth_to_token_amount(gas_limit * max_fee_per_gas + l1_data_fee)` +//! 5. Uses effective token limit semantics: //! - `fee_limit == 0` => treat as token balance //! - `fee_limit > balance` => cap by token balance -//! 4. Verifies effective limit can cover required token amount -//! 5. Verifies `eth_balance >= value` (and for `fee_token_id == 0`, ETH covers full cost) +//! 6. Verifies effective limit can cover required token amount #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg), allow(unexpected_cfgs))] diff --git a/crates/txpool/src/morph_tx_validation.rs b/crates/txpool/src/morph_tx_validation.rs index b6abf576..5e0d088d 100644 --- a/crates/txpool/src/morph_tx_validation.rs +++ b/crates/txpool/src/morph_tx_validation.rs @@ -38,7 +38,7 @@ pub struct MorphTxValidationResult { pub token_info: Option, /// The required token amount pub required_token_amount: U256, - /// The amount that will be paid (min of fee_limit and required) + /// The amount that will be paid (equal to `required_token_amount`) pub amount_to_pay: U256, } diff --git a/crates/txpool/src/validator.rs b/crates/txpool/src/validator.rs index 1afea643..73c2ee56 100644 --- a/crates/txpool/src/validator.rs +++ b/crates/txpool/src/validator.rs @@ -5,6 +5,7 @@ //! - Rejection of EIP-4844 blob transactions //! - EIP-3860 max initcode size enforcement //! - Rejection of L1 message transactions from the pool +//! - Rejection of EIP-7702 transactions before Viridian and MorphTx before Emerald //! - L1 data fee validation //! - MorphTx (0x7F) ERC20 token balance validation @@ -102,12 +103,15 @@ impl MorphL1BlockInfo { /// This validator extends [`EthTransactionValidator`] with Morph-specific checks: /// - Rejects EIP-4844 blob transactions (not supported on L2) /// - Rejects L1 message transactions (only included by sequencer) +/// - Rejects EIP-7702 transactions before Viridian and MorphTx before Emerald +/// - Enforces the EIP-3860 initcode size limit regardless of Shanghai activation /// - Validates L1 data fee affordability /// - Validates MorphTx (0x7F) ERC20 token balance and fee_limit /// /// # MorphTx Validation /// -/// For MorphTx (type 0x7F), this validator performs additional checks: +/// For token-fee MorphTx (type 0x7F, `fee_token_id > 0`), this validator performs +/// additional checks: /// 1. Token must be registered and active in L2TokenRegistry /// 2. Fee limit must be sufficient for the calculated token cost /// 3. Token balance must cover the fee @@ -251,6 +255,8 @@ where /// This behaves the same as [`EthTransactionValidator::validate_one`], but in addition: /// - Rejects EIP-4844 blob transactions /// - Rejects L1 message transactions + /// - Rejects EIP-7702 transactions before Viridian and MorphTx before Emerald + /// - Enforces the EIP-3860 initcode size limit regardless of Shanghai activation /// - Validates MorphTx (0x7F) ERC20 token balance and fee_limit /// - Ensures that the account has enough balance to cover the L1 gas cost pub fn validate_one( @@ -415,11 +421,12 @@ where /// to avoid a redundant second `clone_into_consensus()`. /// /// This method performs the following checks (reference: go-ethereum tx_pool.go:727-791): - /// 1. `fee_token_id == 0`: ETH-fee path, require ETH affordability for `cost + l1_fee` - /// 2. `fee_token_id > 0`: token must be registered and active in L2TokenRegistry - /// 3. Token price ratio must be valid (non-zero) - /// 4. Effective token limit must cover required token amount - /// 5. ETH balance must be >= transaction value (value is still in ETH) + /// 1. Structural MorphTx rules (`version`, `fee_limit`, memo length, fee ordering) + /// 2. ETH balance must be >= transaction value (value is still in ETH) + /// 3. `fee_token_id == 0`: ETH-fee path, require ETH affordability for `cost + l1_fee` + /// 4. `fee_token_id > 0`: token must be registered and active in L2TokenRegistry + /// 5. Token price ratio must be valid (non-zero) + /// 6. Effective token limit must cover required token amount fn validate_morph_tx_balance( &self, consensus_tx: &reth_primitives_traits::Recovered, @@ -783,7 +790,7 @@ mod tests { /// (inherited from scroll-tech go-ethereum) that alloy's `Genesis` parser /// ignores, leaving Shanghai un-registered in the hardforks table and /// `is_shanghai_active_at_timestamp` permanently `false` — which means reth's - /// Shanghai-gated EIP-3860 check (`EthTransactionValidator`, `eth.rs:468`) is + /// Shanghai-gated EIP-3860 check (`EthTransactionValidator::validate_stateless`) is /// always skipped. The rejection is therefore enforced unconditionally by /// `MorphTransactionValidator` itself (see `validate_one_with_state`), not by /// the chainspec or the inner reth validator. From 97216bad00b2279794bdd001ad4440d22ff7fb4f Mon Sep 17 00:00:00 2001 From: panos-xyz Date: Wed, 23 Sep 2026 18:48:09 +0800 Subject: [PATCH 2/2] docs: describe exactly which engine calls the failure counters count Address review feedback: the engine-api failure counters' HELP text claimed every erroring call is counted, but head/parent lookup errors return before the counters move. Also spell out what `prune_earliest_state` prunes and that state at the new earliest block stays readable. --- crates/engine-api/src/metrics.rs | 9 ++++++--- crates/proofs/src/db/store.rs | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/engine-api/src/metrics.rs b/crates/engine-api/src/metrics.rs index cd506641..f3f15ab4 100644 --- a/crates/engine-api/src/metrics.rs +++ b/crates/engine-api/src/metrics.rs @@ -26,7 +26,8 @@ pub(crate) struct MorphEngineApiMetrics { // ------------------------------------------------------------------------- /// Latency for `engine_assembleL2Block` and `engine_assembleL2BlockV2` calls. pub(crate) assemble_l2_block_duration_seconds: Histogram, - /// Number of `engine_assembleL2Block`/`engine_assembleL2BlockV2` calls that returned an error. + /// Number of `engine_assembleL2Block`/`engine_assembleL2BlockV2` calls whose payload build + /// failed. pub(crate) assemble_l2_block_failures_total: Counter, // ------------------------------------------------------------------------- @@ -34,7 +35,8 @@ pub(crate) struct MorphEngineApiMetrics { // ------------------------------------------------------------------------- /// Latency for `engine_newL2Block` and `engine_newL2BlockV2` calls. pub(crate) new_l2_block_duration_seconds: Histogram, - /// Number of `engine_newL2Block`/`engine_newL2BlockV2` calls that returned an error. + /// Number of `engine_newL2Block`/`engine_newL2BlockV2` calls rejected for a discontinuous + /// block number or parent hash mismatch, or whose engine import failed. pub(crate) new_l2_block_failures_total: Counter, // ------------------------------------------------------------------------- @@ -42,7 +44,8 @@ pub(crate) struct MorphEngineApiMetrics { // ------------------------------------------------------------------------- /// Latency for `engine_validateL2Block` calls. pub(crate) validate_l2_block_duration_seconds: Histogram, - /// Number of `engine_validateL2Block` calls that returned an error or `success: false`. + /// Number of `engine_validateL2Block` calls rejected for a discontinuous block number or + /// parent hash mismatch, or that returned `success: false`. pub(crate) validate_l2_block_failures_total: Counter, // ------------------------------------------------------------------------- diff --git a/crates/proofs/src/db/store.rs b/crates/proofs/src/db/store.rs index 02e293e1..08a10281 100644 --- a/crates/proofs/src/db/store.rs +++ b/crates/proofs/src/db/store.rs @@ -1194,8 +1194,11 @@ impl MorphProofsStore for MdbxProofsStorage { })? } - /// Prune all historical trie data till `new_earliest_block_ref` (inclusive) using - /// the [`BlockChangeSet`] index. + /// Prune trie and hashed-state history up to `new_earliest_block_ref`, using the + /// [`BlockChangeSet`] index to find the keys changed in the pruned range. Each such key keeps + /// only its latest version at or below the new earliest block (none if that version is a + /// deletion), so state at that block stays readable. The pruned blocks' change sets are + /// deleted and the earliest pointer moves to the new block. /// /// Arguments: /// - `new_earliest_block_ref`: The new earliest block reference; only its block number and