Skip to content

Commit da45da3

Browse files
committed
Update PaymentStore after each Wallet sync
We update the payment store whenever syncing the wallet state finished.
1 parent 142784d commit da45da3

File tree

5 files changed

+251
-23
lines changed

5 files changed

+251
-23
lines changed

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,8 @@ class LibraryTest {
296296
assert(paymentReceivedEvent is Event.PaymentReceived)
297297
node2.eventHandled()
298298

299-
assert(node1.listPayments().size == 1)
300-
assert(node2.listPayments().size == 1)
299+
assert(node1.listPayments().size == 3)
300+
assert(node2.listPayments().size == 2)
301301

302302
node2.closeChannel(userChannelId, nodeId1)
303303

src/payment/store.rs

+5
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@ impl_writeable_tlv_based_enum!(PaymentStatus,
292292
#[derive(Clone, Debug, PartialEq, Eq)]
293293
pub enum PaymentKind {
294294
/// An on-chain payment.
295+
///
296+
/// Payments of this kind will be considered pending until the respective transaction has
297+
/// reached [`ANTI_REORG_DELAY`] confirmations on-chain.
298+
///
299+
/// [`ANTI_REORG_DELAY`]: lightning::chain::channelmonitor::ANTI_REORG_DELAY
295300
Onchain {
296301
/// The transaction identifier of this payment.
297302
txid: Txid,

src/wallet/mod.rs

+86-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ use persist::KVStoreWalletPersister;
1010
use crate::logger::{log_debug, log_error, log_info, log_trace, Logger, LdkLogger};
1111

1212
use crate::fee_estimator::{ConfirmationTarget, FeeEstimator};
13-
use crate::payment::store::PaymentStore;
13+
use crate::payment::store::{ConfirmationStatus, PaymentStore};
14+
use crate::payment::{PaymentDetails, PaymentDirection, PaymentStatus};
1415
use crate::Error;
1516

1617
use lightning::chain::chaininterface::BroadcasterInterface;
18+
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
1719
use lightning::chain::{BestBlock, Listen};
1820

1921
use lightning::events::bump_transaction::{Utxo, WalletSource};
22+
use lightning::ln::channelmanager::PaymentId;
2023
use lightning::ln::inbound_payment::ExpandedKey;
2124
use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage};
2225
use lightning::ln::script::ShutdownScript;
@@ -46,6 +49,7 @@ use bitcoin::{
4649

4750
use std::ops::Deref;
4851
use std::sync::{Arc, Mutex};
52+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
4953

5054
pub(crate) enum OnchainSendAmount {
5155
ExactRetainingReserve { amount_sats: u64, cur_anchor_reserve_sats: u64 },
@@ -110,6 +114,11 @@ where
110114
Error::PersistenceFailed
111115
})?;
112116

117+
self.update_payment_store(&mut *locked_wallet).map_err(|e| {
118+
log_error!(self.logger, "Failed to update payment store: {}", e);
119+
Error::PersistenceFailed
120+
})?;
121+
113122
Ok(())
114123
},
115124
Err(e) => {
@@ -134,6 +143,76 @@ where
134143
Ok(())
135144
}
136145

146+
fn update_payment_store<'a>(
147+
&self, locked_wallet: &'a mut PersistedWallet<KVStoreWalletPersister>,
148+
) -> Result<(), Error> {
149+
let latest_update_timestamp = SystemTime::now()
150+
.duration_since(UNIX_EPOCH)
151+
.unwrap_or(Duration::from_secs(0))
152+
.as_secs();
153+
154+
for wtx in locked_wallet.transactions() {
155+
let id = PaymentId(wtx.tx_node.txid.to_byte_array());
156+
let txid = wtx.tx_node.txid;
157+
let (payment_status, confirmation_status) = match wtx.chain_position {
158+
bdk_chain::ChainPosition::Confirmed { anchor, .. } => {
159+
let confirmation_height = anchor.block_id.height;
160+
let cur_height = locked_wallet.latest_checkpoint().height();
161+
let payment_status = if cur_height >= confirmation_height + ANTI_REORG_DELAY - 1
162+
{
163+
PaymentStatus::Succeeded
164+
} else {
165+
PaymentStatus::Pending
166+
};
167+
let confirmation_status = ConfirmationStatus::Confirmed {
168+
block_hash: anchor.block_id.hash,
169+
height: confirmation_height,
170+
timestamp: anchor.confirmation_time,
171+
};
172+
(payment_status, confirmation_status)
173+
},
174+
bdk_chain::ChainPosition::Unconfirmed { .. } => {
175+
(PaymentStatus::Pending, ConfirmationStatus::Unconfirmed)
176+
},
177+
};
178+
// TODO: It would be great to introduce additional variants for
179+
// `ChannelFunding` and `ChannelClosing`. For the former, we could just
180+
// take a reference to `ChannelManager` here and check against
181+
// `list_channels`. But for the latter the best approach is much less
182+
// clear: for force-closes/HTLC spends we should be good querying
183+
// `OutputSweeper::tracked_spendable_outputs`, but regular channel closes
184+
// (i.e., `SpendableOutputDescriptor::StaticOutput` variants) are directly
185+
// spent to a wallet address. The only solution I can come up with is to
186+
// create and persist a list of 'static pending outputs' that we could use
187+
// here to determine the `PaymentKind`, but that's not really satisfactory, so
188+
// we're punting on it until we can come up with a better solution.
189+
let kind = crate::payment::PaymentKind::Onchain { txid, status: confirmation_status };
190+
let (sent, received) = locked_wallet.sent_and_received(&wtx.tx_node.tx);
191+
let (direction, amount_msat) = if sent > received {
192+
let direction = PaymentDirection::Outbound;
193+
let amount_msat = Some(sent.to_sat().saturating_sub(received.to_sat()) * 1000);
194+
(direction, amount_msat)
195+
} else {
196+
let direction = PaymentDirection::Inbound;
197+
let amount_msat = Some(received.to_sat().saturating_sub(sent.to_sat()) * 1000);
198+
(direction, amount_msat)
199+
};
200+
201+
let payment = PaymentDetails {
202+
id,
203+
kind,
204+
amount_msat,
205+
direction,
206+
status: payment_status,
207+
latest_update_timestamp,
208+
};
209+
210+
self.payment_store.insert_or_update(&payment)?;
211+
}
212+
213+
Ok(())
214+
}
215+
137216
pub(crate) fn create_funding_transaction(
138217
&self, output_script: ScriptBuf, amount: Amount, confirmation_target: ConfirmationTarget,
139218
locktime: LockTime,
@@ -481,7 +560,12 @@ where
481560
}
482561

483562
match locked_wallet.apply_block(block, height) {
484-
Ok(()) => (),
563+
Ok(()) => {
564+
if let Err(e) = self.update_payment_store(&mut *locked_wallet) {
565+
log_error!(self.logger, "Failed to update payment store: {}", e);
566+
return;
567+
}
568+
},
485569
Err(e) => {
486570
log_error!(
487571
self.logger,

tests/common/mod.rs

+88-11
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,36 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
484484
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
485485
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
486486

487+
// Check we saw the node funding transactions.
488+
assert_eq!(
489+
node_a
490+
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
491+
&& matches!(p.kind, PaymentKind::Onchain { .. }))
492+
.len(),
493+
1
494+
);
495+
assert_eq!(
496+
node_a
497+
.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound
498+
&& matches!(p.kind, PaymentKind::Onchain { .. }))
499+
.len(),
500+
0
501+
);
502+
assert_eq!(
503+
node_b
504+
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
505+
&& matches!(p.kind, PaymentKind::Onchain { .. }))
506+
.len(),
507+
1
508+
);
509+
assert_eq!(
510+
node_b
511+
.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound
512+
&& matches!(p.kind, PaymentKind::Onchain { .. }))
513+
.len(),
514+
0
515+
);
516+
487517
// Check we haven't got any events yet
488518
assert_eq!(node_a.next_event(), None);
489519
assert_eq!(node_b.next_event(), None);
@@ -516,6 +546,15 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
516546
node_a.sync_wallets().unwrap();
517547
node_b.sync_wallets().unwrap();
518548

549+
// Check we now see the channel funding transaction as outbound.
550+
assert_eq!(
551+
node_a
552+
.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound
553+
&& matches!(p.kind, PaymentKind::Onchain { .. }))
554+
.len(),
555+
1
556+
);
557+
519558
let onchain_fee_buffer_sat = 5000;
520559
let node_a_anchor_reserve_sat = if expect_anchor_channel { 25_000 } else { 0 };
521560
let node_a_upper_bound_sat =
@@ -565,22 +604,26 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
565604
let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap();
566605
assert_eq!(node_a.bolt11_payment().send(&invoice, None), Err(NodeError::DuplicatePayment));
567606

568-
assert_eq!(node_a.list_payments().first().unwrap().id, payment_id);
607+
assert!(!node_a.list_payments_with_filter(|p| p.id == payment_id).is_empty());
569608

570-
let outbound_payments_a =
571-
node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);
609+
let outbound_payments_a = node_a.list_payments_with_filter(|p| {
610+
p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
611+
});
572612
assert_eq!(outbound_payments_a.len(), 1);
573613

574-
let inbound_payments_a =
575-
node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound);
614+
let inbound_payments_a = node_a.list_payments_with_filter(|p| {
615+
p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
616+
});
576617
assert_eq!(inbound_payments_a.len(), 0);
577618

578-
let outbound_payments_b =
579-
node_b.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);
619+
let outbound_payments_b = node_b.list_payments_with_filter(|p| {
620+
p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
621+
});
580622
assert_eq!(outbound_payments_b.len(), 0);
581623

582-
let inbound_payments_b =
583-
node_b.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound);
624+
let inbound_payments_b = node_b.list_payments_with_filter(|p| {
625+
p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
626+
});
584627
assert_eq!(inbound_payments_b.len(), 1);
585628

586629
expect_event!(node_a, PaymentSuccessful);
@@ -814,8 +857,26 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
814857
node_b.payment(&keysend_payment_id).unwrap().kind,
815858
PaymentKind::Spontaneous { .. }
816859
));
817-
assert_eq!(node_a.list_payments().len(), 6);
818-
assert_eq!(node_b.list_payments().len(), 7);
860+
assert_eq!(
861+
node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(),
862+
5
863+
);
864+
assert_eq!(
865+
node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(),
866+
6
867+
);
868+
assert_eq!(
869+
node_a
870+
.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. }))
871+
.len(),
872+
1
873+
);
874+
assert_eq!(
875+
node_b
876+
.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. }))
877+
.len(),
878+
1
879+
);
819880

820881
println!("\nB close_channel (force: {})", force_close);
821882
if force_close {
@@ -936,6 +997,22 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
936997
assert_eq!(node_a.list_balances().total_anchor_channels_reserve_sats, 0);
937998
assert_eq!(node_b.list_balances().total_anchor_channels_reserve_sats, 0);
938999

1000+
// Now we should have seen the channel closing transaction on-chain.
1001+
assert_eq!(
1002+
node_a
1003+
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
1004+
&& matches!(p.kind, PaymentKind::Onchain { .. }))
1005+
.len(),
1006+
2
1007+
);
1008+
assert_eq!(
1009+
node_b
1010+
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
1011+
&& matches!(p.kind, PaymentKind::Onchain { .. }))
1012+
.len(),
1013+
2
1014+
);
1015+
9391016
// Check we handled all events
9401017
assert_eq!(node_a.next_event(), None);
9411018
assert_eq!(node_b.next_event(), None);

0 commit comments

Comments
 (0)