feat(flashblocks rpc): Full revamp of X Layer custom flashblocks RPC layer#176
feat(flashblocks rpc): Full revamp of X Layer custom flashblocks RPC layer#176
Conversation
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…to state cache 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a tertiary tx hash index (HashMap<TxHash, Arc<CachedTxInfo>>) to ConfirmCache, enabling O(1) lookups for transaction_by_hash, transaction_by_hash_with_meta, and receipt_by_hash from confirmed flashblocks that are ahead of the canonical chain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add collect_cached_block_range_while helper that threads a predicate through both the provider prefix and cached suffix, enabling sealed_headers_while to serve confirmed flashblock headers instead of bypassing the cache entirely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… rpc crate Consolidates flashblocks cache provider trait implementations (block, header, factory, receipt, transaction) into a single eth.rs override module in the rpc crate. Wires FlashblockStateCache into the node's RPC registration. Simplifies the cache layer to only manage state. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…an up cache interfaces 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consolidate the previously split flashblocks service setup and state cache creation into one cohesive `if let Some` block. This fixes a subtle bug where using `.map()` with `?` operators would produce `Option<Result<T, E>>` instead of the intended `Option<T>`, causing `?` to not propagate errors to the outer closure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… access Restructure ConfirmCache to store ExecutedBlock directly instead of just BlockAndReceipts, enabling state provider overlay for flashblock state queries. - Add ConfirmedBlock struct wrapping ExecutedBlock + receipts - Update insert/remove signatures to accept ExecutedBlock - Make PendingSequence.tx_index pub for state access - Fix execution cache test assertions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cks state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
f8a26bf to
5d96010
Compare
5d96010 to
6666f70
Compare
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ate provider helpers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ution mod 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace associated-type-based `Eth` generic with explicit `N` (node) and `Rpc` (converter) generics on `XLayerEthApiExt` and helper functions. Use fully-qualified `EthApiServer::` calls instead of inherent method dispatch for clearer trait resolution. Simplify helper function bounds by parameterizing directly on `Rpc: RpcConvert` instead of `Eth: EthApiTypes`. Update storage slot type to `JsonStorageKey` and fix transaction body access to use direct field/method calls. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename `eth.rs` to `flashblocks.rs` and `xlayer_ext.rs` to `default.rs` to better reflect their responsibilities. Rename `XLayerEthApiExt` to `FlashblocksEthApiExt`, `XlayerRpcExt` to `DefaultRpcExt`, and `XlayerRpcExtApi` to `DefaultRpcExtApi`. Extract `FlashblocksRpcArgs` as a standalone struct with derive macros and update `main.rs` to use the new type names and module paths. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove unused `reth-rpc` and `async-trait` dependencies from `crates/rpc/Cargo.toml`. Remove stale comment on converter field, fix redundant parentheses in payload builder, and use associated `N::Primitives` type instead of concrete `OpPrimitives` in `get_flashblock_state_provider_by_id` return type. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This reverts commit 94e1d8a.
This reverts commit f2d3752.
…rst" This reverts commit 079ecb7.
…nto execution mod" This reverts commit 707f540.
…flashblocks sequence validation (#205) * refactor(flashblocks): merge processor into validator with parallel SR Consolidate `processor.rs` into `validator.rs`, removing the separate module. Add parallel and overlay-based state root computation via `reth-trie-parallel`, `reth-trie-db`, and `reth-engine-tree`. Introduce `OverlayProviderFactory` trait alias and `compute_state_root` dispatcher that tries parallel SR first and falls back to serial. Replace direct `BlockBuilder` usage with `PayloadProcessor`-based execution using a no-op `flashblock_tx_iterator` for pre-recovered transactions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(flashblocks): sequence validator with payload processor 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(flashblocks): add flashblock assembler 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…, better refactors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@claude please review this PR and comment inline |
…lays 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sieniven
left a comment
There was a problem hiding this comment.
Note that the wip tasks left are:
- flashblocks handling pipeline integrating all components (flashblocks state cache, sequence validator, and the flashblocks eth api handle)
- unit test coverages on flashblocks state cache and flashblocks payload validator
They will be added in soon asap.
|
@claude please review this PR and comment inline |
|
Claude finished @sieniven's task in 2m 34s —— View job PR Review: feat(flashblocks rpc): Full revamp of X Layer custom flashblocks RPC layerTasks:
Overall AssessmentThis is a significant architectural improvement over the previous implementation. The separation into cache, execution, and RPC layers is clean and well-reasoned. The design aligns well with the motivation stated in the PR. The codebase is well-documented with clear comments explaining invariants. However, there are several bugs and structural concerns to address. Note: As stated in the review, Critical Issues1. Known bug: Stale hash index on duplicate block height in The test The fix is to look up and remove the old entry from // In insert(), before the HashMap inserts:
if let Some((old_hash, _)) = self.blocks.get(&height) {
self.hash_to_number.remove(old_hash);
}2. Potential panic in receipt: executed_block.execution_output.result.receipts[idx].clone(),This indexes directly without bounds checking. If the execution output has fewer receipts than transactions (e.g., due to a system transaction that doesn't produce receipts, or an error), this will panic. Use 3. fn process_flashblock(&mut self, flashblock: OpFlashblockPayload) {
self.notify_received_flashblock(&flashblock);
// TODO: Insert into the raw flashblocks cache
}The primary pipeline connecting received flashblocks to the Significant Issues4. const DEFAULT_TX_CACHE_SIZE: usize = DEFAULT_CONFIRM_BLOCK_CACHE_SIZE * 10_000;
// = 1_000 * 10_000 = 10_000,000 entriesEach 5. let (receipts_root, logs_bloom) = {
receipt_root_rx.blocking_recv()...
};
let transactions_root = result_rx.blocking_recv()...
6. Err(e) => {
warn!(..., "Failed to get flashblocks state provider: {e}. Flushing cache");
self.inner.write().flush();
None
}A read operation (state provider lookup) triggers a write-side cache flush on error. This is aggressive and has a lock upgrade pattern (drops read lock, acquires write lock). Between the drop and the write, other threads could observe intermediate state. More importantly, a transient error on one read will flush data that other concurrent readers may have gotten correct results from. Consider returning 7. Magic pending: PendingBlock::with_executed_block(
Instant::now() + Duration::from_secs(1),
...
),This 1-second offset looks like a workaround to keep the pending block "alive" for a short time. It's not documented. This should have a comment explaining why +1 second is used and what happens when the Minor Issues8. async fn block_number(&self) -> RpcResult<U256> {
let fb_height = self.flashblocks_state.get_confirm_height();
let canon_height: U256 = EthApiServer::block_number(&self.eth_api)?;
Ok(std::cmp::max(fb_height, canon_height))
}Returning the flashblocks confirm height as the "block number" could confuse clients that call 9. let canon_height = self.flashblocks_state.get_canon_height();
if incoming_block_number > canon_height + 1 {
return Err(...);
}
Ok(None)When 10.
11. The outer 12. } else if chain_spec.is_ecotone_active_at_timestamp(timestamp) {
(Some(0), Some(0))The upstream sets 13.
Positive Observations
|
Summary
This PR revamps the entire flashblocks rpc layer design, similar to the previous Realtime-feature (RTA) cache capabilities on the since discontinued xlayer-erigon (refer to: https://github.com/okx/xlayer-erigon/tree/dev/zk/realtime).
Essentially, this revamp implements the full solution 2 mentioned in the upstream reth's issue discussion thread: paradigmxyz/reth#20491, which is a more elegant design to the complex op-reth's flashblock rpc layer that is tightly coupled to the underlying anchoring canonical chainstate.
Motivations
The best design for allowing minimal latencies is to allow a full state accumulation from incoming flashblocks that are ahead of the canonical chainstate, while allowing for a hybrid sync approach that:
CHANGESET_CACHE_RETENTION_BLOCKS), once canonical chainstate catches uplatesttags) on flashblocks rpc tries to read from both the flashblocks state cache and underlying canon providerNew flashblocks rpc design
1. Flashblock cache layer
FlashblockStateCachelayer which includes both the pending state and confirm state caches2. Revamp execution environment for flashblocks sequence validation
The flashblocks RPC layer revamp contains significant optimizations and alignment to the upstream reth's payload validator logic. The old logic was vastly over-complicated, and the revamped version now encapsulates the flashblocks sequence validation inside the new
FlashblockSequenceValidator. It aligns the sequence validation logic to the highly optimized upstream reth's engine payload validator.Ref: https://github.com/paradigmxyz/reth/blob/main/crates/engine/tree/src/tree/payload_validator.rs
Below are some of the key optimizations:
3. Flashblocks eth rpc extension
eth_sendRawTransactionSynceth_getRawTransactionByBlockHashAndIndexeth_getRawTransactionByBlockNumberAndIndex4. Remove sequence manager, revamp to RawCache
5. Add handlers for flashblocks state update logic
handle_flashblockshandler - handle receiving a raw flashblock, and updating the raw cachehandle_flashblocks_statehandler - handle spawning task execution for flashblocks sequence validation and updating the flashblocks state cachehandle_canonical_block- logic to handle canonical chainstate updates to flush flashblocks state cache layer, and also handle extreme cases like chain reorgs, etcSupported flashblocks eth APIs
Block apis
[✅]
eth_blockNumber[✅]
eth_getBlockByNumber[✅]
eth_getBlockByHash[✅]
eth_getBlockReceipts[✅]
eth_getBlockTransactionCountByNumber[✅]
eth_getBlockTransactionCountByHashTransaction apis
[✅]
eth_getTransactionByHash[✅]
eth_getRawTransactionByHash[✅]
eth_getTransactionReceipt[✅]
eth_getTransactionByBlockHashAndIndex[✅]
eth_getTransactionByBlockNumberAndIndex[✅]
eth_getRawTransactionByBlockHashAndIndex[✅]
eth_getRawTransactionByBlockNumberAndIndex[✅]
eth_sendRawTransactionSyncState apis
[✅]
eth_call[✅]
eth_estimateGas[✅]
eth_getBalance[✅]
eth_getTransactionCount[✅]
eth_getCode[✅]
eth_getStorageAtTODO:
Support eth_getLogs in the future
Design flaws on op-reth flashblocks rpc
On subsequent reth version upgrades (todo):
Todos: