db+swapwallet: canonical activity log store + dual-write projector (#774) - #817
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a canonical activity log to persist wallet activity rows, replacing the legacy derive-on-read merge mechanism. It adds database migrations for the new activity tables, implements the ActivityPersistenceStore and ActivityProjector interface, and integrates the projector into the swapwallet runtime with a startup backfill mechanism. The review feedback highlights two key improvements: adding a defensive check to prevent a potential infinite loop in the startup backfill if the entries slice is empty, and explicitly casting cursor_created to BIGINT in the SQL query to ensure sqlc generates type-safe int64 fields instead of interface{}.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4b2aa32df
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
c4b2aa3 to
46d8afa
Compare
Add migration 000023 with the activity_entries current-state projection (keyed by a stable canonical id) and the append-only activity_events transition log, plus enum lookup tables that mirror the wire EntryKind and EntryStatus one-to-one. Adds the sqlc queries for upsert, append, get, keyset list, and event pull. Foundation for the event-log epic (#774).
Regenerated via make sqlc for migration 000023 and the activity_log queries.
Add ActivityPersistenceStore.ProjectEntry, which atomically upserts the current-state row and appends a transition event only when the projected state actually changed, so redundant re-emits do not accumulate duplicate events. Adds keyset List and event Pull read helpers, wired in via Store.NewActivityStore.
Build the activity store next to the VTXO store and hand it to the walletdkrpc subserver through a top-level config field, set unconditionally so the projector persists activity even when no [swapwallet] config section is present. A nil store disables projection for builds and tests without a database.
Project every emitted WalletEntry into the activity store (project then emit) at the swap monitor, the cooperative-leave submit, and the deadline overlay, plus a one-time startup backfill from the existing collectors (credit-only sends included via the read-path collector). The read path is unchanged: List and SubscribeWallet still use the legacy merge until a later cutover.
Fold in the #776 review: state the best-effort project-then-emit plus startup-reconcile consistency model explicitly (not a transactional outbox), and require every producer, including the credit registry, to be a projector source so none vanishes at the read-path cutover.
46d8afa to
4477c4f
Compare
Foundation for #774 (C1), the first child of the event-log epic #776. Design: #798.
Additive, no read-behavior change. This lands the persisted canonical activity store and dual-writes into it, while
List/SubscribeWalletkeep deriving from the legacy merge inhistory.go. The read-path cutover, the proto cursor/has_morefields, and the daemon-side stable-id hooks for EXIT/DEPOSIT (the #610 fix) are deferred to follow-up PRs.What's here
activity_entries— the current-state projection, one row per operation keyed by a stablecanonical_id, read byList.activity_events— the append-only transition log with a monotonicevent_seq, the basis for a resumableSubscribeWallet.activity_kinds/activity_statusesenum tables seed the proto enum integers, andkind/statusare foreign keys to them — so a row can only hold a defined wireEntryKind/EntryStatusand the projection needs no internal-to-wire mapping. Money columns areBIGINT(a bareINTEGERis 32-bit on the Postgres backend); empty BLOB handles are stored as NULL.db/activity_store.go).ActivityPersistenceStore.ProjectEntryupserts the current-state row and appends the transition event in one transaction (FK-safe order). KeysetListEntries(immutablecreated_atcursor) andPullEvents(event_seq > cursor) read helpers back the eventual cutover.swapwallet/projector.go). ARuntime-owned projector maps each emittedWalletEntryto the store and writes it project-then-emit at the three existing emit sites (swap monitor, cooperative-leave submit, deadline overlay), plus a one-time idempotent startup backfill from the existing collectors. Projection is best-effort: a store error is logged and never blocks or fails the emit, and a nil store (tests, no-DB builds) disables it.SwapWalletConfigfield.Notes
kind/statusmirror the wire enums via the FK (noONCHAIN_SEND/CONFIRMEDdivergence) — an on-chain send is anEXIT-kind row, as today.txid:vout, exactly as the legacy merge behaves; the dual-write test asserts the store mirrors the merge for the stable-id rows.Tests
db/activity_store_test.go: atomic upsert+append, in-place update preservingcreated_atand prior handles, enum FK rejection, NULL-blob handling, keyset paging, event-pull cursor.swapwallet/projector_test.go:WalletEntry→ projection mapping with a losslessentry_jsonround-trip, project-then-emit ordering, nil-store and store-error safety.swapwallet/activity_dualwrite_test.go: backfill mirrors the legacy merge for stable-id rows and is idempotent.make sqlc,make build,make lint-changed-local, and thedb+swapwalletunit suites pass.