Skip to content

On-Chain Settlement Verification Pipeline: Make Horizon-Verified Transactions the Source of Truth for Tip Ingestion #341

Description

@Christopherdominic

Summary

record_tip (src/controllers/tip_controller.rs) persists tips as self-reported client data. Nothing verifies that a corresponding payment actually settled on the Stellar network before the tip enters the database, mutates leaderboards, triggers webhooks, and feeds analytics. Any client can fabricate tips of arbitrary amounts. For a tipping platform this is the core integrity gap.

Task

Build a verification pipeline that makes on-chain settlement the source of truth:

  1. Require a tx_hash on tip submission. Before persisting, verify against Horizon/Soroban RPC (endpoint from typed config) that:
    • the transaction exists and succeeded
    • it contains a payment/tip invocation whose amount, asset, destination (creator address), and memo match the submitted tip within exact equality (amounts in stroops — no float comparison anywhere)
    • the transaction's source account matches the claimed tipper
  2. Enforce exactly-once ingestion: a UNIQUE constraint on tx_hash (new migration) plus a race-safe insert (ON CONFLICT DO NOTHING + explicit duplicate error mapping) so concurrent submissions of the same hash cannot double-record.
  3. Two-phase state: tips enter as pending_verification and transition to confirmed/rejected; webhooks, leaderboard updates, and stats must only fire on confirmed (audit and gate all existing side-effect paths, including the double-webhook-prone creator/tip controller interplay).
  4. Resilience: Horizon outages must not lose submissions — enqueue verification with bounded retries and backoff (reuse src/queue/), a reconciliation job (src/jobs/) that re-drives stuck pending tips, and metrics for verification latency and failure classes.
  5. A verification bypass for tests only, implemented as an injected trait object (TipVerifier) with a mock — never an env flag reachable in production builds.
  6. Tests: happy path against a mocked Horizon response fixture, amount/destination/memo mismatch rejections, duplicate tx_hash race (two concurrent submissions), Horizon 5xx → retry → eventual confirm, and reconciliation of stuck tips.

Acceptance criteria

  • A tip that does not correspond to a settled on-chain payment can no longer reach confirmed state
  • Duplicate-hash race proven safe by a concurrent test
  • All side effects strictly gated on confirmed
  • Horizon downtime degrades to queued verification, not data loss (tested)
  • Migration includes backfill strategy for existing rows; all checks pass

PR requirements (mandatory)

  • ✅ Your PR must pass all checks — PRs with failing or skipped checks will not be merged.
  • 📸 You must attach a screenshot in the PR description demonstrating the result (for this issue: a real testnet transaction being verified end-to-end, or the full test-suite output covering the verification pipeline).
  • 🔗 You must link this issue number in your PR description (e.g. Closes #<issue-number>). PRs without a linked issue will not be reviewed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26architectureStructural or architectural changedifficulty: very hardRequires deep expertise and substantial effortsecuritySecurity hardening or vulnerability work

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions