Skip to content

multi: stable canonical id for on-chain send (#610) - #843

Merged
darioAnongba merged 8 commits into
mainfrom
dario/774-onchain-send-id
Jul 6, 2026
Merged

multi: stable canonical id for on-chain send (#610)#843
darioAnongba merged 8 commits into
mainfrom
dario/774-onchain-send-id

Conversation

@darioAnongba

@darioAnongba darioAnongba commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Fourth PR in the C1 series (#774), stacked on #842.

Fixes the flagship symptom of the epic, #610: a send --onchain (cooperative-leave) activity row is keyed by the consumed VTXO outpoint, which is destroyed when the round seals — so the handle vanishes (activity inspect returns NotFound), it can't survive a restart, and one outpoint slot can't represent a multi-input sweep.

Key realization

The daemon already derives and persists a stable id for the send: the PendingIntentID (wallet/pending_intent.go) — a sha256(kind + sorted consumed-outpoints + payload). It is deterministic (stable across restart), 32 bytes, and represents the whole multi-input set as one id. It was simply never returned to the wallet layer. So this reuses it rather than inventing new daemon persistence.

Change

  • Daemon: SendOnChainResponse gains send_job_id (proto). handleSendOnChain surfaces the already-derived PendingIntentID on its submitted response (zero on a dry-run preview); the RPC handler hex-encodes it into send_job_id.
  • swapwallet: the on-chain-send activity row's canonical id becomes send_job_id (falling back to the first consumed outpoint when the daemon returns none, preserving old behavior). The first consumed outpoint is retained in vtxo_outpoint, and decorateCooperativeLeaveEntry now matches the forfeit-driven completion on that retained outpoint rather than the id.
  • doc.go V1 LIMITATIONS updated.

Scope / deferred (C2)

This fixes the id. The live, cross-restart terminal reconciliation under that id — projecting forfeit/settlement into the store rather than relying on the startup backfill — is C2 (a VTXO-forfeiture / unroll-status reconciler, like the credit poll loop). DEPOSIT's stable id is the sibling follow-up (PR5).

Tests

  • wallet: the submitted SendOnChainResponse carries the persisted PendingIntentID (extends the persist-before-registration test). PendingIntentID determinism is already covered.
  • swapwallet: leaveEntryStub uses the leave-job id as the row id and retains the first outpoint in vtxo_outpoint (plus the empty-id fallback); decorateCooperativeLeaveEntry flips to COMPLETE on the retained outpoint, not the id.

make rpc / make build / make lint-changed-local / make commitmsg-lint, and the wallet + swapwallet + db unit suites pass. End-to-end (send --onchain, restart, activity inspect) is manual/itest, deferred.

@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 stable, durable send_job_id (derived from the pending intent ID) for on-chain send and cooperative-leave operations. This ID is returned in SendOnChainResponse and persisted as the row ID in the swap wallet, allowing the operation handle to survive restarts and round seals. The forfeit-driven completion logic is updated to correlate using the retained consumed outpoint (vtxo_outpoint) instead of the row ID, with fallback support for legacy rows. There are no review comments, so I have no feedback to provide.

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.

@darioAnongba
darioAnongba force-pushed the dario/774-onchain-send-id branch 2 times, most recently from a3909b4 to d8f58f5 Compare July 2, 2026 15:30
Base automatically changed from dario/774-list-cutover to main July 2, 2026 15:56
Add the stable leave-job id (hex of the pending-intent id) to
SendOnChainResponse so callers can key an on-chain send's activity row
by a handle that survives the round seal and a restart, rather than the
consumed VTXO outpoint (#610).
handleSendOnChain returns the derived PendingIntentID on the submitted
response so the RPC layer can expose it as the send's stable id. The
dry-run preview leaves it zero.
Populate SendOnChainResponse.send_job_id with the hex of the wallet's
pending-intent id, empty for a dry-run preview.
Use the daemon's returned send_job_id as the on-chain-send (cooperative
leave) activity row's canonical id, so it survives the round seal and a
restart and represents a multi-input sweep as one row, instead of the
consumed VTXO outpoint (#610). The first consumed outpoint is retained
in vtxo_outpoint and the forfeit-driven completion matches on it. Falls
back to the outpoint when the daemon returns no id.
@darioAnongba
darioAnongba force-pushed the dario/774-onchain-send-id branch from d8f58f5 to edfe5cf Compare July 2, 2026 15:59
@darioAnongba darioAnongba self-assigned this Jul 2, 2026
Re-keying the on-chain-send row to the hex send_job_id broke its
completion path. decorateExitEntry fed entry.GetId() to the daemon's
GetUnrollStatus, which parses its argument as a txid:vout outpoint and
returns InvalidArgument for a bare hash. The error aborted before the
Found=false fall-through to decorateCooperativeLeaveEntry, so every
cooperative-leave row was stranded PENDING and never flipped to
COMPLETE even after the leave round confirmed — a regression from the
parseable txid:vout id, which returned Found=false and completed.

Look up GetUnrollStatus by a real outpoint: the id for unilateral-exit
and legacy rows, else the retained vtxo_outpoint; with no queryable
outpoint the row cannot be a unilateral exit, so treat it as a
cooperative leave. Also stop decorateCooperativeLeaveEntry from falling
its correlation outpoint back to a non-outpoint id.

The test harness could not catch this: the fake GetUnrollStatus never
validated the outpoint and the only hash-id test called
decorateCooperativeLeaveEntry directly, bypassing the broken gate. The
fake now rejects a non-outpoint like the daemon, and a new test drives
a hash-keyed leave row through decorateExitEntry to COMPLETE.
The comment called it a durable handle that survives a restart, which
overstates it: the id is deterministic (reproducible from the same
inputs) and stable across a restart, but it is a correlation id, not a
lookup handle — no RPC resolves it and the send's terminal status is not
reconciled by id across a restart yet. Document that, that it is
non-secret and recomputable (must not gate a privileged action), and
that empty means preview/dry-run or an older daemon, not a submitted
send from a current daemon.
Generated by make rpc from the reworded send_job_id comment. Comment
only; no wire change.
@darioAnongba
darioAnongba merged commit be53694 into main Jul 6, 2026
19 of 21 checks passed
darioAnongba added a commit that referenced this pull request Jul 6, 2026
The WalletEntry.id and DEPOSIT invariants still described the pre-#843/
#857 state (EXIT/DEPOSIT do not share an id). Update them to record the
leave-job id (EXIT), the deposit-<address> id (DEPOSIT), and the
address-granularity trade-off, matching doc.go.
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