amaru-relay-bootstrap: the long-lived Antithesis relay entrypoint.
bootstrap-producer: the one-shot producer primitive called by the
relay wrapper and by local checks.
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.
The relay writes the startup marker before bootstrap work. That lets the
Antithesis setup phase complete while the bootstrap itself continues in
the test phase. The marker is not an Amaru-sync proof; it is a container
startup contract.
There is no downstream Compose service waiting on
service_completed_successfully in relay mode. The relay container does
not stop after bootstrap; it execs amaru run.
In standalone mode the producer writes <bundle>/<network>. In relay
mode it writes to scratch, and the wrapper promotes the contents of
<scratch-out>/<network> into /srv/amaru so amaru run can open:
Nonces and bootstrap headers are baked into chain.<network>.db by
amaru bootstrap; they are no longer separate bundle artefacts. The
snapshots/<network>/ 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
flowchart LR
writer["cardano-node\nlive writer"] --> state["state volume\nChainDB"]
state --> imm["immutable chunks\nread by tools"]
state --> ledger["ledger DB\ncopied but not mutated"]
state --> vol["volatile DB\ncopied for ChainDB shape"]
imm --> tip["header-extractor tip-info"]
imm --> headers["header-extractor headers"]
state -. "mounted read-only in relay\ncopied to writable scratch" .-> relay["amaru-relay-bootstrap"]
relay --> scratch["scratch ChainDB\nopened read-write by producer tools"]
The one-shot bootstrap-producer still needs a writable ChainDB path
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
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.
The retry loop belongs in the relay entrypoint, not in Compose
dependency semantics. This matters under Antithesis faults: a short,
failed producer attempt should refresh from a newer /live snapshot
instead of blocking the whole setup behind a one-shot service.
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:
These files must match the custom testnet genesis/config used by the
paired cardano-node. They are separate from the
history.<slot>.<hash>.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.
Retargeting to another node release is an explicit project task. It is
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 (standalone emitter)
flowchart LR
raw["node 10.7.1 ledger state"] --> utxo["UTxOState\ncanonical TxIn/TxOut"]
raw --> wrapper["Shelley wrapper\npre-Peras shape"]
raw --> pstate["Conway/Dijkstra PState\ncurrent/future/retiring pools"]
raw --> dstate["Conway/Dijkstra DState\nlegacy delegation wrapper"]
utxo --> out["Legacy ExtLedgerState\nfor Amaru"]
wrapper --> out
pstate --> out
dstate --> out
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
sequenceDiagram
participant Check as amaru-run-bootstrap
participant Producer as bootstrap-producer
participant Bundle as Bundle
participant Amaru as amaru run
Check->>Producer: synthesize chain DB, produce bundle
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
Check-->>Check: timeout is expected, early bootstrap failure is not
The CI proof is deliberately peer-free: it does not prove live chain
synchronisation. It proves the produced stores are sufficient for Amaru
to open its ledger and chain state and enter node startup.