Skip to content

Re-validate pending sol_purchases when blocknumber catches up#818

Open
rickyrombo wants to merge 1 commit into
mainfrom
revalidate-pending-purchases-on-blocknumber
Open

Re-validate pending sol_purchases when blocknumber catches up#818
rickyrombo wants to merge 1 commit into
mainfrom
revalidate-pending-purchases-on-blocknumber

Conversation

@rickyrombo
Copy link
Copy Markdown
Contributor

Summary

  • Adds a trigger on tracks/playlists that emits NOTIFY 'pending_purchase_revalidation' when blocknumber advances past any pending sol_purchases row's valid_after_blocknumber. Cheap EXISTS guard so the vast majority of track/playlist updates skip the notify entirely.
  • Adds a Go listener (PurchaseRevalidator) in the Solana indexer that consumes notifications and re-runs the existing validatePurchase for affected pending rows. A 5-minute sweep + startup sweep covers cases where NOTIFY drops (no connected listener) or pending rows that predate the trigger.
  • Validation logic stays in validatePurchase as the single source of truth — the trigger only signals which content_id changed. No SQL port, no parity drift with config-driven values like NetworkTakeRate.

Why

A sol_purchases row is inserted with is_valid = NULL ("pending") when its valid_after_blocknumber hasn't been indexed by the local node yet (MAX(blocks.number) < valid_after_blocknumber at insert time). Before this PR there was no mechanism to flip pending rows to a final verdict, so they stayed NULL indefinitely even after the indexer caught up.

Notes for reviewers

  • sql/01_schema.sql diff is large because the dump hadn't been regenerated since Backfill sol_purchases from usdc_purchases #815 merged — it picks up that PR's handle_sol_purchase function and a few other already-merged trigger updates alongside this PR's notify_pending_purchase_revalidation.
  • Bumped pgxpool.MaxConns by 1 to budget for the revalidator's persistent LISTEN connection (in addition to the existing artist_coins listener).
  • Lifecycle is purely ctx-driven — no explicit Stop(). Cancelling the parent ctx unblocks WaitForNotification, the deferred conn.Release() returns the LISTEN connection to the pool, both goroutines exit.

Test plan

  • Trigger function applied via regenerated schema dump (make test-schema)
  • TestParseRevalidationPayload — unit coverage for payload parsing
  • TestRevalidatorRevalidateContent — direct call: pending row + matching sol_payments flips to true
  • TestRevalidatorEndToEnd — full trigger → NOTIFY → LISTEN → revalidate chain via UPDATE tracks SET blocknumber
  • Existing TestPurchaseValidation still passes
  • Deploy to stage and watch a pending row resolve in the wild

🤖 Generated with Claude Code

A sol_purchases row is inserted with is_valid = NULL ("pending") when its
valid_after_blocknumber hasn't been indexed yet at insert time. Until now
there was no mechanism to flip pending rows to a final verdict — they
stayed NULL indefinitely even after the indexer caught up.

This adds a trigger on tracks/playlists that emits NOTIFY
'pending_purchase_revalidation' when blocknumber advances past any
pending row's valid_after_blocknumber, plus a Go listener in the Solana
indexer that consumes the notification and re-runs the existing
validatePurchase. A 5-minute sweep + startup sweep covers cases where
NOTIFY drops (no connected listener) or rows that pre-date the trigger.

The trigger does no validation work itself — it only signals which
content_id changed, so the math stays in validatePurchase as the single
source of truth. No SQL port, no parity risk with config-driven values
like NetworkTakeRate.
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