Skip to content

fix(prover): accept reth L1 message tx fields#969

Closed
panos-xyz wants to merge 1 commit into
mainfrom
fix/reth-l1msg-tx-deserialize
Closed

fix(prover): accept reth L1 message tx fields#969
panos-xyz wants to merge 1 commit into
mainfrom
fix/reth-l1msg-tx-deserialize

Conversation

@panos-xyz

@panos-xyz panos-xyz commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Allow transaction trace deserialization to accept reth-style hash for transaction hashes while preserving existing txHash output.
  • Default missing nonce, v, r, and s fields so reth 0x7e L1 message transactions can be parsed.
  • Add a regression test for reth-style L1 message RPC transaction JSON.

Test plan

  • cargo test -p prover-primitives --lib
  • DEVNET=true SHOULD_THROTTLE_REQUESTS=false RUST_BACKTRACE=1 cargo test -p morph-prove --lib -- execute::tests::test_execute_local_traces --exact --nocapture
  • DEVNET=true SHOULD_THROTTLE_REQUESTS=false RUST_BACKTRACE=0 cargo test -p morph-prove --lib -- execute::tests::test_execute_range --exact --nocapture -- --start-block 209 --end-block 209 --rpc http://morph-panos-fullnode-test01.bitkeep.tools:8545 now reaches the expected historical proof window error instead of failing to deserialize the L1 message transaction.

Summary by CodeRabbit

  • Bug Fixes
    • Improved transaction data handling to tolerate missing signature components and transaction fields, with support for multiple field name formats to enhance compatibility with various transaction data sources.

Allow prover transaction traces to deserialize reth-style L1 message RPC responses that omit signature fields and use the standard transaction hash key.

Constraint: Preserve existing Morph trace field names while accepting reth RPC aliases
Confidence: high
Scope-risk: narrow
@panos-xyz panos-xyz requested a review from a team as a code owner June 2, 2026 06:39
@panos-xyz panos-xyz requested review from SecurityLife and removed request for a team June 2, 2026 06:39

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The TransactionTrace struct is updated to deserialize more leniently by adding default values to several fields: tx_hash now accepts both txHash and hash aliases, nonce defaults when missing, and signature components v, r, and s default when absent. A new test validates that a Reth L1 message transaction JSON without signature fields deserializes correctly.

Changes

TransactionTrace deserialization robustness

Layer / File(s) Summary
TransactionTrace serde field updates and validation
prover/crates/primitives/src/types/tx.rs
tx_hash is updated with #[serde(default, rename = "txHash", alias = "hash")] to accept both field names and default when missing; nonce is set to default on deserialization; signature fields v, r, and s each gain #[serde(default)] to tolerate absence. A test module validates that a Reth L1 message transaction without signatures deserializes and produces expected ty, queue_index, and tx_hash values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A transaction trace hops along,
With fields now lenient and strong,
Missing hash? An alias will do,
Signatures optional, defaults shine through,
Tests confirm what once was strange,
Flexibility drives robust change! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: enabling acceptance of reth L1 message transaction fields during deserialization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reth-l1msg-tx-deserialize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
prover/crates/primitives/src/types/tx.rs (1)

198-226: ⚡ Quick win

Consider expanding test coverage to validate all defaulted fields.

The test successfully validates that the reth L1 message transaction deserializes and that ty(), queue_index(), and tx_hash() work correctly. However, it doesn't verify that the defaulted fields (nonce, v, r, s) are actually set to their default values.

Consider adding assertions to confirm:

  • tx.nonce() returns 0
  • tx.sig_v() returns 0
  • tx.signature() returns a signature with zero scalars (or document expected behavior)
🧪 Suggested additional assertions
         assert_eq!(
             tx.tx_hash(),
             "0x4c03b51e272570d42b0135d48ac612ef59c670e5759df97971be35b93c6310f9"
                 .parse::<B256>()
                 .unwrap()
         );
+        // Verify defaulted fields
+        assert_eq!(tx.nonce(), 0, "nonce should default to 0");
+        assert_eq!(tx.sig_v(), 0, "v should default to 0");
+        // Signature with zero scalars - document expected behavior
+        let sig = tx.signature().expect("signature method should not error");
+        assert_eq!(sig.r(), alloy_primitives::U256::ZERO);
+        assert_eq!(sig.s(), alloy_primitives::U256::ZERO);
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prover/crates/primitives/src/types/tx.rs` around lines 198 - 226, Add
assertions to the test
deserializes_reth_l1_message_transaction_without_signature_fields to validate
defaulted signature-related fields: call TransactionTrace::nonce() and assert it
equals 0, call TransactionTrace::sig_v() (or the accessor used in the code) and
assert it equals 0, and call TransactionTrace::signature() (or the associated
accessors like sig_r()/sig_s() if present) and assert the signature components
are zero/default (e.g., zero-length or zero-value scalars) so the test verifies
nonce, v, r, s default values after deserialization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prover/crates/primitives/src/types/tx.rs`:
- Around line 19-20: The tx_hash field uses serde(default) which can silently
produce a zero B256 when "txHash" or "hash" are missing; add validation to
reject zero hashes by either (a) implementing a validate(&self) -> Result<(), _>
on TransactionTrace that checks self.tx_hash != B256::ZERO and calling it after
deserialization, or (b) replace the default with a custom deserializer (e.g.
deserialize_non_zero_hash) wired via #[serde(deserialize_with = "...", rename =
"txHash", alias = "hash")] that returns an error when the deserialized B256
equals B256::ZERO; reference tx_hash, TransactionTrace, B256::ZERO, validate,
and deserialize_non_zero_hash when making the change.
- Around line 85-92: The v/r/s fields now default to zero which allows
deserializing Reth L1 message txs but leads signature() (method signature()) to
build an invalid all-zero Signature; add a doc comment on the transaction struct
explaining that v, r, s default to zero for L1 message (type 0x7e) and that an
all-zero signature denotes “no signature” for that type; add a helper method
has_signature(&self) -> bool (or requires_signature(&self) -> bool) that checks
the tx type and whether v/r/s are non-zero as appropriate, update documentation
for signature() to state callers must call has_signature()/check tx type before
using signature() for verification, and update any call sites to guard
signature() with the new helper.

---

Nitpick comments:
In `@prover/crates/primitives/src/types/tx.rs`:
- Around line 198-226: Add assertions to the test
deserializes_reth_l1_message_transaction_without_signature_fields to validate
defaulted signature-related fields: call TransactionTrace::nonce() and assert it
equals 0, call TransactionTrace::sig_v() (or the accessor used in the code) and
assert it equals 0, and call TransactionTrace::signature() (or the associated
accessors like sig_r()/sig_s() if present) and assert the signature components
are zero/default (e.g., zero-length or zero-value scalars) so the test verifies
nonce, v, r, s default values after deserialization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a82fc1d-2c3e-47bc-8651-3d406bd4cc9f

📥 Commits

Reviewing files that changed from the base of the PR and between aeb86c6 and d270a43.

📒 Files selected for processing (1)
  • prover/crates/primitives/src/types/tx.rs

Comment on lines +19 to 20
#[serde(default, rename = "txHash", alias = "hash")]
pub(crate) tx_hash: B256,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Consider validating that tx_hash is non-zero after deserialization.

Adding default means that if neither "txHash" nor "hash" is present in the JSON, the field will silently become a zero hash (B256::default()). A zero hash is never a valid transaction hash and could mask missing data, leading to subtle bugs downstream.

Consider one of these approaches:

  • Add a post-deserialization validation step that rejects zero hashes
  • Document explicitly that zero hash is expected/acceptable for certain transaction types
  • Use a custom deserializer that returns an error when both field names are absent
🛡️ Example validation approach

Add a validation method and call it after deserialization:

impl TransactionTrace {
    fn validate(&self) -> Result<(), &'static str> {
        if self.tx_hash == B256::ZERO {
            return Err("tx_hash must be non-zero");
        }
        Ok(())
    }
}

Or use serde's deserialize_with for custom validation:

#[serde(default, rename = "txHash", alias = "hash", deserialize_with = "deserialize_non_zero_hash")]
pub(crate) tx_hash: B256,

fn deserialize_non_zero_hash<'de, D>(deserializer: D) -> Result<B256, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let hash = B256::deserialize(deserializer)?;
    if hash == B256::ZERO {
        return Err(serde::de::Error::custom("tx_hash cannot be zero"));
    }
    Ok(hash)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prover/crates/primitives/src/types/tx.rs` around lines 19 - 20, The tx_hash
field uses serde(default) which can silently produce a zero B256 when "txHash"
or "hash" are missing; add validation to reject zero hashes by either (a)
implementing a validate(&self) -> Result<(), _> on TransactionTrace that checks
self.tx_hash != B256::ZERO and calling it after deserialization, or (b) replace
the default with a custom deserializer (e.g. deserialize_non_zero_hash) wired
via #[serde(deserialize_with = "...", rename = "txHash", alias = "hash")] that
returns an error when the deserialized B256 equals B256::ZERO; reference
tx_hash, TransactionTrace, B256::ZERO, validate, and deserialize_non_zero_hash
when making the change.

Comment on lines +85 to 92
#[serde(default)]
pub(crate) v: U64,
/// signature r
#[serde(default)]
pub(crate) r: U256,
/// signature s
#[serde(default)]
pub(crate) s: U256,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Document that signature fields default for L1 message transactions.

The signature fields (v, r, s) now default to zero when missing, which enables deserialization of reth L1 message transactions (type 0x7e) that lack traditional ECDSA signatures. However, the signature() method at lines 160-166 will construct a Signature with zero scalars and default parity, which is invalid for cryptographic verification.

Consider:

  • Adding a doc comment to the struct explaining that zero signature fields are valid for L1 message transactions
  • Adding a helper method like has_signature(&self) -> bool that checks if the transaction type requires/has a signature
  • Documenting that callers must check transaction type before calling signature() for validation
📝 Suggested documentation
 /// Transaction Trace
+/// 
+/// Note: Signature fields (v, r, s) default to zero for transaction types
+/// that don't require signatures (e.g., L1 message transactions, type 0x7e).
+/// Callers should check the transaction type before attempting signature
+/// verification via the `signature()` method.
 #[serde_as]
 #[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone, Hash, PartialEq, Eq)]
 pub struct TransactionTrace {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prover/crates/primitives/src/types/tx.rs` around lines 85 - 92, The v/r/s
fields now default to zero which allows deserializing Reth L1 message txs but
leads signature() (method signature()) to build an invalid all-zero Signature;
add a doc comment on the transaction struct explaining that v, r, s default to
zero for L1 message (type 0x7e) and that an all-zero signature denotes “no
signature” for that type; add a helper method has_signature(&self) -> bool (or
requires_signature(&self) -> bool) that checks the tx type and whether v/r/s are
non-zero as appropriate, update documentation for signature() to state callers
must call has_signature()/check tx type before using signature() for
verification, and update any call sites to guard signature() with the new
helper.

@panos-xyz

Copy link
Copy Markdown
Contributor Author

Closing this PR because the compatibility fix is being handled in morph-reth instead of the prover.

@panos-xyz panos-xyz closed this Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant