feat: decode tx_input directly from RLP - #2653
Conversation
PR Reviewer Guide 🔍(Review updated until commit 6b243b5)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 6b243b5
Previous suggestionsSuggestions up to commit 7db6dee
|
There was a problem hiding this comment.
Summary
Nice refactor overall: decoding now builds TransactionInput directly from TxEnvelope, and the new signature_hash() path is covered by a focused consistency test against to_tx_envelope().signature_hash() across tx types 0–4.
I reviewed for correctness around signer recovery and hash derivation, and the implementation stays behaviorally aligned with the previous envelope-based hash flow for the fields currently persisted in TransactionInput.
|
Persistent review updated to latest commit 6b243b5 |
There was a problem hiding this comment.
Wrong idea. The idea is to decode transaction input directly from the received rlp. This means that we never even create the alloy types in the first place, the rlp gets decoded directly to TransactionInput, this means we don't ever even decode to TxEnvelope.
Before doing this however, a better issue to solve first is #2548 . Since then when receiving an eth_sendRawTransaction we wouldn't have to do any conversions with alloy at all.
There was a problem hiding this comment.
Thanks for the refactor — moving decode toward TransactionInput directly is a good direction. I found one blocking correctness issue in signer recovery:
Blocking: signature_hash() now reconstructs typed txs with fields that are not persisted in TransactionInput (access list, 1559 priority fee, 4844 blob fields, 7702 authorization list) using defaults. For any real tx where those fields are non-default, recovered prehash will differ from the original signed prehash, so signer recovery can fail or recover the wrong address.
Concretely in this diff:
- type 1/2:
access_list: AccessList::default() - type 2/3/4:
max_priority_fee_per_gas = gas_price - type 3:
blob_versioned_hashes = [],max_fee_per_blob_gas = 0 - type 4:
authorization_list = []
Because build_transaction_input_from_envelope() now calls recover_signer_address() (which uses this new hash path), this can break immediately on decode of valid raw txs carrying those fields.
Suggested fix options:
- Keep using envelope-native signing hash for recovery when decoding from raw tx (
envelope.signature_hash()), and only use field-derived hash where you can guarantee complete persisted fields; or - Extend
TransactionInput/ExecutionInfoto store all signing-relevant fields per tx type, then compute hash from fully faithful data.
Also, current test only checks signature_hash() vs to_tx_envelope() built from the same reduced fields, so it can’t catch this class of mismatch. Please add a test with a real typed tx (e.g. 1559 with non-empty access list / different priority fee, and/or 4844 with blob hashes) and assert recovered signer matches the signer from the original envelope/raw tx.
There was a problem hiding this comment.
Summary
Thanks for the refactor toward direct raw-transaction decoding; the structure is clearer and the decode-path tests are useful. I found one blocking correctness issue that can break signer recovery for valid typed transactions.
Blocking: signature_hash() is reconstructed from a reduced field set and injects defaults/placeholders for typed-tx fields that are actually signed. For tx types 1/2/3/4 this can change the signing preimage (e.g., non-empty access list, differing 1559 tip vs max fee, 4844 blob fields, 7702 authorization list), so recover_signer_address() may recover the wrong address or fail.
Current tests don’t catch this because they only exercise default/degenerate values (empty lists and equal fee fields), where reconstructed and original preimages happen to match.
There was a problem hiding this comment.
Summary
Thanks for the refactor — decoding from raw bytes and removing the rlp crate dependency is a good direction. However, there is still one blocking correctness issue in signer recovery for typed transactions.
Blocking
TransactionInput::signature_hash() reconstructs typed preimages with placeholder/default values for signed fields that are not preserved in TransactionInput (e.g. access list, 1559 priority fee, 4844 blob fields, 7702 authorization list). Because recover_signer_address() now depends on this reconstructed hash, valid transactions with non-default values can recover the wrong signer or fail recovery.
Current tests only cover degenerate/default cases (empty lists and equal tip/max-fee), so they do not exercise this failure mode.
There was a problem hiding this comment.
Summary
Thanks for the refactor toward direct raw-byte decoding and for adding decode/signature-hash tests. I still found one blocking correctness issue in signer recovery for typed transactions.
Blocking
TransactionInput::signature_hash() reconstructs typed signing payloads using placeholder/default values for signed fields that are not persisted in TransactionInput (e.g. access list, 1559 priority fee, 4844 blob fields, 7702 authorization list). Because recover_signer_address() now uses this reconstructed hash, valid transactions with non-default values in those fields can recover the wrong signer or fail recovery.
Current new tests only validate degenerate/default cases (empty lists and equal tip/max-fee), so this failure mode is not covered.
There was a problem hiding this comment.
Summary
Nice refactor overall — moving to direct alloy-rlp decoding and adding coverage for all tx families is a good step. I found one blocking correctness issue in signature recovery for typed transactions.
Blocking
- Typed transactions are storing
vdirectly from RLP and later converting it toAlloySignaturewith a boolean parity check (value.v == U64::ONE). For EIP-1559/2930/4844/7702 this can be encoded as27/28by some producers, and your current conversion will treat both asfalse, producing wrong signer recovery/hashes in those cases.
If you normalize typed v to parity (0/1) at decode time (or normalize inside From<Signature> for AlloySignature), this should be safe.
There was a problem hiding this comment.
Summary
Good refactor direction overall (direct alloy RLP decode + dedicated error type + broad tx-family tests), but I found one blocking correctness issue that can cause signer recovery failures and wrong signatures for valid typed transactions.
Blocking: typed transaction v/y-parity is not normalized before conversion to AlloySignature.
There was a problem hiding this comment.
Summary
Good direction overall (direct alloy-rlp decoding, typed tx coverage, and signature-hash parity tests), but there is one blocking correctness issue still present in signer recovery for typed transactions.
Blocking: typed v (y-parity) is stored without normalization and then converted to AlloySignature with value.v == U64::ONE, which misinterprets valid 27/28 encodings as false parity.
PR Type
Enhancement, Tests
Description
Add
signature_hash()method toTransactionInputRefactor RLP decoding flow
build_transaction_input_from_envelope()try_from_alloy_transaction()logicRemove redundant transaction conversion code
Add unit tests for
signature_hash()correctnessDiagram Walkthrough
File Walkthrough
transaction_input.rs
Add signature_hash and refactor decodingsrc/eth/types/transaction/transaction_input.rs
signature_hash()computing B256 from stored fieldsbuild_transaction_input_from_envelope()try_from_alloy_transaction()conversion path