55// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66// accordance with one or both of these licenses.
77
8+ use std:: collections:: HashMap ;
9+ use std:: ops:: Deref ;
10+ use std:: sync:: { Arc , Mutex } ;
11+
812use bitcoin:: Txid ;
913use lightning:: impl_writeable_tlv_based;
1014use lightning:: ln:: channelmanager:: PaymentId ;
15+ use lightning_types:: payment:: PaymentHash ;
1116
12- use crate :: data_store:: { StorableObject , StorableObjectUpdate } ;
17+ use crate :: data_store:: { DataStore , StorableObject , StorableObjectUpdate } ;
18+ use crate :: logger:: LdkLogger ;
1319use crate :: payment:: store:: PaymentDetailsUpdate ;
1420use crate :: payment:: { PaymentDetails , PaymentKind } ;
21+ use crate :: types:: DynStore ;
22+ use crate :: Error ;
1523
1624/// One candidate transaction in an interactive-funding (splice) RBF history, holding this node's
1725/// share of the funding amount and fee for that candidate. Both are `None` for a candidate this
@@ -47,25 +55,38 @@ pub struct PendingPaymentDetails {
4755 /// RBF history, keyed by each candidate's txid. Empty for non-funding payments and for
4856 /// records written before per-candidate tracking existed.
4957 pub ( crate ) candidates : Vec < FundingTxCandidate > ,
58+ /// The timestamp after which this pending payment can be pruned.
59+ pub expires_at : Option < u64 > ,
5060}
5161
5262impl PendingPaymentDetails {
5363 pub ( crate ) fn new (
5464 details : PaymentDetails , conflicting_txids : Vec < Txid > , candidates : Vec < FundingTxCandidate > ,
5565 ) -> Self {
56- Self { details, conflicting_txids, candidates }
66+ Self { details, conflicting_txids, candidates, expires_at : None }
67+ }
68+
69+ pub ( crate ) fn new_with_expiry (
70+ details : PaymentDetails , conflicting_txids : Vec < Txid > , expires_at : Option < u64 > ,
71+ ) -> Self {
72+ Self { details, conflicting_txids, candidates : Vec :: new ( ) , expires_at }
5773 }
5874
5975 /// Returns this node's recorded funding figures for the candidate with the given txid, if any.
6076 pub ( crate ) fn candidate ( & self , txid : Txid ) -> Option < & FundingTxCandidate > {
6177 self . candidates . iter ( ) . find ( |candidate| candidate. txid == txid)
6278 }
79+
80+ pub ( crate ) fn has_expired ( & self , now : u64 ) -> bool {
81+ self . expires_at . map_or ( false , |expires_at| expires_at <= now)
82+ }
6383}
6484
6585impl_writeable_tlv_based ! ( PendingPaymentDetails , {
6686 ( 0 , details, required) ,
6787 ( 2 , conflicting_txids, optional_vec) ,
6888 ( 4 , candidates, optional_vec) ,
89+ ( 6 , expires_at, option) ,
6990} ) ;
7091
7192#[ derive( Clone , Debug , PartialEq , Eq ) ]
@@ -74,6 +95,7 @@ pub(crate) struct PendingPaymentDetailsUpdate {
7495 pub payment_update : Option < PaymentDetailsUpdate > ,
7596 pub conflicting_txids : Option < Vec < Txid > > ,
7697 pub candidates : Vec < FundingTxCandidate > ,
98+ pub expires_at : Option < Option < u64 > > ,
7799}
78100
79101impl StorableObject for PendingPaymentDetails {
@@ -112,6 +134,13 @@ impl StorableObject for PendingPaymentDetails {
112134 updated = true ;
113135 }
114136
137+ if let Some ( new_expires_at) = update. expires_at {
138+ if self . expires_at != new_expires_at {
139+ self . expires_at = new_expires_at;
140+ updated = true ;
141+ }
142+ }
143+
115144 updated
116145 }
117146
@@ -138,7 +167,146 @@ impl From<&PendingPaymentDetails> for PendingPaymentDetailsUpdate {
138167 payment_update : Some ( value. details . to_update ( ) ) ,
139168 conflicting_txids,
140169 candidates : value. candidates . clone ( ) ,
170+ expires_at : Some ( value. expires_at ) ,
171+ }
172+ }
173+ }
174+
175+ pub ( crate ) struct IndexedPendingPaymentStore < L : Deref >
176+ where
177+ L :: Target : LdkLogger ,
178+ {
179+ inner : DataStore < PendingPaymentDetails , L > ,
180+ mutation_lock : tokio:: sync:: Mutex < ( ) > ,
181+ payment_hash_index : Mutex < HashMap < PaymentHash , Vec < PaymentId > > > ,
182+ }
183+
184+ impl < L : Deref > IndexedPendingPaymentStore < L >
185+ where
186+ L :: Target : LdkLogger ,
187+ {
188+ pub ( crate ) fn new (
189+ pending_payments : Vec < PendingPaymentDetails > , primary_namespace : String ,
190+ secondary_namespace : String , kv_store : Arc < DynStore > , logger : L ,
191+ ) -> Self {
192+ // TODO: Revisit this initialization once pending payments are no longer all kept in
193+ // memory.
194+ let payment_hash_index = Mutex :: new ( Self :: build_payment_hash_index ( & pending_payments) ) ;
195+ let inner = DataStore :: new (
196+ pending_payments,
197+ primary_namespace,
198+ secondary_namespace,
199+ kv_store,
200+ logger,
201+ ) ;
202+ Self { inner, mutation_lock : tokio:: sync:: Mutex :: new ( ( ) ) , payment_hash_index }
203+ }
204+
205+ pub ( crate ) async fn insert_or_update (
206+ & self , pending_payment : PendingPaymentDetails ,
207+ ) -> Result < bool , Error > {
208+ let _guard = self . mutation_lock . lock ( ) . await ;
209+ let id = pending_payment. id ( ) ;
210+ let before = self . inner . get ( & id) ;
211+ let updated = self . inner . insert_or_update ( pending_payment) . await ?;
212+ if updated {
213+ let after = self . inner . get ( & id) ;
214+ self . replace_in_index ( before. as_ref ( ) , after. as_ref ( ) ) ;
215+ }
216+ Ok ( updated)
217+ }
218+
219+ pub ( crate ) async fn remove ( & self , id : & PaymentId ) -> Result < ( ) , Error > {
220+ let _guard = self . mutation_lock . lock ( ) . await ;
221+ let before = self . inner . get ( id) ;
222+ self . inner . remove ( id) . await ?;
223+ if let Some ( payment) = before. as_ref ( ) {
224+ self . remove_from_index ( payment) ;
141225 }
226+ Ok ( ( ) )
227+ }
228+
229+ pub ( crate ) fn get ( & self , id : & PaymentId ) -> Option < PendingPaymentDetails > {
230+ self . inner . get ( id)
231+ }
232+
233+ pub ( crate ) fn contains_key ( & self , id : & PaymentId ) -> bool {
234+ self . inner . contains_key ( id)
235+ }
236+
237+ pub ( crate ) fn list_filter < F : FnMut ( & & PendingPaymentDetails ) -> bool > (
238+ & self , f : F ,
239+ ) -> Vec < PendingPaymentDetails > {
240+ self . inner . list_filter ( f)
241+ }
242+
243+ pub ( crate ) fn list_by_payment_hash (
244+ & self , payment_hash : & PaymentHash ,
245+ ) -> Vec < PendingPaymentDetails > {
246+ let ids = self
247+ . payment_hash_index
248+ . lock ( )
249+ . expect ( "lock" )
250+ . get ( payment_hash)
251+ . cloned ( )
252+ . unwrap_or_default ( ) ;
253+ ids. into_iter ( ) . filter_map ( |id| self . inner . get ( & id) ) . collect ( )
254+ }
255+
256+ fn build_payment_hash_index (
257+ pending_payments : & [ PendingPaymentDetails ] ,
258+ ) -> HashMap < PaymentHash , Vec < PaymentId > > {
259+ let mut index = HashMap :: new ( ) ;
260+ for payment in pending_payments {
261+ Self :: insert_into_hash_index ( & mut index, payment) ;
262+ }
263+ index
264+ }
265+
266+ fn replace_in_index (
267+ & self , before : Option < & PendingPaymentDetails > , after : Option < & PendingPaymentDetails > ,
268+ ) {
269+ let mut index = self . payment_hash_index . lock ( ) . expect ( "lock" ) ;
270+ if let Some ( payment) = before {
271+ Self :: remove_from_hash_index ( & mut index, payment) ;
272+ }
273+ if let Some ( payment) = after {
274+ Self :: insert_into_hash_index ( & mut index, payment) ;
275+ }
276+ }
277+
278+ fn remove_from_index ( & self , payment : & PendingPaymentDetails ) {
279+ let mut index = self . payment_hash_index . lock ( ) . expect ( "lock" ) ;
280+ Self :: remove_from_hash_index ( & mut index, payment) ;
281+ }
282+
283+ fn insert_into_hash_index (
284+ index : & mut HashMap < PaymentHash , Vec < PaymentId > > , payment : & PendingPaymentDetails ,
285+ ) {
286+ if let Some ( payment_hash) = payment_hash ( & payment. details ) {
287+ index. entry ( payment_hash) . or_default ( ) . push ( payment. details . id ) ;
288+ }
289+ }
290+
291+ fn remove_from_hash_index (
292+ index : & mut HashMap < PaymentHash , Vec < PaymentId > > , payment : & PendingPaymentDetails ,
293+ ) {
294+ if let Some ( payment_hash) = payment_hash ( & payment. details ) {
295+ if let Some ( ids) = index. get_mut ( & payment_hash) {
296+ ids. retain ( |id| * id != payment. details . id ) ;
297+ if ids. is_empty ( ) {
298+ index. remove ( & payment_hash) ;
299+ }
300+ }
301+ }
302+ }
303+ }
304+
305+ fn payment_hash ( payment : & PaymentDetails ) -> Option < PaymentHash > {
306+ match payment. kind {
307+ PaymentKind :: Bolt11 { hash, .. } | PaymentKind :: Spontaneous { hash, .. } => Some ( hash) ,
308+ PaymentKind :: Bolt12Offer { hash, .. } | PaymentKind :: Bolt12Refund { hash, .. } => hash,
309+ PaymentKind :: Onchain { .. } => None ,
142310 }
143311}
144312
0 commit comments