Skip to content

Delivery is recorded in the database before the contract call, and a failure loses the escrow #507

Description

@Omoboi-dev

Category: Bug
Difficulty: Medium

Description

TrackingPollWorker.run does, for each shipped escrow reported delivered by the provider:

await this.escrowRepository.markDelivered(escrow.id, deliveredAt);
await this.contractService.recordDelivery(escrow.id);

If recordDelivery throws, the catch logs and moves on. But the escrow is already DELIVERED in the
database, so findShippedWithTracking (which selects state: 'SHIPPED') will not return it again.
The contract is never told, and no retry is possible. The escrow is left permanently out of step
with the chain, and nothing is written to the dead-letter queue either, so the failure is only a log
line.

AutoReleaseWorker in the same directory gets this right: it claims the escrow, calls the chain, and
clears the claim on failure so the next cycle retries.

Location

  • src/workers/tracking-poll.worker.ts
  • src/workers/auto-release.worker.ts
  • src/escrow/escrow.repository.ts
  • src/dlq/dlq.service.ts
  • test/unit/tracking-poll.worker.spec.ts

Example commits

fix(workers): make delivery recording recoverable when the contract call fails
test(workers): assert a failed recordDelivery leaves the escrow retryable

Acceptance Criteria

  • A failing recordDelivery leaves the escrow in a state the next poll cycle picks up again, or enqueues a DLQ record for replay.
  • A successful call still results in a DELIVERED escrow with delivery recorded on chain.
  • The same escrow is not recorded as delivered twice on chain.
  • Tests are required. Cover the success path and the contract-failure path, asserting the escrow is retryable after a failure.

Technical Notes

AutoReleaseWorker.run is the pattern to follow. EscrowRepository already has
markAutoReleaseSubmitting and clearAutoReleaseSubmitting as an example of the claim-and-release
shape.
Enqueueing to the DLQ is the alternative if you prefer not to add another claim column; either is
acceptable, say which you chose and why.
Out of scope: the polling interval and the provider integration itself.


Before you start

  • Set up with the steps in CONTRIBUTING.md. Node 22 is required (.nvmrc), use npm ci rather than npm install, and copy .env.example to .env before running npx prisma generate — the Prisma config reads DATABASE_URL at load.
  • Branch from dev and open your pull request against dev. main is the released baseline.
  • Tests that need an authenticated caller should use the bearer() helper in test/auth-helper.ts, which mints a genuinely signed SEP-10 token. Do not send a raw Stellar address as a bearer token; that path was removed deliberately.
  • You may cover more than one issue in a single pull request.

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave programbugSomething isn't workingcomplexity: medium150 pts. New endpoint, service method, integration test

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions