You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#159, #160 and #161 shipped the network column, per-network RPC clients and one indexer loop per network. Every acceptance criterion was met and 290 unit tests pass.
None of it has ever run against two networks at once. The unit tests assert that the right arguments are passed; nothing asserts that two live loops, writing to one database, keep their rows apart.
That gap matters more than usual here, because every failure mode in this area is silent. network carries DEFAULT 'testnet', so a write that forgets to set it compiles, typechecks, passes CI and inserts a row — just under the wrong chain. Three such writes were found by reading during #161 (upsertHostFnLogs, the INGEST_WORKERS > 1 parallel path, fetchNftMetadata's passphrase). Reading is not a repeatable control.
What to build
An integration harness that runs two indexer loops concurrently against one database with two independently controlled event sources, and asserts isolation.
src/indexer/sources/index.ts — SourceSwitcherConfig.network, the injection seam
Suggested execution
The loops are infinite, so drive pollOnce via a controllable source rather than startIndexer directly, or run startIndexer with a stub source and stop it between assertions.
Build two fake event sources returning disjoint, identifiable events — e.g. testnet emits contractIdCTEST…, mainnet CMAIN…, at deliberately different ledger heights (mainnet far behind testnet, which is the real-world shape).
Run both loops to completion over a fixed range.
Assert isolation across every table that carries network.
Acceptance criteria
Each must fail if the corresponding scoping is removed — please verify that by actually reverting it, not by inspection.
Every row written by the testnet loop has network='testnet', and vice versa, across TokenTransfer, HostFnLog, NftTransfer, NftMetadata, AccountSummary
IndexerState holds one row per network with differentlastIndexedLedger values; neither loop advances the other's cursor
The same eventId present on both networks produces two rows, not one (this is the collision @@unique([network, eventId]) exists to allow — assert it directly)
AccountSummary for one address active on both networks yields two rows with independent totals, and neither total includes the other chain's amounts
rollbackToLedger(n, 'testnet') deletes no mainnet rows, even when mainnet has rows above ledger n
pruneOldTransfers('testnet') deletes no mainnet rows
Killing one loop mid-run leaves the other's cursor and row counts untouched
/status reports both networks with the correct, differing lastIndexedLedger
The harness runs in CI
Notes
INGEST_WORKERS > 1 takes a different code path (pollParallel). Cover it — it is where one of the three untagged writes lived.
Do not assert only that queries succeed. A query missing its network filter still succeeds; it just returns both chains. Assert the row counts and the tags.
_resetIndexerLoops() and _resetRpcClients() exist for exactly this.
Drips Wave · Complexity: Advanced · 200 points
Required: Before submitting, join the contributor Telegram so your work can be tracked and counted toward the Stellar Wave: https://t.me/+fxHXq8f1SwlkZDBk
Background
#159, #160 and #161 shipped the
networkcolumn, per-network RPC clients and one indexer loop per network. Every acceptance criterion was met and 290 unit tests pass.None of it has ever run against two networks at once. The unit tests assert that the right arguments are passed; nothing asserts that two live loops, writing to one database, keep their rows apart.
That gap matters more than usual here, because every failure mode in this area is silent.
networkcarriesDEFAULT 'testnet', so a write that forgets to set it compiles, typechecks, passes CI and inserts a row — just under the wrong chain. Three such writes were found by reading during #161 (upsertHostFnLogs, theINGEST_WORKERS > 1parallel path,fetchNftMetadata's passphrase). Reading is not a repeatable control.What to build
An integration harness that runs two indexer loops concurrently against one database with two independently controlled event sources, and asserts isolation.
Key files
tests/integration/— existing suite,vitest.integration.config.tssrc/indexer.ts—startIndexer(network),startAllIndexers(),_resetIndexerLoops()src/rpc.ts—getRpc(network),_resetRpcClients()src/indexer/sources/index.ts—SourceSwitcherConfig.network, the injection seamSuggested execution
The loops are infinite, so drive
pollOncevia a controllable source rather thanstartIndexerdirectly, or runstartIndexerwith a stub source and stop it between assertions.networkcolumn across all Prisma models #159 migration applied.contractIdCTEST…, mainnetCMAIN…, at deliberately different ledger heights (mainnet far behind testnet, which is the real-world shape).network.Acceptance criteria
Each must fail if the corresponding scoping is removed — please verify that by actually reverting it, not by inspection.
network='testnet', and vice versa, acrossTokenTransfer,HostFnLog,NftTransfer,NftMetadata,AccountSummaryIndexerStateholds one row per network with differentlastIndexedLedgervalues; neither loop advances the other's cursoreventIdpresent on both networks produces two rows, not one (this is the collision@@unique([network, eventId])exists to allow — assert it directly)AccountSummaryfor one address active on both networks yields two rows with independent totals, and neither total includes the other chain's amountsrollbackToLedger(n, 'testnet')deletes no mainnet rows, even when mainnet has rows above ledgernpruneOldTransfers('testnet')deletes no mainnet rows/statusreports both networks with the correct, differinglastIndexedLedgerNotes
INGEST_WORKERS > 1takes a different code path (pollParallel). Cover it — it is where one of the three untagged writes lived._resetIndexerLoops()and_resetRpcClients()exist for exactly this.Required: Before submitting, join the contributor Telegram so your work can be tracked and counted toward the Stellar Wave: https://t.me/+fxHXq8f1SwlkZDBk