diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 3d4c38e..ee4e407 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -31,13 +31,13 @@ Every step of the bootstrap pipeline must use either: Examples currently in use: - `db-synthesizer` (upstream `ouroboros-consensus`, mode (a)) — fixture chain DB synthesis +- `db-analyser` (upstream `ouroboros-consensus`, mode (a)) — bundled in the runtime image as the engine `amaru create-snapshots` drives - `header-extractor` (in-repo, mode (b), consumes `ouroboros-consensus` as a library) — chain DB queries -- `ledger-state-emitter` (in-repo, mode (b), consumes `ouroboros-consensus` and `cardano-ledger` as libraries) — Amaru-compatible ledger-state emission for the pinned node release -- `amaru` ([`pragma-org/amaru`](https://github.com/pragma-org/amaru), mode (a)) — convert / import / run +- `ledger-state-emitter` (in-repo, mode (b), consumes `ouroboros-consensus` and `cardano-ledger` as libraries) — standalone Amaru-compatible ledger-state emission for the pinned node release; no longer in the producer pipeline +- `amaru` ([`pragma-org/amaru`](https://github.com/pragma-org/amaru), mode (a), currently consumed via the `lambdasistemi/amaru` `feat/testnet-bootstrap` branch — upstream main plus a minimal upstreamable delta — SHA-pinned in `flake.lock`) — create-snapshots / bootstrap / run -`db-analyser` and `snapshot-converter` remain available for historical -Phase 0 checks, but they are not in the bootstrap-producer runtime -image after the R-011 pivot. +`snapshot-converter` remains available for historical Phase 0 checks, +but it is not in the bootstrap-producer runtime image. What is **not** permitted: @@ -118,10 +118,18 @@ Amendments require a PR, a rationale in the PR description, and the All PRs must verify compliance with these principles. When the constitution and a convenience are in conflict, the constitution wins. -**Version**: 1.2.0 | **Ratified**: 2026-04-27 | **Last Amended**: 2026-04-29 +**Version**: 1.3.0 | **Ratified**: 2026-04-27 | **Last Amended**: 2026-06-12 ### Amendment log +- **1.3.0 (2026-06-12)**: Principle II examples updated after the + producer migrated to upstream `amaru create-snapshots` + + `amaru bootstrap`. `db-analyser` returned to the runtime image as the + engine that `create-snapshots` drives; `ledger-state-emitter` is + retained as a standalone tool but is no longer the runtime + ledger-state producer; the Amaru example commands are now + create-snapshots / bootstrap / run, consumed via the SHA-pinned + `lambdasistemi/amaru` `feat/testnet-bootstrap` branch. - **1.2.0 (2026-04-29)**: Principle II examples updated after the Phase 2 R-011 pivot. `ledger-state-emitter` is now the runtime ledger-state producer, consuming `ouroboros-consensus` and diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8789748 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,54 @@ +# Repository Agent Guide + +## What this repo is + +`amaru-bootstrap` builds the +`ghcr.io/lambdasistemi/amaru-bootstrap-producer` Docker image and the +tools inside it, used to start relay-only +[Amaru](https://github.com/pragma-org/amaru) nodes on custom Cardano +testnets (primarily the Antithesis testnets in +`cardano-foundation/cardano-node-antithesis`). Amaru cannot sync such +testnets from genesis, so the `bootstrap-producer` orchestrator reads a +cardano-node ChainDB, drives `amaru create-snapshots` and +`amaru bootstrap`, and commits a bundle of ledger/chain stores that +`amaru run` opens. Everything is pinned to one cardano-node release at a +time (currently 10.7.1) because ledger-state CBOR drifts between +releases. + +Read [`.specify/memory/constitution.md`](.specify/memory/constitution.md) +before making design decisions — it gates them (no forks of upstream +Cardano code, stock tools + custom orchestration, SHA pinning, Nix-first). + +## How to work here + +- Build/test everything CI builds: `just build-gate` +- Full local CI mirror (Build Gate + Phase 0 smoke verdict + Docker live + verifier): `just ci` +- Phase 0 smoke test against the vendored fixture: `just smoke` +- One flake check: `nix build .#checks.x86_64-linux.` (never + `nix develop -c cabal test` in CI) +- Run a tool: `nix run .#bootstrap-producer`, `.#smoke-test`, + `.#header-extractor`, `.#ledger-state-emitter`, `.#amaru`, + `.#db-synthesizer`, `.#db-analyser`, `.#snapshot-converter` +- Shell lint: `just shellcheck`; scripts are bash with + `set -euo pipefail`, shellcheck-clean +- Haskell: GHC 9.6, fourmolu (70-char limit, leading commas/arrows), + Haddock on all exports, Hackage-ready (`cabal check` clean) +- Pinning discipline: cabal `source-repository-package` entries carry + nix32 `--sha256` (not SRI); Docker images are tagged by commit SHA, + never `:main`/`:latest` +- Workflow: every ticket runs through speckit + (`specify` → `plan` → `tasks` → `implement`); specs live under + `specs/`; linear history on `main` (rebase merge only); `Build Gate` + is the required CI check +- CI runs on self-hosted `nixos` runners; the docs site deploys via + `mkdocs gh-deploy` from `.github/workflows/deploy-docs.yml` + +## Skills + +Activatable procedures live under `skills/`. Load the one whose +description matches your task: + +- [`skills/amaru-bootstrap-guide/`](skills/amaru-bootstrap-guide/SKILL.md) + — repository map, build/test/run commands, code navigation, and how to + use the bootstrap-producer image and its tools. diff --git a/CLAUDE.md b/CLAUDE.md index 7be5247..74bd45d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,42 +1,17 @@ # amaru-bootstrap -Project-specific guidance for AI agents. **Read [`.specify/memory/constitution.md`](./.specify/memory/constitution.md) first** — it gates all design decisions. - -## Workflow - -- Every ticket runs through speckit: `speckit-specify` -> `speckit-plan` -> `speckit-tasks` -> `speckit-implement`. No implementation without a spec on disk. -- One worktree per branch. Main repo (`/code/amaru-bootstrap`) stays on `main`; feature work happens in `/code/amaru-bootstrap-`. -- Linear history on `main` (rebase merge only). -- Branch protection: `Build Gate` required, admin bypass. - -## Tooling - -- **Nix-first**: every binary built and run via the flake. Never `pip install`, `cargo install`, `cabal install`, `curl | sh`. -- **Haskell tools** (`db-synthesizer`, `db-analyser`): haskell.nix + CHaP, declared in `cabal.project`, exposed via `nix/iog-tools.nix`. -- **Rust tool** (`amaru`): crane wrapping `pragma-org/amaru` flake input, exposed via `nix/amaru.nix`. -- **Orchestrator** (`scripts/smoke-test.sh`): bash, shellcheck-clean, `set -euo pipefail`. -- **CI**: `runs-on: nixos`. Build Gate first; downstream jobs gated. Never `nix develop -c cabal test` in CI — always `nix build .#checks..`. - -## Pinning discipline (constitution Principle III) - -- All Haskell SRPs in `cabal.project` carry `--sha256:` in **nix32** format (NOT SRI). Use `nix flake prefetch` then `nix hash convert --to nix32`. -- All Docker images in any production-facing context tagged by commit SHA. `:main` is a bug. - -## Speckit phase budgets - -- `plan.md` <= 200 lines. Push detail into `research.md`, `data-model.md`, `contracts/`, `quickstart.md`. -- After each `speckit-implement` phase, compact a Status block back into `plan.md`. - -## Active feature - -- `001-snapshot-format-smoke` — Phase 0 hypothesis-validation smoke test. See [`specs/001-snapshot-format-smoke/`](./specs/001-snapshot-format-smoke/). - - - - -## Active Technologies -- Bash 5.x (orchestrator script); Haskell GHC 9.6.x (existing tools, plus the small header-extractor tool from R-001) (003-amaru-bootstrap-producer) -- filesystem only — read cluster's chain DB, write the bundle to a docker volume. No database, no state. (003-amaru-bootstrap-producer) - -## Recent Changes -- 003-amaru-bootstrap-producer: Added Bash 5.x (orchestrator script); Haskell GHC 9.6.x (existing tools, plus the small header-extractor tool from R-001) +Agent guidance for this repository lives in [AGENTS.md](AGENTS.md). +Start there; it covers what the repo is, the working commands, the +constitution gate, and the skills under `skills/`. + +Claude-specific notes only below this line. + +- Read [`.specify/memory/constitution.md`](.specify/memory/constitution.md) + before design decisions — it gates them. +- Every ticket runs through speckit: `speckit-specify` -> + `speckit-plan` -> `speckit-tasks` -> `speckit-implement`. No + implementation without a spec on disk. `plan.md` <= 200 lines; push + detail into `research.md`, `data-model.md`, `contracts/`, + `quickstart.md`, and compact a Status block back into `plan.md` + after each implement phase. +- One worktree per branch; the main checkout stays on `main`. diff --git a/HANDOFF-T019b.md b/HANDOFF-T019b.md deleted file mode 100644 index 4412821..0000000 --- a/HANDOFF-T019b.md +++ /dev/null @@ -1,68 +0,0 @@ -# Handoff: T019b - `ledger-state-emitter` - -**Status**: implemented on branch `003-amaru-bootstrap-producer`. - -T019b is no longer a design-only item. The PR now contains the -`ledger-state-emitter` library and executable, the real -`bootstrap-producer` app/image wiring, and flake checks for both the -full synthesized pipeline and the concurrent producer race. - -## What changed - -`ledger-state-emitter` replaces the runtime -`db-analyser --store-ledger` + `snapshot-converter` pair. It opens the -cardano-node chain DB, replays to the immutable target slot selected by -`header-extractor tip-info`, and writes a single Legacy -`ExtLedgerState` CBOR file for `amaru convert-ledger-state`. - -The emitter targets the repository's pinned `cardano-node 10.7.1` -dependency set. This matters: Cardano ledger state CBOR drifts laterally -between node releases, so a successful compile against arbitrary ledger -packages is not enough to prove compatibility. - -## Amaru projection - -The emitted CBOR is intentionally not raw node-10.7.1 ledger CBOR. It is -the Amaru bootstrap projection of that state: - -- UTxO entries are encoded canonically through `EncCBOR` for `TxIn` and - `TxOut`, not through the consensus ledger-table `MemPack` shortcut. -- The Shelley ledger wrapper omits the node-10.7.1 Peras certificate - field because Amaru's converter still walks the pre-Peras wrapper - shape. -- Conway/Dijkstra `PState` is projected to the three fields Amaru - imports: current pool params, future pool params, retirements. The - node-side VRF-key index is omitted. -- Conway/Dijkstra `DState` is projected into Amaru's legacy delegation - state wrapper. Balance, deposit, stake-pool delegation, and DRep - delegation are preserved; pointer indexes and the deposits accumulator - are placeholders because Amaru skips them during bootstrap. - -## Verification - -Relevant checks: - -```bash -nix build .#checks.x86_64-linux.ledger-state-emitter -nix build .#checks.x86_64-linux.bootstrap-producer-synthesized -nix build .#checks.x86_64-linux.bootstrap-producer-bats -just build-gate -``` - -`bootstrap-producer-synthesized` runs the real producer pipeline against -the synthesized Conway-ready `testnet_42` chain DB and asserts that -`amaru convert-ledger-state`, `amaru import-ledger-state`, -`amaru import-headers`, and `amaru import-nonces` all succeed. - -`bootstrap-producer-bats` now includes T016: two real producer processes -race against the same era-ready chain DB and must both exit 0 with a -single complete final bundle and no surviving temp directories. - -## Specs - -The contract is documented in: - -- `specs/003-amaru-bootstrap-producer/research.md#r-011` -- `specs/003-amaru-bootstrap-producer/data-model.md` -- `specs/003-amaru-bootstrap-producer/contracts/bootstrap-producer-cli.md` -- `specs/003-amaru-bootstrap-producer/tasks.md` diff --git a/README.md b/README.md index c58ec53..cf46586 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Bootstrap image and tools for running [Amaru](https://github.com/pragma-org/amaru) on custom Cardano testnets. -## What this repo provides +## What is this Amaru cannot currently synchronise from genesis on arbitrary custom testnets. It must start from prepared ledger and chain stores, plus the @@ -20,32 +20,116 @@ entrypoints: `amaru run`. - `bootstrap-producer`: the lower-level one-shot bundle producer used by the relay entrypoint and by local verification. It reads a cardano-node - ChainDB, emits the Amaru ledger projection, imports headers and nonces, - then exits with a classed status code. + ChainDB, derives snapshot targets from the chain itself, runs + `amaru create-snapshots` and `amaru bootstrap`, then exits with a + classed status code. The image default Docker entrypoint remains `bootstrap-producer` for standalone compatibility. Antithesis Compose stacks must override it to `amaru-relay-bootstrap`. -## Production data flow +## Architecture + +```mermaid +flowchart LR + node["paired cardano-node\nChainDB + config"] --> producer["bootstrap-producer\n(one-shot)"] + relay["amaru-relay-bootstrap\n(long-lived container)"] --> producer + producer --> extractor["header-extractor\ntip-info / list-blocks"] + producer --> snaps["amaru create-snapshots\n(db-analyser engine)"] + producer --> boot["amaru bootstrap"] + boot --> bundle["bundle\nledger.net.db + chain.net.db\nsnapshots + era-history.json"] + bundle --> run["exec amaru run"] + relay --> run + runtime["/amaru-runtime\nera-history.json\nglobal-parameters.json"] --> run +``` The production path does not use `db-synthesizer`. It reads a live cardano-node ChainDB and runs: -1. `ledger-state-emitter` - reads the pinned cardano-node 10.7.1 ledger - state and emits the Amaru bootstrap projection. -2. `amaru convert-ledger-state` - converts the projected ledger states - into Amaru snapshot artifacts. -3. `header-extractor` - extracts the headers needed by Amaru's chain - store and nonce alignment. -4. `amaru import-ledger-state`, `amaru import-headers`, and - `amaru import-nonces` - populate the stores that `amaru run` opens. +1. `header-extractor tip-info` / `list-blocks` - poll the immutable DB + until the chain is era-ready, then pick the last block of each of + the three most recent completed epochs as snapshot targets. +2. `amaru create-snapshots` - materialize per-epoch snapshot + directories (with packaged bootstrap headers) directly from the + local chain DB, using `--targets-file` and `--cardano-db-dir` to + bypass Koios and Mithril. Internally this drives the `db-analyser` + engine, which is why `db-analyser` is bundled in the image. +3. era-history sidecars - the producer writes + `history...json` next to each snapshot and an + `era-history.json` at the bundle root, both built from the mounted + Shelley genesis `epochLength`. +4. `amaru bootstrap` - populate `ledger..db` and + `chain..db` from the snapshots, deriving nonces and + importing the packaged headers. +5. atomic commit - `mv -T` of the staging directory into + `/`. `db-synthesizer` remains in this repository only for fixtures and checks. It fabricates ChainDB inputs for CI; it is not in the Antithesis runtime -path. +path. `ledger-state-emitter` (the in-repo node-10.7.1 ledger projection +tool) is still built, shipped, and exposed as a flake app, but it is no +longer invoked by the producer pipeline since the migration to upstream +`create-snapshots` + `bootstrap`. + +## Install + +The supported runtime artifact is the published Docker image: + +```text +ghcr.io/lambdasistemi/amaru-bootstrap-producer: +``` + +After CI succeeds on `main`, GitHub Actions publishes a tag named by the +full commit SHA. After CI succeeds on a same-repository pull request, it +also publishes: + +```text +ghcr.io/lambdasistemi/amaru-bootstrap-producer: +ghcr.io/lambdasistemi/amaru-bootstrap-producer:pr-- +``` + +Downstream Compose files should pin a full commit-SHA tag. The project +does not publish moving runtime tags such as `latest`. + +Local image build (x86_64-linux only): + +```bash +nix build .#packages.x86_64-linux.bootstrap-producer-image \ + -o result-bootstrap-producer-image +docker load -i result-bootstrap-producer-image +``` + +The matching CI artifact is named `bootstrap-producer-image-` +and contains `amaru-bootstrap-producer-.tar.gz`. + +## Quickstart + +Produce a bundle from an existing cardano-node ChainDB without Docker: + +```bash +nix run .#bootstrap-producer -- \ + /path/to/cardano-node/db \ + /path/to/cardano-node/config \ + /tmp/amaru-bundle \ + testnet_42 +``` + +That command waits for the chain to become era-ready, writes +`/tmp/amaru-bundle/testnet_42`, and exits. For the full Compose relay +shape, follow the [tutorial](docs/tutorial.md). + +## Usage -## Antithesis relay contract +All tools are exposed as flake apps: + +| App | Purpose | +|-----|---------| +| `nix run .#bootstrap-producer` | One-shot bundle producer (` `) | +| `nix run .#smoke-test` | Phase 0 format-compatibility smoke test (` `) | +| `nix run .#header-extractor` | Immutable chain-DB queries: `tip-info`, `list-blocks`, `get-header` | +| `nix run .#ledger-state-emitter` | Standalone node-10.7.1 ledger-state projection (not in the producer pipeline) | +| `nix run .#amaru` | The pinned Amaru binary | +| `nix run .#db-synthesizer` / `.#db-analyser` / `.#snapshot-converter` | Pinned upstream consensus tools | In the downstream [`cardano-foundation/cardano-node-antithesis`](https://github.com/cardano-foundation/cardano-node-antithesis) @@ -91,45 +175,26 @@ must contain: - `global-parameters.json`: the custom testnet consensus parameters passed to `--global-parameters-file`. -## Published images - -After CI succeeds on `main`, GitHub Actions publishes: - -```text -ghcr.io/lambdasistemi/amaru-bootstrap-producer: -``` - -After CI succeeds on a same-repository pull request, it also publishes: - -```text -ghcr.io/lambdasistemi/amaru-bootstrap-producer: -ghcr.io/lambdasistemi/amaru-bootstrap-producer:pr-- -``` - -Downstream Compose files should pin a full commit-SHA tag. The project -does not publish moving runtime tags such as `latest`. - -Local build: - -```bash -nix build .#packages.x86_64-linux.bootstrap-producer-image \ - -o result-bootstrap-producer-image -docker load -i result-bootstrap-producer-image -``` - -The matching CI artifact is named `bootstrap-producer-image-` -and contains `amaru-bootstrap-producer-.tar.gz`. - -## Compatibility target +### Compatibility target This repository currently targets `cardano-node 10.7.1`. That is deliberate: Cardano ledger-state CBOR changes across node releases, so compiling against a random ledger package set is not enough. Retargeting the producer means updating `cabal.project`, `flake.lock`, and the documented projection in -`specs/003-amaru-bootstrap-producer/research.md#r-011`. +[`specs/003-amaru-bootstrap-producer/research.md#r-011`](specs/003-amaru-bootstrap-producer/research.md). + +## Documentation + +The MkDocs site is published at + and starts at +[`docs/index.md`](docs/index.md). The current operator path is +[`docs/tutorial.md`](docs/tutorial.md), and the Antithesis-specific +contract is in [`docs/antithesis.md`](docs/antithesis.md). -## Local verification +For AI agents, start at [AGENTS.md](AGENTS.md). + +## Development ```bash just ci @@ -153,12 +218,6 @@ These checks prove bundle production, Amaru import, and Amaru startup alignment for the pinned release boundary. They are not exhaustive mainnet ledger-content coverage. -## Documentation - -The MkDocs site starts at [`docs/index.md`](docs/index.md). The current -operator path is [`docs/tutorial.md`](docs/tutorial.md), and the -Antithesis-specific contract is in [`docs/antithesis.md`](docs/antithesis.md). - ## License Apache-2.0. See [`LICENSE`](LICENSE). diff --git a/docs/antithesis.md b/docs/antithesis.md index 239d7e9..47ffea7 100644 --- a/docs/antithesis.md +++ b/docs/antithesis.md @@ -91,10 +91,20 @@ variables: | `AMARU_RELAY_CONFIG_DIR` | no | `/cardano/config/configs` | Mounted node config directory. | | `AMARU_RELAY_RUNTIME_DIR` | no | `/amaru-runtime` | Runtime JSON directory. | | `AMARU_RELAY_STARTUP_DIR` | no | `/startup` | Startup marker directory. | +| `AMARU_WAIT_DEADLINE_SECONDS` | no | `30` | Per-attempt era-readiness deadline exported to the producer. | +| `AMARU_CLUSTER_READY_DEADLINE_SECONDS` | no | `30` | Per-attempt chain-DB-appears deadline exported to the producer. | +| `AMARU_POLL_INTERVAL_SECONDS` | no | `5` | Producer poll interval exported to the producer. | +| `BOOTSTRAP_PRODUCER_BIN` | no | `/bin/bootstrap-producer` | Producer binary invoked by the loop. | +| `AMARU_BIN` | no | `/bin/amaru` | Amaru binary used for the final `exec`. | The first two positional arguments may also provide `RELAY_NAME` and `AMARU_PEER`, but environment variables win. +The wrapper deliberately tightens the producer's own deadlines (which +default to 300 s / 5400 s standalone) to 30 seconds per attempt: the +outer retry loop, which refreshes the `/live` snapshot between attempts, +is the right place to wait for the chain to mature. + ## Startup Marker The entrypoint writes this file before doing bootstrap work: diff --git a/docs/architecture.md b/docs/architecture.md index 1449543..c7b6352 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,8 +6,10 @@ The repository has two layers: - `bootstrap-producer`: the one-shot producer primitive called by the relay wrapper and by local checks. -The critical code boundary is still release-pinned ledger projection: -`ledger-state-emitter` targets one cardano-node release at a time. This +The critical code boundary is still the release-pinned ledger-state +format: the whole toolset (`header-extractor`, the `db-analyser` engine +that `amaru create-snapshots` drives, and the standalone +`ledger-state-emitter`) targets one cardano-node release at a time. This branch targets `cardano-node 10.7.1`. ## Relay Runtime @@ -21,9 +23,9 @@ flowchart LR relay --> scratch["private scratch\n/srv/amaru/.work"] scratch --> producer["bootstrap-producer\nretry attempts"] - producer --> produced["scratch bundle\n/"] + producer --> produced["scratch bundle\n<scratch-out>/<network>"] produced --> promote["promote complete bundle"] - promote --> stores["/srv/amaru\nledger.*.db\nchain.*.db\nnonces.json"] + promote --> stores["/srv/amaru\nledger.*.db\nchain.*.db\nera-history.json"] stores --> amaru["exec amaru run"] runtime --> amaru amaru --> peer["peer cardano-node\n$AMARU_PEER"] @@ -42,19 +44,16 @@ not stop after bootstrap; it `exec`s `amaru run`. ```mermaid flowchart LR - mount["ChainDB snapshot\nconfig"] --> preflight["pre-flight\nconfig + era readiness"] - preflight --> emitter["ledger-state-emitter x3\nnode-10.7.1 projection"] - emitter --> legacy["Legacy ExtLedgerState CBOR\nlatest + two prior epochs"] - legacy --> convert["amaru convert-ledger-state x3"] - convert --> snapshots["snapshot CBOR\nhistory JSON\nnonces JSON"] - - mount --> headers["header-extractor\nlist-blocks/get-header"] - headers --> headerFiles["header.*.cbor files"] - - snapshots --> compose["nonce tail rewrite"] - headerFiles --> compose - compose --> imports["amaru import-ledger-state\namaru import-headers\namaru import-nonces"] - imports --> bundle["complete bundle\n/"] + mount["ChainDB snapshot\nconfig"] --> preflight["pre-flight\nconfig + era readiness\nheader-extractor tip-info"] + preflight --> blocks["preflight block list\nheader-extractor list-blocks"] + blocks --> targets["targets.json\nsnapshots.json\nlast block of each of the\n3 completed epochs"] + targets --> snaps["amaru create-snapshots\ndb-analyser engine\n--targets-file + --cardano-db-dir"] + snaps --> snapdirs["snapshots/<net>/<slot>.<hash>/\nwith packaged bootstrap headers"] + snapdirs --> sidecars["era-history sidecars\nhistory.<slot>.<hash>.json\n+ bundle era-history.json"] + sidecars --> boot["amaru bootstrap\nderives nonces\nimports packaged headers"] + boot --> stores["ledger.<net>.db\nchain.<net>.db"] + stores --> commit["mv -T staging final\natomic commit"] + commit --> bundle["complete bundle\n<bundle>/<network>"] ``` In standalone mode the producer writes `/`. In relay @@ -66,10 +65,16 @@ mode it writes to scratch, and the wrapper promotes the contents of |-- chain..db/ |-- ledger..db/ |-- snapshots/ -|-- nonces.json -`-- headers/ +`-- era-history.json ``` +Nonces and bootstrap headers are baked into `chain..db` by +`amaru bootstrap`; they are no longer separate bundle artefacts. The +`snapshots//` directory keeps the materialized epoch snapshots +and their era-history sidecars for re-bootstrap; `era-history.json` at +the bundle root is the consume-time override for +`amaru run --era-history-file`. + ## Live ChainDB Contract ```mermaid @@ -89,9 +94,12 @@ because node-10.7.1's consensus ImmutableDB validation path opens chunk files through APIs that reject a read-only filesystem. The relay wrapper therefore copies the paired cardano-node `/live` state into private writable scratch before invoking the producer. The producer behavior is -immutable-only: readiness comes from immutable chunks, and the ledger -replay uses an in-memory LedgerDB backend rather than flushing into the -node-owned LedgerDB. +immutable-only: readiness comes from immutable chunks, and +`amaru create-snapshots` is given an isolated `--cardano-db-dir` view in +which the immutable chunks are symlinked from the source ChainDB while +the ledger snapshots its `db-analyser` engine materializes land in +producer-owned writable directories. The producer never mutates the +source ChainDB, so concurrent producers can run against the same chain. ## Relay State Machine @@ -124,31 +132,34 @@ instead of blocking the whole setup behind a one-shot service. ```mermaid stateDiagram-v2 [*] --> Preflight - Preflight --> Success: existing complete bundle - Preflight --> ClusterNotReady: chain DB timeout - Preflight --> ChainNotReady: era-readiness timeout - Preflight --> Emit: ready immutable tip - - Emit --> Convert: Legacy CBOR written - Emit --> EmitError: emitter failed + Preflight --> Success: existing complete bundle (rc 0) + Preflight --> ClusterNotReady: chain DB timeout (rc 1) + Preflight --> ChainNotReady: era-readiness timeout (rc 2) + Preflight --> ConfigError: config or genesis invalid (rc 3) + Preflight --> ExtractError: chain DB unreadable (rc 7) + Preflight --> Targets: era-readiness predicate holds - Convert --> ExtractHeaders: snapshot converted - Convert --> ConvertError: amaru convert failed + Targets --> CreateSnapshots: targets.json + snapshots.json written + Targets --> TargetsError: block list incomplete (rc 5) - ExtractHeaders --> ComposeNonces: headers written - ExtractHeaders --> ExtractError: header extraction failed + CreateSnapshots --> EraSidecars: 3 snapshot dirs materialized + CreateSnapshots --> SnapshotsError: amaru create-snapshots failed (rc 6) - ComposeNonces --> Import: nonces.json written - ComposeNonces --> NonceError: nonce rewrite failed + EraSidecars --> Bootstrap: history sidecars + era-history.json + EraSidecars --> SnapshotsError: sidecar write failed (rc 6) - Import --> Commit: Amaru stores populated - Import --> ImportError: amaru import failed + Bootstrap --> Commit: ledger + chain stores populated + Bootstrap --> BootstrapError: amaru bootstrap failed (rc 9) - Commit --> Success: mv -T temp final + Commit --> Success: mv -T staging final Commit --> Success: another producer won race - Commit --> CommitError: final bundle incomplete + Commit --> CommitError: rename failed (rc 10) ``` +Any other uncaught failure exits with `64 + rc` via the internal-error +trap. The relay wrapper treats exit codes 1, 2, 5, 6, 7, and 8 as +transient and retries; 0 promotes; anything else is fatal. + ## Runtime Parameters The relay passes deployment-provided runtime JSON to `amaru run`: @@ -159,9 +170,12 @@ The relay passes deployment-provided runtime JSON to `amaru run`: ``` These files must match the custom testnet genesis/config used by the -paired cardano-node. They are separate from the snapshot sidecar history -files that `amaru convert-ledger-state` writes next to each converted -snapshot. +paired cardano-node. They are separate from the +`history...json` sidecars the producer writes next to each +snapshot directory (consumed by `amaru bootstrap`) and from the +`era-history.json` it writes at the bundle root (the consume-time +override for `amaru run --era-history-file`). All three documents are +built from the same genesis `epochLength`. ## Node-Release Boundary @@ -173,18 +187,19 @@ flowchart TB ledger["cardano-ledger-* versions"] end - Pinned --> emitter["ledger-state-emitter"] - emitter --> projection["Amaru bootstrap projection"] - projection --> amaru["Amaru importer"] + Pinned --> tools["in-repo tools\nheader-extractor\nledger-state-emitter"] + Pinned --> analyser["db-analyser\ncreate-snapshots engine"] + analyser --> amaru["amaru create-snapshots\n+ amaru bootstrap"] + tools --> amaru other["Future node release"] -. "requires retargeting" .-> Pinned ``` Retargeting to another node release is an explicit project task. It is -not just a Cabal compile check: the emitted ledger-state shape has to -match what Amaru imports for that release. +not just a Cabal compile check: the ledger-state CBOR that the pinned +tools read and that Amaru imports has to match for that release. -## Ledger-State Projection +## Ledger-State Projection (standalone emitter) ```mermaid flowchart LR @@ -203,6 +218,13 @@ The projection preserves the fields Amaru imports and omits node-side acceleration or wrapper fields that Amaru does not consume during bootstrap. +`ledger-state-emitter` implements this projection as an in-repo +executable. Since the producer migrated to upstream +`amaru create-snapshots` + `amaru bootstrap`, the emitter is no longer +part of the producer pipeline; it remains in the image and as the flake +app `nix run .#ledger-state-emitter` for standalone snapshot emission +and debugging against the pinned node release. + ## CI Startup Proof ```mermaid @@ -213,7 +235,7 @@ sequenceDiagram participant Amaru as amaru run Check->>Producer: synthesize chain DB, produce bundle - Producer->>Bundle: import ledger, headers, nonces + Producer->>Bundle: create-snapshots + bootstrap stores Check->>Bundle: copy to writable test directory Check->>Amaru: run with ledger-dir and chain-dir Amaru-->>Check: build_ledger trace diff --git a/docs/bootstrap-producer.md b/docs/bootstrap-producer.md index f6ee4cc..a33993a 100644 --- a/docs/bootstrap-producer.md +++ b/docs/bootstrap-producer.md @@ -58,60 +58,90 @@ the relay startup marker and the relay process continues as `amaru run`. 1. Check whether `/` is already complete. If so, exit 0. -2. Validate the node config and wait for the chain DB to appear. -3. Poll `header-extractor tip-info` until the immutable tip is in - Conway and at least two Conway epochs are available. -4. Run `ledger-state-emitter` at the selected target slot and the two - preceding epoch slots. -5. Run `amaru convert-ledger-state` for all emitted ledger states. -6. Correct the converted current-era history sidecars from the node - genesis `epochLength`. -7. Run `header-extractor list-blocks` and `get-header` to collect the - headers Amaru needs. -8. Rewrite `nonces.json` so `tail` points at the previous-epoch header - hash. -9. Run `amaru import-ledger-state`, `amaru import-headers`, and - `amaru import-nonces`. -10. Atomically rename the unique temp directory into the final bundle - path. +2. Validate the node config and genesis (`config.json`, + `shelley-genesis.json`, positive `epochLength`) and wait for the + chain DB to appear. +3. Poll `header-extractor tip-info` until the era-readiness predicate + holds: the immutable tip is in Conway, the tip epoch is at least 3, + and `header-extractor list-blocks` finds a block in each of the + three most recent completed epochs, all at or after the Conway fork + slot. The snapshot targets are the last immutable block of each of + those three epochs. +4. Compose `targets.json` (epoch/slot/hash/parent_point) and + `snapshots.json` from the chain's own block list, bypassing Koios. +5. Run `amaru create-snapshots` with `--targets-file` and an isolated + `--cardano-db-dir` (immutable chunks symlinked from the source + chain DB), materializing one snapshot directory per epoch with + packaged bootstrap headers. +6. Write a `history...json` era-history sidecar next to + each snapshot and an `era-history.json` at the bundle root, both + built from the genesis `epochLength`. +7. Run `amaru bootstrap`, which populates `ledger..db` and + `chain..db`, deriving nonces from the latest snapshot and + importing the packaged headers. +8. Atomically rename (`mv -T`) the unique staging directory into the + final bundle path. The final layout is: ```text -// -├── chain..db/ -├── ledger..db/ -├── snapshots/ -├── nonces.json -└── headers/ +/ +├── .logs/ # per-phase stderr logs +└── / + ├── chain..db/ + ├── ledger..db/ + ├── snapshots// + └── era-history.json ``` +Nonces and bootstrap headers are baked into `chain..db` by +`amaru bootstrap`; they are no longer separate bundle artefacts. + The ledger store must contain `live/` plus at least three numeric historical epoch directories. `amaru run` opens the live ledger and then loads the two prior historical snapshots for rewards and leader-schedule -stake distribution. A bundle with only the latest imported snapshot can -pass `amaru import-ledger-state` and still fail to start. - -The chain store must also contain the exact header for the ledger tip. -If the latest converted snapshot is -`snapshots/..cbor`, the bundle must include -`headers/header...cbor` and import it into -`chain..db/`; otherwise `amaru run` fails during startup with -`ledger tip header not found`. - -For custom testnets, `amaru import-ledger-state` reads -`snapshots/history...json` next to each snapshot. Amaru's -converter currently fills the open-ended current era with the network -default epoch size, so the producer rewrites that sidecar to the -`epochLength` from the mounted Shelley genesis before import. Without -that correction a 120-slot Antithesis testnet imports slot 9, then fails -at slot 129 because the sidecar still maps slot 129 to epoch 0. - -`amaru import-nonces` also receives the producer-generated era-history -input so imported nonce epochs match short-epoch testnets. The runtime -`era-history.json` and `global-parameters.json` consumed later by -`amaru run` are deployment files mounted at `/amaru-runtime` in relay -mode; see [Antithesis deployment](antithesis.md#runtime-parameter-files). +stake distribution. + +For custom testnets, `amaru bootstrap` reads the +`history...json` sidecar next to each snapshot directory. +Without it, Amaru defaults to the built-in testnet era history +(epoch size 86400) and computes wrong nonces on short-epoch networks. +The bundle-root `era-history.json` is the same document, shipped for +`amaru run --era-history-file` at consume time. The runtime +`era-history.json` and `global-parameters.json` consumed by `amaru run` +in relay mode are deployment files mounted at `/amaru-runtime`; see +[Antithesis deployment](antithesis.md#runtime-parameter-files). + +### Environment Knobs + +| Variable | Default | Meaning | +|----------|---------|---------| +| `AMARU_NETWORK` | 4th positional argument | Overrides the network name. | +| `AMARU_CLUSTER_READY_DEADLINE_SECONDS` | `300` | Deadline for the chain DB to appear (exit 1 past it). | +| `AMARU_WAIT_DEADLINE_SECONDS` | `5400` | Deadline for the era-readiness predicate (exit 2 past it). | +| `AMARU_POLL_INTERVAL_SECONDS` | `10` | Sleep between readiness polls. | + +The relay wrapper tightens the two deadlines to 30 seconds and the poll +interval to 5 seconds, because its outer retry loop is the right place +to wait for the chain to mature. + +### Exit Codes + +| Code | Class | +|------|-------| +| 0 | success (bundle committed, or an existing complete bundle found) | +| 1 | cluster-not-ready: chain DB never appeared | +| 2 | chain-not-era-ready: readiness predicate never held | +| 3 | configuration-error: missing/invalid config or genesis | +| 5 | tool-error: targets composition failed | +| 6 | tool-error: `amaru create-snapshots` or sidecar write failed | +| 7 | tool-error: `header-extractor` could not read the chain DB (for example a read-only mount) | +| 9 | tool-error: `amaru bootstrap` failed | +| 10 | output-write-error: atomic rename failed | +| >= 64 | internal error (bash `ERR` trap: `64 + rc`) | + +Every tool invocation's stderr lands in `/.logs/.stderr`, +and the producer tails the failing phase's log onto its own stderr. ## Node-Release Target @@ -169,16 +199,23 @@ volumes: This is not a write contract for the producer. `header-extractor` opens only the immutable DB and the readiness predicate is derived only from -immutable chunks. `ledger-state-emitter` opens the LedgerDB with an -in-memory backend and does not flush replayed state into the node-owned -LedgerDB. The read-write mount is required because the node-10.7.1 -consensus ImmutableDB opener validates chunk files through APIs that -fail on a read-only filesystem. - -## Ledger-State Projection - -`ledger-state-emitter` writes the Amaru bootstrap projection of the -node-10.7.1 ledger state: +immutable chunks. `amaru create-snapshots` works against an isolated +`--cardano-db-dir` in which the immutable chunks are symlinked from the +source chain DB, so the ledger snapshots its `db-analyser` engine +materializes never land in the node-owned LedgerDB. The read-write +mount is required because the node-10.7.1 consensus ImmutableDB opener +validates chunk files through APIs that fail on a read-only filesystem; +the producer detects that case and exits 7 with a pointer to the +`tip-info` stderr log. + +## Ledger-State Projection (standalone emitter) + +`ledger-state-emitter` is the in-repo projection tool. It is no longer +invoked by the producer pipeline (upstream `amaru create-snapshots` + +`amaru bootstrap` own snapshot materialization now), but it remains in +the image and as the flake app `nix run .#ledger-state-emitter` for +standalone emission and debugging. It writes the Amaru bootstrap +projection of the node-10.7.1 ledger state: - UTxO entries are canonical `EncCBOR` entries, not consensus ledger-table `MemPack` bytes. @@ -220,8 +257,10 @@ just live-bootstrap-producer ``` `bootstrap-producer-synthesized` runs the real producer pipeline against -a synthesized Conway-ready `testnet_42` chain DB and verifies that Amaru -accepts the resulting ledger state, headers, and nonces. +a synthesized Conway-ready `testnet_42` chain DB and asserts the +canonical bundle layout: a ledger store with `live/` plus at least three +numeric historical snapshots, a chain store, and the bundle-root +`era-history.json`. `amaru-run-bootstrap` copies that produced bundle into a writable test directory, starts `amaru run` without a live peer, and requires Amaru to @@ -236,17 +275,19 @@ to exercise every transaction, script, UTxO, stake, governance, or reward shape a long-running public network can contain. `antithesis-short-epoch-samples` generates a deterministic short-epoch -ChainDB corpus from the pinned node 10.7.1 tooling, emits the observed -early bootstrap slots `9`, `129`, and `249`, and converts them through -`amaru convert-ledger-state`. The check also rewrites the current-era -history sidecars to the generated Shelley genesis `epochLength`, matching -the production producer path. The source ChainDB is generated during the -Nix build; the repository does not commit bulky database artifacts. - -`antithesis-short-epoch-golden` imports those converted snapshots into -Amaru's ledger store. It is the regression gate for the Antithesis -cold-start ledger-state family: convert success alone is not enough, the -same sampled states must also pass `amaru import-ledger-state`. +(`epochLength=120`) ChainDB corpus from the pinned node 10.7.1 tooling, +runs the full producer pipeline against it, and asserts that at least +three snapshot directories and the bundle-root `era-history.json` were +materialized. The source ChainDB is generated during the Nix build; the +repository does not commit bulky database artifacts. + +`antithesis-short-epoch-golden` is the issue #29 regression gate for the +Antithesis cold-start family. It starts `amaru run` on the short-epoch +bundle with `--era-history-file` pointing at the bundle's +`era-history.json` (the network built-in epoch size is 86400), and +requires Amaru to reach `build_ledger` and stay alive until the test +timeout. Bundle production alone is not enough; the short-epoch stores +must also be usable as Amaru startup state. `just live-bootstrap-producer` is the Docker-level verifier. It seeds a stock `testnet_42` ChainDB with `db-synthesizer`, starts diff --git a/docs/history/what-amaru-needs.md b/docs/history/what-amaru-needs.md index 24def1a..34ce37c 100644 --- a/docs/history/what-amaru-needs.md +++ b/docs/history/what-amaru-needs.md @@ -7,14 +7,14 @@ The current runtime path is: ```text cardano-node ChainDB - -> ledger-state-emitter - -> amaru convert-ledger-state - -> header-extractor - -> amaru import-* + -> header-extractor tip-info / list-blocks + -> amaru create-snapshots + -> amaru bootstrap -> amaru run ``` -`db-synthesizer` now belongs to fixtures and CI checks only. +`db-synthesizer` now belongs to fixtures and CI checks only. See +[Architecture](../architecture.md) for the current pipeline. ## Original Bundle Layout @@ -89,10 +89,10 @@ That fork was the original maintenance problem: it lagged far behind upstream consensus and mixed fixture generation with bootstrap snapshot extraction. -## The No-Fork Replacement +## The No-Fork Replacement (first iteration) -This repository replaced the forked snapshot writer with in-repo tools -that consume the stock node libraries: +This repository first replaced the forked snapshot writer with in-repo +tools that consume the stock node libraries: ```bash ledger-state-emitter \ @@ -108,13 +108,15 @@ header-extractor tip-info|list-blocks|get-header ... 10.7.1 dependency set and emits the Amaru bootstrap projection documented in `specs/003-amaru-bootstrap-producer/research.md#r-011`. -The producer calls it three times: `target_slot`, -`target_slot - epochLength`, and `target_slot - 2 * epochLength`. -`amaru convert-ledger-state` still owns the final snapshot slicing, -history JSON, and nonce JSON formats. - -For custom testnets, the producer corrects converted -`history...json` files before import: the open-ended current -era's `epoch_size_slots` is set to the mounted Shelley genesis -`epochLength`. That keeps short-epoch networks consistent with the -ledger snapshot epoch number that Amaru checks during import. +In that first iteration the producer called the emitter three times +(`target_slot`, `target_slot - epochLength`, and +`target_slot - 2 * epochLength`) and `amaru convert-ledger-state` owned +the final snapshot slicing, history JSON, and nonce JSON formats. + +That iteration has since been superseded as well: once upstream Amaru +gained `create-snapshots --targets-file --cardano-db-dir` and +`bootstrap`, the producer migrated to driving those two commands +directly, and nonces and headers became internal artefacts of +`chain..db`. `ledger-state-emitter` remains available as a +standalone tool. The emit/convert/import bundle shape on this page is +historical context only. diff --git a/docs/index.md b/docs/index.md index 82a8c9c..d1f95c1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,10 +35,14 @@ bootstrapper and the Amaru node. - `bootstrap-producer`: one-shot primitive used by the relay wrapper and by local checks. It produces the Amaru ledger and chain stores from a cardano-node ChainDB. +- `header-extractor`: Haskell executable for `tip-info`, `list-blocks`, + and `get-header` against immutable ChainDB chunks. The producer's + era-readiness poll and snapshot-target selection are built on it. - `ledger-state-emitter`: Haskell executable that projects a pinned cardano-node 10.7.1 ledger state into the legacy shape Amaru imports. -- `header-extractor`: Haskell executable for `tip-info`, `list-blocks`, - and `get-header` against immutable ChainDB chunks. + Still shipped in the image and exposed as a flake app, but no longer + invoked by the producer pipeline since the migration to upstream + `amaru create-snapshots` + `amaru bootstrap`. - `amaru-runtime/`: deployment-provided runtime files consumed by `amaru run`: `era-history.json` and `global-parameters.json`. @@ -48,16 +52,18 @@ The production bootstrap pipeline is: ```text cardano-node ChainDB - -> ledger-state-emitter - -> amaru convert-ledger-state - -> header-extractor - -> nonce tail rewrite - -> amaru import-ledger-state/import-headers/import-nonces + -> header-extractor tip-info / list-blocks (era-readiness + targets) + -> targets.json + snapshots.json (from the chain's own blocks) + -> amaru create-snapshots (db-analyser engine, offline) + -> era-history sidecars (from genesis epochLength) + -> amaru bootstrap (ledger + chain stores) -> amaru run ``` `db-synthesizer` is not part of this runtime path. It remains available -for fixture generation and CI checks. +for fixture generation and CI checks. `amaru create-snapshots` drives the +`db-analyser` engine internally, which is why `db-analyser` is bundled in +the published image. ## Specs And History diff --git a/docs/tutorial.md b/docs/tutorial.md index 1f5247c..6ca6e49 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -154,8 +154,9 @@ Expected relay log shape: [amaru-relay-1] bootstrap attempt #1: refreshing snapshot from /live [amaru-relay-1] bootstrap attempt #1: invoking bootstrap-producer [amaru-relay-1 bootstrap-producer] + era-readiness predicate satisfied - target_slot=... -[amaru-relay-1 bootstrap-producer] + ledger-state-emitter @ ... -[amaru-relay-1 bootstrap-producer] + amaru import-ledger-state +[amaru-relay-1 bootstrap-producer] + wrote targets.json (3 epochs) + snapshots.json +[amaru-relay-1 bootstrap-producer] + amaru create-snapshots (epoch N + 2) +[amaru-relay-1 bootstrap-producer] + amaru bootstrap [amaru-relay-1] bootstrap attempt #1: committed bundle to /srv/amaru [amaru-relay-1] bundle already complete at /srv/amaru, skipping bootstrap loop [amaru-relay-1] bundle ready at /srv/amaru, exec'ing amaru run @@ -175,8 +176,7 @@ After promotion, the relay's private `/srv/amaru` volume contains: |-- chain.testnet_42.db/ |-- ledger.testnet_42.db/ |-- snapshots/ -|-- nonces.json -`-- headers/ +`-- era-history.json ``` `amaru run` opens the stores directly from that directory: @@ -187,8 +187,9 @@ After promotion, the relay's private `/srv/amaru` volume contains: ``` The ledger store must include `live/` and at least three numeric -historical snapshots. The chain store must include the exact header for -the latest ledger snapshot. +historical snapshots. Nonces and the bootstrap headers (including the +header for the latest ledger snapshot) are baked into +`chain.testnet_42.db` by `amaru bootstrap`. ## Failure Diagnosis @@ -202,7 +203,8 @@ Relay failures are usually visible in the wrapper prefix: | repeated `transient rc=1` | ChainDB is not ready or the snapshot copy is too early. | | repeated `transient rc=2` | The chain has not reached the producer's era-readiness window. | | `fatal rc=3` | Config/genesis files are missing or invalid. | -| `fatal rc=9` | One of the Amaru import commands failed. Check the prefixed producer output. | +| repeated `transient rc=6` | `amaru create-snapshots` failed. Check the prefixed producer output. | +| `fatal rc=9` | `amaru bootstrap` failed. Check the prefixed producer output. | | Amaru starts then fails VRF/nonce checks | Check `amaru-runtime/era-history.json` and `global-parameters.json` against the generated testnet. | In relay mode, avoid `depends_on: service_completed_successfully` for diff --git a/mkdocs.yml b/mkdocs.yml index 78c6ee8..fc84b3f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,14 +63,16 @@ markdown_extensions: nav: - Home: index.md - - Antithesis deployment: antithesis.md - - Tutorial: tutorial.md - - Concepts: - - Architecture: architecture.md + - User manual: + - Tutorial: tutorial.md + - Antithesis deployment: antithesis.md + - Architecture: + - Overview: architecture.md - Bootstrap producer: bootstrap-producer.md - History: - What Amaru needed: history/what-amaru-needs.md - - Constitution: constitution.md + - Development: + - Constitution: constitution.md extra: social: diff --git a/skills/amaru-bootstrap-guide/SKILL.md b/skills/amaru-bootstrap-guide/SKILL.md new file mode 100644 index 0000000..895473d --- /dev/null +++ b/skills/amaru-bootstrap-guide/SKILL.md @@ -0,0 +1,138 @@ +--- +name: amaru-bootstrap-guide +description: Working guide for the lambdasistemi/amaru-bootstrap repository, which builds the ghcr.io/lambdasistemi/amaru-bootstrap-producer Docker image and tools for bootstrapping relay-only Amaru nodes on custom Cardano testnets. Load when working on or asking about bootstrap-producer, amaru-relay-bootstrap, header-extractor, ledger-state-emitter, amaru create-snapshots, amaru bootstrap, amaru run, the testnet_42 fixture, era-history.json or global-parameters.json runtime files, bundle layout (ledger..db, chain..db), the era-readiness predicate, producer exit codes (cluster-not-ready, chain-not-era-ready), Antithesis amaru-relay-N containers and startup markers, scripts/bootstrap-producer.sh, scripts/amaru-relay-bootstrap.sh, scripts/smoke-test.sh, nix flake checks like bootstrap-producer-synthesized or antithesis-short-epoch-golden, the cardano-node 10.7.1 pin, or the just recipes (just ci, just smoke, just build-gate, just live-bootstrap-producer). +--- + +# amaru-bootstrap guide + +## Repository map + +- `scripts/bootstrap-producer.sh` — the one-shot producer orchestrator + (preflight → targets → `amaru create-snapshots` → era-history + sidecars → `amaru bootstrap` → atomic commit). The production + deliverable. +- `scripts/amaru-relay-bootstrap.sh` — Antithesis relay container + entrypoint: startup marker, producer retry loop, bundle promotion, + final `exec amaru run`. +- `scripts/smoke-test.sh` — Phase 0 format-compatibility smoke test + (db-synthesizer → db-analyser `--store-ledger` → + `amaru convert-ledger-state`). +- `lib/` — Haskell library: `HeaderExtractor` (tip-info, list-blocks, + get-header over the immutable ChainDB), `LedgerStateEmitter` + (node-10.7.1 → Amaru legacy `ExtLedgerState` projection), + `AmaruBootstrap` (marker module so haskell.nix resolves the pinned + consensus packages). +- `app/header-extractor/`, `app/ledger-state-emitter/` — CLI wrappers + over the library (optparse-applicative; failures exit 7). +- `nix/` — `project.nix` (haskell.nix), `amaru.nix` (crane build of the + pinned Amaru), `iog-tools.nix` (db-synthesizer, db-analyser, + snapshot-converter from the pinned consensus), `apps.nix` (flake + apps), `checks.nix` (all flake checks), + `bootstrap-producer-image.nix` (layered Docker image). +- `tests/` — bats suites for the producer, relay, and smoke scripts; + `test/` — hspec suite for `HeaderExtractor`. +- `specs/` — speckit feature specs; `specs/003-amaru-bootstrap-producer/` + holds the producer contract, research (R-001…R-011), and data model. +- `docs/` + `mkdocs.yml` — MkDocs Material site; + `.specify/memory/constitution.md` — project principles (symlinked at + `docs/constitution.md`). +- `.github/workflows/` — `ci.yml` (Build Gate + smoke verdict + live + verifier), `publish-bootstrap-image.yml` (GHCR push, SHA tags), + `deploy-docs.yml` (mkdocs gh-deploy). + +## Build, test, run + +- `just build-gate` — build every flake check CI's Build Gate builds. +- `just ci` — full local CI mirror (Build Gate, Phase 0 smoke verdict, + Docker-level live verifier; needs a Docker daemon for the last step). +- `just smoke` — smoke test against the vendored fixture + (`specs/001-snapshot-format-smoke/fixtures/p1-config`). +- `just live-bootstrap-producer` — Docker-level verifier against a real + `cardano-node:10.7.1` container. +- Single check: `nix build .#checks.x86_64-linux.` — names: + `amaru`, `db-synthesizer`, `db-analyser`, `ledger-state-emitter`, + `shellcheck`, `smoke-test-bats`, `header-extractor-spec`, + `header-extractor-cli-bats`, `bootstrap-producer-bats`, + `bootstrap-producer-synthesized`, `amaru-run-bootstrap`, + `antithesis-short-epoch-samples`, `antithesis-short-epoch-golden`, + `bootstrap-producer-image`. +- Run the producer locally: + `nix run .#bootstrap-producer -- `. +- Everything is x86_64-linux only. + +## Navigating the code + +- The producer's behavior contract (arguments, exit codes, state + machine) is documented at the top of `scripts/bootstrap-producer.sh` + and in `specs/003-amaru-bootstrap-producer/contracts/` and + `data-model.md`. +- The era-readiness predicate (tip in Conway, tip epoch >= 3, a block + in each of the three most recent completed epochs) lives in + `phase_preflight` in `scripts/bootstrap-producer.sh`, with the + rationale in the long comment above the polling loop. +- The bundle-completeness predicate (`bundle_complete`) exists twice: + in `scripts/bootstrap-producer.sh` (standalone) and + `scripts/amaru-relay-bootstrap.sh` (relay, additionally requires + RocksDB `CURRENT` files and the `.bootstrap-complete` sentinel). + Keep them consistent when changing the bundle shape. +- The ledger projection rules (UTxO canonical CBOR, pre-Peras wrapper, + PState/DState projection, completed zero reward update) are in the + module haddock of `lib/LedgerStateEmitter.hs` and in + `specs/003-amaru-bootstrap-producer/research.md` R-011. +- The pinned dependency set is in `cabal.project` (CHaP index states, + consensus 3.0.1.0 source-repository-package with nix32 `--sha256`) + and `flake.nix` inputs (Amaru pinned to + `lambdasistemi/amaru/feat/testnet-bootstrap` via `flake.lock`). +- CI check definitions are all in `nix/checks.nix`; the synthesized + fixtures (`mkSynthesizedChainDb`, the short-epoch corpus) are defined + at the top of that file. + +## Using the bootstrap-producer image + +- Published tags: + `ghcr.io/lambdasistemi/amaru-bootstrap-producer:` + (after CI on `main`) plus `:` and + `:pr--` for same-repo PRs. Never use a moving + tag. +- Default entrypoint is `bootstrap-producer` taking + ` `; Antithesis relay + services override `entrypoint: amaru-relay-bootstrap` and configure + via `RELAY_NAME`, `AMARU_PEER`, `AMARU_NETWORK`, + `AMARU_BOOTSTRAP_RETRY_SECONDS` (see `docs/antithesis.md` for the + full env table). +- The chain DB must be mounted read-write even though the producer only + reads immutable chunks (node-10.7.1 consensus opens chunk files with + write permissions); the relay copies `/live` to scratch for this. +- Producer exit codes: 0 success/already-complete, 1 cluster-not-ready, + 2 chain-not-era-ready, 3 configuration error, 5 targets error, + 6 create-snapshots error, 7 header-extractor error, 9 bootstrap + error, 10 commit error, >=64 internal. The relay retries 1/2/5/6/7/8. +- The bundle that `amaru run` consumes: `ledger..db/` (live + + >=3 numeric epoch snapshots), `chain..db/` (nonces and + headers baked in), `snapshots//`, `era-history.json`. + `amaru run` additionally needs deployment-provided + `era-history.json` and `global-parameters.json` (mounted at + `/amaru-runtime` in relay mode). + +## Answering questions + +- "What does this repo do / how does bootstrap work?" — README + *What is this* + *Architecture*; the full pipeline is in + `docs/architecture.md`, and `scripts/bootstrap-producer.sh` is the + source of truth. +- "How do I wire it into a Compose testnet?" — `docs/tutorial.md` + (relay shape) and `docs/antithesis.md` (env contract, startup + markers, runtime parameter files). +- "Why doesn't Amaru start / wrong nonces / epoch boundaries?" — check + the era-history story: sidecars and bundle-root `era-history.json` in + `docs/bootstrap-producer.md`, runtime files in `docs/antithesis.md`. +- "Why is everything pinned to cardano-node 10.7.1?" — README + *Compatibility target*, `docs/architecture.md` *Node-Release + Boundary*, `cabal.project` comments. +- "Why no fork of consensus / why these tools?" — the constitution + (`.specify/memory/constitution.md`) and + `docs/history/what-amaru-needs.md` for how the pipeline evolved + (emit/convert/import → create-snapshots/bootstrap). +- "What proves this works?" — the Verification section of + `docs/bootstrap-producer.md` maps each flake check to what it + asserts.