Skip to content

v0.8.0 — incremental state-fold strategy (config-selected)

Choose a tag to compare

@github-actions github-actions released this 12 Jul 09:28
· 46 commits to main since this release
6e76791

What's new

A second, config-selected state-fold strategy: incremental (PR #26, #25).

The reference fold (DecodeState) is a stateless pure reducer: every ledger it rebuilds its whole in-memory mirror from the prior LedgerState and re-sorts the full typed state — O(total accumulated state) per ledger, so replay throughput decays as users accumulate. Measured on a production mainnet replay: ~312 ledgers/s with an empty prior vs ~51 ledgers/s carrying ~600k ledgers of accumulated state.

blend.Config gains a StateMode knob, selected once at construction and swapped as a whole strategy:

  • paranoid (default, and the default when the knob is empty) — today's code path, byte-for-byte. It remains the reference oracle: fully stateless, run-twice byte-identical, the strategy every optimization is validated against.
  • incremental — a persistent builder mirror carried across ledgers plus caches for the per-user position blocks and their global sort order. Per-ledger cost drops to O(this ledger's changes) plus output materialization. Output is byte-identical to paranoid — same slices, same ordering, same serialized checksums — enforced by a permanent CI parity gate (golden fixtures + a synthetic multi-ledger churn sequence, per-ledger state and delta-stream comparison, and a red-check that proves seeded divergences are caught).

Measured (real mainnet checkpoint state, 20,000 real ledgers folded from a stored production checkpoint at ledger 57,166,999 carrying ~600k ledgers of accumulated state):

  • Byte-parity on production data: sha256 state checksums identical between modes at every measured interval (ledgers 57,171,999 / 57,176,999 / 57,181,999 / 57,186,999).
  • Fold throughput at real state size: paranoid 372.1s for 20k ledgers (53.8 l/s — independently reproducing the production replay's observed ~51 l/s decay) vs incremental 4.8s (4,198.9 l/s) — 78.1x. The fold stops being the replay bottleneck; stateless extraction becomes the dominant term.

In-repo benchmark at 50,000 carried user positions, one touched user per ledger: paranoid 453.6 ms/ledger vs incremental 10.4 ms/ledger (~44x).

Statefulness contract

An incremental adapter carries fold state between calls: do not share one across concurrent folds, and treat the returned LedgerState as immutable. DecodeState remains a pure function of (prior content, changes): the carried mirror is only trusted when prior is the adapter's own previous return value; any other prior (first ledger, checkpoint restore, rewind) reseeds from it. The default paranoid mode is untouched and fully stateless.

Compatibility

Minor version bump: additive API only (StateMode on blend.Config). No LedgerState shape change — snapshots and fold checkpoints round-trip unchanged between versions and between modes; a fold may be seeded from a checkpoint written by either mode. Paranoid remains the default and the reference; consumers opt into incremental deliberately, per deployment.

Full diff: v0.7.1...v0.8.0