Skip to content

fix(primitives): override rlp_decode/encode_signed for TxMorph#36

Merged
chengwenxi merged 4 commits intomainfrom
morphtx-rlp
Mar 4, 2026
Merged

fix(primitives): override rlp_decode/encode_signed for TxMorph#36
chengwenxi merged 4 commits intomainfrom
morphtx-rlp

Conversation

@chengwenxi
Copy link
Contributor

@chengwenxi chengwenxi commented Mar 3, 2026

Summary by CodeRabbit

  • New Features

    • Version-aware transaction encoding/decoding for MorphTx: supports V0 and V1 formats, with V1 using an explicit version prefix; signature-aware encoding and length calculations now include the V1 prefix.
  • Bug Fixes

    • Stricter V0 detection to avoid misclassification and improve decoding correctness across versions.
  • Tests

    • Comprehensive roundtrip and edge-case tests for V0/V1 encoding, prefix presence, length checks, validation, and error paths.

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a9acae5-99a8-49a3-94c7-875cb2338807

📥 Commits

Reviewing files that changed from the base of the PR and between 13f39e0 and 1b15aaf.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • Cargo.toml

📝 Walkthrough

Walkthrough

Adds version-prefix handling for TxMorph RLP: V1 is encoded/decoded with a leading 0x01 byte while V0 remains raw RLP. Introduces signed encode/length APIs and a decode-with-signature path; updates header/length logic and expands tests for V0/V1 behaviors and edge cases.

Changes

Cohort / File(s) Summary
MorphTx V1-aware RLP encode/decode & tests
crates/primitives/src/transaction/morph_transaction.rs
Implements V1 version-prefix semantics (leading 0x01) and preserves V0 detection via RLP list prefix. Adds rlp_encode_signed, rlp_encoded_length_with_signature, and rlp_decode_with_signature. Adjusts header/length logic and decoding branches; adds/extends tests for roundtrips, EIP-2718 flows, length/prefix validation, and version edge cases.
Workspace version bump
Cargo.toml
Workspace package version changed from 0.7.50.1.0 (metadata change).

Sequence Diagram(s)

(Skipped)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • panos-xyz
  • anylots

Poem

🐰 I nibble bytes of version one and zero,
I tuck a prefix soft and gentle, hero,
I hop through RLP fields, signatures bright,
I chase each roundtrip into the night,
A tiny hop for morphs, a joyous byte!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly references the main change: overriding rlp_decode/encode_signed methods for TxMorph to handle version-byte prefixes and fix version detection logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch morphtx-rlp

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

@chengwenxi chengwenxi changed the title fix(primitives): override rlp_decode/encode_signed for TxMorph V1 ver… fix(primitives): override rlp_decode/encode_signed for TxMorph Mar 3, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/primitives/src/transaction/morph_transaction.rs`:
- Around line 717-733: The version-detection treats 0x00 as MORPH_TX_VERSION_0
but doesn't advance the buffer, so Header::decode(buf) sees the 0x00 and fails;
update the detection logic around first_byte, MORPH_TX_VERSION_1 and
MORPH_TX_VERSION_0 to either reject 0x00 or consume it — implement the suggested
fix by, when first_byte == 0, advancing buf (same as the MORPH_TX_VERSION_1
branch) before setting version to MORPH_TX_VERSION_0 so Header::decode receives
the correct RLP list start; ensure the code references first_byte, buf,
MORPH_TX_VERSION_0, MORPH_TX_VERSION_1 and Header::decode.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b44669d and f608cd9.

📒 Files selected for processing (1)
  • crates/primitives/src/transaction/morph_transaction.rs

@chengwenxi
Copy link
Contributor Author

chengwenxi commented Mar 3, 2026

Code review

Found 1 issue:

  1. first_byte == 0 branch in rlp_decode_with_signature is dead code — the buffer is never advanced for 0x00, so Header::decode will see 0x00 and return a non-list header (list = false), causing the !header.list check to immediately fail with
    UnexpectedString. Valid V0 transactions are RLP lists starting with 0xC0–0xFF; 0x00 is the RLP encoding of an integer, not a list. The condition should be first_byte >= 0xC0 only.

// Detect version:
// - V1: first byte is version byte (0x01), skip it
// - V0: first byte is RLP list prefix (>= 0xC0) or 0x00
let version = if first_byte == MORPH_TX_VERSION_1 {
*buf = &buf[1..]; // skip version byte
MORPH_TX_VERSION_1
} else if first_byte == 0 || first_byte >= 0xC0 {
MORPH_TX_VERSION_0
} else {
return Err(alloy_rlp::Error::Custom("unsupported morph tx version"));
};
// Now decode: RLP([fields..., V, R, S])
let header = Header::decode(buf)?;
if !header.list {
return Err(alloy_rlp::Error::UnexpectedString);
}

0x00 is not a valid RLP list header (valid range is 0xC0-0xFF), so
the first_byte == 0 condition in decode_fields and rlp_decode_with_signature
was dead code — Header::decode would return list=false and immediately
fail with UnexpectedString. Remove it so invalid inputs fall through to
the unsupported version error instead.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/primitives/src/transaction/morph_transaction.rs (1)

1905-2072: Add a targeted regression test for invalid 0x00 prefix inputs.

Given the version-detection fix, it’s worth locking this down with a negative test that asserts 0x00 is rejected (both signed and unsigned decode paths).

Test sketch
+    #[test]
+    fn test_morph_decode_rejects_zero_prefix() {
+        let mut buf = [0x00u8].as_slice();
+        let err = TxMorph::decode(&mut buf).unwrap_err();
+        assert!(matches!(err, alloy_rlp::Error::Custom(_)));
+    }
+
+    #[test]
+    fn test_morph_signed_decode_rejects_zero_prefix() {
+        use alloy_consensus::transaction::RlpEcdsaDecodableTx;
+        let mut buf = [0x00u8].as_slice();
+        let err = TxMorph::rlp_decode_with_signature(&mut buf).unwrap_err();
+        assert!(matches!(err, alloy_rlp::Error::Custom(_)));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/primitives/src/transaction/morph_transaction.rs` around lines 1905 -
2072, Add a negative regression test that verifies inputs starting with 0x00 are
rejected for both signed and unsigned decode paths: create a buffer beginning
with 0x00 followed by arbitrary bytes and assert
TxMorph::rlp_decode_with_signature returns an Err (or None) for signed decode
and TxMorph::rlp_decode (or the unsigned decode helper used) also returns an Err
for unsigned decode; additionally test the EIP-2718 path by constructing an
eip2718 buffer with MORPH_TX_TYPE_ID then 0x00 and assert
Signed::<TxMorph>::decode_2718 returns an error. Use the existing test helpers
and the same signature/tx structures from
test_morph_signed_v1_decode_2718_roundtrip and
test_morph_signed_v0_decode_2718_roundtrip to keep consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/primitives/src/transaction/morph_transaction.rs`:
- Around line 351-353: Decodable::decode still accepts a leading 0x00 byte
causing a different failure path than the version-aware decoders in
morph_transaction.rs; update Decodable::decode to explicitly detect and reject a
leading 0x00 (same check/early-error path used by the version-aware decoder
logic in morph_transaction) and return the same error variant used elsewhere so
all decode entry points behave consistently when encountering 0x00. Ensure you
modify the first-byte inspection in Decodable::decode to mirror the first_byte
>= 0xC0 / version handling and add the 0x00 rejection branch returning the
common decode error.

---

Nitpick comments:
In `@crates/primitives/src/transaction/morph_transaction.rs`:
- Around line 1905-2072: Add a negative regression test that verifies inputs
starting with 0x00 are rejected for both signed and unsigned decode paths:
create a buffer beginning with 0x00 followed by arbitrary bytes and assert
TxMorph::rlp_decode_with_signature returns an Err (or None) for signed decode
and TxMorph::rlp_decode (or the unsigned decode helper used) also returns an Err
for unsigned decode; additionally test the EIP-2718 path by constructing an
eip2718 buffer with MORPH_TX_TYPE_ID then 0x00 and assert
Signed::<TxMorph>::decode_2718 returns an error. Use the existing test helpers
and the same signature/tx structures from
test_morph_signed_v1_decode_2718_roundtrip and
test_morph_signed_v0_decode_2718_roundtrip to keep consistency.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f608cd9 and d7bec78.

📒 Files selected for processing (1)
  • crates/primitives/src/transaction/morph_transaction.rs

…ode_fields

Align the first-byte check in Decodable::decode with decode_fields so that
a leading 0x00 byte is rejected with "unsupported morph tx version" instead
of being silently passed through as a V0 transaction.
@panos-xyz
Copy link
Contributor

Code review

Found 1 issue:

  1. Decodable::decode still accepts first_byte == 0 as V0 while decode_fields and rlp_decode_with_signature now reject it — same invalid input takes different error paths across the three decode APIs

The PR correctly removes the dead first_byte == 0 branch from decode_fields (line 353) and the new rlp_decode_with_signature (line 723), but Decodable::decode was not updated. Its condition first_byte != 0 && first_byte < 0xC0 silently lets 0x00 fall through, then fails later with UnexpectedString (from Header::decode) rather than the consistent Custom("unsupported morph tx version"). The comment on line 823 is also now stale.

if first_byte == MORPH_TX_VERSION_1 {
// V1: skip version byte, then decode RLP
*buf = &buf[1..];
} else if first_byte != 0 && first_byte < 0xC0 {
// Invalid: not a version we support and not an RLP list
return Err(alloy_rlp::Error::Custom("unsupported morph tx version"));
}
// V0: first_byte is 0 or >= 0xC0 (RLP list prefix)
let header = Header::decode(buf)?;

Note: 0x00 is not a valid V0 wire byte (go-ethereum never produces it), so this does not affect valid transactions. The fix is to align the condition to else if first_byte < 0xC0 and update the comment on line 823 to remove the "is 0 or" part.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
crates/primitives/src/transaction/morph_transaction.rs (2)

351-353: Comment wording still implies unsupported versions are accepted.

Line 352 says “V1+ format” with (0x01, 0x02, ...), but the implementation only accepts MORPH_TX_VERSION_1 and rejects others. Please tighten the comment to avoid signaling v2+ support.

Suggested doc-only patch
-        // - V1+ format: first byte is version (0x01, 0x02, ...) followed by RLP
+        // - V1 format: first byte is version (0x01) followed by RLP
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/primitives/src/transaction/morph_transaction.rs` around lines 351 -
353, The comment above the version check currently implies "V1+ (0x01, 0x02,
...)" is accepted but the code only permits MORPH_TX_VERSION_1; update the
comment near the if first_byte >= 0xC0 check in morph_transaction.rs to state
that only V0 (legacy AltFeeTx) and V1 (MORPH_TX_VERSION_1) are supported and
that higher version numbers are not accepted, so readers won't expect v2+
compatibility.

1905-2072: Add a direct regression test for leading 0x00 across decode entry points.

These roundtrip tests are good, but the exact bug fixed in this PR (0x00 classification) is not asserted directly. A focused test would lock this behavior.

Suggested regression test
+    #[test]
+    fn test_morph_decode_rejects_leading_zero_consistently() {
+        use alloy_consensus::transaction::RlpEcdsaDecodableTx;
+
+        let raw = [0x00u8];
+
+        let err_decode = TxMorph::decode(&mut raw.as_slice()).unwrap_err();
+        assert!(matches!(
+            err_decode,
+            alloy_rlp::Error::Custom("unsupported morph tx version")
+        ));
+
+        let err_fields = TxMorph::decode_fields(&mut raw.as_slice()).unwrap_err();
+        assert!(matches!(
+            err_fields,
+            alloy_rlp::Error::Custom("unsupported morph tx version")
+        ));
+
+        let err_signed =
+            TxMorph::rlp_decode_with_signature(&mut raw.as_slice()).unwrap_err();
+        assert!(matches!(
+            err_signed,
+            alloy_rlp::Error::Custom("unsupported morph tx version")
+        ));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/primitives/src/transaction/morph_transaction.rs` around lines 1905 -
2072, Add a focused regression test (e.g., test_morph_leading_zero_regression)
that constructs a V0 TxMorph and signature, then ensures decoding tolerates a
leading 0x00 byte across both entry points: call tx.rlp_encode_signed + prepend
a 0x00 to the signed buffer and assert TxMorph::rlp_decode_with_signature
decodes successfully with version==MORPH_TX_VERSION_0; also build a
Signed::new_unhashed(tx, sig) and produce eip2718_buf via Signed::encode_2718,
then insert a leading 0x00 before the RLP payload and assert
Signed::<TxMorph>::decode_2718 decodes successfully and tx().is_v0(); reference
the symbols TxMorph::rlp_encode_signed, TxMorph::rlp_decode_with_signature,
Signed::new_unhashed, Signed::encode_2718, and Signed::<TxMorph>::decode_2718
when adding the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/primitives/src/transaction/morph_transaction.rs`:
- Around line 351-353: The comment above the version check currently implies
"V1+ (0x01, 0x02, ...)" is accepted but the code only permits
MORPH_TX_VERSION_1; update the comment near the if first_byte >= 0xC0 check in
morph_transaction.rs to state that only V0 (legacy AltFeeTx) and V1
(MORPH_TX_VERSION_1) are supported and that higher version numbers are not
accepted, so readers won't expect v2+ compatibility.
- Around line 1905-2072: Add a focused regression test (e.g.,
test_morph_leading_zero_regression) that constructs a V0 TxMorph and signature,
then ensures decoding tolerates a leading 0x00 byte across both entry points:
call tx.rlp_encode_signed + prepend a 0x00 to the signed buffer and assert
TxMorph::rlp_decode_with_signature decodes successfully with
version==MORPH_TX_VERSION_0; also build a Signed::new_unhashed(tx, sig) and
produce eip2718_buf via Signed::encode_2718, then insert a leading 0x00 before
the RLP payload and assert Signed::<TxMorph>::decode_2718 decodes successfully
and tx().is_v0(); reference the symbols TxMorph::rlp_encode_signed,
TxMorph::rlp_decode_with_signature, Signed::new_unhashed, Signed::encode_2718,
and Signed::<TxMorph>::decode_2718 when adding the test.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a80f24a8-d13b-4fa8-a54b-4284d96a804b

📥 Commits

Reviewing files that changed from the base of the PR and between d7bec78 and 13f39e0.

📒 Files selected for processing (1)
  • crates/primitives/src/transaction/morph_transaction.rs

@chengwenxi
Copy link
Contributor Author

Code review

Found 1 issue:

  1. Decodable::decode still accepts first_byte == 0 as V0 while decode_fields and rlp_decode_with_signature now reject it — same invalid input takes different error paths across the three decode APIs

The PR correctly removes the dead first_byte == 0 branch from decode_fields (line 353) and the new rlp_decode_with_signature (line 723), but Decodable::decode was not updated. Its condition first_byte != 0 && first_byte < 0xC0 silently lets 0x00 fall through, then fails later with UnexpectedString (from Header::decode) rather than the consistent Custom("unsupported morph tx version"). The comment on line 823 is also now stale.

if first_byte == MORPH_TX_VERSION_1 {
// V1: skip version byte, then decode RLP
*buf = &buf[1..];
} else if first_byte != 0 && first_byte < 0xC0 {
// Invalid: not a version we support and not an RLP list
return Err(alloy_rlp::Error::Custom("unsupported morph tx version"));
}
// V0: first_byte is 0 or >= 0xC0 (RLP list prefix)
let header = Header::decode(buf)?;

Note: 0x00 is not a valid V0 wire byte (go-ethereum never produces it), so this does not affect valid transactions. The fix is to align the condition to else if first_byte < 0xC0 and update the comment on line 823 to remove the "is 0 or" part.

🤖 Generated with Claude Code

  • If this code review was useful, please react with 👍. Otherwise, react with 👎.

fixed 13f39e0

@chengwenxi chengwenxi requested review from anylots and panos-xyz March 4, 2026 02:43
@chengwenxi chengwenxi merged commit e74ac39 into main Mar 4, 2026
9 checks passed
@chengwenxi chengwenxi deleted the morphtx-rlp branch March 4, 2026 03:29
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.

2 participants