Telegram (ask questions / claim the issue here first): https://t.me/+DOylgFv1jyJlNzM0
Labels: bug, backend, Stellar Wave, database, critical
The withdrawn-amount update runs before the dedup check, so replaying events under-pays recipients.
soroban-event-worker.ts:631-641 computes newWithdrawnAmount = BigInt(stream.withdrawnAmount) + BigInt(amount) and calls tx.stream.update unconditionally. The dedup check at line 643 only guards inserting the StreamEvent row, not the financial field. So when the admin POST /v1/admin/indexer/replay resets the cursor and re-polls the same ledgers, every replay adds amount again even though the StreamEvent row is skipped. withdrawnAmount inflates, claimable shrinks, and the recipient is short-changed. The replay endpoint's Swagger doc even claims it's idempotent.
What the fix has to hold to
- The stream's
withdrawnAmount only changes when the StreamEvent row is newly created, in the same transaction
- Replay/reset is genuinely idempotent
Done when
Where to start
backend/src/workers/soroban-event-worker.ts for the ordering fix; indexerService.ts and admin.routes.ts are listed for the replay path and audit. On-chain event schema is out of scope. Medium.
Labels:
bug,backend,Stellar Wave,database,criticalThe withdrawn-amount update runs before the dedup check, so replaying events under-pays recipients.
soroban-event-worker.ts:631-641computesnewWithdrawnAmount = BigInt(stream.withdrawnAmount) + BigInt(amount)and callstx.stream.updateunconditionally. The dedup check at line 643 only guards inserting theStreamEventrow, not the financial field. So when the adminPOST /v1/admin/indexer/replayresets the cursor and re-polls the same ledgers, every replay addsamountagain even though theStreamEventrow is skipped.withdrawnAmountinflates, claimable shrinks, and the recipient is short-changed. The replay endpoint's Swagger doc even claims it's idempotent.What the fix has to hold to
withdrawnAmountonly changes when theStreamEventrow is newly created, in the same transactionDone when
withdrawnAmountupdate is idempotent (mutate only on a newStreamEventinsert, same tx)tokens_withdrawnevent twice and assertswithdrawnAmountchanges oncehandleStreamCancelled/handleStreamCompletedaudited to confirm they stay idempotentWhere to start
backend/src/workers/soroban-event-worker.tsfor the ordering fix;indexerService.tsandadmin.routes.tsare listed for the replay path and audit. On-chain event schema is out of scope. Medium.