Skip to content

Add SLP validity validation - #67

Open
jchavannes wants to merge 4 commits into
masterfrom
slp_validation
Open

Add SLP validity validation#67
jchavannes wants to merge 4 commits into
masterfrom
slp_validation

Conversation

@jchavannes

Copy link
Copy Markdown
Collaborator

Add consensus-grade SLP validity verdicts to the index, which previously transcribed SLP messages without judging them:

  • Strict spec parser and pure validator (ref/bitcoin/tx/slp) for token types 0x01/0x41/0x81, big.Int sums, validated against the full slp-unit-test-data corpus (vendored in testdata)
  • Per-tx slp_validity topic: status+reason bytes, absence = pending, verdicts final (DAG-validity); fail-to-pending, never fail-to-wrong
  • Batch orchestrator (node/act/slp_validate) resolving inputs via slp rows, parent verdicts, geneses, and TxProcessed
  • Validation wired into the SLP op_return handler on both the mempool and confirmation passes; saver no longer skips addr-independent handlers when no input address parses; SLP prefix match now accepts non-minimal lokad pushes
  • maint slp-validity-sweep: height-ordered backfill/safety-net with per-block fixpoint, completed-height cursor (explicit --start never moves it), and exhaustive per-shard height-block range pagination (chain.GetHeightBlocksRange)
  • GraphQL validity field (VALID|INVALID|PENDING) on SlpOutput/SlpBaton
  • Register missing slp.Send topic; store declared TokenType on slp.Mint/slp.Send

jchavannes and others added 3 commits August 8, 2026 10:04
Add consensus-grade SLP validity verdicts to the index, which previously
transcribed SLP messages without judging them:

- Strict spec parser and pure validator (ref/bitcoin/tx/slp) for token
  types 0x01/0x41/0x81, big.Int sums, validated against the full
  slp-unit-test-data corpus (vendored in testdata)
- Per-tx slp_validity topic: status+reason bytes, absence = pending,
  verdicts final (DAG-validity); fail-to-pending, never fail-to-wrong
- Batch orchestrator (node/act/slp_validate) resolving inputs via slp
  rows, parent verdicts, geneses, and TxProcessed
- Validation wired into the SLP op_return handler on both the mempool
  and confirmation passes; saver no longer skips addr-independent
  handlers when no input address parses; SLP prefix match now accepts
  non-minimal lokad pushes
- maint slp-validity-sweep: height-ordered backfill/safety-net with
  per-block fixpoint, completed-height cursor (explicit --start never
  moves it), and exhaustive per-shard height-block range pagination
  (chain.GetHeightBlocksRange)
- GraphQL validity field (VALID|INVALID|PENDING) on SlpOutput/SlpBaton
- Register missing slp.Send topic; store declared TokenType on
  slp.Mint/slp.Send

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cascade validation (slp_validate.ValidateTxsCascade): when a tx's verdict
lands, spenders of its outputs are looked up and any pending SLP spenders
are validated recursively, so a decided ancestor immediately resolves
pending descendants of any depth on the live save path. Race-free because
spender rows are written by the first saver before validation runs.

The slp-validity-sweep is redesigned around the index's own datasets
instead of walking blocks by height: the default mode iterates the slp
genesis/mint/send topics per shard and validates txs with no verdict (the
routine safety net); --audit scans every chain tx output for an SLP lokad
script, leniently transcribing and validating anything the live path
missed (historical backfill / deep audit). Audit resume uses a per-shard
uid cursor, cleared on completion, and ignores wrong-format legacy values
(the old sweeper stored an 8-byte height under the same status). Visit
order is irrelevant in both modes because the cascade resolves
child-before-parent chains, so the height walker and its completed-height
cursor machinery are deleted.

An end-to-end suite test (test/tasks slp_validity, runs under go test)
drives the full flow through real in-process queue shards: token
lifecycle, fake send/mint, invalid-parent cascade, out-of-order arrival,
4-generation chains, index-sweep and audit backfills incl. an
untranscribed gap tx and the legacy-cursor guard, and NFT1 group/child.
Suite lifecycle fixes make it repeatable in one process: synchronous
listener bind, store cache cleared on suite end, partial startup unwound,
and Server.Stop closes the listener when it beats a backgrounded Serve.
Also fixes a nil-Context panic in populate_p2sh's GetBlockTxs call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GetHeightBlocksRange and paginateHeightBlocks were built for the
abandoned height-ordered sweeper design; the dataset-driven sweep never
touches height blocks. Also drop the never-read Result.Skipped counter
and update doc comments that still described the old design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread db/item/slp/send.go Outdated
}
copy(s.TokenHash[:], jutil.ByteReverse(data))
copy(s.TokenHash[:], jutil.ByteReverse(data[:memo.TxHashLength]))
if len(data) > memo.TxHashLength {

@jchavannes jchavannes Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Should this reverse compatibility be added to dbi/item/slp/send.go and other SLP items for the new TokenType? With it, when reading previously saved tokens it will think token type is 0 when really it just was never set. Also, during db item deserialization should we allow use of if len(data) < X when there are no optional fields? Should we be checking for exact match (if len(data) != X)? Otherwise you're reading an item that has extra data, which should we assume we don't know (or is malformed) and abort?

@@ -0,0 +1,290 @@
// Package slp_validate resolves the inputs of SLP transactions against the

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Let's audit this validation logic against the spec one more time.


// NeedsAddr reports whether the handler requires an input address to be resolved.
// SLP transcription/validation is address-independent, so SLP txs are indexed
// even when no input unlock script yields an address (e.g. P2SH inputs).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Why would some SLPs not have an address? Is this like if they're sent to non-standard outputs or something? (like not P2SH, P2PKH, etc.)

SLP db items now require exact serialized lengths (TokenType always
written), genesis rows store ticker/name/docurl as uint16 length-prefixed
bytes (byte-faithful, handles 0x00 and empty fields), and the validator
resolves unknown parents via the vout-0 chain output lokad check instead
of TxProcessed, so missing slp rows read as pending, never invalid.
Backward compatibility is intentionally dropped: deployment wipes the slp
topics and repopulates from chain data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant