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
16 changes: 8 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,18 @@ jobs:

Required environment variables:

- `SEQ_ETH_RPC_URL`
- `SEQ_CHAIN_ID`
- `SEQ_APP_ADDRESS`
- `SEQ_BATCH_SUBMITTER_PRIVATE_KEY` (or `SEQ_BATCH_SUBMITTER_PRIVATE_KEY_FILE`)
- `CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT`
- `CARTESI_SEQUENCER_BLOCKCHAIN_ID`
- `CARTESI_SEQUENCER_APP_ADDRESS`
- `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY` (or `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY_FILE`)

Example:

```bash
SEQ_ETH_RPC_URL=http://127.0.0.1:8545 \
SEQ_CHAIN_ID=31337 \
SEQ_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
SEQ_BATCH_SUBMITTER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT=http://127.0.0.1:8545 \
CARTESI_SEQUENCER_BLOCKCHAIN_ID=31337 \
CARTESI_SEQUENCER_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
CARTESI_SEQUENCER_AUTH_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
./sequencer
```
EOF
Expand Down
36 changes: 18 additions & 18 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ See [`docs/recovery/README.md`](docs/recovery/README.md) Step 5 for the "everyth

Staleness is only checked against L1 **safe** state, never latest. Stale batches in latest that haven't reached safe yet will eventually become safe, and the check will fire at that point. This avoids reacting to L1 reorgs.

When the sequencer's view of L1 stops advancing — most often because the RPC gateway is stalled or returning stale reads, occasionally because L1 itself is unhealthy — the DB-based staleness check sees a frozen `current_safe_block` and may fail to trigger. The danger detector uses two wall-clock signals: the recorded L1 safe block timestamp must remain younger than `SEQ_L1_READ_STALE_AFTER_BLOCKS`, and unresolved batches are also checked with `estimated_missed_blocks = (now − last_safe_progress_ms) / seconds_per_block` by adjusting the danger threshold downward. This prevents silently issuing doomed soft confirmations during stale-provider periods or L1 outages.
When the sequencer's view of L1 stops advancing — most often because the RPC gateway is stalled or returning stale reads, occasionally because L1 itself is unhealthy — the DB-based staleness check sees a frozen `current_safe_block` and may fail to trigger. The danger detector uses two wall-clock signals: the recorded L1 safe block timestamp must remain younger than `CARTESI_SEQUENCER_L1_READ_STALE_AFTER_BLOCKS`, and unresolved batches are also checked with `estimated_missed_blocks = (now − last_safe_progress_ms) / seconds_per_block` by adjusting the danger threshold downward. This prevents silently issuing doomed soft confirmations during stale-provider periods or L1 outages.

### Formal verification

Expand Down Expand Up @@ -163,7 +163,7 @@ Top-level layout follows the system's data flow. Each sequencer module correspon
- Included txs are persisted as frame/batch data in `batches`, `frames`, `user_ops`, `safe_inputs`, and `sequenced_l2_txs`. Recovery metadata lives in `safe_accepted_batches`; batch lifecycle state (sealed/invalidated) lives on the `batches` row itself as write-once timestamps.
- Frame fee is persisted in `frames.fee` and is fixed for the lifetime of that frame. The next frame's fee is sampled from `batch_policy_derived.recommended_fee` at rotation.
- Wallet state (balances, nonces) is in-memory today — not persisted.
- **EIP-712 domain fields:** `name`, `version`, `chainId`, `verifyingContract`. `chainId` and `verifyingContract` come from `SEQ_CHAIN_ID` and `SEQ_APP_ADDRESS` (validated against the RPC chain id at startup). All four fields must be present on both sides — both the sequencer and the on-chain scheduler construct the domain via `sequencer_core::build_input_domain`, the canonical shared constructor.
- **EIP-712 domain fields:** `name`, `version`, `chainId`, `verifyingContract`. `chainId` and `verifyingContract` come from `CARTESI_SEQUENCER_BLOCKCHAIN_ID` and `CARTESI_SEQUENCER_APP_ADDRESS` (validated against the RPC chain id at startup). All four fields must be present on both sides — both the sequencer and the on-chain scheduler construct the domain via `sequencer_core::build_input_domain`, the canonical shared constructor.

### InputBox payload classification

Expand Down Expand Up @@ -235,21 +235,21 @@ Snapshot endpoints (`/finalized_state`, `/finalized_state/inclusion_block`, `/la

**Required:**

- `SEQ_ETH_RPC_URL`
- `SEQ_CHAIN_ID`
- `SEQ_APP_ADDRESS`
- `SEQ_BATCH_SUBMITTER_PRIVATE_KEY` or `SEQ_BATCH_SUBMITTER_PRIVATE_KEY_FILE`
- `CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT`
- `CARTESI_SEQUENCER_BLOCKCHAIN_ID`
- `CARTESI_SEQUENCER_APP_ADDRESS`
- `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY` or `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY_FILE`

**Optional:**

- `SEQ_HTTP_ADDR` (default `127.0.0.1:3000`)
- `SEQ_DATA_DIR` (default `sequencer-data`; DB file `sequencer.db` inside it)
- `SEQ_LONG_BLOCK_RANGE_ERROR_CODES`
- `SEQ_BATCH_SUBMITTER_IDLE_POLL_INTERVAL_MS` (default 5000)
- `SEQ_BATCH_SUBMITTER_CONFIRMATION_DEPTH` (default 2)
- `SEQ_PREEMPTIVE_MARGIN_BLOCKS` (default 300, ~1h at 12s/block)
- `SEQ_L1_READ_STALE_AFTER_BLOCKS` (default derived before the danger threshold)
- `SEQ_SECONDS_PER_BLOCK` (default 12)
- `CARTESI_SEQUENCER_HTTP_ADDR` (default `127.0.0.1:3000`)
- `CARTESI_SEQUENCER_DATA_DIR` (default `sequencer-data`; DB file `sequencer.db` inside it)
- `CARTESI_SEQUENCER_LONG_BLOCK_RANGE_ERROR_CODES`
- `CARTESI_SEQUENCER_BATCH_SUBMITTER_IDLE_POLL_INTERVAL_MS` (default 5000)
- `CARTESI_SEQUENCER_BATCH_SUBMITTER_CONFIRMATION_DEPTH` (default 2)
- `CARTESI_SEQUENCER_PREEMPTIVE_MARGIN_BLOCKS` (default 300, ~1h at 12s/block)
- `CARTESI_SEQUENCER_L1_READ_STALE_AFTER_BLOCKS` (default derived before the danger threshold)
- `CARTESI_SEQUENCER_SECONDS_PER_BLOCK` (default 12)

## Coding Conventions

Expand Down Expand Up @@ -289,10 +289,10 @@ cargo clippy --all-targets --all-features -- -D warnings
Run server:

```bash
SEQ_ETH_RPC_URL=http://127.0.0.1:8545 \
SEQ_CHAIN_ID=31337 \
SEQ_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
SEQ_BATCH_SUBMITTER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT=http://127.0.0.1:8545 \
CARTESI_SEQUENCER_BLOCKCHAIN_ID=31337 \
CARTESI_SEQUENCER_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
CARTESI_SEQUENCER_AUTH_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
cargo run -p sequencer
```

Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,22 @@ The batch submitter posts closed batches to L1's InputBox contract. Each batch c
## Running

```bash
SEQ_ETH_RPC_URL=http://127.0.0.1:8545 \
SEQ_CHAIN_ID=31337 \
SEQ_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
SEQ_BATCH_SUBMITTER_PRIVATE_KEY=0xac09...f2ff80 \
CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT=http://127.0.0.1:8545 \
CARTESI_SEQUENCER_BLOCKCHAIN_ID=31337 \
CARTESI_SEQUENCER_APP_ADDRESS=0x1111111111111111111111111111111111111111 \
CARTESI_SEQUENCER_AUTH_PRIVATE_KEY=0xac09...f2ff80 \
cargo run -p sequencer
```

Required: `SEQ_ETH_RPC_URL`, `SEQ_CHAIN_ID`, `SEQ_APP_ADDRESS`, `SEQ_BATCH_SUBMITTER_PRIVATE_KEY` (or `_FILE`).
Required: `CARTESI_SEQUENCER_BLOCKCHAIN_HTTP_ENDPOINT`, `CARTESI_SEQUENCER_BLOCKCHAIN_ID`, `CARTESI_SEQUENCER_APP_ADDRESS`, `CARTESI_SEQUENCER_AUTH_PRIVATE_KEY` (or `_FILE`).

Optional: `SEQ_HTTP_ADDR` (default `127.0.0.1:3000`), `SEQ_DATA_DIR` (default `sequencer-data` — SQLite file is `sequencer.db` inside; created if missing), `SEQ_PREEMPTIVE_MARGIN_BLOCKS` (default `300`), `SEQ_SECONDS_PER_BLOCK` (default `12`), `SEQ_LONG_BLOCK_RANGE_ERROR_CODES` (default `-32005,-32600,-32602,-32616`), `SEQ_BATCH_SUBMITTER_PRIVATE_KEY_FILE` (alternative to `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`.
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`.

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`
- `chain_id` and `verifying_contract` come from `CARTESI_SEQUENCER_BLOCKCHAIN_ID` and `CARTESI_SEQUENCER_APP_ADDRESS`

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

Expand Down
2 changes: 1 addition & 1 deletion docs/recovery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ The most common real-world trigger for `L1ViewStale` is a stalled RPC gateway: t

**Other workers during L1 outages**: the inclusion lane and API are purely local (SQLite) and continue operating. The input reader retries L1 polling with error logging. All L1-dependent workers log errors at the `error` level to alert operators.

The `seconds_per_block` parameter (default: 12 for Ethereum) is configurable via `SEQ_SECONDS_PER_BLOCK`. The L1 read-staleness threshold is configurable via `SEQ_L1_READ_STALE_AFTER_BLOCKS`; if unset, startup derives it before the write danger threshold. These estimates are conservative — they may cause earlier detection if blocks are slower than assumed. This is correct: better to crash early than to issue doomed soft confirmations.
The `seconds_per_block` parameter (default: 12 for Ethereum) is configurable via `CARTESI_SEQUENCER_SECONDS_PER_BLOCK`. The L1 read-staleness threshold is configurable via `CARTESI_SEQUENCER_L1_READ_STALE_AFTER_BLOCKS`; if unset, startup derives it before the write danger threshold. These estimates are conservative — they may cause earlier detection if blocks are slower than assumed. This is correct: better to crash early than to issue doomed soft confirmations.

## Dead Batches

Expand Down
8 changes: 4 additions & 4 deletions docs/threat-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,20 @@ These are preconditions the sequencer takes as given. They are neither "trust" n
The wall-clock fallback in [`sequencer/src/recovery/mod.rs`](../../sequencer/src/recovery/mod.rs) estimates missed blocks as:

```
estimated_missed_blocks = (now - last_sync_ms) / SEQ_SECONDS_PER_BLOCK
estimated_missed_blocks = (now - last_sync_ms) / CARTESI_SEQUENCER_SECONDS_PER_BLOCK
```

This assumes a **known, bounded-variance relationship** between elapsed wall-clock time and mined L1 block count. The assumption has three parts:

1. **Known average block time** — `SEQ_SECONDS_PER_BLOCK` (default 12s, Ethereum mainnet) accurately reflects the target chain's block cadence.
1. **Known average block time** — `CARTESI_SEQUENCER_SECONDS_PER_BLOCK` (default 12s, Ethereum mainnet) accurately reflects the target chain's block cadence.
2. **Bounded variance** — over the danger-threshold window (~4h on mainnet), the delta between `elapsed_seconds / avg_block_time` and actual mined blocks is small. On Ethereum mainnet this holds: slot proposers occasionally skip, but >99% of slots produce a block.
3. **Wall clock is monotonic and accurate** — the host's `SystemTime::now()` does not jump backward significantly or drift. Handled by saturating subtraction against clock backward jumps, but not against systematic drift.

**Where it matters.** Only on the fallback path — when L1 is unreachable and we cannot observe block numbers directly. When L1 is up, observed block numbers are authoritative and this assumption is not consulted.

**Violation modes.**
- **Chain with unstable block time.** A chain where average block time drifts substantially (e.g., PoW networks under major hashrate swings) would make the estimate less reliable. Mitigation: `SEQ_SECONDS_PER_BLOCK` should be tuned conservatively (overestimate block time → underestimate missed blocks → more cautious recovery triggers).
- **Operator misconfigures `SEQ_SECONDS_PER_BLOCK`.** Typo or copy-paste error pointing at the wrong chain's cadence. Operator-trust scope.
- **Chain with unstable block time.** A chain where average block time drifts substantially (e.g., PoW networks under major hashrate swings) would make the estimate less reliable. Mitigation: `CARTESI_SEQUENCER_SECONDS_PER_BLOCK` should be tuned conservatively (overestimate block time → underestimate missed blocks → more cautious recovery triggers).
- **Operator misconfigures `CARTESI_SEQUENCER_SECONDS_PER_BLOCK`.** Typo or copy-paste error pointing at the wrong chain's cadence. Operator-trust scope.
- **Significant host clock drift.** A sequencer host whose clock lags or leads the real-world by minutes per day could slowly desynchronize its danger estimates from reality.

**Corollary for test design.** To deterministically exercise the wall-clock fallback, tests must maintain this coupling: when advancing the L1 block count, they should also advance (or simulate) the corresponding wall-clock interval. Our e2e harness does the reverse — it rewinds `l1_safe_head.synced_at_ms` to an older timestamp, which is semantically equivalent to advancing the wall clock.
Expand Down
32 changes: 16 additions & 16 deletions docs/watchdog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ just setup && just canonical-build-machine-image && just watchdog-lua-deps
# Path A — full smoke (Anvil + sequencer + CM + compare), one command:
just test-watchdog-compare-harness

# Path B — two terminals: stack prints WATCHDOG_* exports, then init + tick:
# Path B — two terminals: stack prints CARTESI_WATCHDOG_* exports, then init + tick:
just devnet-for-watchdog # terminal 1 — leave running
# terminal 2: paste exports, then:
export WATCHDOG_LUA_ROOT="$(pwd)"
export WATCHDOG_LUA_BIN=lua
export WATCHDOG_LUA_DEPS=.deps/lua
export CARTESI_WATCHDOG_LUA_ROOT="$(pwd)"
export CARTESI_WATCHDOG_LUA_BIN=lua
export CARTESI_WATCHDOG_LUA_DEPS=.deps/lua
./watchdog/sequencer-watchdog init
./watchdog/sequencer-watchdog tick
```

The `sequencer-watchdog` wrapper wraps `init`/`tick` with an advisory `flock`
on `$WATCHDOG_STATE_DIR/run.lock`. Production schedulers must also prevent
on `$CARTESI_WATCHDOG_STATE_DIR/run.lock`. Production schedulers must also prevent
overlapping ticks
(`flock`, systemd, or Kubernetes `concurrencyPolicy: Forbid`).

Expand All @@ -51,7 +51,7 @@ The watchdog cycle and any test that hits HTTP need a native **`lcurl.so`** buil

```bash
just watchdog-lua-deps # idempotent; writes .deps/lua/lcurl.so
export WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua"
export CARTESI_WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua"
```

You also need **`cartesi-machine`** on `PATH` (in-process `cartesi`
Expand Down Expand Up @@ -166,12 +166,12 @@ then `head.json` is atomically replaced to point at it.

`init` stores the operator-provided bootstrap CM snapshot into this layout. `tick`
requires both `config.json` and `head.json`; it never bootstraps from env.
`WATCHDOG_L1_RPC_URL` is intentionally read at tick time, not persisted in
`CARTESI_WATCHDOG_BLOCKCHAIN_HTTP_ENDPOINT` is intentionally read at tick time, not persisted in
`config.json`, so operators can rotate RPC endpoints without rewriting watchdog
state.

- `WATCHDOG_CM_SNAPSHOT_DIR`
- `WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK`
- `CARTESI_WATCHDOG_CM_SNAPSHOT_DIR`
- `CARTESI_WATCHDOG_CM_SNAPSHOT_SAFE_BLOCK`

## How it runs

Expand All @@ -198,16 +198,16 @@ host scheduling should provide the same non-overlap guarantee. Each tick:

Runtime knobs:

- `WATCHDOG_L1_RPC_URL`: current L1 JSON-RPC endpoint for tick.
- `WATCHDOG_RETRY_ATTEMPTS`: bounded retry attempts per run, default `3`.
- `WATCHDOG_RETRY_DELAY_SEC`: delay between retry attempts, default `5`.
- `CARTESI_WATCHDOG_BLOCKCHAIN_HTTP_ENDPOINT`: current L1 JSON-RPC endpoint for tick.
- `CARTESI_WATCHDOG_RETRY_ATTEMPTS`: bounded retry attempts per run, default `3`.
- `CARTESI_WATCHDOG_RETRY_DELAY_SEC`: delay between retry attempts, default `5`.

## Local Tests

| Command | What it exercises |
|---------|-------------------|
| `just test-watchdog` | Lua unit tests (fake HTTP/RPC/CM; no live chain) |
| `just test-watchdog-e2e` | Real CM: advance, inspect; optional live compare if `WATCHDOG_E2E_SEQUENCER_URL` set |
| `just test-watchdog-e2e` | Real CM: advance, inspect; optional live compare if `CARTESI_WATCHDOG_E2E_SEQUENCER_URL` set |
| `just test-watchdog-compare-harness` | **Full E2E**: Anvil + devnet sequencer + `/finalized_state` + CM inspect + Lua `init`/`tick` |
| `just test-rollups-e2e` | All rollups e2e scenarios; includes watchdog genesis/non-genesis compare plus `watchdog_non_genesis_divergence_test` (needs Sepolia CM image) |
| `just test-watchdog-divergence-drill` | Synthetic divergence signal drill (`watchdog_event` + exit `2`) |
Expand All @@ -220,7 +220,7 @@ just doctor # fail fast before long harness runs
just canonical-build-machine-image # once, if out/ image is missing
just canonical-build-machine-image-sepolia # rollups-e2e divergence trial (auto-built by test-rollups-e2e)
just watchdog-lua-deps
export WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua"
export CARTESI_WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua"
```

### Lua unit tests
Expand All @@ -244,7 +244,7 @@ Scenarios (verbose `step NN/NN` logging):
- `prerequisites` — `cartesi-machine` on PATH and machine image present.
- `cm-inspect-state-query` — real `--cmio-inspect-state` with query `state`.
- `machine-cartesi-store-reload-advance` — store checkpoint snapshot, reload, advance again (in-process binding).
- `compare-runner-with-sequencer` — skipped unless `WATCHDOG_E2E_SEQUENCER_URL` is set.
- `compare-runner-with-sequencer` — skipped unless `CARTESI_WATCHDOG_E2E_SEQUENCER_URL` is set.

Rebuild the machine image after changing the canonical scheduler/dapp. A stale
image makes `cm-inspect-state-query` skip with `inspect endpoint not implemented`.
Expand Down Expand Up @@ -282,7 +282,7 @@ exists; it does **not** detect a stale guest. If you pulled SSZ/inspect changes,
| `finalized_state bytes mismatch (len 87 vs expected 76)` | Wrong golden (Sepolia fixture vs devnet sequencer) and/or raw HTTP chunked framing | Harness expects **devnet** SSZ; `lcurl` decodes chunked responses automatically |
| `CM inspect bytes mismatch (len 27 vs expected 76)` | **Stale CM image** still returns JSON `{"balances":{},"nonces":{}}` from pre-SSZ inspect | `just canonical-build-machine-image` then rerun harness |
| `inspect endpoint not implemented` | Older guest without inspect handler | Same rebuild as above |
| Harness passes step 1–2 but Lua compare fails | `WATCHDOG_LUA_DEPS` or checkpoint/bootstrap | Set `export WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua"`; see [`getting-started.md`](getting-started.md) env table |
| Harness passes step 1–2 but Lua compare fails | `CARTESI_WATCHDOG_LUA_DEPS` or checkpoint/bootstrap | Set `export CARTESI_WATCHDOG_LUA_DEPS="$(pwd)/.deps/lua"`; see [`getting-started.md`](getting-started.md) env table |

Manual equivalent of the recipe:

Expand Down
2 changes: 1 addition & 1 deletion docs/watchdog/design-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ checkpoint layout. `tick` never bootstraps from env; missing `head.json` is an
operator error.

`config.json` stores stable deployment identity (`sequencer_url`,
`input_box_address`, `app_address`, retry knobs). `WATCHDOG_L1_RPC_URL` is read
`input_box_address`, `app_address`, retry knobs). `CARTESI_WATCHDOG_BLOCKCHAIN_HTTP_ENDPOINT` is read
at tick time rather than persisted, because provider URLs and credentials are
operational inputs that may rotate.

Expand Down
Loading
Loading