The producer is intentionally a small orchestration layer around
release-pinned tools. The critical design boundary is that
ledger-state-emitter targets one cardano-node release at a time; the
current branch targets cardano-node 10.7.1.
Runtime Components
flowchart LR
node["cardano-node\nchain DB + config"] --> mount["state mount rw\nconfig mount ro"]
mount --> preflight["bootstrap-producer\npre-flight"]
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 --> snapshot["snapshot CBOR\nhistory JSON\nnonces JSON"]
mount --> headers["header-extractor\nlist-blocks/get-header"]
headers --> headerFiles["header.*.cbor files"]
snapshot --> compose["nonce tail rewrite"]
headerFiles --> compose
compose --> imports["amaru import-ledger-state\namaru import-headers\namaru import-nonces"]
imports --> bundle["complete bundle\n<bundle>/<network>"]
The producer's exit code is the synchronization primitive for Docker
Compose. Downstream Amaru services depend on
service_completed_successfully and start only after the bundle exists.
Live ChainDB Contract
flowchart LR
writer["cardano-node\nlive writer"] --> state["state volume\nChainDB"]
state --> imm["immutable chunks\nappend-only"]
state --> vol["volatile DB\nignored"]
imm --> tip["header-extractor tip-info"]
imm --> headers["header-extractor headers"]
state -. "mounted read-write\nfor consensus validation" .-> tip
The bootstrap-producer's semantic contract is immutable-only access:
readiness is derived from the immutable tip and header extraction walks
immutable chunks. The Docker mount is still read-write because
node-10.7.1's consensus ImmutableDB validation path opens chunk files
through APIs that reject a read-only filesystem. The producer does not
use volatile DB state as a readiness source. The ledger replay opens a
V2 LedgerDB with an in-memory backend and does not flush replayed state
back into the node-owned LedgerDB; flushing there can prune snapshots
from a live node database.
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.
Ledger-State Projection
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.
Concurrency
sequenceDiagram
participant A as Producer A
participant B as Producer B
participant T as Temp dirs
participant F as Final bundle
A->>T: write <network>.tmp.pid.random
B->>T: write <network>.tmp.pid.random
A->>F: mv -T temp final
F-->>A: success
B->>F: mv -T temp final
F-->>B: exists
B->>F: validate complete bundle
B-->>B: exit 0
There is no shared temp directory. Concurrent producers cannot corrupt
each other's intermediate files; one wins the atomic rename and the
others accept the completed bundle.
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: import ledger, headers, nonces
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.