Bootstrap Producer
bootstrap-producer is the lower-level one-shot primitive inside the
published image. The current Antithesis deployment usually runs the same
image with entrypoint: amaru-relay-bootstrap; that relay wrapper calls
/bin/bootstrap-producer internally and then execs amaru run.
Use this page for local debugging, CI checks, and standalone producer integration. Use Tutorial or Antithesis deployment for the relay container shape.
The producer is also exposed as a local flake app:
Standalone Invocation
The Docker image default entrypoint is bootstrap-producer for
standalone compatibility. The image does not have a default Cmd, so a
standalone Compose service must pass the four required arguments:
services:
bootstrap-producer:
image: ghcr.io/lambdasistemi/amaru-bootstrap-producer:<full-commit-sha>
command:
- /cardano/state/db
- /cardano/config
- /srv/amaru
- testnet_42
environment:
AMARU_NETWORK: testnet_42
volumes:
- node-state:/cardano/state
- node-configs:/cardano/config:ro
- amaru-bundle:/srv/amaru
restart: "no"
Argument 1 must be the actual cardano-node ChainDB directory as seen
inside the producer container. If the node stores its database at
/state/db and the state volume is mounted at /cardano/state, pass
/cardano/state/db. If the mounted path already is the database
directory, pass that path directly.
Pipeline
The producer runs once and exits. In standalone integrations its exit
code can be used as the synchronization signal for downstream Amaru
services. In Antithesis relay integrations, the synchronization signal is
the relay startup marker and the relay process continues as amaru run.
- Check whether
<bundle-dir>/<network>is already complete. If so, exit 0. - Validate the node config and genesis (
config.json,shelley-genesis.json, positiveepochLength) and wait for the chain DB to appear. - Poll
header-extractor tip-infountil the era-readiness predicate holds: the immutable tip is in Conway, the tip epoch is at least 3, andheader-extractor list-blocksfinds 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. - Compose
targets.json(epoch/slot/hash/parent_point) andsnapshots.jsonfrom the chain's own block list, bypassing Koios. - Run
amaru create-snapshotswith--targets-fileand an isolated--cardano-db-dir(immutable chunks symlinked from the source chain DB), materializing one snapshot directory per epoch with packaged bootstrap headers. - Write a
history.<slot>.<hash>.jsonera-history sidecar next to each snapshot and anera-history.jsonat the bundle root, both built from the genesisepochLength. - Run
amaru bootstrap, which populatesledger.<network>.dbandchain.<network>.db, deriving nonces from the latest snapshot and importing the packaged headers. - Atomically rename (
mv -T) the unique staging directory into the final bundle path.
The final layout is:
<bundle-dir>/
├── .logs/ # per-phase stderr logs
└── <network>/
├── chain.<network>.db/
├── ledger.<network>.db/
├── snapshots/<network>/
└── era-history.json
Nonces and bootstrap headers are baked into chain.<network>.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.
For custom testnets, amaru bootstrap reads the
history.<slot>.<hash>.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.
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 <bundle-dir>/.logs/<phase>.stderr,
and the producer tails the failing phase's log onto its own stderr.
Node-Release Target
This implementation targets cardano-node 10.7.1. The repository pins
that release through cabal.project, CHaP index states, the
ouroboros-consensus source-repository-package, and flake.lock.
This matters because Cardano ledger-state CBOR drifts between node releases. The producer should be retargeted deliberately for each node release instead of treated as a generic ledger-state serializer.
Published Image
After the full CI workflow succeeds on main, the publish workflow
builds the Nix docker image, loads it into Docker, and pushes:
After the full CI workflow succeeds on a same-repository pull request, the same workflow publishes immutable PR test tags:
ghcr.io/lambdasistemi/amaru-bootstrap-producer:<full-pr-head-sha>
ghcr.io/lambdasistemi/amaru-bootstrap-producer:pr-<pr-number>-<full-pr-head-sha>
The full commit SHA is the runtime integration contract. Downstream
compose files should pin the exact SHA they tested. The project does not
publish moving runtime tags such as latest for the producer.
The same tarball is available as the flake package
.#packages.x86_64-linux.bootstrap-producer-image. CI uploads it from
the Build Gate as an artifact named
bootstrap-producer-image-<github-sha>, containing
amaru-bootstrap-producer-<github-sha>.tar.gz.
To choose a concrete image, open the successful main or
same-repository PR CI run for the commit you want, copy its full head
SHA, then use the matching successful Publish bootstrap-producer image
workflow run. The GHCR image tag and the uploaded artifact name both
contain that same SHA.
ChainDB Mount Contract
The producer must mount the cardano-node state volume read-write:
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. 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
EncCBORentries, not consensus ledger-tableMemPackbytes. - The Shelley ledger wrapper is written in the pre-Peras shape that Amaru's converter walks.
- Conway/Dijkstra pool state is projected to the current pool params, future pool params, and retirements that Amaru imports.
- Conway/Dijkstra account state is projected into Amaru's legacy delegation-state wrapper while preserving rewards, deposits, stake-pool delegation, and DRep delegation.
- Empty reward-update state is projected as a completed zero reward
update because Amaru's import command decodes snapshots with
has_rewards=true.
The detailed contract is in
specs/003-amaru-bootstrap-producer/research.md#r-011.
Verification
Local CI:
just ci includes the Build Gate, the Phase 0 smoke verdict, and the
Docker-level live verifier.
Producer-specific checks:
nix build .#checks.x86_64-linux.bootstrap-producer-synthesized
nix build .#checks.x86_64-linux.amaru-run-bootstrap
nix build .#checks.x86_64-linux.antithesis-short-epoch-samples
nix build .#checks.x86_64-linux.antithesis-short-epoch-golden
nix build .#checks.x86_64-linux.bootstrap-producer-bats
nix build .#checks.x86_64-linux.bootstrap-producer-image
just live-bootstrap-producer
bootstrap-producer-synthesized runs the real producer pipeline against
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
reach its build_ledger startup trace and remain alive until the test
timeout. This is the CI proof that the bundle is usable as Amaru startup
state, not only accepted by the import commands.
The synthesized fixture is not a mainnet ledger-content coverage test. It proves the release-pinned CBOR projection can populate Amaru's stores and that those stores are self-consistent at startup. It does not claim 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
(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
ghcr.io/intersectmbo/cardano-node:10.7.1-amd64 on that DB, and
asserts that the bootstrap-producer can commit a complete bundle while
the official node has the ChainDB open.