Switch challenge reads to sol_reward_disbursements#809
Open
rickyrombo wants to merge 2 commits into
Open
Conversation
efdaa39 to
6bd6ee5
Compare
Routes that joined challenge_disbursements now read from a compatibility view v_challenge_disbursements over the new Solana indexer's sol_reward_disbursements table, with user_id resolved via recipient_eth_address -> users.wallet. Python continues to dual-write challenge_disbursements until the discovery-provider service is decommissioned in a future change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6bd6ee5 to
e060a83
Compare
…sements
Restores the indexes the legacy challenge_disbursements had on user_id
and created_at. v_challenge_disbursements inlines into the route SQL, so
filters like cd.user_id = X push down to users.wallet -> probe
sol_reward_disbursements.recipient_eth_address, which would otherwise
seq-scan. Same for the default created_at sort on
/v1/challenges/disbursements and the date-range filter in
/v1/challenges/{id}/info.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Routes that joined
challenge_disbursements(Python-indexer-written) now read from a new compatibility viewv_challenge_disbursementsbacked by the Go indexer'ssol_reward_disbursementstable. Notifications continue to be created via a new trigger on the sol_* table. Python keeps dual-writing the legacy table for now.This is a deliberate wedge: only the challenges read path is moved. Other Solana-derived reads (transactions history, tips, purchases, withdrawals) are unchanged and stay on the legacy Python-written tables.
In scope (this PR)
Schema
ddl/migrations/0198_v_challenge_disbursements.sql:created_at TIMESTAMP DEFAULT NOW()tosol_reward_disbursements(the column did not exist; backfilled fromchallenge_disbursements.created_atfor matching signatures).v_challenge_disbursements (challenge_id, specifier, amount, signature, slot, created_at, user_id)oversol_reward_disbursements JOIN users ON users.wallet = recipient_eth_address.sql/01_schema.sqlupdated so sqlc parses the new column + view.Trigger
ddl/functions/handle_challenge_disbursements.sql: addshandle_sol_reward_disbursementtrigger onsol_reward_disbursementsthat mirrors the legacy notification-creation logic. Dedupes via the samegroup_id, so it dual-fires safely with the existing trigger during cutover. Also emitspg_notify('challenge_disbursed', ...)for future consumers (no subscriber wired yet).Go readers — now point at the view
api/v1_challenges_disbursements.goapi/v1_challenges_undisbursed.goapi/v1_users_challenges.goapi/v1_challenges_info.goapi/v1_coins_post_redeem.goapi/dbv1/queries/get_undisbursed_challenges.sql(+ regeneratedget_undisbursed_challenges.sql.goandmodels.goviasqlc generate)Test fixtures
database/seed.go— addedsol_reward_disbursementsbase row.api/v1_challenges_disbursements_test.go,api/v1_challenges_info_test.go— switched to seedingusers+sol_reward_disbursementswithrecipient_eth_addressmatchingusers.walletso the view resolvesuser_id.Not in scope / still to be done
These were considered during planning but explicitly deferred:
index_rewards_managercontinues to poll the Reward Manager program and writechallenge_disbursements,reward_manager_txs, andaudio_transactions_historyrewards rows. The legacy trigger onchallenge_disbursementsstays active; the new trigger's dedupe handles overlap. To be addressed when the discovery-provider service is dropped entirely./v1/challenges/{id}/attestport. Still served from Python. Anti-abuse oracle attestation signing needs to be ported to Go; not done in this PR per scope discussion.pg_notify('challenge_disbursed', ...)consumer. Trigger emits the event but nothing subscribes. Reserved for the future Python decommission (or a Go-side challenge-event-bus port).audio_transactions_historyrewards rows (USER_REWARD / TRENDING_REWARD). Reads of audio-transactions history still go through the legacy table; rewards typing in a future typed-history view is a separate workstream.user_balancesrefresh.index_rewards_managercallsenqueue_immediate_balance_refreshon disbursement. Untiluser_balancesreaders are audited and migrated tosol_user_balances, the Python refresh hook keeps running.user_tips,aggregate_user_tips), purchases (usdc_purchases), transactions history (audio + usdc), withdrawals, and Stripe/Coinbase/Coinflow top-up vendor detection are all untouched.Test plan
go build ./...clean (verified locally).go test ./api/ -run 'TestGetChallengeDisbursements|TestV1ChallengesInfo'against a running test postgres on:21300.sol_reward_disbursementsproduces anotificationrow withtype=challenge_rewardand the expectedgroup_id, and that a duplicate insert is a no-op.SELECT COUNT(*) FROM v_challenge_disbursementsis consistent withSELECT COUNT(*) FROM challenge_disbursementsmodulo rows whoserecipient_eth_addressdoesn't map to a current user./v1/challenges/disbursements,/v1/users/{id}/challenges,/v1/challenges/undisbursed,/v1/challenges/{id}/infoagainst a populated DB./v1/coins/{mint}/redeemidempotency check still rejects a second redemption for the same code+specifier.🤖 Generated with Claude Code