Skip to content

Commit 70654b2

Browse files
committed
Store compact pending payment index records
Persist only the payment id, current txid, and conflict txids in the pending payment index. This avoids duplicating payment state before the format ships and keeps RBF lookup aliases intact across replacement events. Co-Authored-By: HAL 9000
1 parent dc8884e commit 70654b2

2 files changed

Lines changed: 87 additions & 56 deletions

File tree

src/payment/store.rs

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -589,49 +589,56 @@ impl StorableObjectUpdate<PaymentDetails> for PaymentDetailsUpdate {
589589
/// Represents a pending payment
590590
#[derive(Clone, Debug, PartialEq, Eq)]
591591
pub(crate) struct PendingPaymentDetails {
592-
/// The full payment details
593-
pub details: PaymentDetails,
592+
/// The payment id tracked in the main payment store.
593+
pub payment_id: PaymentId,
594+
/// The canonical transaction id currently associated with the payment.
595+
pub txid: Txid,
594596
/// Transaction IDs that have replaced or conflict with this payment.
595597
pub conflicting_txids: Vec<Txid>,
596598
}
597599

598600
impl PendingPaymentDetails {
599-
pub(crate) fn new(details: PaymentDetails, conflicting_txids: Vec<Txid>) -> Self {
600-
Self { details, conflicting_txids }
601+
pub(crate) fn new(payment_id: PaymentId, txid: Txid, conflicting_txids: Vec<Txid>) -> Self {
602+
Self { payment_id, txid, conflicting_txids }
601603
}
602604
}
603605

604606
impl_writeable_tlv_based!(PendingPaymentDetails, {
605-
(0, details, required),
606-
(2, conflicting_txids, optional_vec),
607+
(0, payment_id, required),
608+
(2, txid, required),
609+
(4, conflicting_txids, optional_vec),
607610
});
608611

609612
#[derive(Clone, Debug, PartialEq, Eq)]
610613
pub(crate) struct PendingPaymentDetailsUpdate {
611-
pub id: PaymentId,
612-
pub payment_update: Option<PaymentDetailsUpdate>,
613-
pub conflicting_txids: Option<Vec<Txid>>,
614+
pub payment_id: PaymentId,
615+
pub txid: Txid,
616+
pub conflicting_txids: Vec<Txid>,
614617
}
615618

616619
impl StorableObject for PendingPaymentDetails {
617620
type Id = PaymentId;
618621
type Update = PendingPaymentDetailsUpdate;
619622

620623
fn id(&self) -> Self::Id {
621-
self.details.id
624+
self.payment_id
622625
}
623626

624627
fn update(&mut self, update: Self::Update) -> bool {
625628
let mut updated = false;
626629

627-
// Update the underlying payment details if present
628-
if let Some(payment_update) = update.payment_update {
629-
updated |= self.details.update(payment_update);
630+
if self.txid != update.txid {
631+
let old_txid = self.txid;
632+
self.txid = update.txid;
633+
if old_txid != self.txid && !self.conflicting_txids.contains(&old_txid) {
634+
self.conflicting_txids.push(old_txid);
635+
}
636+
updated = true;
630637
}
631638

632-
if let Some(new_conflicting_txids) = update.conflicting_txids {
633-
if self.conflicting_txids != new_conflicting_txids {
634-
self.conflicting_txids = new_conflicting_txids;
639+
for txid in update.conflicting_txids {
640+
if txid != self.txid && !self.conflicting_txids.contains(&txid) {
641+
self.conflicting_txids.push(txid);
635642
updated = true;
636643
}
637644
}
@@ -646,18 +653,17 @@ impl StorableObject for PendingPaymentDetails {
646653

647654
impl StorableObjectUpdate<PendingPaymentDetails> for PendingPaymentDetailsUpdate {
648655
fn id(&self) -> <PendingPaymentDetails as StorableObject>::Id {
649-
self.id
656+
self.payment_id
650657
}
651658
}
652659

653660
impl From<&PendingPaymentDetails> for PendingPaymentDetailsUpdate {
654661
fn from(value: &PendingPaymentDetails) -> Self {
655-
let conflicting_txids = if value.conflicting_txids.is_empty() {
656-
None
657-
} else {
658-
Some(value.conflicting_txids.clone())
659-
};
660-
Self { id: value.id(), payment_update: Some(value.details.to_update()), conflicting_txids }
662+
Self {
663+
payment_id: value.id(),
664+
txid: value.txid,
665+
conflicting_txids: value.conflicting_txids.clone(),
666+
}
661667
}
662668
}
663669

src/wallet/mod.rs

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -270,48 +270,62 @@ impl Wallet {
270270

271271
if payment_status == PaymentStatus::Pending {
272272
let pending_payment =
273-
self.create_pending_payment_from_tx(payment, Vec::new());
273+
self.create_pending_payment_from_tx(payment_id, txid, Vec::new());
274274

275275
self.runtime.block_on(
276276
self.pending_payment_store.insert_or_update(pending_payment),
277277
)?;
278+
} else {
279+
self.runtime.block_on(self.pending_payment_store.remove(&payment_id))?;
278280
}
279281
},
280282
WalletEvent::ChainTipChanged { new_tip, .. } => {
281283
let pending_payments: Vec<PendingPaymentDetails> =
282-
self.pending_payment_store.list_filter(|p| {
283-
debug_assert!(
284-
p.details.status == PaymentStatus::Pending,
285-
"Non-pending payment {:?} found in pending store",
286-
p.details.id,
287-
);
288-
p.details.status == PaymentStatus::Pending
289-
&& matches!(p.details.kind, PaymentKind::Onchain { .. })
290-
});
284+
self.pending_payment_store.list_filter(|_| true);
291285

292286
let mut unconfirmed_outbound_txids: Vec<Txid> = Vec::new();
293287

294-
for mut payment in pending_payments {
295-
match payment.details.kind {
288+
for pending_payment in pending_payments {
289+
let Some(mut payment) = self.payment_store.get(&pending_payment.payment_id)
290+
else {
291+
self.runtime.block_on(
292+
self.pending_payment_store.remove(&pending_payment.payment_id),
293+
)?;
294+
continue;
295+
};
296+
297+
debug_assert!(
298+
payment.status == PaymentStatus::Pending,
299+
"Non-pending payment {:?} found in pending store",
300+
payment.id,
301+
);
302+
if payment.status != PaymentStatus::Pending {
303+
self.runtime.block_on(
304+
self.pending_payment_store.remove(&pending_payment.payment_id),
305+
)?;
306+
continue;
307+
}
308+
309+
match &payment.kind {
296310
PaymentKind::Onchain {
297311
status: ConfirmationStatus::Confirmed { height, .. },
298312
..
299313
} => {
300-
let payment_id = payment.details.id;
301-
if new_tip.height >= height + ANTI_REORG_DELAY - 1 {
302-
payment.details.status = PaymentStatus::Succeeded;
314+
if new_tip.height >= *height + ANTI_REORG_DELAY - 1 {
315+
payment.status = PaymentStatus::Succeeded;
316+
self.runtime
317+
.block_on(self.payment_store.insert_or_update(payment))?;
303318
self.runtime.block_on(
304-
self.payment_store.insert_or_update(payment.details),
319+
self.pending_payment_store
320+
.remove(&pending_payment.payment_id),
305321
)?;
306-
self.runtime
307-
.block_on(self.pending_payment_store.remove(&payment_id))?;
308322
}
309323
},
310324
PaymentKind::Onchain {
311325
txid,
312326
status: ConfirmationStatus::Unconfirmed,
313-
} if payment.details.direction == PaymentDirection::Outbound => {
314-
unconfirmed_outbound_txids.push(txid);
327+
} if payment.direction == PaymentDirection::Outbound => {
328+
unconfirmed_outbound_txids.push(*txid);
315329
},
316330
_ => {},
317331
}
@@ -359,7 +373,7 @@ impl Wallet {
359373
ConfirmationStatus::Unconfirmed,
360374
);
361375
let pending_payment =
362-
self.create_pending_payment_from_tx(payment.clone(), Vec::new());
376+
self.create_pending_payment_from_tx(payment_id, txid, Vec::new());
363377
self.runtime.block_on(self.payment_store.insert_or_update(payment))?;
364378
self.runtime
365379
.block_on(self.pending_payment_store.insert_or_update(pending_payment))?;
@@ -389,8 +403,22 @@ impl Wallet {
389403
);
390404
let payment =
391405
self.payment_store.get(&payment_id).ok_or(Error::InvalidPaymentId)?;
392-
let pending_payment_details =
393-
self.create_pending_payment_from_tx(payment, conflict_txids.clone());
406+
let payment_txid = match &payment.kind {
407+
PaymentKind::Onchain { txid, .. } => *txid,
408+
_ => {
409+
log_error!(
410+
self.logger,
411+
"Payment {:?} is not on-chain during WalletEvent::TxReplaced",
412+
payment_id,
413+
);
414+
continue;
415+
},
416+
};
417+
let pending_payment_details = self.create_pending_payment_from_tx(
418+
payment_id,
419+
payment_txid,
420+
conflict_txids,
421+
);
394422

395423
self.runtime.block_on(
396424
self.pending_payment_store.insert_or_update(pending_payment_details),
@@ -409,7 +437,7 @@ impl Wallet {
409437
ConfirmationStatus::Unconfirmed,
410438
);
411439
let pending_payment =
412-
self.create_pending_payment_from_tx(payment.clone(), Vec::new());
440+
self.create_pending_payment_from_tx(payment_id, txid, Vec::new());
413441
self.runtime.block_on(self.payment_store.insert_or_update(payment))?;
414442
self.runtime
415443
.block_on(self.pending_payment_store.insert_or_update(pending_payment))?;
@@ -1207,9 +1235,9 @@ impl Wallet {
12071235
}
12081236

12091237
fn create_pending_payment_from_tx(
1210-
&self, payment: PaymentDetails, conflicting_txids: Vec<Txid>,
1238+
&self, payment_id: PaymentId, txid: Txid, conflicting_txids: Vec<Txid>,
12111239
) -> PendingPaymentDetails {
1212-
PendingPaymentDetails::new(payment, conflicting_txids)
1240+
PendingPaymentDetails::new(payment_id, txid, conflicting_txids)
12131241
}
12141242

12151243
fn find_payment_by_txid(&self, target_txid: Txid) -> Option<PaymentId> {
@@ -1220,13 +1248,10 @@ impl Wallet {
12201248

12211249
if let Some(replaced_details) = self
12221250
.pending_payment_store
1223-
.list_filter(|p| {
1224-
matches!(p.details.kind, PaymentKind::Onchain { txid, .. } if txid == target_txid)
1225-
|| p.conflicting_txids.contains(&target_txid)
1226-
})
1251+
.list_filter(|p| p.txid == target_txid || p.conflicting_txids.contains(&target_txid))
12271252
.first()
12281253
{
1229-
return Some(replaced_details.details.id);
1254+
return Some(replaced_details.payment_id);
12301255
}
12311256

12321257
None
@@ -1428,11 +1453,11 @@ impl Wallet {
14281453
);
14291454

14301455
let pending_payment_store =
1431-
self.create_pending_payment_from_tx(new_payment.clone(), Vec::new());
1456+
self.create_pending_payment_from_tx(new_payment.id, new_txid, vec![txid]);
14321457

1458+
self.runtime.block_on(self.payment_store.insert_or_update(new_payment))?;
14331459
self.runtime
14341460
.block_on(self.pending_payment_store.insert_or_update(pending_payment_store))?;
1435-
self.runtime.block_on(self.payment_store.insert_or_update(new_payment))?;
14361461

14371462
log_info!(self.logger, "RBF successful: replaced {} with {}", txid, new_txid);
14381463

0 commit comments

Comments
 (0)