Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 118 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,124 @@ Required: `SEQ_ETH_RPC_URL`, `SEQ_CHAIN_ID`, `SEQ_APP_ADDRESS`, `SEQ_BATCH_SUBMI

Optional: `SEQ_HTTP_ADDR` (default `127.0.0.1:3000`), `SEQ_DATA_DIR` (default `sequencer-data`), `SEQ_PREEMPTIVE_MARGIN_BLOCKS` (default `300`), `SEQ_SECONDS_PER_BLOCK` (default `12`), `SEQ_BATCH_SUBMITTER_IDLE_POLL_INTERVAL_MS`, `SEQ_BATCH_SUBMITTER_CONFIRMATION_DEPTH`.

- `SEQ_HTTP_ADDR` defaults to `127.0.0.1:3000`
- `SEQ_DATA_DIR` defaults to `sequencer-data` (SQLite file is `sequencer.db` inside that directory; the directory is created if missing)
- `SEQ_LONG_BLOCK_RANGE_ERROR_CODES` defaults to `-32005,-32600,-32602,-32616`
- `SEQ_BATCH_SUBMITTER_PRIVATE_KEY_FILE` instead of `SEQ_BATCH_SUBMITTER_PRIVATE_KEY` (first line of the file is the key)
- `SEQ_BATCH_SUBMITTER_IDLE_POLL_INTERVAL_MS`, `SEQ_BATCH_SUBMITTER_CONFIRMATION_DEPTH`

Required runtime inputs:

- `SEQ_ETH_RPC_URL`
- `SEQ_CHAIN_ID`
- `SEQ_APP_ADDRESS`
- `SEQ_BATCH_SUBMITTER_PRIVATE_KEY` or `SEQ_BATCH_SUBMITTER_PRIVATE_KEY_FILE`

Fixed protocol identity (EIP-712):

- domain name: `CartesiAppSequencer`
- domain version: `1`
- `chain_id` and `verifying_contract` come from `SEQ_CHAIN_ID` and `SEQ_APP_ADDRESS`

Most queue sizes, polling intervals, and safety limits are now internal runtime constants instead of public launch-time configuration.

## API

### `POST /tx`

Request shape:

```json
{
"message": {
"nonce": 0,
"max_fee": 1,
"data": "0x..."
},
"signature": "0x...",
"sender": "0x..."
}
```

Notes:

- `signature` must be 65 bytes.
- `sender` is required and must match the recovered signer.
- `message.data` is SSZ-encoded method payload bytes.
- payload size is bounded at ingress; oversized requests are rejected before entering the hot path.
- overload is enforced at queue admission: if the inclusion-lane queue is full, `POST /tx` returns HTTP `429` with code `OVERLOADED` and message `queue full`.
- queue capacity is an internal runtime constant tuned alongside inclusion-lane chunking to absorb short bursts; if this starts triggering persistently, it is a signal to revisit runtime sizing or throughput rather than add another admission layer.

### `GET /ws/subscribe?from_offset=<u64>`

WebSocket stream of sequenced L2 transactions from persisted order.

Notes:

- `from_offset` is optional and defaults to `0`.
- messages are JSON text frames.
- binary fields are hex-encoded (`0x`-prefixed).
- the current runtime enforces a subscriber cap of `64` and a catch-up cap of `50000` events.
- if the requested catch-up window exceeds that cap, the server upgrades and then immediately closes the socket with close code `1008` (`POLICY`) and reason `catch-up window exceeded`.

Message shapes:

```json
{ "kind": "user_op", "offset": 10, "sender": "0x...", "fee": 1, "data": "0x..." }
```

```json
{ "kind": "direct_input", "offset": 11, "payload": "0x..." }
```

Success response:

```json
{
"ok": true,
"sender": "0x...",
"nonce": 0
}
```

## Storage Model

- `batches`: batch metadata
- `frames`: frame boundaries within each batch
- `frames.fee`: committed fee for each frame
- `user_ops`: included user operations
- `sequenced_l2_txs`: append-only ordered replay rows (`UserOp` xor `DirectInput`); inserting into `user_ops` also appends the corresponding replay row via trigger `trg_sequence_user_op`
- `safe_inputs`: direct-input payload stream
- `batch_policy`: singleton knobs and constants for DA-style batch sizing and fee derivation; `batch_policy_derived` view exposes `recommended_fee` and `batch_size_target`

## Project Layout

- `sequencer/src/main.rs`: thin binary entrypoint
- `sequencer/src/lib.rs`: public crate surface
- `sequencer/src/config.rs`: runtime input parsing and EIP-712 domain construction
- `sequencer/src/runtime.rs`: sequencer bootstrap and component wiring
- `sequencer/src/api/`: HTTP API and error mapping
- `sequencer/src/inclusion_lane/`: hot-path inclusion loop, chunk/frame/batch rotation, catch-up
- `sequencer/src/input_reader/`: safe-input ingestion from InputBox into SQLite
- `sequencer/src/l2_tx_feed/`: DB-backed ordered-L2Tx feed for WS subscriptions
- `sequencer/src/storage/`: schema, migrations, SQLite persistence, and replay reads
- `sequencer-core/src/`: shared domain types and interfaces (`Application`, `SignedUserOp`, `SequencedL2Tx`, feed message types)
- `examples/app-core/src/`: wallet prototype implementing `Application`
- `tests/benchmarks/`: benchmark harnesses and benchmark spec

Related docs:
- App snapshot format: `docs/app-snapshot-format.md`

## Prototype Limits

- Wallet state is in-memory and not persisted.
- Schema and migrations are still in prototype mode and may change.

## Local Test Prerequisites

- Some `sequencer` tests spin up `Anvil`; install Foundry locally if you want the full test suite:
- Self-contained benchmarks also spawn `Anvil` from a preloaded rollups state dump.

## Development

```bash
Expand Down
69 changes: 69 additions & 0 deletions docs/app-snapshot-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# App Snapshot Format (Wallet Toy App)

This document defines the current on-disk snapshot format for the toy wallet app in `examples/app-core`.

Scope note: this only covers the **capability** to serialize/deserialize app state. It does not define when snapshots are triggered or how runtime wiring invokes save/load.

## Encoding

- **Format:** SSZ
- **Current version:** `WalletSnapshotV1` (Rust struct name)
- **Byte order for balances:** big-endian 32-byte integers (`U256`)

## Serialized State

`WalletSnapshotV1` encodes:

- `erc20_portal_address` (`[u8; 20]`)
- `supported_erc20_token` (`[u8; 20]`)
- `sequencer_address` (`[u8; 20]`)
- `balances` (`Vec<SnapshotBalance>`)
- `address` (`[u8; 20]`)
- `balance_be` (`[u8; 32]`)
- `nonces` (`Vec<SnapshotNonce>`)
- `address` (`[u8; 20]`)
- `nonce` (`u32`)
- `executed_input_count` (`u64`)

## Determinism Guarantees

`WalletApp` stores balances/nonces in hash maps, so iteration order is nondeterministic. Before encoding:

- `balances` entries are sorted by `address`
- `nonces` entries are sorted by `address`

This guarantees stable snapshot bytes for equivalent logical state.

## Compatibility Policy

- Restores must decode the exact current snapshot schema.
- Malformed bytes fail restore with a decode error.
- Future breaking changes must introduce a new versioned schema type (for example, `WalletSnapshotV2`) and explicit migration/dispatch logic.
- Do not reorder or reinterpret existing fields in-place without a version bump.

## API Surface

Current wallet snapshot API:

- `snapshot_bytes(&self) -> Vec<u8>`
- `restore_from_snapshot_bytes(&mut self, snapshot: &[u8]) -> Result<(), WalletSnapshotError>`
- `save_snapshot<P: AsRef<Path>>(&self, path: P) -> Result<(), WalletSnapshotError>`
- `load_snapshot<P: AsRef<Path>>(&mut self, path: P) -> Result<(), WalletSnapshotError>`

## Disk Write Semantics

`save_snapshot` uses an atomic replacement pattern:

- write bytes to a temporary file in the same directory as the target
- `sync_all` the temp file
- rename temp file to the final path

This avoids exposing partially written snapshot bytes at the target path.

## Out of Scope

This document intentionally does not define:

- periodic vs explicit snapshot trigger policy
- mount paths and runtime drive conventions
- atomic file replacement protocol for production snapshot lifecycle
1 change: 1 addition & 0 deletions examples/app-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ ssz = { package = "ethereum_ssz", version = "0.10" }
ssz_derive = { package = "ethereum_ssz_derive", version = "0.10" }
tracing = "0.1"
types = { workspace = true }
thiserror = "1"
2 changes: 1 addition & 1 deletion examples/app-core/src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ mod wallet;
pub use anvil_accounts::default_private_keys;
pub use method::{MAX_METHOD_PAYLOAD_BYTES, Method, Transfer, Withdrawal};
pub use notice::{DepositNotice, TransferNotice};
pub use wallet::{WalletApp, WalletConfig};
pub use wallet::{WalletApp, WalletConfig, WalletSnapshotError};
Loading