Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ Yano's initial goal is to fill three roles:
## Highlights

- **Real ledger state** — UTxO, account state (stakes / delegations / rewards), epoch snapshots,
Conway-era governance (DReps, proposals, votes, committee), with full rollback support.
Conway-era governance (DReps, proposals, votes, committee), with full rollback support,
including bounded epoch-boundary pool retirement and refund recovery. See
[account state and rollback](docs/ACCOUNT_STATE_AND_ROLLBACK.md).
- **Devnet block producer** — produce blocks from a configured genesis and serve them to
downstream nodes (Haskell `cardano-node`, Dingo, your own indexer) via the n2n protocol on port 13337.
- **Past-time-travel mode** — start a fresh devnet "in the past", produce blocks deterministically
Expand Down
863 changes: 863 additions & 0 deletions adr/050-complete-live-stake-pool-retirement.md

Large diffs are not rendered by default.

111 changes: 111 additions & 0 deletions adr/reports/adr-050-phase-0-2026-08-31.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# ADR-050 Phase 0 Reproduction and Baseline

## Status

Phase 0 evidence complete; production implementation not started

## Date

2026-08-31

## Source baseline

- Branch: `fix/issue_62`
- Base: merged `main` at `874e96ff`
- Reference implementation reviewed: `cardano-ledger` at
`9714940b3f6527633ba37e47b93342b882ff7e67`

## Deterministic reproduction

The new RocksDB fixture is
`DefaultAccountStateStorePoolLifecycleTest`. It uses one pool and five stake
credentials and asserts the reference-ledger result rather than accepting the
current Yano behavior.

Command:

```text
./gradlew :ledger-state:test \
--tests com.bloxbean.cardano.yano.ledgerstate.DefaultAccountStateStorePoolLifecycleTest \
--continue
```

Result: six tests executed, five failed for the intended reason, and the reverse
ordering control passed.

| Reproducer | Expected reference result | Current observed result |
|---|---|---|
| `RetirePool -> RegPool`, one transaction | retirement absent | `Optional[12]` |
| `RetirePool`, then `RegPool`, later transaction in same block | retirement absent | `Optional[12]` |
| same-block `RegPool -> RegPool` | first parameters at `N+2`, update at `N+3` | second parameters overwrite `N+2` |
| fresh registration in epoch 10 | stored deposit `500000010` | stored deposit `500000000` from epoch-zero lookup |
| effective POOLREAP | pool, retirement and five pool delegations absent | pool remains registered; first assertion fails |

The `RegPool -> RetirePool` control passed and retained retirement epoch 12, so
the fixture distinguishes the two required certificate orders.

The test class compiled normally. No failure depends on timing, map iteration or
an external service.

## Retained mainnet baseline

Source log:

```text
/Users/satya/Downloads/yano-try/adr-048-mainnet-final-g1/yano.log
```

The accepted ADR-048 mainnet run provides 40 consecutive POOLREAP phase samples
through epoch 652:

| Current refund-only POOLREAP | Value |
|---|---:|
| Samples | 40 |
| Minimum | 3 ms |
| Average | 7.60 ms |
| Maximum | 59 ms |

Thirty of those epochs logged at least one retiring pool:

| Retiring pools per non-empty epoch | Value |
|---|---:|
| Average | 3.33 |
| Maximum | 21, at epoch 641 |

The epoch-651 snapshot log examined a mainnet-scale credential-major pool
delegation population of approximately 1,477,478 rows:

```text
1,341,127 written
+ 30,542 zero-balance
+ 103,929 retired-pool
+ 1,880 stale-delegation
= 1,477,478 examined delegation rows
```

This is the appropriate scale gate for ADR-050's added sequential
`PREFIX_POOL_DELEG` scan. The current 3–59 ms measurement is not the expected
post-fix time: it measures only refund lookup/credit and intentionally performs
no delegation scan or live cleanup.

## Phase-1 comparison gates

The ordered certificate overlay must make the three certificate/deposit tests
green without changing unrelated block application. The effective POOLREAP test
remains the Phase-2 gate.

After the scan implementation exists, measure it against the retained
approximately 1.48 million-row prefix and compare:

- rows examined and removed;
- scan and cleanup wall time separately;
- chunk count;
- heap/RSS before, peak and after; and
- total boundary time against the 60-second target and 120-second maximum.

## Conclusion

Issue #62 and the additional registration-epoch deposit defect are reproduced.
The current mainnet phase has effectively zero cleanup cost because it does not
perform the required transition. Phase 1 may proceed after review of this
evidence; Phase 2 retains the explicit mainnet-scale scan gate.
43 changes: 43 additions & 0 deletions adr/reports/adr-050-phase-2-2026-08-31.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ADR-050 Phase 2 mainnet-scale scan evidence

Date: 2026-08-31 (Asia/Singapore)

## Dataset and method

The scan used the retained mainnet epoch-652 RocksDB chainstate at:

`/Users/satya/Downloads/yano-try/adr-048-mainnet-final-g1/chainstate`

The database was opened read-only with `maxOpenFiles=128`. No Yano process had
that chainstate open and the benchmark performed no writes. The test JVM used a
512 MiB maximum heap. Other processes were active on the host, so both runs are
labelled contended; the operating-system page-cache state was not controlled.

The probe selected the 21 pools recorded as retiring at epoch 641, the largest
retiring-pool set observed in the retained epoch-boundary log. It scanned the
live `PREFIX_POOL_DELEG` range with `ReadOptions.fillCache(false)`, decoded the
same `PoolDelegation` value used by `PoolReapProcessor`, and compared the pool
hash with the 21-entry lookup set.

The temporary benchmark test was removed after the runs and is not part of the
production or test source set.

## Results

| Trial | Iterator shape | Rows | Matching rows | Time |
|---|---|---:|---:|---:|
| First pass | one cache-disabled iterator | 1,446,334 | 1,051 | 1,124 ms |
| Production-shaped pass | 15 cache-disabled iterators, durable-cursor boundaries every 100,000 examined rows | 1,446,334 | 1,051 | 1,264 ms |

The production-shaped scan remained far below the 60-second typical-boundary
target even on the contended host. Reopening the iterator at bounded cursor
boundaries added approximately 140 ms in these two consecutive observations.
The measurement covers ordered read/decode/comparison work, not the 1,051
deletion writes, refund writes, fsync latency or the rest of the epoch boundary.

## Decision gate

The measured sequential scan does not justify adding a pool-major reverse
delegation index. ADR-050 therefore keeps the simpler credential-major scan and
its bounded write chunks. Full boundary timing, memory and correctness remain
Phase-5 network-validation gates.
107 changes: 107 additions & 0 deletions adr/reports/adr-050-phase-3-2026-08-31.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# ADR-050 Phase 3 — Recovery, rollback and provider-contract evidence

Date: 2026-08-31

## Result

Phase 3 is complete locally. POOLREAP now has explicit test-only commit
checkpoints around every durable write, startup recovery treats its progress
marker as an application-readiness gate, rollback-v1 reverses both partial and
complete runs, archive staging remains exactly-once across restart, and the
test-only in-memory store follows the same live cleanup semantics.

## Crash and resume matrix

The fixture forces the five-delegator transition into three delegation chunks
by setting the batch-operation limit to two. It injects a simulated process
failure immediately before and immediately after each of these commits:

| Sequence | Durable work | Before commit | After commit |
| ---: | --- | --- | --- |
| 0 | Initial progress marker | restart starts from no marker | restart reads `DELEGATIONS` |
| 1 | First delegation chunk | chunk is replayed | durable cursor skips it |
| 2 | Middle delegation chunk | chunk is replayed | durable cursor skips it |
| 3 | Final delegation chunk and `POOLS` transition | chunk is replayed | restart enters `POOLS` |
| 4 | Pool cleanup, refund and marker deletion | staged work aborts | completed phase is a no-op |

Every case resumes through a newly constructed `DefaultAccountStateStore` and
asserts:

- the reward account receives the exact registration-epoch deposit once;
- all live pool, retirement and pool-delegation rows are absent;
- the DRep delegation remains present; and
- the POOLREAP progress marker is absent.

## Application-readiness gate

`EpochBoundaryProcessor.recoverInterruptedBoundary()` now inspects POOLREAP
progress before normal startup recovery. It fails closed when progress is
orphaned or disagrees with the durable epoch, boundary slot or `STEP_SNAPSHOT`
state. For a valid interrupted boundary it resumes the transition and verifies
that the marker is gone before returning.

This method is invoked by `LedgerStateSubsystem.completeStartupRecovery()`
before UTXO recovery, account reconciliation, pointer-index readiness and the
flag that marks derived-state startup recovery complete. The production path
therefore cannot advertise a partially reaped chain tip as ready.

The tests cover both a valid restart from sequence 0 and rejection of a marker
without boundary-step state.

## Rollback-v1 evidence

Two state snapshots include pool lifecycle rows, stake accounts, pool and DRep
delegations, and accumulated reward rows:

- rollback of a completed POOLREAP restores the snapshot byte for byte;
- rollback after only a delegation chunk was committed restores it byte for
byte; and
- a simulated crash during rollback is resumed by a newly constructed store
from the existing `meta.rollback.v1.target-slot` marker.

No rollback-v2 codec or phase-ordering mechanism was added.

## Archive staging evidence

The recording archive sink verifies the final refund chunk on both sides of
the commit boundary:

- failure before commit aborts the writer; restart commits one `REFUND` fact;
- failure after commit leaves one committed `REFUND` fact; restart is a no-op.

The archived credential, pool, earned epoch and amount match the authoritative
pool lifecycle record.

## In-memory provider contract

`InMemoryAccountStateStore` is now documented as deterministic test support,
not a durable production fallback. It:

- charges a fresh pool registration from the current epoch parameters;
- preserves that deposit on active re-registration;
- reaps effective live pools and their stake-pool delegations at an epoch
transition; and
- preserves stake accounts and DRep delegations.

Durable recovery and boundary rollback acceptance remain requirements of the
RocksDB implementation only.

## Verification

Commands:

```text
./gradlew :ledger-state:test \
--tests 'com.bloxbean.cardano.yano.ledgerstate.DefaultAccountStateStorePoolLifecycleTest' \
--tests 'com.bloxbean.cardano.yano.ledgerstate.InMemoryAccountStateStoreTest'

./gradlew :ledger-state:test
```

Results:

- focused lifecycle/provider run: green;
- full `ledger-state` module: 362 tests, 0 failures, 0 errors, 0 skipped;
- `DefaultAccountStateStorePoolLifecycleTest`: 31 tests;
- `InMemoryAccountStateStoreTest`: 19 tests.

85 changes: 85 additions & 0 deletions adr/reports/adr-050-phase-4-2026-08-31.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# ADR-050 Phase 4 — Preview compatibility and documentation evidence

Date: 2026-08-31

## Result

Phase 4 is complete locally. Fresh account stores atomically initialize the
`pool-lifecycle-state-v1` semantic readiness marker with the existing
epoch-boundary/index markers. Populated stores without it and stores with an
unknown value fail closed before mutation with the typed
`IncompatibleChainStateException`.

## Marker contract

The current account-state initialization batch now contains:

- `meta.epoch_boundary_state_version = 1`;
- `meta.snapshot_dereg_index_version = 1`;
- `meta.reward_event_index_version = 1`; and
- `meta.pool_lifecycle_state_version = 1`.

On reopen, the existing epoch-boundary v1 path requires all three subordinate
readiness markers. A missing pool-lifecycle marker reports the explicit
`pool-lifecycle-state-v1` incompatibility and a clean-resync action. Any other
value is unsupported and is also rejected.

This adds no epoch-boundary-v2, rollback-v2, compatibility decoder or promotion
branch.

## Rejection evidence

`EpochBoundaryStateVersionTest` contains six cases. The new cases prove:

- a fresh empty store sees all readiness markers after the single synchronous
initialization batch;
- a populated account fixture with the pool-lifecycle marker removed throws
`IncompatibleChainStateException` and its complete account-state key/value
digest is unchanged; and
- an unknown pool-lifecycle marker value throws the same typed exception and
leaves the complete digest unchanged.

The existing application startup test confirms that a wrapped
`IncompatibleChainStateException` is recovered by the fail-fast startup path
and rethrown as the same typed instance. Persistent-store incompatibility does
not select the in-memory test implementation.

## Operator documentation

The README now links to `docs/ACCOUNT_STATE_AND_ROLLBACK.md`. That guide records:

- SNAP-before-POOLREAP ordering and live/history ownership;
- progress-marker startup readiness;
- clean-resync compatibility behavior;
- the one-shot `rollback-to-slot` and `rollback-to-epoch` debug properties;
- reversal of POOLREAP lifecycle rows, delegation removal and exact refunds;
and
- rollback-floor failure behavior and rollback-v1 scope.

It also contains one operator-facing marker inventory table distinguishing the
epoch-boundary and pool-lifecycle permanent guards, the coordinate-pinned UTXO
pointer readiness marker, the genesis bootstrap identity guard, and the
rollback-v1/POOLREAP transient recovery cursors. For each it states when the
marker is written and how absence or incompatibility appears to an operator.

`docs/UPGRADING.md` states that the current mainnet tip and e447/e505/e610/e628
checkpoint ladder predate the marker. They must be retained only as backups or
comparison fixtures; the accepted rollout needs a clean full replay and a
rebuilt checkpoint ladder.

## Verification

```text
./gradlew :ledger-state:test \
--tests 'com.bloxbean.cardano.yano.ledgerstate.EpochBoundaryStateVersionTest' \
:app:test --tests 'com.bloxbean.cardano.yano.app.YanoProducerTest'

./gradlew :ledger-state:test
```

Results:

- compatibility and application fail-fast tests: green;
- full `ledger-state` module: 364 tests, 0 failures, 0 errors, 0 skipped;
- `EpochBoundaryStateVersionTest`: 6 tests, all green;
- `git diff --check`: green.
Loading
Loading