Tombstones for deleted contracts#155
Conversation
…nvironment settings
…hema and indexer utilities
|
@githoboman 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! 🚀 |
Miracle656
left a comment
There was a problem hiding this comment.
The tombstone model is nicely designed — contractId-unique so first-detection-wins and re-detection is a no-op, createMany with skipDuplicates for idempotency, the "unknown liveUntilLedger is never treated as expired" guard, and thorough unit tests on tombstoneFor / detectExpiredContracts. But there's a gap that blocks merge:
The feature is never invoked — it's dead code. detectExpiredContracts / tombstoneExpiredContracts are only referenced by their own tests; this PR doesn't modify src/indexer.ts (or any poll/cron path), so nothing ever calls them against live ledgers. As shipped, the migration adds a ContractTombstone table that stays permanently empty and downstream consumers watching it never get a signal.
To land this, please wire the detection into the indexing loop:
- Call
tombstoneExpiredContracts(contracts, currentLedger)frompollOnce(or a periodic job), sourcing each tracked contract's instanceliveUntilLedger. If that requires an RPC/getLedgerEntries call that isn't available in the poll path yet, that's the missing piece — either add it or split it into a follow-up and note the intended trigger here. - A small test asserting a row actually lands in the table when an expired contract flows through the wired path.
Also please rebase onto latest main — #154 landed with the lockfile (@emnapi/core) reconciled, so rebasing will clear the npm ci desync this branch inherits. Solid foundation; it just needs to actually run.
mplemented contract liveness tracking with tombstone emission on storage TTL expiry, matching the codebase's existing conventions (modeled closely on sac-detect.ts).
Closes #143
Files changed
New: src/indexer/tombstones.ts — the core module:
isExpired(liveUntilLedger, currentLedger) — pure expiry rule. A contract is live through its liveUntilLedger, so it's only expired once currentLedger > liveUntilLedger. A null/unknown TTL is never tombstoned.
tombstoneFor(liveness, currentLedger) — pure: builds a tombstone record or returns null.
fetchLiveUntilLedger — reads the contract instance entry's liveUntilLedgerSeq via getRpc().getContractData(...), exactly like SAC detection reads the executable. Wrapped in an injectable TtlFetcher type for testing.
fetchLiveness / detectExpiredContracts — de-duplicate contract IDs, one RPC call per unique contract.
insertTombstones — idempotent createMany({ skipDuplicates: true }) keyed by the unique contractId (first expiry detection wins).
tombstoneExpiredContracts — the end-to-end entry point: detect + persist.
New: src/tests/tombstones.test.ts — 15 tests over a TTL fixture (live / expired / border / unknown contracts) with an injected fetcher and a mocked Prisma client. No network, no DB.
Schema + migration — added the ContractTombstone model to schema.prisma (unique contractId, liveUntilLedger, detectedLedger, indexed by detectedLedger) plus the matching SQL migration.
Acceptance criteria
✅ Tombstones inserted on expiry detection — tombstoneExpiredContracts / insertTombstones.
✅ Tested with fixture — fixture-driven suite, 15/15 passing.
Verification
npx jest src/tests/tombstones.test.ts → 15 passed
npx tsc --noEmit (and the test tsconfig) → clean, exit 0
npx prisma generate → client regenerated for the new model