Reinstate JSON-based history feature flag and add staking reward snapshots
Background
The history feature flag previously wrote full JSON snapshots of key credits.aleo mappings at each finalized block height (commit before 09f729f7). This was behind #[cfg(all(feature = "history", feature = "rocks"))] and only ran in FinalizeMode::RealRun. It wrote five mappings per block:
delegated
bonded
metadata
unbonding
withdraw
These were written as JSON files at the time of finalization, allowing external consumers (e.g. block explorers and data pipelines) to efficiently reconstruct historical staking state at any block height.
Problem
In commit 09f729f7 ("perf: revamp historical mapping storage", Feb 2026), this approach was replaced with a per-key RocksDB store in ledger/store/src/program/finalize.rs. The new approach writes every individual mapping key change across all programs at every block, storing (program_id, mapping_name, key, height) → value in RocksDB.
This is not performant for external data consumers that only care about specific, well-known mappings (the five credits.aleo staking mappings above). The old approach was targeted and cheap — it wrote exactly the data needed once per block, as a single coherent snapshot. The new approach writes orders of magnitude more data with no scoping.
Proposal
1. Reinstate the JSON-based history feature flag
Restore the original behavior: when compiled with --features history, write JSON snapshots of the five credits.aleo mappings at the end of each FinalizeMode::RealRun finalization. The removed code was in synthesizer/src/vm/finalize.rs inside the credits finalize block:
#[cfg(all(feature = "history", feature = "rocks"))]
{
if IS_FINALIZE {
let history = History::new(N::ID, store.storage_mode());
history.store_mapping(state.block_height(), MappingName::Delegated, &next_delegated_map)?;
history.store_mapping(state.block_height(), MappingName::Bonded, &next_bonded_map)?;
// ...metadata, unbonding, withdraw
}
}
And the History struct itself (deleted in synthesizer/src/vm/helpers/history.rs) needs to be restored.
2. Extend the history flag to also write staking rewards per block
During credits finalization, the VM already computes per-staker rewards via staking_rewards(). This produces a map of:
staker_address → (validator_address, reward_microcredits, new_total_stake)
This data is computed but not persisted anywhere in the canonical chain state — it is derived, ephemeral, and only available at finalization time. External consumers that want to reconstruct historical staking rewards (e.g. for APY calculations, attribution, or tax reporting) currently cannot access this without replaying the entire chain.
We'd like the history flag to also write this computed staking reward data alongside the mapping snapshots, one JSON entry per staker per block. This does not need to be stored in the credits program state or in RocksDB — a JSON file per block height alongside the existing mapping history files is sufficient.
The history-staking-rewards feature that was briefly present in this repo (commit 62a05e3ee, later disabled) attempted to do this via store.staking_rewards_map(). We'd prefer this to be merged into the history feature itself and written to disk in the same JSON snapshot pattern rather than introduced as a separate RocksDB map.
3. Keep slipstream-plugins as a separate, independent feature
The slipstream-plugins feature flag should remain decoupled from history and history-staking-rewards. We'd like these two concerns kept orthogonal — enabling history snapshots should not require or imply slipstream, and vice versa.
What we're not asking for
- We're not asking for staking rewards to be written into
credits.aleo state or be part of consensus.
- We're not asking for real-time indexing or streaming. A file per finalized block is fine.
Why this matters
Without the scoped JSON snapshot approach, external nodes that need historical staking state must either replay the full chain or maintain their own side-channel tracking at significant operational cost. For block explorers, attribution dashboards, and validators tracking delegator rewards, the targeted per-block snapshot approach is the right interface.
The old implementation was minimal, correct, and required no changes to consensus — it was purely a compile-time opt-in for node operators who wanted this data.
Questions
- Is there appetite to restore
synthesizer/src/vm/helpers/history.rs and the associated snapshot writes in finalize.rs?
Reinstate JSON-based
historyfeature flag and add staking reward snapshotsBackground
The
historyfeature flag previously wrote full JSON snapshots of keycredits.aleomappings at each finalized block height (commit before 09f729f7). This was behind#[cfg(all(feature = "history", feature = "rocks"))]and only ran inFinalizeMode::RealRun. It wrote five mappings per block:delegatedbondedmetadataunbondingwithdrawThese were written as JSON files at the time of finalization, allowing external consumers (e.g. block explorers and data pipelines) to efficiently reconstruct historical staking state at any block height.
Problem
In commit 09f729f7 ("perf: revamp historical mapping storage", Feb 2026), this approach was replaced with a per-key RocksDB store in
ledger/store/src/program/finalize.rs. The new approach writes every individual mapping key change across all programs at every block, storing(program_id, mapping_name, key, height) → valuein RocksDB.This is not performant for external data consumers that only care about specific, well-known mappings (the five
credits.aleostaking mappings above). The old approach was targeted and cheap — it wrote exactly the data needed once per block, as a single coherent snapshot. The new approach writes orders of magnitude more data with no scoping.Proposal
1. Reinstate the JSON-based
historyfeature flagRestore the original behavior: when compiled with
--features history, write JSON snapshots of the fivecredits.aleomappings at the end of eachFinalizeMode::RealRunfinalization. The removed code was insynthesizer/src/vm/finalize.rsinside the credits finalize block:And the
Historystruct itself (deleted insynthesizer/src/vm/helpers/history.rs) needs to be restored.2. Extend the
historyflag to also write staking rewards per blockDuring credits finalization, the VM already computes per-staker rewards via
staking_rewards(). This produces a map of:This data is computed but not persisted anywhere in the canonical chain state — it is derived, ephemeral, and only available at finalization time. External consumers that want to reconstruct historical staking rewards (e.g. for APY calculations, attribution, or tax reporting) currently cannot access this without replaying the entire chain.
We'd like the
historyflag to also write this computed staking reward data alongside the mapping snapshots, one JSON entry per staker per block. This does not need to be stored in the credits program state or in RocksDB — a JSON file per block height alongside the existing mapping history files is sufficient.The
history-staking-rewardsfeature that was briefly present in this repo (commit 62a05e3ee, later disabled) attempted to do this viastore.staking_rewards_map(). We'd prefer this to be merged into thehistoryfeature itself and written to disk in the same JSON snapshot pattern rather than introduced as a separate RocksDB map.3. Keep
slipstream-pluginsas a separate, independent featureThe
slipstream-pluginsfeature flag should remain decoupled fromhistoryandhistory-staking-rewards. We'd like these two concerns kept orthogonal — enabling history snapshots should not require or imply slipstream, and vice versa.What we're not asking for
credits.aleostate or be part of consensus.Why this matters
Without the scoped JSON snapshot approach, external nodes that need historical staking state must either replay the full chain or maintain their own side-channel tracking at significant operational cost. For block explorers, attribution dashboards, and validators tracking delegator rewards, the targeted per-block snapshot approach is the right interface.
The old implementation was minimal, correct, and required no changes to consensus — it was purely a compile-time opt-in for node operators who wanted this data.
Questions
synthesizer/src/vm/helpers/history.rsand the associated snapshot writes infinalize.rs?