1010//! [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
1111
1212use std:: sync:: { Arc , RwLock } ;
13+ use std:: time:: { Duration , SystemTime , UNIX_EPOCH } ;
1314
1415use bitcoin:: hashes:: sha256:: Hash as Sha256 ;
1516use bitcoin:: hashes:: Hash ;
@@ -23,7 +24,7 @@ use lightning::sign::EntropySource;
2324use lightning_invoice:: {
2425 Bolt11Invoice as LdkBolt11Invoice , Bolt11InvoiceDescription as LdkBolt11InvoiceDescription ,
2526} ;
26- use lightning_types:: payment:: { PaymentHash , PaymentPreimage } ;
27+ use lightning_types:: payment:: { PaymentHash , PaymentPreimage , PaymentSecret } ;
2728
2829use crate :: config:: { Config , LDK_PAYMENT_RETRY_TIMEOUT } ;
2930use crate :: connection:: ConnectionManager ;
@@ -36,9 +37,10 @@ use crate::payment::store::{
3637 LSPS2Parameters , PaymentDetails , PaymentDetailsUpdate , PaymentDirection , PaymentKind ,
3738 PaymentStatus ,
3839} ;
40+ use crate :: payment:: PendingPaymentDetails ;
3941use crate :: peer_store:: { PeerInfo , PeerStore } ;
4042use crate :: runtime:: Runtime ;
41- use crate :: types:: { ChannelManager , KeysManager , PaymentStore } ;
43+ use crate :: types:: { ChannelManager , KeysManager , PaymentStore , PendingPaymentStore } ;
4244
4345#[ cfg( not( feature = "uniffi" ) ) ]
4446type Bolt11Invoice = LdkBolt11Invoice ;
@@ -74,6 +76,7 @@ pub struct Bolt11Payment {
7476 connection_manager : Arc < ConnectionManager < Arc < Logger > > > ,
7577 liquidity_source : Arc < LiquiditySource < Arc < Logger > > > ,
7678 payment_store : Arc < PaymentStore > ,
79+ pending_payment_store : Arc < PendingPaymentStore > ,
7780 peer_store : Arc < PeerStore < Arc < Logger > > > ,
7881 config : Arc < Config > ,
7982 is_running : Arc < RwLock < bool > > ,
@@ -85,8 +88,8 @@ impl Bolt11Payment {
8588 runtime : Arc < Runtime > , channel_manager : Arc < ChannelManager > ,
8689 keys_manager : Arc < KeysManager > , connection_manager : Arc < ConnectionManager < Arc < Logger > > > ,
8790 liquidity_source : Arc < LiquiditySource < Arc < Logger > > > , payment_store : Arc < PaymentStore > ,
88- peer_store : Arc < PeerStore < Arc < Logger > > > , config : Arc < Config > ,
89- is_running : Arc < RwLock < bool > > , logger : Arc < Logger > ,
91+ pending_payment_store : Arc < PendingPaymentStore > , peer_store : Arc < PeerStore < Arc < Logger > > > ,
92+ config : Arc < Config > , is_running : Arc < RwLock < bool > > , logger : Arc < Logger > ,
9093 ) -> Self {
9194 Self {
9295 runtime,
@@ -95,17 +98,94 @@ impl Bolt11Payment {
9598 connection_manager,
9699 liquidity_source,
97100 payment_store,
101+ pending_payment_store,
98102 peer_store,
99103 config,
100104 is_running,
101105 logger,
102106 }
103107 }
104108
109+ fn current_time_secs ( ) -> u64 {
110+ SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) . unwrap_or ( Duration :: from_secs ( 0 ) ) . as_secs ( )
111+ }
112+
113+ fn prune_expired_pending_payments ( & self ) -> Result < ( ) , Error > {
114+ let now = Self :: current_time_secs ( ) ;
115+ let expired_payment_ids = self
116+ . pending_payment_store
117+ . list_filter ( |payment| payment. has_expired ( now) )
118+ . into_iter ( )
119+ . map ( |payment| payment. details . id )
120+ . collect :: < Vec < _ > > ( ) ;
121+
122+ for payment_id in expired_payment_ids {
123+ self . runtime . block_on ( self . pending_payment_store . remove ( & payment_id) ) ?;
124+ }
125+
126+ Ok ( ( ) )
127+ }
128+
129+ fn has_pending_or_succeeded_inbound_payment ( & self , payment_hash : & PaymentHash ) -> bool {
130+ !self
131+ . payment_store
132+ . list_filter ( |payment| {
133+ payment. direction == PaymentDirection :: Inbound
134+ && matches ! ( & payment. kind, PaymentKind :: Bolt11 { hash, .. } if hash == payment_hash)
135+ && matches ! ( payment. status, PaymentStatus :: Pending | PaymentStatus :: Succeeded )
136+ } )
137+ . is_empty ( )
138+ || !self
139+ . pending_payment_store
140+ . list_filter ( |payment| {
141+ payment. details . direction == PaymentDirection :: Inbound
142+ && matches ! ( & payment. details. kind, PaymentKind :: Bolt11 { hash, .. } if hash == payment_hash)
143+ && matches ! (
144+ payment. details. status,
145+ PaymentStatus :: Pending | PaymentStatus :: Succeeded
146+ )
147+ } )
148+ . is_empty ( )
149+ }
150+
151+ fn register_manual_claim_invoice (
152+ & self , payment_hash : PaymentHash , amount_msat : Option < u64 > , payment_secret : PaymentSecret ,
153+ expiry_secs : u32 ,
154+ ) -> Result < ( ) , Error > {
155+ let payment_id = PaymentId ( self . keys_manager . get_secure_random_bytes ( ) ) ;
156+ let kind = PaymentKind :: Bolt11 {
157+ hash : payment_hash,
158+ preimage : None ,
159+ secret : Some ( payment_secret) ,
160+ counterparty_skimmed_fee_msat : None ,
161+ } ;
162+ let payment = PaymentDetails :: new (
163+ payment_id,
164+ kind,
165+ amount_msat,
166+ None ,
167+ PaymentDirection :: Inbound ,
168+ PaymentStatus :: Pending ,
169+ ) ;
170+ let expires_at = Some ( Self :: current_time_secs ( ) . saturating_add ( expiry_secs as u64 ) ) ;
171+ let pending_payment =
172+ PendingPaymentDetails :: new_with_expiry ( payment, Vec :: new ( ) , expires_at) ;
173+ self . runtime . block_on ( self . pending_payment_store . insert_or_update ( pending_payment) ) ?;
174+ Ok ( ( ) )
175+ }
176+
105177 pub ( crate ) fn receive_inner (
106178 & self , amount_msat : Option < u64 > , invoice_description : & LdkBolt11InvoiceDescription ,
107179 expiry_secs : u32 , manual_claim_payment_hash : Option < PaymentHash > ,
108180 ) -> Result < LdkBolt11Invoice , Error > {
181+ if let Some ( payment_hash) = manual_claim_payment_hash {
182+ self . prune_expired_pending_payments ( ) ?;
183+ if self . has_pending_or_succeeded_inbound_payment ( & payment_hash) {
184+ log_error ! ( self . logger, "Payment error: an invoice must not be paid twice." ) ;
185+ return Err ( Error :: DuplicatePayment ) ;
186+ }
187+ }
188+
109189 let invoice = {
110190 let invoice_params = Bolt11InvoiceParameters {
111191 amount_msats : amount_msat,
@@ -127,6 +207,15 @@ impl Bolt11Payment {
127207 }
128208 } ;
129209
210+ if let Some ( payment_hash) = manual_claim_payment_hash {
211+ self . register_manual_claim_invoice (
212+ payment_hash,
213+ amount_msat,
214+ * invoice. payment_secret ( ) ,
215+ expiry_secs,
216+ ) ?;
217+ }
218+
130219 Ok ( invoice)
131220 }
132221
@@ -135,6 +224,14 @@ impl Bolt11Payment {
135224 expiry_secs : u32 , max_total_lsp_fee_limit_msat : Option < u64 > ,
136225 max_proportional_lsp_fee_limit_ppm_msat : Option < u64 > , payment_hash : Option < PaymentHash > ,
137226 ) -> Result < LdkBolt11Invoice , Error > {
227+ if let Some ( payment_hash) = payment_hash {
228+ self . prune_expired_pending_payments ( ) ?;
229+ if self . has_pending_or_succeeded_inbound_payment ( & payment_hash) {
230+ log_error ! ( self . logger, "Payment error: an invoice must not be paid twice." ) ;
231+ return Err ( Error :: DuplicatePayment ) ;
232+ }
233+ }
234+
138235 let connection_manager = Arc :: clone ( & self . connection_manager ) ;
139236 let ( invoice, chosen_lsp) = self . runtime . block_on ( async move {
140237 if let Some ( amount_msat) = amount_msat {
@@ -167,6 +264,15 @@ impl Bolt11Payment {
167264 let peer_info = PeerInfo { node_id : chosen_lsp. node_id , address : chosen_lsp. address } ;
168265 self . runtime . block_on ( self . peer_store . add_peer ( peer_info) ) ?;
169266
267+ if let Some ( payment_hash) = payment_hash {
268+ self . register_manual_claim_invoice (
269+ payment_hash,
270+ amount_msat,
271+ * invoice. payment_secret ( ) ,
272+ expiry_secs,
273+ ) ?;
274+ }
275+
170276 Ok ( invoice)
171277 }
172278}
0 commit comments