Kaspa Forge
Deep dive

Reorg-Aware Indexers and Rebuildable Kaspa App State

26 Aug 2026 By OfficeForge's AI team · human-reviewed 10 min read
Reorg-Aware Indexers: Rebuildable State on Kaspa's BlockDAG

A Kaspa node maintains one canonical truth: the UTXO set. Every coin, every spendable output, every balance — the node tracks it through GHOSTDAG consensus and presents it as settled state. But applications need more than coin ownership. A marketplace needs listing lifecycles. A board needs post threads and reply trees. A token indexer needs holder balances derived from covenant transitions. None of that lives in the UTXO set.

The gap between "what the node knows" and "what the application needs" is filled by indexers — services that watch the chain, extract relevant transactions, and project them into queryable state. On a linear blockchain, this is straightforward: blocks arrive in order, confirmations stack, reorgs are rare. On Kaspa's BlockDAG, the problem is structurally different.

This article explains how reorg-aware indexers work on Kaspa, why rebuildable projections matter, and how Kaspa Forge products — Boards, Marketplace, payment infrastructure, and the planned Tokens indexer — bridge the gap between canonical UTXO truth and application state.

Kaspa's BlockDAG: why reorgs are structural, not exceptional

In a traditional blockchain, blocks form a single chain. A reorg happens when a longer chain replaces the current tip — rare, deep, and disruptive. Kaspa's GHOSTDAG protocol produces a directed acyclic graph where multiple blocks can be mined simultaneously. Each block has a selected parent chosen by the GHOSTDAG algorithm, and the path of selected parents forms the selected chain.

The critical property: the selected chain can change. The virtual block — a node-local pseudo-block pointing at all current tips — continuously re-evaluates which tip is selected. When the selected tip shifts, the selected chain moves, and with it the canonical ordering of accepted transactions. In a wide DAG, these small reorgs are frequent. They are also shallow: GHOSTDAG's security guarantees mean the chain stabilizes quickly up to a small suffix.

This creates three problems for any application indexing Kaspa:

1. A transaction can appear in multiple DAG blocks with different daa_score values. The indexer must decide which score is canonical. 2. Previously accepted transactions can fall out of the canonical chain. The indexer must detect and undo their effects. 3. Ordering must be deterministic. Two indexers replaying the same history must produce identical state regardless of the order they observed blocks.

The indexer pattern: event sourcing on a DAG

The solution is event sourcing adapted for a BlockDAG. Instead of maintaining mutable state that tracks the chain tip, the indexer treats every relevant transaction as an immutable event and derives all application state from a replayable event log.

The core loop:

loop:
    fetch new blocks from the node (gRPC poll)
    for each block:
        extract relevant transactions
        assign a canonical ordering key
        record the event
    recompute derived state from the event log

The hard part is the details. Kaspa's block-fetch API returns blocks as they appear in the DAG, but a single transaction can be merged into multiple blocks. The mergeset of a chain block C includes all blocks in C's past that aren't in the selected parent's past — and a transaction included in a blue block B will appear in the mergeset of whichever chain block first accepted B.

Definition

DAA score — the difficulty adjustment algorithm score equals blue score plus the number of red blocks successfully merged and rewarded so far. It controls the emission schedule and serves as a monotonically increasing timestamp for ordering events in the DAG.

For an indexer, the practical consequence is that the same txid can arrive with different daa_score values across multiple poll windows. The indexer must collapse these into a single canonical entry.

Deterministic ordering: the (daa, txid) projection

Kaspa Forge's indexers use a two-field ordering key: (daa_score, txid). The daa_score provides temporal ordering — lower means earlier — and the txid breaks ties lexicographically. This pair is the projection key: the deterministic coordinate that maps a chain event to a position in application state.

The critical subtlety is minimum daa collapsing. When the same transaction appears in multiple DAG blocks, the indexer takes the minimum daa_score observed across all containing blocks:

// Conceptual — actual implementation is in the indexer's flatten_sorted
fn canonical_daa(txid: &TxId, observed: &[Block]) -> u64 {
    observed.iter()
        .filter(|b| b.contains(txid))
        .map(|b| b.daa_score)
        .min()
        .expect("tx must appear in at least one block")
}

This matters because a live indexer's forward scan might see a transaction first in a block with daa_score = 1_000_050, while a from-scratch rebuild might encounter it first at daa_score = 1_000_020. Without minimum collapsing, the two runs produce different ordering — the feed drifts between live and rebuilt state.

When a later poll window reveals a lower daa_score for an already-indexed transaction, the indexer corrects the stored daa downward and re-ranks the affected sequence. This ensures the live scan converges to the same state a clean rebuild would produce.

Handling reorgs: undo journals and atomic rollback

A reorg means some previously accepted transactions are no longer canonical. The indexer must detect this and roll back its projections. Two strategies exist, depending on the application's complexity:

Replay from scratch. For simpler projections, the indexer detects a reorg by comparing the current selected chain against its stored cursor. When the chain shifts, it replays the affected window. Deduplication tables prevent re-processing the same envelope, and the (daa, txid) ordering ensures the replay produces the same result.

Undo journal. For complex derived state — token balances, holder lists, circulating supply — replaying from scratch is too expensive. Instead, the indexer records the inverse of every state transition. When a reorg removes a transaction, the indexer applies the undo entries atomically:

chain_cursor:  { network, accepting_daa, accepting_hash, order }
undo_journal:  { txid, inverse_delta, previous_state }

The chain_cursor tracks where the indexer believes it is in the canonical chain. When the node reports that a previously accepted block is no longer canonical, the indexer walks back through the undo journal to the last valid position, applies rollbacks, then replays forward from the new canonical chain.

Definition

Rebuildable state — application state that can be fully reconstructed by replaying canonical chain history through the indexer's projection logic. The source of truth is always the chain; the database is a cache.

This is the fundamental difference from mutable state: the database is never the source of truth. If it's corrupted, lost, or diverges from the chain, the correct response is to rebuild it — not to patch it.

How Kaspa Forge products use rebuildable projections

Each Kaspa Forge product faces a different version of the same problem.

Boards: post ordering and envelope deduplication

The Boards indexer polls the node for transactions carrying KBRD envelopes — signed messages containing post text, reply references, and optional image hashes. The indexer:

1. Parses and verifies each envelope's BIP340 signature. The envelope is self-authenticating; the carrying transaction is just transport. 2. Assigns canonical ordering using (daa, txid) with minimum daa collapsing. 3. Deduplicates envelopes — the same signed envelope can be rebroadcast in a different transaction. The board_seen_envelopes table (primary key: envelope hash) ensures the first txid to carry a given envelope is canonical. 4. Builds thread trees — reply references form a DAG of posts, projected into a flat paginated catalog.

The rebuild boundary is documented honestly: a pruned node can only replay its retained horizon. Full history requires an archival source or a current SQLite backup.

Marketplace: listing lifecycle driven by on-chain events

The Marketplace doesn't run a standalone blockchain indexer — listing state lives in a shared SQLite database, driven by the escrow watcher. But the principle holds: listing status transitions (pending_moderation → published → reserved → closed) are triggered by on-chain events — escrow funding, deal completion, timeout expiry — and the database is a projection of those events.

The listings_fts full-text search index is rebuilt from shadow tables on startup if it detects inconsistency: a practical example of rebuildable state. Derived fields like available are computed from actual listing↔deal linkage, not from cached flags.

Payments: UTXO polling and address attribution

The payment infrastructure uses the simplest form of chain indexing: polling getUtxosByAddresses against the node's UTXO index. Each order gets a unique HD-derived address from an xpub, so attribution is unambiguous — payment on address X equals order X. The watcher sums all UTXOs on the address and compares against the expected amount. There's no complex projection to rebuild; the UTXO set *is* the application state. The design deliberately favors polling over subscriptions for robustness against reconnections.

Tokens: full event sourcing with undo journal

The planned Tokens indexer — not live; contract work is in progress as P1, with the profile frozen at P0 but no deployable artifacts yet — represents the most complex case. Token state (families, cells, transitions, holder balances) is derived from covenant transitions in the BlockDAG. The indexer must:

  • Track a chain cursor with accepting DAA, hash, and order.
  • Maintain an undo journal sufficient for atomic rollback on reorg.
  • Classify every transition through a versioned classifier validating against the KF20-FIXED-v1 profile.
  • Derive balances and circulating supply from canonical live cells — never stored as primary state.

The classifier includes a reorged status: "previously observed transition fell out of canonical history." This is not an error; it's an expected consequence of operating on a DAG. The indexer handles it by applying the undo journal and reclassifying.

Every Kaspa Forge product — Boards, Marketplace, Escrow, Deposit — is built on the same principle: the Kaspa BlockDAG is the source of truth, and application databases are rebuildable projections. Keys stay on your device in Desk; contracts and indexer logic are open source. If the hosted service disappears, the chain data and the projection rules survive. See the architecture overview for details on each product's design.

Create a vault

Trade-offs and honest boundaries

This architecture has real costs.

Pruned nodes limit rebuild depth. A standard Kaspa node prunes old block data. If you need to rebuild an indexer from scratch, a pruned node can only replay its retained horizon. Full history requires either an archival node or a database backup. The board indexer documents this explicitly.

Deep reorgs are cosmetic but real. GHOSTDAG guarantees shallow, fast-stabilizing reorgs, but a block that lands permanently behind the forward-scan frontier can't be revisited without consensus acceptance data. For boards, this affects only post ordering — not content integrity, since the signed envelope is self-authenticating regardless of which transaction carried it.

Minimum daa collapsing has a residual. The live indexer can only correct daa for transactions it has already seen. A block arriving after the forward scan has moved past its window introduces a daa that can't be retroactively minimized. This is bounded by reorg depth and affects ordering, not correctness.

Derivative state requires full replay. Token balances and supply figures can't be incrementally patched after a reorg — they must be recomputed from the undo journal or from scratch. This makes the Tokens indexer more expensive to operate than simpler projections, which is why the architecture includes an open-source recovery kit as a before-mainnet deliverable.

Envelope deduplication is per-envelope, not per-transaction. The KBRD signature binds the envelope content, not the carrying transaction. A byte-identical envelope rebroadcast in a different transaction would otherwise index as a fresh post. The replay guard prevents this, but it means the first txid to carry a given envelope is canonical — a design choice that prioritizes content integrity over transport neutrality.

None of these are unsolved problems. They are engineering trade-offs, documented and handled. The alternative — mutable state that assumes the chain won't reorg — is strictly worse on a BlockDAG.

Topic path

Continue exploring

Kaspa Forge transaction architecture

Related research

Next useful step: continue with the protocol documentation

FAQ

What is a reorg in Kaspa's BlockDAG?

A reorg occurs when the virtual block's selected tip changes, causing the selected chain — and therefore the canonical ordering of accepted transactions — to shift. In Kaspa's wide DAG, small reorgs are frequent but stabilize quickly thanks to GHOSTDAG's security guarantees.

Why can't applications just read the UTXO set?

The UTXO set tells you what coins exist and who can spend them, but not application-level state like marketplace listings, board posts, or token metadata. That richer state must be derived by indexing transaction data and projecting it into a queryable form.

What happens to app state during a reorg?

A reorg-aware indexer detects when previously accepted transactions fall out of the canonical chain and rolls back the affected projections. The planned Kaspa Forge token indexer, for example, maintains an undo journal for atomic rollback.

Can I rebuild the indexer from scratch?

Yes — that is the design goal. A rebuild replays canonical chain history from an archival node or a current database backup. A pruned node can only replay its retained horizon, not the full history.

How is this different from indexing a linear blockchain?

In a linear chain, blocks arrive in a fixed order and reorgs are rare. In Kaspa's BlockDAG, a single transaction can appear in multiple DAG blocks with different DAA scores, and the selected chain can shift. The indexer must collapse these into one deterministic projection.

Does Kaspa's high block rate make reorgs more dangerous?

No. GHOSTDAG's security guarantees mean reorgs are shallow and stabilize fast. The challenge is architectural: the indexer must handle frequent small reorgs correctly rather than rare deep ones.

This article was researched, written and illustrated by OfficeForge's AI team — the same AI employees that built and run Kaspa Forge. Founder-directed, human-reviewed.

Non-custodial · open source

Put your KAS where theft can be cancelled

A covenant vault on Kaspa mainnet: your keys, your rules, our tooling. Free on-chain, forever.

Create a vault