Skip to content

db+swapwallet: canonical activity log store + dual-write projector (#774) - #817

Merged
darioAnongba merged 6 commits into
mainfrom
dario/774-activity-store-foundation
Jul 1, 2026
Merged

db+swapwallet: canonical activity log store + dual-write projector (#774)#817
darioAnongba merged 6 commits into
mainfrom
dario/774-activity-store-foundation

Conversation

@darioAnongba

Copy link
Copy Markdown
Collaborator

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/SubscribeWallet keep deriving from the legacy merge in history.go. The read-path cutover, the proto cursor/has_more fields, and the daemon-side stable-id hooks for EXIT/DEPOSIT (the #610 fix) are deferred to follow-up PRs.

What's here

  • Schema (migration 000022). Two tables modelled on the design doc:
    • activity_entries — the current-state projection, one row per operation keyed by a stable canonical_id, read by List.
    • activity_events — the append-only transition log with a monotonic event_seq, the basis for a resumable SubscribeWallet.
    • activity_kinds / activity_statuses enum tables seed the proto enum integers, and kind/status are foreign keys to them — so a row can only hold a defined wire EntryKind/EntryStatus and the projection needs no internal-to-wire mapping. Money columns are BIGINT (a bare INTEGER is 32-bit on the Postgres backend); empty BLOB handles are stored as NULL.
  • Store (db/activity_store.go). ActivityPersistenceStore.ProjectEntry upserts the current-state row and appends the transition event in one transaction (FK-safe order). Keyset ListEntries (immutable created_at cursor) and PullEvents (event_seq > cursor) read helpers back the eventual cutover.
  • Projector (swapwallet/projector.go). A Runtime-owned projector maps each emitted WalletEntry to 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.
  • Wiring. The daemon builds the store next to the VTXO store and injects it into the walletdkrpc subserver in-process via a programmatic SwapWalletConfig field.

Notes

  • The Codex review's two points on docs: canonical activity log design (event-log epic) #798 are handled by construction here: kind/status mirror the wire enums via the FK (no ONCHAIN_SEND/CONFIRMED divergence) — an on-chain send is an EXIT-kind row, as today.
  • Until the daemon stable-id hooks land, a cooperative-leave EXIT is still keyed on the consumed outpoint and a DEPOSIT on 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 preserving created_at and prior handles, enum FK rejection, NULL-blob handling, keyset paging, event-pull cursor.
  • swapwallet/projector_test.go: WalletEntry → projection mapping with a lossless entry_json round-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 the db + swapwallet unit suites pass.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread swapwallet/projector.go
Comment thread db/sqlc/queries/activity_log.sql Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread darepod/server.go Outdated
Comment thread swapwallet/projector.go
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.
@darioAnongba
darioAnongba force-pushed the dario/774-activity-store-foundation branch from 46d8afa to 4477c4f Compare July 1, 2026 16:53
Comment thread darepod/config.go Outdated
Comment thread darepod/config.go Outdated
Comment thread darepod/server.go Outdated
Comment thread db/sqlc/migrations/000023_canonical_activity_log.up.sql Outdated
Comment thread db/sqlc/migrations/000023_canonical_activity_log.up.sql Outdated
@darioAnongba darioAnongba self-assigned this Jul 1, 2026
@darioAnongba
darioAnongba merged commit 6fd7700 into main Jul 1, 2026
25 of 27 checks passed
@darioAnongba
darioAnongba deleted the dario/774-activity-store-foundation branch July 1, 2026 17:19
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.

1 participant