Ledger Proof Layers
All four layers are complete. Ledger has full persistence proof parity with governance.
Layer 1 — Direct Struct Sled Write Proof ✅
What it proves: A JournalEntry written through Ledger::new(Arc<SledStore>) →
append_entry() survives an in-process sled drop-and-reopen. The in-memory
cached_balances is gone after the drop. The fresh Ledger::new reads back via sled only.
Also proven: The reopened store accepts a second write (not opened read-only).
Artifact: crates/icn-ledger/tests/ledger_persistence.rs →
test_ledger_entry_survives_drop_and_reopen
Run:
cargo test -p icn-ledger --test ledger_persistence
What is asserted:
- Author DID survives round-trip
- Both
AccountDeltasides survive with exact values (account_id, currency, amounts) ProvenanceRef::SystemGenerated { reason }survives round-trip (proves full body)- Reopened ledger accepts a second
append_entrywithout error
Layer 2 — Store-backed Sled Write Proof ✅
Architecture note: apps/ledger provides two sled-backed stores (SledEscrowStore,
SledBudgetStore) used directly by the daemon — the same role as SledGovernanceStateStore
in governance. There is no Tokio actor in apps/ledger.
What it proves: State written through SledEscrowStore::put() survives a drop-and-reopen
boundary with exact field values. The reopened store accepts further writes.
Also noted: LedgerServiceImpl (in icn-core) has a separate existing proof:
test_submit_treasury_entry_idempotency_survives_restart in ledger_service.rs.
Artifact: apps/ledger/tests/actor_persistence_proof.rs →
test_escrow_record_survives_drop_and_reopen
Run:
cargo test -p icn-ledger-actor --test actor_persistence_proof
What is asserted:
escrow_id,scope_id,funder_did,beneficiary_did,amount,currency,statussurvive sled round-trip with exact valuesEscrowStatus::Released+release_decision_hashsurvive a state-transition write
Layer 3 — Service-layer Same-Runtime Reopen Proof ✅
What it proves: A JournalEntry written through LedgerServiceImpl::submit_treasury_entry()
(the canonical production service path that wraps Arc<RwLock<Ledger>>) survives dropping all
runtime state and reopening sled in the same runtime. Unlike governance Layer 3, no JoinHandle
or background-task shutdown is needed — there is no scheduler task.
Artifact: crates/icn-core/tests/ledger_service_persistence.rs →
test_ledger_service_entry_survives_drop_and_reopen
Run:
cargo test -p icn-core --test ledger_service_persistence test_ledger_service_entry_survives_drop_and_reopen
What is asserted:
count_entries() == 1on freshLedger::new— entry came from sled, not in-memory stateget_entry(hash)returns entry with exactauthorDID and 2AccountDeltasProvenanceRef::Governance { receipt_id }survives round-trip (proves the service-layer provenance path, not just system provenance)- Reopened ledger accepts a second direct
append_entry(writable, not read-only)
Layer 4 — Cross-Process Restart Proof ✅
What it proves: Ledger state written through LedgerServiceImpl in one OS process is
readable in a completely fresh OS process. No shared memory. No shared runtime. True
process-boundary restart.
Implementation:
- Helper binary:
crates/icn-core/src/bin/ledger_restart_helper.rswrite <sled_path>— constructsLedgerServiceImpl, writes oneJournalEntry, prints entry hash (hex) to stdout, exits 0.read <sled_path> <entry_hash_hex>— opens freshLedger::new, reads by hash, asserts exact invariants, exits 0.
- Integration test:
crates/icn-core/tests/ledger_service_persistence.rs→test_ledger_service_entry_survives_cross_process_restart
Runtime note: Unlike the governance helper (new_current_thread()),
ledger_restart_helper uses new_multi_thread() because submit_treasury_entry calls
tokio::task::block_in_place internally.
No shutdown protocol needed: Ledger has no background scheduler task. Dropping
Arc<RwLock<Ledger>> and Arc<SledStore> is sufficient; sled flushes on drop(rt).
Artifact: crates/icn-core/tests/ledger_service_persistence.rs →
test_ledger_service_entry_survives_cross_process_restart
Run:
cargo test -p icn-core --test ledger_service_persistence test_ledger_service_entry_survives_cross_process_restart
What is asserted (in read subprocess):
count_entries() == 1— exactly one entry survived the OS process boundaryget_entry(hash)returns Some(entry) — readable by the exact hash from write processentry.accounts.len() == 2— both account deltas survivedProvenanceRef::Governance { receipt_id }matches hardcoded constant — exact provenance round-trip through an OS process boundary
Run Full Layer 1–4 Suite
cargo test -p icn-ledger --test ledger_persistence
cargo test -p icn-ledger-actor --test actor_persistence_proof
cargo test -p icn-core --test ledger_service_persistence
What Is NOT Proven
| Gap | Why it matters | Status |
|---|---|---|
| Receipt-index idempotency across restart | Receipt index is a separate sled store — proven separately in test_submit_treasury_entry_idempotency_survives_restart |
Already covered |
| Balance cache rebuild correctness | Separate correctness concern, not persistence | Dedicated test |
| Gossip sync across restart | Callbacks not exercised | Multi-node integration test |
Comparison with Governance Proof Stack
| Layer | Governance | Ledger |
|---|---|---|
| 1 — Direct struct sled write | ✅ (implicit in persistence_proof) | ✅ ledger_persistence.rs |
| 2 — Store/service-backed sled write | ✅ persistence_proof.rs write phase |
✅ actor_persistence_proof.rs |
| 3 — Same-runtime close+reopen | ✅ persistence_proof.rs Phase 2 |
✅ ledger_service_persistence.rs |
| 4 — Cross-process restart | ✅ governance_restart_helper binary |
✅ ledger_restart_helper binary |
See governance-proof-layers.md for the full governance pattern reference.