Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
cartesi-machine-sha256-arm64: ${{ env.CARTESI_MACHINE_SHA256_ARM64 }}

- name: Download canonical app deps
run: just -f examples/canonical-app/justfile download-deps
run: just canonical download-deps

- name: Run guest tests
run: just canonical-test-guest
Expand Down
32 changes: 17 additions & 15 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Release workflow: git tag v* builds sequencer tarballs, CM images, and pushes
# Release workflow: git tag v* builds wallet-sequencer tarballs, CM images, and pushes
# watchdog OCI images to ghcr.io + docker.io (requires DOCKERHUB_USERNAME /
# DOCKERHUB_TOKEN org or repo secrets, same as cartesi/cli).

Expand All @@ -25,8 +25,8 @@ permissions:
packages: write

jobs:
build-sequencer:
name: Build sequencer (${{ matrix.arch }})
build-wallet-sequencer:
name: Build wallet-sequencer (${{ matrix.arch }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down Expand Up @@ -70,8 +70,10 @@ jobs:
with:
tool: cross

# `sequencer` is a library crate; the runnable reference binary is the
# `wallet-sequencer` example app (sequencer lib + the placeholder wallet).
- name: Build (release)
run: cross build -p sequencer --release --locked --target ${{ matrix.target }}
run: cross build -p wallet-sequencer --release --locked --target ${{ matrix.target }}

- name: Package
env:
Expand All @@ -82,14 +84,14 @@ jobs:
set -euo pipefail
mkdir -p dist

mkdir -p "package/sequencer-${TAG}-linux-${ARCH}"
cp "target/${TARGET}/release/sequencer" "package/sequencer-${TAG}-linux-${ARCH}/sequencer"
mkdir -p "package/wallet-sequencer-${TAG}-linux-${ARCH}"
cp "target/${TARGET}/release/wallet-sequencer" "package/wallet-sequencer-${TAG}-linux-${ARCH}/wallet-sequencer"
bash scripts/generate-release-manifest.sh \
--tag "${TAG}" \
--git-sha "${GITHUB_SHA}" \
--output "package/sequencer-${TAG}-linux-${ARCH}/RELEASE.json"
--output "package/wallet-sequencer-${TAG}-linux-${ARCH}/RELEASE.json"

cat > "package/sequencer-${TAG}-linux-${ARCH}/RUNNING.md" <<'EOF'
cat > "package/wallet-sequencer-${TAG}-linux-${ARCH}/RUNNING.md" <<'EOF'
## Running

Required environment variables:
Expand All @@ -106,17 +108,17 @@ jobs:
CARTESI_SEQUENCER_BLOCKCHAIN_ID=31337 \
CARTESI_SEQUENCER_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
CARTESI_SEQUENCER_AUTH_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
./sequencer
./wallet-sequencer
```
EOF

tar -C package -czf "dist/sequencer-${TAG}-linux-${ARCH}.tar.gz" "sequencer-${TAG}-linux-${ARCH}"
tar -C package -czf "dist/wallet-sequencer-${TAG}-linux-${ARCH}.tar.gz" "wallet-sequencer-${TAG}-linux-${ARCH}"

- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: sequencer-linux-${{ matrix.arch }}
path: dist/sequencer-*.tar.gz
name: wallet-sequencer-linux-${{ matrix.arch }}
path: dist/wallet-sequencer-*.tar.gz

build-canonical-machine-image:
name: Build canonical machine images
Expand All @@ -140,7 +142,7 @@ jobs:
cartesi-machine-sha256-arm64: ${{ env.CARTESI_MACHINE_SHA256_ARM64 }}

- name: Download canonical app deps
run: just -f examples/canonical-app/justfile download-deps
run: just canonical download-deps

- name: Build machine image (devnet)
run: just canonical-build-machine-image
Expand Down Expand Up @@ -230,7 +232,7 @@ jobs:
name: Publish GitHub Release
runs-on: ubuntu-latest
needs:
- build-sequencer
- build-wallet-sequencer
- build-canonical-machine-image
- build-watchdog-image
steps:
Expand All @@ -244,7 +246,7 @@ jobs:
uses: actions/download-artifact@v6
with:
path: dist
pattern: "{sequencer-linux-*,canonical-machine-images}"
pattern: "{wallet-sequencer-linux-*,canonical-machine-images}"

- name: Flatten artifacts
env:
Expand Down
139 changes: 94 additions & 45 deletions AGENTS.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ Rust edition 2024 / Axum API / SQLite (rusqlite, WAL) / EIP-712 signing / SSZ en

## Workspace Layout

- `sequencer/` — main sequencer binary and library.
- `sequencer/` — sequencer library (no binary; app crates build the binary).
- `sequencer-core/` — shared domain types consumed by both sequencer and scheduler.
- `examples/app-core/` — placeholder wallet app implementing `Application`.
- `examples/wallet-sequencer/` — binary crate: wallet app + sequencer library.
- `examples/canonical-app/` — on-chain scheduler reference implementation.
- `examples/canonical-test/` — e2e test harness for the canonical app.
- `sdk/rust-client/` — Rust client library for the sequencer API.
Expand All @@ -53,6 +54,9 @@ Rust edition 2024 / Axum API / SQLite (rusqlite, WAL) / EIP-712 signing / SSZ en
## Before You Start Real Work

- **[`AGENTS.md`](AGENTS.md)** — mission, requirements, invariants, duality, recovery, conventions, rules.
- **[`docs/protocol/`](docs/protocol/)** — the authoritative protocol contracts: [`scheduler-semantics.md`](docs/protocol/scheduler-semantics.md) (canonical acceptance algorithm) and [`application-contract.md`](docs/protocol/application-contract.md) (the `Application` FFI trait). Read before touching the scheduler, the gold frontier, the fold, or an `Application` impl.
- **[`docs/invariants.md`](docs/invariants.md)** — cross-module invariants register + the fail-loud check policy. Check it before changing anything it lists as load-bearing.
- **[`docs/review/`](docs/review/)** — dated correctness-review ledgers: known-open findings, settled designs, work packages. Check for open findings in code you're about to touch.
- **[`docs/threat-model/README.md`](docs/threat-model/README.md)** — trust boundaries and in-scope threats.
- **[`docs/recovery/README.md`](docs/recovery/README.md)** — preemptive recovery design + TLA+ proofs.
- **[`docs/snapshots/lifecycle.md`](docs/snapshots/lifecycle.md)** — snapshot lifecycle design + invariants (take/promote/GC, crash-safety). Read before touching the inclusion lane's safe-frontier/snapshot path.
Expand Down
69 changes: 64 additions & 5 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ members = [
"examples/app-core",
"examples/canonical-app",
"examples/canonical-test",
"examples/wallet-sequencer",
"tests/benchmarks",
"tests/harness",
"tests/e2e",
]
default-members = ["sequencer"]
default-members = ["sequencer", "examples/wallet-sequencer"]

[workspace.package]
version = "0.1.0"
Expand Down
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ The sequencer is a **centralized, single-writer** system. It cannot steal funds

**Direct inputs** (L1 → L2 messages, used for deposits) bypass the sequencer entirely. They are posted directly to L1 and are **uncensorable** by the sequencer — the scheduler drains them at every `safe_block` boundary. A censoring sequencer can delay when a direct input is executed (up to `MAX_WAIT_BLOCKS`, ~4h), but cannot prevent it.

Soft confirmations are an **optimistic prediction**: the sequencer also
cross-checks every batch the scheduler accepts on L1 against the batch it
sealed locally (a content-identity check), and refuses to operate further the
moment they differ. Detection happens when the divergent batch reaches L1
*safe* finality, so soft confirmations issued inside that window (~2 L1
epochs) can be built on already-diverged state — an inherent, bounded
property of the optimistic model.

The third case is handled by the recovery subsystem. Batches that are too old when they reach L1 (`inclusion_block − safe_block ≥ MAX_WAIT_BLOCKS`) are skipped by the scheduler. This "staleness" poisons the nonce counter: all subsequent batches become unreachable regardless of their individual freshness. The sequencer detects this via a danger-zone threshold, preemptively goes offline, flushes the L1 mempool, and cascade-invalidates the doomed chain. See [`docs/recovery/`](docs/recovery/) for the full design, TLA+ formal verification, and design history.

The sequencer trusts its own code is bug-free. Recovery means recovery from liveness failures, which can legitimately happen even in the absence of bugs (infrastructure outages, network failures, gateway failure). Code-level bugs are a separate problem handled by tests and review. See [`docs/threat-model/README.md`](docs/threat-model/README.md) for the complete threat model applied across the codebase.
Expand Down Expand Up @@ -63,17 +71,36 @@ The batch submitter posts closed batches to L1's InputBox contract. Each batch c

## Running

The sequencer runs in two phases. **`setup`** pins the
deployment identity, does the initial L1 sync, and registers the genesis
snapshot — run it once. It is L1-read-only: it takes the batch-submitter
*address*, never the signing key. **`run`** boots the sequencer from the
set-up DB, reading identity from it (so chain id / app address are not `run`
arguments); it holds the signing key because it submits.

```bash
# Phase A — set up the data dir (run once; idempotent).
CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT=http://127.0.0.1:8545 \
CARTESI_SEQUENCER_BLOCKCHAIN_ID=31337 \
CARTESI_SEQUENCER_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
CARTESI_SEQUENCER_BATCH_SUBMITTER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
cargo run -p wallet-sequencer -- setup

# Phase B — run the sequencer.
CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT=http://127.0.0.1:8545 \
CARTESI_SEQUENCER_AUTH_PRIVATE_KEY=0xac09...f2ff80 \
cargo run -p sequencer
cargo run -p wallet-sequencer -- run
```

Required: `CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT`, `CARTESI_SEQUENCER_BLOCKCHAIN_ID`, `CARTESI_SEQUENCER_APP_ADDRESS`, `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY` (or `_FILE`).
A third subcommand, **`flush-mempool`**, settles the batch-submitter wallet
nonce on demand (keyed operator tool).

`setup` requires: `CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT`, `CARTESI_SEQUENCER_BLOCKCHAIN_ID`, `CARTESI_SEQUENCER_APP_ADDRESS`, `CARTESI_SEQUENCER_BATCH_SUBMITTER_ADDRESS`.
`run` requires: `CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT`, `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY` (or `_FILE`); it refuses to boot until `setup` has completed.

Optional: `CARTESI_SEQUENCER_HTTP_ADDR` (default `127.0.0.1:3000`, `run`), `CARTESI_SEQUENCER_DATA_DIR` (default `sequencer-data` — SQLite file is `sequencer.db` inside; created if missing), `CARTESI_SEQUENCER_PREEMPTIVE_MARGIN_BLOCKS` (default `300`), `CARTESI_SEQUENCER_SECONDS_PER_BLOCK` (default `12`), `CARTESI_SEQUENCER_L1_READ_STALE_AFTER_BLOCKS` (default `600`), `CARTESI_SEQUENCER_LONG_BLOCK_RANGE_ERROR_CODES` (default `-32005,-32600,-32602,-32616`), `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY_FILE` (alternative to `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY`; first line of the file is the key), `CARTESI_SEQUENCER_BATCH_SUBMITTER_IDLE_POLL_INTERVAL_MS`, `CARTESI_SEQUENCER_BATCH_SUBMITTER_CONFIRMATION_DEPTH`.

Optional: `CARTESI_SEQUENCER_HTTP_ADDR` (default `127.0.0.1:3000`), `CARTESI_SEQUENCER_DATA_DIR` (default `sequencer-data` — SQLite file is `sequencer.db` inside; created if missing), `CARTESI_SEQUENCER_PREEMPTIVE_MARGIN_BLOCKS` (default `300`), `CARTESI_SEQUENCER_SECONDS_PER_BLOCK` (default `12`), `CARTESI_SEQUENCER_LONG_BLOCK_RANGE_ERROR_CODES` (default `-32005,-32600,-32602,-32616`), `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY_FILE` (alternative to `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY`; first line of the file is the key), `CARTESI_SEQUENCER_BATCH_SUBMITTER_IDLE_POLL_INTERVAL_MS`, `CARTESI_SEQUENCER_BATCH_SUBMITTER_CONFIRMATION_DEPTH`.
Process exit codes follow the R4 orchestrator contract: `0` clean shutdown, `10` restart (expect a recovery boot), `20` transient refusal (retry with backoff), `30` terminal (operator required — e.g. setup not complete, identity mismatch, canonical divergence), `1`/`101` unclassified/panic.

Fixed protocol identity (EIP-712):

Expand Down Expand Up @@ -129,7 +156,7 @@ Message shapes:
```

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

Success response:
Expand Down Expand Up @@ -174,8 +201,8 @@ released even on client disconnect.

## Project Layout

- `sequencer/src/main.rs`: thin binary entrypoint
- `sequencer/src/lib.rs`: public crate surface (`run`, `RunConfig`)
- `sequencer/src/lib.rs`: public crate surface (`run`, `RunConfig`) — the sequencer is a library; app crates build the binary (see `examples/wallet-sequencer/`)
- `examples/wallet-sequencer/`: binary crate composing the sequencer library with the placeholder wallet app
- `sequencer/src/http.rs`: shared HTTP error type, JSON error shape, and `axum::serve` orchestration
- `sequencer/src/runtime/`: process bootstrap, config parsing, EIP-712 domain, shutdown signal, shared clock
- `sequencer/src/ingress/`: public write path — `POST /tx` (`api.rs`) and the inclusion lane (`inclusion_lane/`: hot-path loop, chunk/frame/batch rotation, catch-up, snapshot lifecycle)
Expand Down
Loading
Loading