perf(l1): decode engine_newPayload transactions in parallel#6965
perf(l1): decode engine_newPayload transactions in parallel#6965ilitteri wants to merge 1 commit into
Conversation
into_block decoded the payload's transactions sequentially on the engine_newPayload critical path (before execution, outside the block-processing pipeline's measured window). The transactions are independent RLP blobs, so decode them with rayon's par_iter — order-preserving and short-circuiting on the first decode error, so the result is identical to the sequential decode (test asserts order + content match). eip-8025 (stateless/zkVM guest) builds keep the deterministic sequential path, mirroring get_transactions_with_sender.
Lines of code reportTotal lines added: Detailed view |
🤖 Kimi Code ReviewOverall Assessment: The change is correct and well-structured. Parallel RLP decoding of transactions is safe because they're independent blobs, and Rayon preserves order in Specific Feedback: 1. Dependency Management (Cargo.toml) rayon = { version = "1.x", optional = true }
[features]
default = ["rayon"]
eip-8025 = []Verify this is handled in the Cargo.toml changes not shown in the diff. 2. Test Clarity ( // Option: Explicitly test parallel path regardless of feature flags
#[cfg(not(feature = "eip-8025"))]
{
// parallel decode test
}3. Parallelism Threshold (Optimization) if self.transactions.len() > 16 {
// use par_iter()
} else {
// use iter()
}This is optional and should be guided by profiling on the 4. Comment Accuracy ( // eip-8025: internal feature flag for stateless/ZK guest builds (deterministic execution)Security Verification:
Nitpick: Approval: LGTM pending verification of the Cargo.toml dependency gating mentioned in Point 1. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code Review
Beyond that, the happy-path change looks mechanically sound: Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
There was a problem hiding this comment.
Pull request overview
This PR parallelizes transaction decoding when converting an ExecutionPayload into a Block, reducing time spent in engine_newPayload’s pre-execution critical path while keeping a sequential path for eip-8025 builds.
Changes:
- Decode
ExecutionPayloadtransactions in parallel viarayon::par_iter()(sequential undereip-8025). - Update the payload deserialization test to assert that parallel decoding preserves transaction order and content vs a sequential reference decode.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let block = payload | ||
| .clone() | ||
| .into_block(Some(H256::zero()), None, None) | ||
| .unwrap(); |
Motivation
engine_newPayloadruns on the block-import critical path: the consensus clientwaits for the response, and the handler executes before returning
VALID. Profilingthat path showed a small but real amount of work sits before execution and is not
counted by the block-processing pipeline's timing (
gigagas/total_ms): turning thepayload into a
Block. Part of that is decoding the transactions.ExecutionPayload::into_blockdecoded the payload's transactions sequentially, eventhough each transaction is an independent RLP blob.
Description
Decode the payload's transactions in parallel with
rayon'spar_iter. The result isidentical to the sequential decode:
par_iter().map().collect::<Result<Vec<_>, _>>()is order-preserving andshort-circuits on the first decode error, so the decoded
Vec<Transaction>and anyerror are exactly what the sequential path produced.
eip-8025(stateless / zkVM guest) build keeps the deterministic sequential path,mirroring the existing
get_transactions_with_sender.Scope is intentionally tiny: only the decode loop in
into_blockchanges.How to test
cargo test -p ethrex-rpc --lib types::payload— thedeserialize_payload_into_blocktest decodes a mixed legacy + EIP-1559 payload and now also asserts that the parallel
decode matches a sequential decode (same order, same content).
Notes
This is a pure, deterministic parallelization (no consensus behavior change). A follow-up
change computes the transactions root directly from the payload's raw canonical bytes
(skipping a decode→re-encode round-trip); it is kept separate as it warrants its own review.