Skip to content

feat(sequencer): local fork mode via --fork CLI flag#445

Open
paschal533 wants to merge 14 commits intologos-blockchain:mainfrom
paschal533:feat/local-fork-mode
Open

feat(sequencer): local fork mode via --fork CLI flag#445
paschal533 wants to merge 14 commits intologos-blockchain:mainfrom
paschal533:feat/local-fork-mode

Conversation

@paschal533
Copy link
Copy Markdown

@paschal533 paschal533 commented Apr 14, 2026

Summary

Closes part of #379 (local fork mode).

When the sequencer is built with --features standalone, passing --fork <rpc-url> at startup:

  1. Calls the new getStateSnapshot RPC on the remote sequencer to fetch the current execution state as opaque Borsh bytes plus the chain head block ID.
  2. Deserialises the bytes into a V03State in sequencer/service/src/fork.rs.
  3. Injects the state via a new override_initial_state: Option<Box<V03State>> field in SequencerConfig (skipped during JSON serde, so config files are unaffected).
  4. SequencerCore::start_from_config checks the override field first, if present, it skips genesis initialisation and uses the remote chain's state directly.
  5. genesis_id is set to the fork block ID, so new blocks are produced starting at fork_block_id + 1.

Changes

Crate / file Change
common/src/snapshot.rs New StateSnapshot { state_bytes, block_id } type with Borsh + serde
sequencer/core/src/config.rs Add override_initial_state: Option<Box<V03State>> with #[serde(skip)]
sequencer/core/src/lib.rs Consult override_initial_state before genesis/DB at startup
sequencer/service/rpc/src/lib.rs Add getStateSnapshot to RPC trait
sequencer/service/src/service.rs Implement get_state_snapshot; add roundtrip test
sequencer/service/src/fork.rs New: fetch_fork_state(url), fetches + deserialises snapshot
sequencer/service/src/lib.rs New run_forked() public entry-point; expose fork module
sequencer/service/src/main.rs Add --fork <url> CLI argument (standalone only)

Test plan

  • RISC0_DEV_MODE=1 cargo test --release -p sequencer_service ,new get_state_snapshot_returns_deserializable_state test passes
  • RISC0_DEV_MODE=1 cargo test --release -p common, StateSnapshot borsh/serde roundtrip tests pass
  • cargo check --features standalone -p sequencer_service, compiles without warnings
  • cargo check -p sequencer_service, non-standalone build unchanged; --fork flag absent
  • Manual: start a standalone sequencer, then start a second with --fork http://localhost:<port>, second sequencer logs "Forked state fetched at block N" and continues producing blocks from N+1

Notes

  • StateSnapshot.state_bytes is intentionally opaque: common does not depend on nssa, keeping the crate boundary clean. Only callers that need to deserialise the state need a direct nssa dependency.
  • override_initial_state is #[serde(skip)] so it never appears in or is read from config JSON files, it is only ever injected programmatically.
  • This PR stacks on top of the simulate-transaction and tx-receipt PRs from the same issue.

…ationError

Replace panicking .expect() calls in send_transaction with proper error
propagation using TransactionMalformationError variants. Unify the size
error to use TransactionMalformationError::TransactionTooLarge instead
of a raw format string. Add unit tests for too-large and valid transaction
paths.
Registers the new rejected-transaction column family in RocksDB,
and exposes rejected_tx_column, put_rejected_tx, and get_rejected_tx
on RocksDBIO for storing and retrieving RejectedTxRecord by hash.
…ulate block timestamp

Restructure get_transaction_receipt to scope the sequencer lock to a
block expression, collect a terminal receipt (Rejected/Included) under
the lock, release the lock, then lazily remove the hash from pending_txs
before returning.

This fixes two related issues:
- pending_txs grew without bound because insert() in send_transaction
  had no corresponding remove() when TXs were confirmed or rejected
- a TX that had been included could still appear as Pending if the
  caller never queried its receipt

Also populate timestamp_ms for Included receipts by fetching the block
from the store (an additional DB read already cached by RocksDB's
block cache), replacing the previous None placeholder.

Lock ordering is preserved: sequencer lock is always released before
pending_txs is acquired, matching the order in send_transaction.
Implements the local fork mode described in issue logos-blockchain#379. When
--features standalone is active, passing `--fork <rpc-url>` fetches the
remote sequencer's execution state snapshot via the new `getStateSnapshot`
RPC method and boots the local sequencer from that state.

Changes:
- common: add StateSnapshot type (opaque Borsh bytes + block_id)
- sequencer/core: add override_initial_state field to SequencerConfig;
  start_from_config consults it before genesis/DB state
- sequencer/service/rpc: add getStateSnapshot to RPC trait
- sequencer/service: implement get_state_snapshot, add run_forked(),
  add --fork CLI arg, expose fork module; add service test for snapshot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants