feat: add realtime fork as new sibling crate#953
Conversation
Introduces the realtime fork (`realtime/`) as a clean sibling to shasta/permissionless/pacaya, ported from the surge-real-time-poc and feat/l2-to-l1-to-l2-sync work without dragging in the shasta-side and pacaya-side modifications that those branches accumulated. What's in the new fork: - L1 + L2 execution layers with bridge-callback simulation - Async proposal submitter, batch builder, bridge handler (UserOp status tracking, mempool scan for return signals) - Realtime chain monitor for `RealTimeInbox::ProposedAndProved` - Raiko v3 client; deferred-finalization multicall builder - Self-contained `NodeConfig` (does not rely on pacaya::node::config, which was removed in #941 when pacaya became a utility crate) Wiring: - `Fork::Realtime` enum variant + `FromStr` impl + `FORK` env var override in common/src/fork_info, default-disabled timestamp (only activates when `FORK=realtime` is set) - `Realtime` match arm in `Node/src/main.rs` - Workspace `Cargo.toml`: adds realtime member + `sled` dep Common deltas (consumed by realtime): - `taiko_driver::reorg_stale_block` RPC + `ReorgStaleBlock{Request,Response}` - `BuildPreconfBlockResponse.state_root` (parsed leniently — defaults to B256::ZERO if missing, so existing shasta/permissionless JSON paths remain compatible) - `transaction_monitor::monitor_new_transaction` accepts optional tx-hash and tx-result oneshot notifiers; existing 2-arg callers are unaffected (only the new realtime async submitter passes Some) Untouched from origin/master: - `shasta/`, `pacaya/` — zero diff - `permissionless/` — single line: `state_root: B256::ZERO` placeholder in the BuildPreconfBlockResponse construction, mirroring mikhailUshakoff's PR #939 approach - `common/src/config/mod.rs` — the `TAIKO_BRIDGE_L2_ADDRESS` → `L2_BRIDGE_ADDRESS` rename flagged on PR #945 is intentionally deferred to a separate coordinated PR - `common/src/shared/internal_server.rs` and the warp→axum migration — preserved as-is - Dockerfile + x86-64 CI fixes — separate PR Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…FORK env var Drops the bespoke `FORK` env-var override + `Fork::FromStr` impl in favour of the existing per-fork timestamp pattern. Adds `config.realtime_timestamp_sec` (default 99999999999) which is then threaded through `ForkInfoConfig`, matching how Shasta and Permissionless are activated. To run in realtime mode, set REALTIME_TIMESTAMP_SEC=0 (or any past timestamp) at startup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cargo.lock resolution after the realtime crate's deps no longer pulls in the affected `lru` version, so cargo-deny's `advisory-not-detected` warning fails the audit. Removing the now unused ignore entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes all `#[allow(dead_code)]` markers in realtime/ and the items they were guarding, rather than silencing warnings. Touches: - ProtocolConfig: drop unused proof_verifier, signal_service fields and get_max_anchor_height_offset getter. - RaikoClient: drop l2_network/l1_network fields and the corresponding RAIKO_L2_NETWORK / RAIKO_L1_NETWORK env reads — they were stored but never sent in proof requests (request body uses None). - ContractAddresses + EthereumL1Config: drop signal_service field, L1_SIGNAL_SERVICE_ADDRESS env read, and the dead raiko_client chain (RaikoClient is constructed at lib.rs and routed through AsyncSubmitter, not ExecutionLayer). - BridgeHandler: drop the l1_chain_id field and the unused parameter chain back to Node::new (lib.rs). - BatchBuilder: drop unused `metrics` field/param + add_recovered_l2_block, add_l2_user_op_id methods. - BatchManager: drop unused metrics, cancel_token fields and reanchor_block method. - Node: drop unused metrics field/param. - L2ExecutionLayer: drop unused TaikoConfig field, get_head_l1_origin, get_last_synced_block_params_from_geth, decode_block_params_from_tx_data, get_anchor_tx_data methods. - Taiko: drop unused coinbase field, get_protocol_config, get_l2_block_by_number, fetch_l2_blocks_until_latest, decode_anchor_id_from_tx_data, get_anchor_tx_data wrappers. - proposal::Proposals type alias. Net: -249 / +5 lines, no new warnings, all 122 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat/realtime-fork
Sled is unmaintained, cargo deny didn't accept it.
send batch functions and tx monitor
|
@claude review |
|
Claude finished @mskrzypkows's task in 3m 53s —— View job PR Review: feat/realtime-forkTasks:
Overall this is a solid, well-structured port. The fork isolation goals are met — shasta/pacaya are untouched, and the common-crate changes are backwards-compatible. The bridge simulation logic and deferred-finalization multicall design are especially well-thought-out. Several issues are worth addressing before merging. Addressing the existing review comment
The file cannot be removed as-is. Bugs / Correctness1. Signal slot orphaned after failed block advance [ When To fix: either clear the added signal slots in the error path of 2. Silent stub: pub async fn transfer_eth_from_l2_to_l1(...) -> Result<(), Error> {
warn!("Implement bridge transfer logic here");
Ok(())
}This returns 3. assert!(!self.is_busy(), "Cannot submit while another submission is in flight");
Security4. Unauthenticated RPC server [ The
At minimum, consider binding to Architecture5. Cross-fork
6. Hardcoded let node_config = node::config::NodeConfig {
preconf_heartbeat_ms: config.preconf_heartbeat_ms,
handover_window_slots: 8,
handover_start_buffer_ms: 500,
l1_height_lag: 8,
simulate_not_submitting_at_the_end_of_epoch: false,
};The last four fields are hardcoded constants, not read from Fragility / Correctness Concerns7. Bridge The L1 callback simulation overrides storage slots 253 and 254 of the Bridge contract, assuming they correspond to 8. Hardcoded gas and fee values [
The anchor gas limit is a copy of existing behavior, but the bridge call's 1 gwei max fee will cause the transaction to be stuck or fail if L2 base fee exceeds 1 gwei. This should be set dynamically from Performance9. Duplicate blob encoding per proposal [ The manifest is encoded → compressed → built into a blob sidecar twice per submission: once here to extract blob hashes for the Raiko request, and again inside 10. No HTTP timeout on Raiko client [ client: Client::new(),The Client::builder().timeout(Duration::from_secs(30)).build()?Minor / Nits11. Duplicate Defined identically in both 12. The 13. Fjall DB path hardcoded [ UserOpStatusStore::open("data/user_op_status")?;Relative path, not configurable. Different deployment environments (containerized, multiple instances) will share or conflict on this path. 14. Status cleanup delay hardcoded to 60s [ 60 seconds starts after L1 tx submission — if the tx monitor takes time and the proof took time, by the time a client checks 15. The Summary table
The most important items to address before merge are #1 (signal orphan), #3 (assert! panic), #4 (unauthenticated RPC), #8 (hardcoded max fee), and #10 (no HTTP timeout). The rest are improvements or follow-up candidates. |
Picks up the actionable items from the automated review on #953 and leaves the rest documented as follow-ups. Net change: +117 / -46 in realtime/. Bug / correctness: * (#1) Defer staging additions to the in-flight proposal until `advance_head_to_new_l2_block` succeeds. Previously a failed advance left orphan signal slots / user ops in the proposal that no L2 block corresponds to, so the next attempt would re-add the same slot from the mempool scan and `_verifySignalSlots` would revert on duplicate. `pending_return_signal` and `pending_mempool_tx_hash` are now only consumed in the Ok arm so retries see the same slot. * (#3) `AsyncSubmitter::submit` no longer panics on the `is_busy()` invariant — returns `Err` and the caller propagates. * (#2) `transfer_eth_from_l2_to_l1` returns an explicit "not implemented" `Err` instead of `Ok(())` to make accidental wiring loud. Realtime does not run the funds_controller flow. * (#8) `processMessage` L2 call now uses the L2 slot's actual base fee for `max_fee_per_gas` instead of a hardcoded 1 gwei. Matches the anchor tx pattern. * (#10) Raiko reqwest client now has a configurable timeout (`RAIKO_TIMEOUT_SEC`, default 30s) — a hung Raiko no longer deadlocks the async submitter forever. Security: * (#4) `BRIDGE_RPC_ADDR` defaults to `127.0.0.1:4545` instead of `0.0.0.0:4545` so the unauthenticated `surge_*` JSON-RPC endpoints are not exposed externally unless an operator explicitly opts in. Usability: * (#13) The fjall DB path for `UserOpStatusStore` is now configurable via `USER_OP_STATUS_DB_PATH` (default `data/user_op_status`), threaded through `BatchManager` → `BridgeHandler`. Diagnostics / cleanup: * (#12) `RealtimeConfig` Display now includes bridge, l2_signal_service, raiko_max_retries, raiko_timeout_sec, mock_mode, bridge_rpc_addr, user_op_status_db_path — startup log shows the full picture. * (#11) `SEND_MESSAGE_SELECTOR` lives in `realtime/src/shared_abi/mod.rs` instead of being duplicated in both execution layers. Deferred (separate PRs / issues): * (#5) Cross-fork pacaya dependency — design decision; pacaya is a shared utility crate on master, deliberately reused. * (#6) Hardcoded NodeConfig values (handover_window_slots etc.) — fine for the only current deployment; expose when a 2nd one shows up. * (#7) Bridge `__ctx` storage slots hardcoded — needs runtime layout check; out of scope here. * (#9) Duplicate blob encoding per submission — perf optimization. * (#14) `surge_txStatus` cleanup timing. * (#15) `getConfig()` called twice on startup. Quality gate: `cargo build --workspace`, `cargo clippy --all-features` on touched crates, `cargo fmt --check`, `cargo test --workspace` (122 passed) all clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed in Fixed (9 items):
Deferred (6 items, by intent):
Quality gate after the fix landed on top of the rebased branch (which now includes the sled→fjall switch and tx-monitor handler refactor): |
becaue it's expected that there could be no one to listen for the result.
Summary
Lands the realtime fork as a clean, self-contained sibling of
shasta/permissionless/pacaya, ported fromsurge-real-time-poc+feat/l2-to-l1-to-l2-syncwithout dragging in the shasta-side and pacaya-side modifications those branches accumulated.realtime/(~7,000 LOC): L1/L2 execution layers with bridge-callback simulation, async proposal submitter + bridge handler (UserOp status, mempool scan for return signals), realtime chain monitor, Raiko v3 client, deferred-finalization multicall builder, self-containedNodeConfig.Fork::Realtimeenum +FromStr+FORKenv-var override (default-disabled timestamp; only activates onFORK=realtime).taiko_driver::reorg_stale_blockRPC,BuildPreconfBlockResponse.state_root(parsed leniently — defaults toB256::ZEROwhen missing so existing shasta/permissionless paths still work),monitor_new_transactionaccepts optional tx-hash / tx-result notifiers (existing 2-arg callers untouched).What's intentionally not here
shasta/,pacaya/— zero diff vs master.permissionless/— one line:state_root: B256::ZEROplaceholder in theBuildPreconfBlockResponseconstruction, mirroring @mikhailUshakoff's approach in chore(realtime): restore Shasta and Permissionless folders #939.TAIKO_BRIDGE_L2_ADDRESS→L2_BRIDGE_ADDRESSrename flagged on PR feat: L2→L1→L2 synchronous composability #945 is deferred to a separate, coordinated PR with the infra team — keeping the existing env var name preserves running deployments.internal_server.rsis not restored.Relationship to existing PRs
PR #945 (
feat/l2-to-l1-to-l2-sync→surge-real-time-poc) and thesurge-real-time-poc→masterPR are left open for now as fallback. Once this PR lands, they can be closed.Test plan
cargo build --workspacecargo clippy -p realtime -p common -p shasta -p permissionless -p node -p pacaya --all-featuresclean (no new warnings)cargo fmt --all -- --checkcargo test --workspace— 122 tests pass across 16 suitesgit diff --stat origin/master..feat/realtime-fork -- shasta/ pacaya/is emptygit diff origin/master..feat/realtime-fork -- permissionless/is exactly 1 line (state_root placeholder)e2e_pytest.yml) passes against shasta config — proves shasta is not regressedFORK=realtimeend-to-end on devnet — L2→L1→L2 mempool scan picks up return signal, multicall finalizes correctly🤖 Generated with Claude Code