Skip to content

fix(settlement): prevent duplicates with unique keys and upsert - #1233

Open
DeePrincipal-dev-lang wants to merge 1 commit into
CredenceOrg:mainfrom
DeePrincipal-dev-lang:fix/settlement-duplicates-fresh
Open

fix(settlement): prevent duplicates with unique keys and upsert#1233
DeePrincipal-dev-lang wants to merge 1 commit into
CredenceOrg:mainfrom
DeePrincipal-dev-lang:fix/settlement-duplicates-fresh

Conversation

@DeePrincipal-dev-lang

Copy link
Copy Markdown

Problem

Under replay or concurrent race conditions, the same on-chain transaction could produce
duplicate settlement rows. The old schema enforced uniqueness only on the composite (bond_id,
transaction_hash) pair, meaning the same transaction_hash arriving from a different bond — or
replayed concurrently — could insert a second row.

Changes

src/db/schema.ts

  • Replaced UNIQUE (bond_id, transaction_hash) with UNIQUE (transaction_hash) on the settlements
    table, aligning the in-memory schema used in tests with what migration
    006_settlement_tx_idempotency already applies to production.
  • Added composite index idx_settlements_bond_tx (bond_id, transaction_hash) for efficient
    bond-scoped settlement queries.

tests/integration/settlementsRepository.integration.test.ts (new)

Full integration test suite against real Postgres (testcontainers or TEST_DATABASE_URL)
covering:

  • Basic idempotency — first upsert returns isDuplicate=false; replays return isDuplicate=true
    with the same id; N sequential replays produce exactly one row
  • Field updates on conflict — status, amount, and settled_at are updated; bond_id stays pinned
    to the first inserter
  • Global uniqueness across bonds — same transaction_hash from two different bonds produces one
    row; original bond_id is preserved
  • Concurrent race conditions — 10 parallel upserts produce exactly one row; all callers receive
    the same settlement id; exactly one caller gets isDuplicate=false
  • DB-level constraint enforcement — raw INSERT with a duplicate hash throws Postgres error
    23505; ON CONFLICT DO UPDATE succeeds where a plain insert would fail
  • Tests skip gracefully when Docker is unavailable (pg-mem fallback detected)

How it works

The SettlementsRepository.upsert() uses:

INSERT INTO settlements (bond_id, amount, transaction_hash, settled_at, status)
VALUES (...)
ON CONFLICT (transaction_hash) DO UPDATE
SET amount = EXCLUDED.amount,
status = EXCLUDED.status,
settled_at = EXCLUDED.settled_at,
updated_at = NOW()
RETURNING ...

This makes every settlement write fully idempotent on transaction_hash. Replayed Horizon events
and concurrent requests collapse into a single row at the database level — no application-level
locking required.

Testing

Unit tests (pg-mem, no Docker needed)

npm test src/db/repositories/settlementsRepository.test.ts

Integration tests (requires Postgres via testcontainers or TEST_DATABASE_URL)

TEST_DATABASE_URL=postgresql://credence:credence@localhost:5433/credence_test
npm test tests/integration/settlementsRepository.integration.test.ts
closes #965

- Update src/db/schema.ts: replace composite UNIQUE (bond_id, transaction_hash)
  with UNIQUE (transaction_hash) to match migration 006_settlement_tx_idempotency;
  add composite index idx_settlements_bond_tx (bond_id, transaction_hash) for
  efficient bond-scoped queries
- Add tests/integration/settlementsRepository.integration.test.ts: full
  integration test suite against real Postgres covering basic idempotency,
  field updates on conflict, global tx hash uniqueness across bonds, concurrent
  race conditions (10 parallel upserts), and DB-level constraint enforcement

Closes CredenceOrg#965
@drips-wave

drips-wave Bot commented Aug 5, 2026

Copy link
Copy Markdown

@DeePrincipal-dev-lang Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fresh 2026-04][Backend] DB: prevent duplicate settlement records (unique keys + safe upsert)

1 participant