Indexer semantics
Validation, indexing and finality
Two implementations that apply the same rules in a different order can still agree on results, but only if every rule is a hard rejection. Drops is designed that way on purpose: there is no repair step, no partial acceptance, and no ordering-dependent outcome.
1. Verification order
The reference implementation performs these checks in this order. The order matters only for which reason string you get back, never for whether a candidate is accepted.
- Witness shape
The input witness must hold exactly three items: signature, leaf script, control block. Each must be an even-length hex string. A final item beginning
0x50is an annex and disqualifies the input. - Signature length
64 or 65 bytes. Nothing else.
- Leaf parse
Decompile the leaf. Ten chunks, five pushes at even positions, the OP_DROP and OP_CHECKSIG sequence at odd positions. Then field-by-field validation: marker, content type, hash length, body length and encodability, key point validity. Then re-serialize and compare byte for byte.
Failure here reports that the leaf did not satisfy specification section 3.
- Body hash
SHA-256 the body push and compare against the hash push.
- Previous output
Fetch the spent output. It must be a 34-byte native P2TR script. If it cannot be fetched, the reason is
missing_previous_output, which is an unresolved candidate rather than a proven rejection. - Control block structure
Length at least 33,
(length - 33)a multiple of 32, and at most 128 path hashes. - Leaf version
controlBlock[0] & 0xfemust be0xc0. - Internal key
Bytes 1 through 32 must be a valid x-only point.
- Merkle fold and tweak
Compute the tapleaf hash, fold the path with sorted TapBranch hashing, and tweak the internal key.
- Parity and key equality
Output key parity must equal
controlBlock[0] & 1, and the resulting x-only key must equal the 32 bytes of the spent script. The comparison is constant-time in the reference implementation.
Any exception raised anywhere in steps 5 through 10 is reported as Taproot commitment could not be verified and treated as failure. An implementation that treats a thrown error as anything other than "not a Drop" has a vulnerability, not a bug.
2. Confirmation policy
A Drops artifact is recorded only when its reveal transaction has reached the deployment's configured confirmation depth. On mainnet that depth has a hard floor of six confirmations, and the reference indexer refuses to start with a shallower mainnet value. Deeper values are accepted. Shallower values remain available on regtest, where they exist so tests can exercise boundary behaviour.
For a configured depth N, the scanner advances to a finalized target of tip - (N - 1) and persists records only up to that height. Nothing below the target is treated as state.
The effective depth is not a secret and should not be guessed: a conforming service exposes it in its health and status responses, and the Drops health payload carries it as confirmationsRequired. Historical one-confirmation behaviour is not compatible with the current policy and must be treated as transition input, never as authority to weaken the floor.
2.1 Mempool
There is no mempool parser for Drops artifacts in the reference baseline. An unconfirmed reveal simply does not exist as far as the Drops view is concerned. This is a deliberate asymmetry with the OP_DROP token layer, which does publish provisional observations, clearly separated and never affecting authoritative balances.
OP_DROP provisional activity is labelled finality: provisional and carries a chainState of mempool, mined, finalized or reorged. A Drops implementation that wants to show pending activity should build the same explicit separation rather than blending it into recorded state.
3. Reorganisation behaviour
Confirmation depth reduces the chance of a reorganisation touching recorded state; it does not eliminate it. The rule is that records are immutable and projections are rebuilt.
| Code | Recovery class | Meaning |
|---|---|---|
automatic_reorg_ancestor_ambiguous | chain_reorg | The common ancestor could not be determined without guessing. |
automatic_reorg_depth_exceeded | chain_reorg | The rollback is deeper than the automatic budget allows. |
automatic_rebuild_estimate_failed | chain_reorg | The cost of the rebuild could not be estimated. |
automatic_rebuild_budget_exceeded | chain_reorg | The rebuild would touch more rows than the budget allows. |
bip110_assessment_identity_mismatch | bip110_assessment | The recorded BIP-110 assessment identity does not match the current one. |
A change to the confirmation depth that intersects already-indexed history is treated the same way: it is a policy reconciliation that must be audited and replayed, under the recovery class confirmation_policy. Silently reinterpreting existing rows under a new depth is not permitted.
4. One writer, many readers
The reference deployment separates the read path from the chain scanner. Read replicas serve recorded state; a single scanner holds a writer lock and advances the shared chain cursor. That separation is what lets maintenance and catch-up happen without presenting partial scanner work as confirmed state.
A scanner reports a leadership state, one of not_applicable, disabled, acquiring, leader, contended, unavailable, lost or released. An indexer whose scanner does not hold leadership must not claim readiness; the reference health endpoint returns 503 in that case.
Indexer state is reported as one of disabled, api_only, writer_lock_unavailable, leadership_lost, operator_required, paused, unavailable, syncing or synced. Only synced means the recorded view is current to the finalized target.
4.1 Custody integrity
Because custody is a projection, it can drift from the immutable records if something goes wrong. A conforming implementation should therefore audit it rather than assume it. The reference health payload reports a custody integrity block with counters including unresolved legacy records, active records missing an owner or a custody outpoint, burned-state mismatches, custody mismatches, event discontinuities, transfer-count mismatches, spent-outpoint counts, failed transitions, reconciliation backlog and retries, quarantined records, and the age of the oldest unresolved record. A single custodyHealthy boolean summarises them.
Publishing these counters is part of the contract. An indexer that cannot say how far its projection has drifted is asking to be trusted rather than verified.
5. Determinism requirements
- V-1
Two implementations reading the same chain at the same height with the same confirmation depth must record the same set of Drop identities.
- V-2
They may disagree about
sequenceanddisplayName, because those depend on where each implementation started scanning. - V-3
An implementation must not use a wallet's claim, an API response from another service, or a label in its own database as evidence. The only evidence is chain data.
- V-4
An implementation should record why a candidate was rejected, and must not let a recorded rejection affect any other record's state.
- V-5
An implementation must publish its confirmation depth, its network, and its indexed height alongside any data it serves, so a reader can tell what the answer is relative to.
The conformance checklist turns these into a list you can work through, and test vectors gives concrete cases for each rejection.