Problem
1045 supplier addresses appear twice in the nodes table (75% of all staked rows). Both rows are identical (same owner, provider, stake, status) and were created within seconds of each other.
Evidence (mainnet middleman DB, 2026-05-27)
- 1023 duplicate pairs created on 2026-04-09, gap of exactly 61s between inserts
- 20 duplicate pairs created on 2026-05-24, gap of 11s
Root cause
packages/db/src/middleman/schema/node.ts:35 — address column has no UNIQUE constraint (composite or otherwise)
apps/middleman/src/actions/ImportSuppliers.ts:127 — uses .onConflictDoNothing() without a target, which silently does nothing when no unique constraint exists
CompleteImportAttempt reads getExistingNodes() then inserts — classic check-then-act race window. Two concurrent requests (double-click on "Import", retry, etc.) both pass the existence check and both insert.
Proposed fix
- Add unique index on
nodes.address (or composite (address, providerId))
- Run migration to dedupe existing rows first (delete newer row, keep oldest, remap any
transactions_to_nodes)
- Make
.onConflictDoNothing() target the new unique index
Impact
- UI may render some nodes twice
- Reconciliation jobs do duplicate work
- supplier_changes can fire twice per real event
Cleanup task (separate)
Production DB has 1043 dup pairs to remove. Plan: keep min(id), move any transactions_to_nodes references to the kept row, delete the dup.
Problem
1045 supplier addresses appear twice in the
nodestable (75% of all staked rows). Both rows are identical (same owner, provider, stake, status) and were created within seconds of each other.Evidence (mainnet middleman DB, 2026-05-27)
Root cause
packages/db/src/middleman/schema/node.ts:35—addresscolumn has no UNIQUE constraint (composite or otherwise)apps/middleman/src/actions/ImportSuppliers.ts:127— uses.onConflictDoNothing()without atarget, which silently does nothing when no unique constraint existsCompleteImportAttemptreadsgetExistingNodes()then inserts — classic check-then-act race window. Two concurrent requests (double-click on "Import", retry, etc.) both pass the existence check and both insert.Proposed fix
nodes.address(or composite(address, providerId))transactions_to_nodes).onConflictDoNothing()target the new unique indexImpact
Cleanup task (separate)
Production DB has 1043 dup pairs to remove. Plan: keep min(id), move any transactions_to_nodes references to the kept row, delete the dup.