# First-match indexer drops duplicate records

Indexer resolvers that return or continue on first match, silently dropping sibling records in multi-input transactions

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2024-08-04. Updated 2026-10-29._

## How it works
An indexer assumes one transaction references at most one tracked record. Bitcoin transactions have many inputs, so a single tx can spend several tracked UTXOs at once. When the resolver returns on the first match, or the dispatcher `continue`s after one handler, the remaining records are never updated. The offchain database then disagrees with the chain: spent records stay marked unspent, or state transitions never fire.

## Where it shows up
- A function that resolves "the" referenced entity from a tx's inputs/outputs and returns inside the loop [E1](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.1.md)[E2](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.2.md)
- `return x, i` or `break` on first match in an input-scanning loop [E1](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.1.md)[E2](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.2.md)
- Per-input-type handlers dispatched with `continue` after the first branch matches [E3](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.3.md)
- Reconciliation code that assumes one record per transaction
- Any parser over multi-input/multi-output transactions with single-result semantics

## How to review
Check the invariant: every tracked record referenced by a transaction must be updated, not just the first. For each resolver, ask whether the loop collects all matches or returns early. For each dispatcher, ask whether one transaction can match more than one category; if so, all matching handlers must run. Trace `HandleConfirmedBlock`-style control flow end to end and confirm no `continue`/`return` skips a sibling handler. Verify the DB update path handles a list, not a single record.

## Variants seen
- Staking-spend resolver returns only the first spent staking UTXO per tx [E1](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.1.md)
- Unbonding-spend resolver returns only the first spent unbonding UTXO per tx [E2](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.2.md)
- Early `continue` after the staking-spend branch skips unbonding-spend handling when one tx spends both [E3](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.3.md)

## Impact and severity
Offchain accounting drifts from chain state: spent stakes and unbondings remain marked unspent, and unbonding state stays stuck un-withdrawn [E1](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.1.md)[E2](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.2.md)[E3](https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.3.md). Severity is moderate to high because the corruption is silent and persistent, and any downstream logic reading the indexer (balances, rewards, withdrawals) inherits the wrong state. Multi-input transactions make it worse: the more UTXOs a tx spends, the more records are dropped.

## Evidence
- E1: https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.1.md
- E2: https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.2.md
- E3: https://bugbountylibrary.com/r/cantina%3Ababylon_may2024%3A3.1.3.md
