Compute (potentially reorging) Settlement Status From Wallet Data#1692
Compute (potentially reorging) Settlement Status From Wallet Data#1692DanGould wants to merge 5 commits into
Conversation
Introduce outpoint-based settlement attribution for a monitored
session. A pure, total classify maps caller-supplied chain facts to a
settlement status without any I/O or persisted events.
Add the compute types SettlementStatus and SettlementOutcome
{Cooperative, Fallback, Other} (sealed #[non_exhaustive]) plus
ChainView/OutpointSpend, and the primitive getters the wallet needs
(payjoin_txid, fallback_txid, contested_outpoints, receiver_input).
classify recognizes a cooperative settlement by either signal: the
spending transaction carries the receiver's contributed input (the
input fingerprint) or, for an output-substitution proposal, reproduces
the proposal's output set (a cut-through). Both are structural, so a
non-SegWit txid change does not hide them. Precedence is
Cooperative > Fallback > Other.
Add Receiver<Monitor>::conclude, which persists the single terminal Closed(SessionOutcome) event once the caller's confidence bar is met, mapping Cooperative -> Success, Fallback -> FallbackBroadcasted, and Other -> Other. Grow the persisted SessionOutcome for settlement attribution: collapse Success to a unit variant (the spending transaction is chain data the caller already holds, so witnesses are not persisted), add Other(Txid) mapped to SessionStatus::Failed, and seal the enum #[non_exhaustive] with an aborted() constructor so downstream crates keep building the replay sentinel. Re-express the legacy check_for_transaction over classify via a txid-only ChainView, concluding a unit Success. Update the payjoin-cli SessionOutcome matches and history construction, and the integration assertions, for the unit Success.
Add the foreign-facing settlement surface: a ChainBackend trait that reports each contested outpoint's spend status, the ChainSpend record, and SettlementStatus/SettlementOutcome uniffi mirrors. Monitor::classify queries the backend, builds a ChainView, and returns the status without persisting anything. SettlementOutcome is #[non_exhaustive] in core, so the conversion keeps a wildcard arm that degrades a finer label this binding predates to Other until the binding is regenerated.
Drive the monitor loop from classify: build a ChainView by querying the wallet for each contested outpoint (get_raw_transaction_verbose for the spending tx and its confirmations), then render the status each tick as Pending / cooperative / fallback / other, concluding once a configurable confirmation bar is met.
Spell out the Monitor contract so integrators do not trust an imprecise 0-conf verdict. The proposal and the fallback double-spend the sender's inputs, so detection is necessary but not sufficient: only confirmation resolves the race, and confirmation depth is a confidence dial, not a state transition. Document the division of labor (library knows the semantics, wallet knows the ground truth and holds the spending transaction), the classify/conclude flow, and that the legacy check_for_transaction cannot surface Other and concludes optimistically on first sight.
8832e47 to
e7c0dde
Compare
Coverage Report for CI Build 28389258738Coverage increased (+0.3%) to 85.769%Details
Uncovered Changes
Coverage Regressions2 previously-covered lines in 2 files lost coverage.
Coverage Stats
💛 - Coveralls |
|
IMO such an API change should wait until after 1.0 release and is a candidate for a 2.0 API:
In the meantime, I think it's sufficient to add a big disclaimer in the
|
|
Field evidence in favor of this direction, from the BTCPay Server payjoin plugin (a settlement-accounting receiver over payjoin-ffi): we independently hit both flaws this PR identifies. Our accounting layer pins the proposal's expected transaction ID at hand-out time for exact reconciliation, which is silently wrong for non-SegWit senders (we currently reject them as policy, ValeraFinebits/btcpayserver-payjoin-plugin#78), and our stored reconciliation verdict shares the stale-on-reorg problem — a re-runnable In the meantime, for whatever it's worth to the 1.0 discussion: #1718 proposes the machine-readable version of the disclaimer suggested above — |
This is following up on #1684 where I figured out Monitor's status can go stale in the case of a reorg. Instead, we can run a classifier that can always classify the status based on mempool+chain state data instead.
This classification, that tracks
OutPointstatus also makes the #1218 workaround that skips monitoring the status of non-segwit outputs obsolete and can actually monitor non-segwit spending Payjoin cooperative txs.Sessions still close once a
Receiver<Monitor>detects a transaction but the actual SettlementStatus is determined by callingclassifyon chain state each time.Co-authored by Claude code
Pull Request Checklist
Please confirm the following before requesting review:
AI
in the body of this PR.