Skip to content

Commit b19aae4

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 70c3da5 commit b19aae4

5 files changed

Lines changed: 319 additions & 66 deletions

File tree

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2245,6 +2245,7 @@ fn build_with_store_internal(
22452245
scorer,
22462246
peer_store,
22472247
payment_store,
2248+
pending_payment_store,
22482249
lnurl_auth,
22492250
is_running,
22502251
node_metrics,

src/event.rs

Lines changed: 139 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;
@@ -50,10 +51,11 @@ use crate::payment::asynchronous::static_invoice_store::StaticInvoiceStore;
5051
use crate::payment::store::{
5152
PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus,
5253
};
53-
use crate::payment::PaymentMetadata;
54+
use crate::payment::{PaymentMetadata, PendingPaymentDetails};
5455
use crate::runtime::Runtime;
5556
use crate::types::{
56-
CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, Sweeper, Wallet,
57+
CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, PendingPaymentStore,
58+
Sweeper, Wallet,
5759
};
5860
use crate::{
5961
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
runtime: Arc<Runtime>,
@@ -549,10 +552,10 @@ where
549552
channel_manager: Arc<ChannelManager>, connection_manager: Arc<ConnectionManager<L>>,
550553
output_sweeper: Arc<Sweeper>, network_graph: Arc<Graph>,
551554
liquidity_source: Arc<LiquiditySource<Arc<Logger>>>, payment_store: Arc<PaymentStore>,
552-
peer_store: Arc<PeerStore<L>>, keys_manager: Arc<KeysManager>,
553-
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
554-
om_mailbox: Option<Arc<OnionMessageMailbox>>, runtime: Arc<Runtime>, logger: L,
555-
config: Arc<Config>,
555+
pending_payment_store: Arc<PendingPaymentStore>, peer_store: Arc<PeerStore<L>>,
556+
keys_manager: Arc<KeysManager>, static_invoice_store: Option<StaticInvoiceStore>,
557+
onion_messenger: Arc<OnionMessenger>, om_mailbox: Option<Arc<OnionMessageMailbox>>,
558+
runtime: Arc<Runtime>, logger: L, config: Arc<Config>,
556559
) -> Self {
557560
Self {
558561
event_queue,
@@ -564,6 +567,7 @@ where
564567
network_graph,
565568
liquidity_source,
566569
payment_store,
570+
pending_payment_store,
567571
peer_store,
568572
keys_manager,
569573
logger,
@@ -605,6 +609,47 @@ where
605609
})
606610
}
607611

612+
fn current_time_secs() -> u64 {
613+
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_secs()
614+
}
615+
616+
async fn prune_expired_pending_payments(&self) -> Result<(), ReplayEvent> {
617+
let now = Self::current_time_secs();
618+
let expired_payment_ids = self
619+
.pending_payment_store
620+
.list_filter(|payment| payment.has_expired(now))
621+
.into_iter()
622+
.map(|payment| payment.details.id)
623+
.collect::<Vec<_>>();
624+
625+
for payment_id in expired_payment_ids {
626+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
627+
log_error!(
628+
self.logger,
629+
"Failed to remove expired pending payment with ID {}: {}",
630+
payment_id,
631+
e
632+
);
633+
return Err(ReplayEvent());
634+
}
635+
}
636+
637+
Ok(())
638+
}
639+
640+
async fn find_pending_inbound_payment(
641+
&self, payment_hash: &PaymentHash,
642+
) -> Result<Option<PendingPaymentDetails>, ReplayEvent> {
643+
self.prune_expired_pending_payments().await?;
644+
Ok(self.pending_payment_store.list_by_payment_hash(payment_hash).into_iter().find(
645+
|payment| {
646+
payment.details.direction == PaymentDirection::Inbound
647+
&& payment.details.status == PaymentStatus::Pending
648+
&& matches!(&payment.details.kind, PaymentKind::Bolt11 { hash, .. } if hash == payment_hash)
649+
},
650+
))
651+
}
652+
608653
pub async fn handle_event(&self, event: LdkEvent) -> Result<(), ReplayEvent> {
609654
match event {
610655
LdkEvent::FundingGenerationReady {
@@ -718,6 +763,11 @@ where
718763
} => {
719764
let payment_id = PaymentId(payment_hash.0);
720765
let payment_info = self.payment_store.get(&payment_id);
766+
let pending_payment = if payment_info.is_none() {
767+
self.find_pending_inbound_payment(&payment_hash).await?
768+
} else {
769+
None
770+
};
721771
if let Some(info) = payment_info.as_ref() {
722772
if info.direction == PaymentDirection::Outbound {
723773
log_info!(
@@ -803,6 +853,17 @@ where
803853
counterparty_skimmed_fee_msat,
804854
);
805855
self.fail_claimable_payment(payment_id, &payment_hash).await?;
856+
if pending_payment.is_some() {
857+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
858+
log_error!(
859+
self.logger,
860+
"Failed to remove pending payment with ID {}: {}",
861+
payment_id,
862+
e
863+
);
864+
return Err(ReplayEvent());
865+
}
866+
}
806867
return Ok(());
807868
};
808869

@@ -815,6 +876,17 @@ where
815876
max_total_opening_fee_msat,
816877
);
817878
self.fail_claimable_payment(payment_id, &payment_hash).await?;
879+
if pending_payment.is_some() {
880+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
881+
log_error!(
882+
self.logger,
883+
"Failed to remove pending payment with ID {}: {}",
884+
payment_id,
885+
e
886+
);
887+
return Err(ReplayEvent());
888+
}
889+
}
818890
return Ok(());
819891
}
820892

@@ -838,6 +910,57 @@ where
838910
}
839911
}
840912

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

1214+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
1215+
log_error!(
1216+
self.logger,
1217+
"Failed to remove pending payment with ID {}: {}",
1218+
payment_id,
1219+
e
1220+
);
1221+
return Err(ReplayEvent());
1222+
}
1223+
10911224
let event = Event::PaymentReceived {
10921225
payment_id,
10931226
payment_hash,

src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ use runtime::Runtime;
176176
pub use tokio;
177177
use types::{
178178
Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph,
179-
HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper,
180-
Wallet,
179+
HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, PendingPaymentStore,
180+
Router, Scorer, Sweeper, Wallet,
181181
};
182182
pub use types::{
183183
ChannelCounterparty, ChannelDetails, CustomTlvRecord, PeerDetails, ReserveType, UserChannelId,
@@ -244,6 +244,7 @@ pub struct Node {
244244
scorer: Arc<Mutex<Scorer>>,
245245
peer_store: Arc<PeerStore<Arc<Logger>>>,
246246
payment_store: Arc<PaymentStore>,
247+
pending_payment_store: Arc<PendingPaymentStore>,
247248
lnurl_auth: Arc<LnurlAuth>,
248249
is_running: Arc<RwLock<bool>>,
249250
node_metrics: Arc<PersistedNodeMetrics>,
@@ -605,6 +606,7 @@ impl Node {
605606
Arc::clone(&self.network_graph),
606607
Arc::clone(&self.liquidity_source),
607608
Arc::clone(&self.payment_store),
609+
Arc::clone(&self.pending_payment_store),
608610
Arc::clone(&self.peer_store),
609611
Arc::clone(&self.keys_manager),
610612
static_invoice_store,
@@ -948,6 +950,7 @@ impl Node {
948950
Arc::clone(&self.connection_manager),
949951
Arc::clone(&self.liquidity_source),
950952
Arc::clone(&self.payment_store),
953+
Arc::clone(&self.pending_payment_store),
951954
Arc::clone(&self.peer_store),
952955
Arc::clone(&self.config),
953956
Arc::clone(&self.is_running),
@@ -966,6 +969,7 @@ impl Node {
966969
Arc::clone(&self.connection_manager),
967970
Arc::clone(&self.liquidity_source),
968971
Arc::clone(&self.payment_store),
972+
Arc::clone(&self.pending_payment_store),
969973
Arc::clone(&self.peer_store),
970974
Arc::clone(&self.config),
971975
Arc::clone(&self.is_running),

0 commit comments

Comments
 (0)