Skip to content

feat(indexer): genesis import framework with account and balance seed - #3587

Merged
baktun14 merged 3 commits into
feat/indexer-scaffold-chain-indexer-appfrom
feat/indexer-genesis-account-balance-seed
Aug 12, 2026
Merged

feat(indexer): genesis import framework with account and balance seed#3587
baktun14 merged 3 commits into
feat/indexer-scaffold-chain-indexer-appfrom
feat/indexer-genesis-account-balance-seed

Conversation

@baktun14

Copy link
Copy Markdown
Contributor

Why

Balance tracking is only trustworthy if it starts from the network's first block. The indexer already seeds validators from genesis, but account balances have no starting point, so any balance derived from block deltas alone would drift from the truth.

Closes CON-805

What

A genesis import framework that seeds per-module state in a single transaction before the first block is indexed:

  • Genesis accounts, interned to stable ids
  • Account balances, written as genesis-reason rows in the balance-change ledger
  • Validators and their staking delegations

The seed runs exactly once. A marker row in indexer_state short-circuits repeat runs, and an onConflictDoNothing claim keeps concurrent fresh starts from double-seeding. Balance tracking also refuses to begin unless the effective start height is the network's genesis height; a mid-chain start throws with a message pointing at the correct SYNC_START_HEIGHT.

Migration

0001_long_mach_iv.sql is additive only: new accounts, account_balances, balance_changes, delegations, and validators tables plus the balance_change_reason enum. Every index is created on an empty table, so it runs without locking existing data.

Stack

Stacked on #3579 (chain-indexer scaffold); #3585 (raw block archive) already merged into that branch, so the base here is feat/indexer-scaffold-chain-indexer-app. It retargets to main once #3579 lands.

…seed

Balance tracking is only trustworthy from a network's genesis, so this
seeds genesis state before the first block: accounts, per-denom balances
(recorded as genesis-reason ledger entries), validators, and staking
delegations, all in one transaction.

It is gated behind GENESIS_IMPORT (default off). When enabled, the sync
role runs a per-module seeder framework exactly once, using a genesis
checkpoint in indexer_state so a restart is a no-op, and it rejects a
fresh start that is not at the network's genesis height. Genesis is
fetched over RPC /genesis_chunked and its chain_id is asserted against
the node being indexed.

This adds the accounts, account_balances, balance_changes, validators,
and delegations tables (L-3); the ongoing per-block ledger lands in L-4.

Refs CON-805
@baktun14
baktun14 requested a review from a team as a code owner August 12, 2026 16:18
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92cc79ed-db5f-4ed0-94ba-6e5f19eb59db

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Comment thread apps/chain-indexer/src/genesis/genesis-schema.ts
Comment thread apps/chain-indexer/src/genesis/bank-seeder.service.ts Outdated
Comment thread apps/chain-indexer/src/pipeline/sync-runner.service.ts
Extract mapDescription/mapCommissionRates so the staking and gentx validator
mappers stop duplicating the description and commission field mapping, and add
an insertChunked helper the bank and staking seeders call instead of
hand-rolling the chunked insert loop.
The seed only runs on a fresh start, so turning GENESIS_IMPORT on after the
indexer already has a sync checkpoint silently skips it and leaves the account
and balance tables empty with no signal. Add a hasSeeded check on resume and
log a warning so the operator knows genesis was never backfilled.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR (automated review) and didn't find any bugs beyond the nits already noted inline. Because it introduces a new DB migration and non-trivial genesis-parsing/import logic in a data-integrity-sensitive path, a human look would still be worthwhile before merging.

What was reviewed: the genesis-import transaction flow (marker claim, mid-chain rejection, per-module seeders), the genesis-schema parsing of Cosmos SDK auth/bank/staking/genutil shapes, address derivation (bech32 re-encoding, ed25519 consensus hex address), and the sync-runner's fresh-start vs. resume gating for the one-time seed. Also checked that account interning happens before the balance/staking seeders reference it, and that the migration is additive-only.

Extended reasoning...

Overview

This PR adds a genesis-import framework to apps/chain-indexer: a new migration (accounts, account_balances, balance_changes, delegations, validators tables + an enum), a GenesisImportService that seeds all modules in one transaction gated by a marker row in indexer_state, per-module seeders (account/bank/staking), a genesis-schema parser for Cosmos SDK genesis documents, address-derivation helpers (bech32 re-encoding, consensus hex address from ed25519 pubkey), an RPC genesis-chunk fetcher/reassembler, and changes to sync-runner.service.ts to gate the seed on a fresh start vs. resume.

Security risks

No injection, auth, or data-exposure concerns — all writes are parameterized via Drizzle, and the RPC genesis fetch validates chain_id against the node's reported network before importing. The main correctness risk is data-integrity, not security: a wrong parse or wrong height gate could silently seed incorrect balances, which is why the PR leans on strict validation (mid-chain rejection, chain_id check, marker-based idempotency).

Level of scrutiny

This warrants a higher level of scrutiny than average: it's a new, additive migration (low risk to existing data) paired with non-trivial one-time business logic (bech32/hex address derivation, nested schema variants for validators from two different genesis shapes, chunked genesis reassembly) in a data-correctness-sensitive area. The bug-hunting pass found no confirmed bugs this run — three candidate issues (mid-chain error ordering relative to the marker check, sequential genesis chunk fetching, and balance/ledger desync) were raised and refuted by verifiers — and two non-blocking nits (validator-mapping duplication, chunked-insert-loop duplication) remain from a prior pass. Given the size (XL) and the fact this is laying the foundation for balance tracking that other features will depend on, a human sanity-check of the parsing/derivation logic against real genesis exports is worth the extra pass.

Other factors

Test coverage is thorough — dedicated specs for the address helpers, schema parsing (including edge cases like malformed operator addresses and missing account numbers), each seeder, the import service's concurrency/idempotency paths, and sync-runner's genesis gating/warning behavior. The migration is additive-only and every new index is on an empty table, so it carries no lock risk against existing data.

@baktun14
baktun14 merged commit 6f4d27f into feat/indexer-scaffold-chain-indexer-app Aug 12, 2026
7 checks passed
@baktun14
baktun14 deleted the feat/indexer-genesis-account-balance-seed branch August 12, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant