Skip to content

[Regression on closed #273] assignReward's double-payout guard is keyed on (pool, recipient) instead of (pool, issue) — the same issue can still be paid twice #458

Open

Description

@chonilius

Problem

#273 ("no guard against being called twice for the same issueId") was closed with this check in MaintenancePoolService.assignReward():

// Guard against double/triple payout for the same issue (#273).
const existingPayment = await this.paymentRepo.findOne({
  where: { recipientId: recipientId ?? null },
});
const existingPoolPayment = await this.paymentRepo
  .createQueryBuilder('payment')
  .innerJoin('payment.escrow', 'escrow')
  .where('escrow.maintenancePoolId = :poolId', { poolId: id })
  .andWhere('payment.recipientId = :recipientId', { recipientId: recipientId ?? null })
  .getOne();
if (existingPoolPayment) {
  throw new ConflictException(`Issue ${issueId} has already received a reward from pool ${id}`);
}

issueId doesn't appear in either query, and Payment has no issue column at all. The guard checks who was paid from this pool, not which issue was rewarded:

  1. The same issue can still be paid twice. Call assignReward(pool, issue#1, …, recipientA) and then assignReward(pool, issue#1, …, recipientB), and both succeed. This is the double payout MaintenancePoolService.assignReward has no guard against being called twice for the same issueId — double/triple payout of a single completed task #273 asked to prevent.
  2. Anonymous payouts are never guarded. When recipientId is omitted, the query becomes payment.recipientId = NULL, which is never true in SQL. So repeated rewards for the same issue with no recipientId always pass.
  3. Legitimate rewards are rejected. A maintainer who fixes two different maintenance issues gets a 409 on the second one, with a misleading message saying that issue was already rewarded.
  4. existingPayment is dead code. It's an unscoped query across every payment in the system, whose result is never read, and it costs an extra DB round trip per call.

Suggested fix

  • Record the rewarded issue on the payment, for example a nullable Payment.maintenanceIssueId (FK to issues), plus a unique index on (escrowId, maintenanceIssueId).
  • Guard on (pool escrow, issueId), and let the unique index close the concurrent-call race.
  • Remove the unused existingPayment query.
  • Add tests: the same issue with two recipients gets a 409, and two issues for the same recipient both succeed.

Related: #273, #274, #276.

Activity

  1. added
    bugSomething isn't working
    very hardVery difficult task, expert-level effort required
    securitySecurity-related issue
    Stellar WaveIssues in the Stellar wave program
    on Sep 27, 2026
  2. Proxima84-code commented on Sep 28, 2026

    @Proxima84-code

    Fix submitted in the PR above. The idempotency guard now filters on (poolId, issueId), Payment records maintenanceIssueId under a composite unique index (escrowId, maintenanceIssueId) to prevent concurrent races, and dead queries were removed. Verified with 20/20 and 58/58 passing unit tests.

  3. Proxima84-code commented on Sep 28, 2026

    @Proxima84-code

    Resolved via PR #472.

  4. drips-wave commented on Sep 29, 2026

    @drips-wave

    Congratulations, @Ultra-Tech-code! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on September 30, 2026.

    🧑‍💻 @Ultra-Tech-code: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

  5. MrOmale commented on Oct 20, 2026

    @MrOmale

    @MrOmale has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    I would love to work on this issue

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @MrOmale to this issue.

  6. drips-wave commented on Oct 21, 2026

    @drips-wave

    Congratulations, @MrOmale! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.

    🧑‍💻 @MrOmale: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave programbugSomething isn't workingsecuritySecurity-related issuevery hardVery difficult task, expert-level effort required

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions