Skip to content

feat(state): skip the transparent address index in pruned checkpoint-sync mode#358

Merged
p0mvn merged 2 commits into
state-checkpoint-sync-configfrom
state-skip-address-index
Jul 1, 2026
Merged

feat(state): skip the transparent address index in pruned checkpoint-sync mode#358
p0mvn merged 2 commits into
state-checkpoint-sync-configfrom
state-skip-address-index

Conversation

@p0mvn

@p0mvn p0mvn commented Jul 1, 2026

Copy link
Copy Markdown

Stacked on #357 — targets that branch, so this PR shows only its own diff.
Rebase onto ironwood-main once the base PR merges.

Motivation

On a pruned, checkpoint-syncing node (the minimal fast-validator configuration),
the transparent address index — balances, address→utxo, address→tx — is RPC-only
state that consensus never reads. Building it costs per-block address-balance
reads and index writes on the commit path. Pruned mode already drops
raw-transaction storage for the same reason; this extends that to the address
index.

Solution

  • Config::skip_address_index() = Pruned && checkpoint_sync gates the behavior.
  • The commit path skips the per-block address-balance reads and the 3 address
    index CF writes. The UTXO set (utxo_by_out_loc), tx_loc_by_hash, nullifiers,
    note commitment trees, and value pool are unchanged.
  • Rollback and the block-info/address-received format check tolerate the absent
    index (they only touched it to maintain the address CFs).
  • The address-lookup RPCs (getaddressbalance/getaddressutxos/getaddresstxids)
    return an explicit "index disabled in pruned mode" error rather than wrong
    (empty) results.

Archive nodes, and pruned nodes with checkpoint sync disabled (full semantic
verification), keep the index unchanged.

Test evidence

  • Gate unit test (skip_address_index_only_when_pruned_and_checkpoint_syncing).
  • Finalized-state test: the 3 address CFs are empty in pruned+checkpoint and
    populated in archive, with utxo_by_out_loc still written when skipping.
  • Full zebra-state lib suite: 205 passed, 0 failed. Build + clippy + fmt clean.

AI disclosure

Implemented with Claude Code (the skip, RPC guards, rollback/format-check
adaptations, and tests).

…sync mode

In pruned + checkpoint-sync mode (the minimal fast-validator configuration), the
transparent address index — balances, address→utxo, address→tx — is RPC-only
state that consensus never reads. Skip building it, the way pruned mode already
drops raw-transaction storage.

- `Config::skip_address_index()` = `Pruned && checkpoint_sync` gates the behavior.
- The commit path skips the per-block address-balance reads and the 3 address
  index CF writes. The UTXO set (`utxo_by_out_loc`), `tx_loc_by_hash`, nullifiers,
  note commitment trees, and value pool are all unchanged.
- Rollback and the block-info/address-received format check tolerate the absent
  index — they only touched it to maintain the address CFs.
- The address-lookup RPCs (getaddressbalance/utxos/txids) return an explicit
  "index disabled in pruned mode" error rather than wrong (empty) results.

Archive nodes, and pruned nodes with checkpoint sync disabled (full semantic
verification), keep the index unchanged.

Tests: a gate unit test, plus a finalized-state test asserting the 3 address CFs
are empty in pruned+checkpoint and populated in archive, with `utxo_by_out_loc`
still written when skipping. Full zebra-state lib suite passes (205).

Stacked on the checkpoint_sync config mirror (PR A).
@v12-auditor

v12-auditor Bot commented Jul 1, 2026

Copy link
Copy Markdown

Note

Complete: Audit complete. V12 found two issues worth reviewing.

Open the full results here.

FindingSeverityDetails
F-94749 🟡 Medium
Spent-output index skipped

The diff makes pruned checkpoint-sync commits skip the per-transaction helper prepare_spending_transparent_tx_ids_batch whenever Config::skip_address_index() is true. That helper is also the normal indexer build path that writes TX_LOC_BY_SPENT_OUT_LOC, which maps a transparent spent output location to the transaction that spent it. The column family is not one of the three auxiliary address-index tables, and block commit still stores the transaction hash/location indexes needed to resolve a stored transaction location. ZebraDb::spending_transaction_hash resolves Spend::OutPoint through this missing mapping, and the public read API exposes the result as ReadResponse::TransactionId(None). A pruned checkpoint-sync indexer node therefore records consensus UTXO deletion correctly but falsely reports no finalized spender for affected transparent outpoints.

F-94753 🟡 Medium
Transient address-index mode corrupts reads and rollback

The transparent address index is treated as a transient runtime choice instead of durable database state. checkpoint_sync is skipped in serde, the database path does not encode whether address indexing was skipped, and both forward writes and rollback logic consult the current skip_address_index() value rather than what historical blocks actually stored. As a result, a pruned database can commit finalized blocks without balance_by_transparent_addr, address→UTXO, or address→transaction rows, then later be reopened under a configuration that assumes those rows exist and exposes the same on-disk state as if it were complete. The read side interprets missing rows as real zero balances or empty result sets, while rollback under the opposite configuration either aborts on MissingAddressBalance for never-indexed history or skips deleting stale address rows for previously indexed history. The common implementation work is to persist address-index completeness/history with the database and make reopen, reads, and rollback honor that persisted state rather than recomputing it from the current config.

Analyzed six files, diff 78c329b...570708e.

@p0mvn
p0mvn marked this pull request as draft July 1, 2026 04:17
@p0mvn
p0mvn marked this pull request as ready for review July 1, 2026 05:25
@p0mvn
p0mvn merged commit aade044 into state-checkpoint-sync-config Jul 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant