Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions packages/rs-platform-wallet-ffi/src/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,13 @@ impl FFIPersister {
// wallet-event adapter would couple the flip to a round that
// silently drops it: the accepted-and-ignored shape the sweep
// bit's own gating exists to prevent, reproduced one channel over.
// Unlike `CORE_SWEEP_REMOVAL` below, this bit does NOT fold in the
// begin/end pair: its contract is per-callback durability of the
// overlay rows, which holds on a non-atomic host too. The
// round-coupling the adapter's staging needs is expressed as the
// `ROUND_COUPLED_PAYMENT_FLIPS` composite (this bit plus
// `ATOMIC_CHANGESETS`), so atomicity stays attested once, by the
// bit that owns it.
if self.callbacks.on_persist_dashpay_payments_fn.is_some() {
capabilities = capabilities.union(PersistenceCapabilities::DASHPAY_PAYMENTS);
}
Expand Down Expand Up @@ -6352,8 +6359,13 @@ mod tests {
/// vtable leaves `on_persist_dashpay_payments_fn` unset, so even a
/// host blindly OR-ing the bit must read as payments-blind: the
/// wallet-event adapter keys the sweep's Failed-flip staging on this
/// bit, and an accepted-and-dropped overlay is exactly the shape the
/// gating exists to prevent.
/// bit (composed with `ATOMIC_CHANGESETS` — the
/// `ROUND_COUPLED_PAYMENT_FLIPS` composite — since the staging also
/// needs the round to commit as one unit), and an accepted-and-dropped
/// overlay is exactly the shape the gating exists to prevent. The bit
/// itself deliberately stays atomicity-free: it attests per-callback
/// durability, and the positive case below is such a host — one the
/// adapter now refuses to stage round-coupled overlays for.
#[test]
fn dashpay_payments_requires_the_slot_and_the_declaration() {
fn persister_with(
Expand Down
1,915 changes: 1,914 additions & 1 deletion packages/rs-platform-wallet/src/changeset/core_bridge.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ impl PersistenceCapabilities {
/// `CORE_SWEEP_REMOVAL`. On the FFI surface Rust honours the
/// declaration only when `on_persist_dashpay_payments_fn` is actually
/// wired.
///
/// This bit alone attests only per-callback durability. The adapter's
/// round-coupled staging additionally requires `ATOMIC_CHANGESETS`
/// (see [`Self::ROUND_COUPLED_PAYMENT_FLIPS`]): on a host whose
/// callbacks commit independently, the Core record and watermark can
/// become durable while the process stops before the payments
/// callback — and a one-shot chainlocked reinstatement never
/// re-emits, so its payment would stay durably `Failed` beside a
/// durably recorded reinstatement.
pub const DASHPAY_PAYMENTS: Self = Self(1 << 11);

/// Capabilities required before exporting and funding an invitation voucher.
Expand All @@ -103,6 +112,22 @@ impl PersistenceCapabilities {
pub const ASSET_LOCK_RECONCILIATION: Self =
Self(Self::ATOMIC_CHANGESETS.0 | Self::TRACKED_ASSET_LOCKS.0 | Self::WALLET_RESTORE.0);

/// Capabilities required before the wallet-event adapter stages a
/// sweep's `Failed` flip or a reinstatement's `Confirmed` correction
/// onto the triggering record's own store round. The point of that
/// staging is that the flip and the record land or fail together —
/// `DASHPAY_PAYMENTS` proves the overlay rows are durably applied,
/// and `ATOMIC_CHANGESETS` proves the round commits or rolls back as
/// one unit. A payments-durable host without the atomic round gives
/// neither the coupling nor the fail-closed watermark backstop: it
/// can commit the Core record and watermark, then stop before the
/// payments write — and a one-shot reinstatement never re-emits to
/// retry the orphaned flip. Such a host is treated as payments-blind
/// for staging (the in-memory flip still happens; funds-safe, as
/// payment entries are display metadata).
pub const ROUND_COUPLED_PAYMENT_FLIPS: Self =
Self(Self::ATOMIC_CHANGESETS.0 | Self::DASHPAY_PAYMENTS.0);

pub const fn from_bits_retain(bits: u64) -> Self {
Self(bits)
}
Expand Down Expand Up @@ -208,6 +233,10 @@ mod tests {
PersistenceCapabilities::ASSET_LOCK_RECONCILIATION.bits(),
0x281
);
assert_eq!(
PersistenceCapabilities::ROUND_COUPLED_PAYMENT_FLIPS.bits(),
0x801
);
}

#[test]
Expand Down
8 changes: 5 additions & 3 deletions packages/rs-platform-wallet/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,11 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
// with SPV's write lock.
let lock_handler = Arc::new(LockNotifyHandler::new(Arc::clone(&lock_notify)));
let balance_handler = Arc::new(BalanceUpdateHandler::new(Arc::clone(&wallets)));
// DashPayPaymentHandler records incoming DashPay payments and
// confirms sent ones off the wallet-event fan-out, keeping that
// domain logic out of the generic core-changeset bridge. It holds
// DashPayPaymentHandler records incoming DashPay payments off the
// wallet-event fan-out, keeping that domain logic out of the
// generic core-changeset bridge. (Sent-payment verdicts do NOT
// run here: they are the wallet-event adapter's to apply in
// emission order — see `payment_handler`'s module docs.) It holds
// the wallet-manager (for the in-memory payment state it mutates)
// and the persister (to write the resulting payment rows).
let dashpay_payment_handler = Arc::new(DashPayPaymentHandler::new(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ pub use invitation::{
};
mod payment_handler;
pub(crate) use payment_handler::DashPayPaymentHandler;
// Re-exported for the payments unit tests, which drive the hooks
// directly; the handler itself calls it module-locally.
// Re-exported for the core-bridge ordering regressions, which release a
// "parked" hook task directly; the handler itself calls it module-locally.
#[cfg(test)]
pub(crate) use payment_handler::run_dashpay_payment_hooks;
mod payments;
pub(crate) use payments::{
confirm_sent_dashpay_payment, confirm_sent_dashpay_payment_by_txid,
record_incoming_dashpay_payments,
confirm_final_sent_payments_for_store, flip_swept_sent_payments_for_store,
record_incoming_dashpay_payments, rollback_payment_flips, PaymentFlipUndo, SweptPaymentFlips,
};
mod profile;
pub(crate) mod sdk_writer;
Expand Down
Loading
Loading