Skip to content

Commit 1d05f9c

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

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
@@ -237,8 +237,8 @@ class LibraryTest {
237237
assert(paymentReceivedEvent is Event.PaymentReceived)
238238
node2.eventHandled()
239239

240-
assert(node1.listPayments().size == 1)
241-
assert(node2.listPayments().size == 1)
240+
assert(node1.listPayments().size == 3)
241+
assert(node2.listPayments().size == 2)
242242

243243
node2.closeChannel(userChannelId, nodeId1)
244244

src/payment/store.rs

+5
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@ impl_writeable_tlv_based_enum!(PaymentStatus,
284284
#[derive(Clone, Debug, PartialEq, Eq)]
285285
pub enum PaymentKind {
286286
/// An on-chain payment.
287+
///
288+
/// Payments of this kind will be considered pending until the respective transaction has
289+
/// reached [`ANTI_REORG_DELAY`] confirmations on-chain.
290+
///
291+
/// [`ANTI_REORG_DELAY`]: lightning::chain::channelmonitor::ANTI_REORG_DELAY
287292
Onchain {
288293
/// The transaction identifier of this payment.
289294
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, FilesystemLogger, Logger};
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
@@ -483,6 +483,36 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
483483
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
484484
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
485485

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

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

567-
assert_eq!(node_a.list_payments().first().unwrap().id, payment_id);
606+
assert!(!node_a.list_payments_with_filter(|p| p.id == payment_id).is_empty());
568607

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

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

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

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

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

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

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

0 commit comments

Comments
 (0)