Skip to content

Commit 2ed9d9a

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 72fb575 commit 2ed9d9a

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
@@ -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: 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;
@@ -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,41 @@ 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.get_pending_manual_bolt11_by_payment_hash(payment_hash))
645+
}
646+
608647
pub async fn handle_event(&self, event: LdkEvent) -> Result<(), ReplayEvent> {
609648
match event {
610649
LdkEvent::FundingGenerationReady {
@@ -718,6 +757,11 @@ where
718757
} => {
719758
let payment_id = PaymentId(payment_hash.0);
720759
let payment_info = self.payment_store.get(&payment_id);
760+
let pending_payment = if payment_info.is_none() {
761+
self.find_pending_inbound_payment(&payment_hash).await?
762+
} else {
763+
None
764+
};
721765
if let Some(info) = payment_info.as_ref() {
722766
if info.direction == PaymentDirection::Outbound {
723767
log_info!(
@@ -803,6 +847,17 @@ where
803847
counterparty_skimmed_fee_msat,
804848
);
805849
self.fail_claimable_payment(payment_id, &payment_hash).await?;
850+
if pending_payment.is_some() {
851+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
852+
log_error!(
853+
self.logger,
854+
"Failed to remove pending payment with ID {}: {}",
855+
payment_id,
856+
e
857+
);
858+
return Err(ReplayEvent());
859+
}
860+
}
806861
return Ok(());
807862
};
808863

@@ -815,6 +870,17 @@ where
815870
max_total_opening_fee_msat,
816871
);
817872
self.fail_claimable_payment(payment_id, &payment_hash).await?;
873+
if pending_payment.is_some() {
874+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
875+
log_error!(
876+
self.logger,
877+
"Failed to remove pending payment with ID {}: {}",
878+
payment_id,
879+
e
880+
);
881+
return Err(ReplayEvent());
882+
}
883+
}
818884
return Ok(());
819885
}
820886

@@ -838,6 +904,57 @@ where
838904
}
839905
}
840906

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

1208+
if let Err(e) = self.pending_payment_store.remove(&payment_id).await {
1209+
log_error!(
1210+
self.logger,
1211+
"Failed to remove pending payment with ID {}: {}",
1212+
payment_id,
1213+
e
1214+
);
1215+
return Err(ReplayEvent());
1216+
}
1217+
10911218
let event = Event::PaymentReceived {
10921219
payment_id,
10931220
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)