Skip to content

Commit 4a62c03

Browse files
committed
Track manual BOLT11 invoices
Manual BOLT11 invoices need a pending entry before HTLC arrival. That lets duplicate registrations be rejected without scanning finalized payments. Keep the legacy hash-derived payment ID in this step; the next commit switches BOLT11 payments to randomized IDs. Co-Authored-By: HAL 9000
1 parent 4ef9c03 commit 4a62c03

5 files changed

Lines changed: 309 additions & 66 deletions

File tree

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,6 +2333,7 @@ fn build_with_store_internal(
23332333
scorer,
23342334
peer_store,
23352335
payment_store,
2336+
pending_payment_store,
23362337
lnurl_auth,
23372338
is_running,
23382339
node_metrics,

src/event.rs

Lines changed: 133 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use core::task::{Poll, Waker};
1010
use std::collections::VecDeque;
1111
use std::ops::Deref;
1212
use std::sync::{Arc, Mutex};
13+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1314

1415
use bitcoin::blockdata::locktime::absolute::LockTime;
1516
use bitcoin::secp256k1::PublicKey;
@@ -51,11 +52,12 @@ use crate::payment::asynchronous::static_invoice_store::StaticInvoiceStore;
5152
use crate::payment::store::{
5253
PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus,
5354
};
54-
use crate::payment::PaymentMetadata;
55+
use crate::payment::{PaymentMetadata, PendingPaymentDetails};
5556
use crate::probing::Prober;
5657
use crate::runtime::Runtime;
5758
use crate::types::{
58-
CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, Sweeper, Wallet,
59+
CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, PendingPaymentStore,
60+
Sweeper, Wallet,
5961
};
6062
use crate::{
6163
hex_utils, BumpTransactionEventHandler, ChannelManager, Error, Graph, PeerInfo, PeerStore,
@@ -529,6 +531,7 @@ where
529531
network_graph: Arc<Graph>,
530532
liquidity_source: Arc<LiquiditySource<Arc<Logger>>>,
531533
payment_store: Arc<PaymentStore>,
534+
pending_payment_store: Arc<PendingPaymentStore>,
532535
peer_store: Arc<PeerStore<L>>,
533536
keys_manager: Arc<KeysManager>,
534537
static_invoice_store: Option<StaticInvoiceStore>,
@@ -550,10 +553,10 @@ where
550553
channel_manager: Arc<ChannelManager>, connection_manager: Arc<ConnectionManager<L>>,
551554
output_sweeper: Arc<Sweeper>, network_graph: Arc<Graph>,
552555
liquidity_source: Arc<LiquiditySource<Arc<Logger>>>, payment_store: Arc<PaymentStore>,
553-
peer_store: Arc<PeerStore<L>>, keys_manager: Arc<KeysManager>,
554-
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
555-
om_mailbox: Option<Arc<OnionMessageMailbox>>, prober: Option<Arc<Prober>>,
556-
runtime: Arc<Runtime>, logger: L, config: Arc<Config>,
556+
pending_payment_store: Arc<PendingPaymentStore>, peer_store: Arc<PeerStore<L>>,
557+
keys_manager: Arc<KeysManager>, static_invoice_store: Option<StaticInvoiceStore>,
558+
onion_messenger: Arc<OnionMessenger>, om_mailbox: Option<Arc<OnionMessageMailbox>>,
559+
prober: Option<Arc<Prober>>, runtime: Arc<Runtime>, logger: L, config: Arc<Config>,
557560
) -> Self {
558561
Self {
559562
event_queue,
@@ -565,6 +568,7 @@ where
565568
network_graph,
566569
liquidity_source,
567570
payment_store,
571+
pending_payment_store,
568572
peer_store,
569573
keys_manager,
570574
static_invoice_store,
@@ -607,6 +611,41 @@ where
607611
})
608612
}
609613

614+
fn current_time_secs() -> u64 {
615+
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_secs()
616+
}
617+
618+
async fn prune_expired_pending_payments(&self) -> Result<(), ReplayEvent> {
619+
let now = Self::current_time_secs();
620+
let expired_payment_ids = self
621+
.pending_payment_store
622+
.list_filter(|payment| payment.has_expired(now))
623+
.into_iter()
624+
.map(|payment| payment.details.id)
625+
.collect::<Vec<_>>();
626+
627+
for payment_id in expired_payment_ids {
628+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
629+
log_error!(
630+
self.logger,
631+
"Failed to remove expired pending payment with ID {}: {}",
632+
payment_id,
633+
e
634+
);
635+
return Err(ReplayEvent());
636+
}
637+
}
638+
639+
Ok(())
640+
}
641+
642+
async fn find_pending_inbound_payment(
643+
&self, payment_hash: &PaymentHash,
644+
) -> Result<Option<PendingPaymentDetails>, ReplayEvent> {
645+
self.prune_expired_pending_payments().await?;
646+
Ok(self.pending_payment_store.get_pending_manual_bolt11_by_payment_hash(payment_hash))
647+
}
648+
610649
pub async fn handle_event(&self, event: LdkEvent) -> Result<(), ReplayEvent> {
611650
match event {
612651
LdkEvent::FundingGenerationReady {
@@ -720,6 +759,11 @@ where
720759
} => {
721760
let payment_id = PaymentId(payment_hash.0);
722761
let payment_info = self.payment_store.get(&payment_id);
762+
let pending_payment = if payment_info.is_none() {
763+
self.find_pending_inbound_payment(&payment_hash).await?
764+
} else {
765+
None
766+
};
723767
if let Some(info) = payment_info.as_ref() {
724768
if info.direction == PaymentDirection::Outbound {
725769
log_info!(
@@ -805,6 +849,17 @@ where
805849
counterparty_skimmed_fee_msat,
806850
);
807851
self.fail_claimable_payment(payment_id, &payment_hash).await?;
852+
if pending_payment.is_some() {
853+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
854+
log_error!(
855+
self.logger,
856+
"Failed to remove pending payment with ID {}: {}",
857+
payment_id,
858+
e
859+
);
860+
return Err(ReplayEvent());
861+
}
862+
}
808863
return Ok(());
809864
};
810865

@@ -817,6 +872,17 @@ where
817872
max_total_opening_fee_msat,
818873
);
819874
self.fail_claimable_payment(payment_id, &payment_hash).await?;
875+
if pending_payment.is_some() {
876+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
877+
log_error!(
878+
self.logger,
879+
"Failed to remove pending payment with ID {}: {}",
880+
payment_id,
881+
e
882+
);
883+
return Err(ReplayEvent());
884+
}
885+
}
820886
return Ok(());
821887
}
822888

@@ -840,6 +906,57 @@ where
840906
}
841907
}
842908

909+
let payment_info = if let Some(pending_payment) = pending_payment.as_ref() {
910+
let mut payment = pending_payment.details.clone();
911+
if let PaymentKind::Bolt11 {
912+
counterparty_skimmed_fee_msat: stored_fee, ..
913+
} = &mut payment.kind
914+
{
915+
if counterparty_skimmed_fee_msat > 0 {
916+
*stored_fee = Some(counterparty_skimmed_fee_msat);
917+
}
918+
}
919+
920+
match self.payment_store.insert(payment.clone()).await {
921+
Ok(false) => (),
922+
Ok(true) => {
923+
log_error!(
924+
self.logger,
925+
"Bolt11InvoicePayment with ID {} was previously known",
926+
payment_id,
927+
);
928+
debug_assert!(false);
929+
},
930+
Err(e) => {
931+
log_error!(
932+
self.logger,
933+
"Failed to insert payment with ID {}: {}",
934+
payment_id,
935+
e
936+
);
937+
return Err(ReplayEvent());
938+
},
939+
}
940+
941+
let mut pending_payment = pending_payment.clone();
942+
pending_payment.expires_at = None;
943+
if let Err(e) =
944+
self.pending_payment_store.insert_or_update(pending_payment).await
945+
{
946+
log_error!(
947+
self.logger,
948+
"Failed to update pending payment with ID {}: {}",
949+
payment_id,
950+
e
951+
);
952+
return Err(ReplayEvent());
953+
}
954+
955+
Some(payment)
956+
} else {
957+
payment_info
958+
};
959+
843960
if let Some(info) = payment_info {
844961
// If this is known by the store but ChannelManager doesn't know the preimage,
845962
// the payment has been registered via `_for_hash` variants and needs to be manually claimed via
@@ -1090,6 +1207,16 @@ where
10901207
},
10911208
}
10921209

1210+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
1211+
log_error!(
1212+
self.logger,
1213+
"Failed to remove pending payment with ID {}: {}",
1214+
payment_id,
1215+
e
1216+
);
1217+
return Err(ReplayEvent());
1218+
}
1219+
10931220
let event = Event::PaymentReceived {
10941221
payment_id,
10951222
payment_hash,

src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ use runtime::Runtime;
181181
pub use tokio;
182182
use types::{
183183
Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph,
184-
HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper,
185-
Wallet,
184+
HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, PendingPaymentStore,
185+
Router, Scorer, Sweeper, Wallet,
186186
};
187187
pub use types::{
188188
ChannelCounterparty, ChannelDetails, CustomTlvRecord, PeerDetails, ReserveType, UserChannelId,
@@ -249,6 +249,7 @@ pub struct Node {
249249
scorer: Arc<Mutex<Scorer>>,
250250
peer_store: Arc<PeerStore<Arc<Logger>>>,
251251
payment_store: Arc<PaymentStore>,
252+
pending_payment_store: Arc<PendingPaymentStore>,
252253
lnurl_auth: Arc<LnurlAuth>,
253254
is_running: Arc<RwLock<bool>>,
254255
node_metrics: Arc<PersistedNodeMetrics>,
@@ -611,6 +612,7 @@ impl Node {
611612
Arc::clone(&self.network_graph),
612613
Arc::clone(&self.liquidity_source),
613614
Arc::clone(&self.payment_store),
615+
Arc::clone(&self.pending_payment_store),
614616
Arc::clone(&self.peer_store),
615617
Arc::clone(&self.keys_manager),
616618
static_invoice_store,
@@ -962,6 +964,7 @@ impl Node {
962964
Arc::clone(&self.connection_manager),
963965
Arc::clone(&self.liquidity_source),
964966
Arc::clone(&self.payment_store),
967+
Arc::clone(&self.pending_payment_store),
965968
Arc::clone(&self.peer_store),
966969
Arc::clone(&self.config),
967970
Arc::clone(&self.is_running),
@@ -980,6 +983,7 @@ impl Node {
980983
Arc::clone(&self.connection_manager),
981984
Arc::clone(&self.liquidity_source),
982985
Arc::clone(&self.payment_store),
986+
Arc::clone(&self.pending_payment_store),
983987
Arc::clone(&self.peer_store),
984988
Arc::clone(&self.config),
985989
Arc::clone(&self.is_running),

0 commit comments

Comments
 (0)