Skip to content

Commit fd79933

Browse files
committed
Add pending payment expiry metadata
Pending manual invoice records need persisted expiry times so stale reservations can be pruned before checking for duplicate payment hashes. Pending BOLT11 reservations also need indexed lookup by payment hash so duplicate checks can avoid scanning the full pending store. Co-Authored-By: HAL 9000
1 parent d4a0e9c commit fd79933

3 files changed

Lines changed: 179 additions & 5 deletions

File tree

src/payment/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub use bolt12::Bolt12Payment;
2222
pub use onchain::OnchainPayment;
2323
pub(crate) use pending_payment_store::FundingTxCandidate;
2424
pub(crate) use pending_payment_store::PendingPaymentDetails;
25+
pub(crate) use pending_payment_store::PendingPaymentStore;
2526
pub use spontaneous::SpontaneousPayment;
2627
pub use store::{
2728
Channel, ConfirmationStatus, LSPS2Parameters, PaymentDetails, PaymentDirection, PaymentKind,

src/payment/pending_payment_store.rs

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
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+
812
use bitcoin::Txid;
913
use lightning::impl_writeable_tlv_based;
1014
use 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;
1319
use crate::payment::store::PaymentDetailsUpdate;
14-
use crate::payment::{PaymentDetails, PaymentKind};
20+
use crate::payment::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
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

5262
impl 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

6585
impl_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

79101
impl 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,151 @@ 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 PendingPaymentStore<L: Deref>
176+
where
177+
L::Target: LdkLogger,
178+
{
179+
inner: DataStore<PendingPaymentDetails, L>,
180+
mutation_lock: tokio::sync::Mutex<()>,
181+
manual_bolt11_payment_hash_index: Mutex<HashMap<PaymentHash, Vec<PaymentId>>>,
182+
}
183+
184+
impl<L: Deref> PendingPaymentStore<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 manual_bolt11_payment_hash_index =
195+
Mutex::new(Self::build_manual_bolt11_payment_hash_index(&pending_payments));
196+
let inner = DataStore::new(
197+
pending_payments,
198+
primary_namespace,
199+
secondary_namespace,
200+
kv_store,
201+
logger,
202+
);
203+
Self { inner, mutation_lock: tokio::sync::Mutex::new(()), manual_bolt11_payment_hash_index }
204+
}
205+
206+
pub(crate) async fn insert_or_update(
207+
&self, pending_payment: PendingPaymentDetails,
208+
) -> Result<bool, Error> {
209+
let _guard = self.mutation_lock.lock().await;
210+
let id = pending_payment.id();
211+
let before = self.inner.get(&id);
212+
let updated = self.inner.insert_or_update(pending_payment).await?;
213+
if updated {
214+
let after = self.inner.get(&id);
215+
self.replace_in_index(before.as_ref(), after.as_ref());
216+
}
217+
Ok(updated)
218+
}
219+
220+
pub(crate) async fn remove(&self, id: &PaymentId) -> Result<(), Error> {
221+
let _guard = self.mutation_lock.lock().await;
222+
let before = self.inner.get(id);
223+
self.inner.remove(id).await?;
224+
if let Some(payment) = before.as_ref() {
225+
self.remove_from_index(payment);
141226
}
227+
Ok(())
228+
}
229+
230+
pub(crate) fn get(&self, id: &PaymentId) -> Option<PendingPaymentDetails> {
231+
self.inner.get(id)
232+
}
233+
234+
pub(crate) fn contains_key(&self, id: &PaymentId) -> bool {
235+
self.inner.contains_key(id)
236+
}
237+
238+
pub(crate) fn list_filter<F: FnMut(&&PendingPaymentDetails) -> bool>(
239+
&self, f: F,
240+
) -> Vec<PendingPaymentDetails> {
241+
self.inner.list_filter(f)
242+
}
243+
244+
pub(crate) fn get_pending_manual_bolt11_by_payment_hash(
245+
&self, payment_hash: &PaymentHash,
246+
) -> Option<PendingPaymentDetails> {
247+
let ids = self
248+
.manual_bolt11_payment_hash_index
249+
.lock()
250+
.expect("lock")
251+
.get(payment_hash)
252+
.cloned()
253+
.unwrap_or_default();
254+
ids.into_iter().find_map(|id| self.inner.get(&id))
255+
}
256+
257+
fn build_manual_bolt11_payment_hash_index(
258+
pending_payments: &[PendingPaymentDetails],
259+
) -> HashMap<PaymentHash, Vec<PaymentId>> {
260+
let mut index = HashMap::new();
261+
for payment in pending_payments {
262+
Self::insert_into_manual_bolt11_hash_index(&mut index, payment);
263+
}
264+
index
265+
}
266+
267+
fn replace_in_index(
268+
&self, before: Option<&PendingPaymentDetails>, after: Option<&PendingPaymentDetails>,
269+
) {
270+
let mut index = self.manual_bolt11_payment_hash_index.lock().expect("lock");
271+
if let Some(payment) = before {
272+
Self::remove_from_manual_bolt11_hash_index(&mut index, payment);
273+
}
274+
if let Some(payment) = after {
275+
Self::insert_into_manual_bolt11_hash_index(&mut index, payment);
276+
}
277+
}
278+
279+
fn remove_from_index(&self, payment: &PendingPaymentDetails) {
280+
let mut index = self.manual_bolt11_payment_hash_index.lock().expect("lock");
281+
Self::remove_from_manual_bolt11_hash_index(&mut index, payment);
282+
}
283+
284+
fn insert_into_manual_bolt11_hash_index(
285+
index: &mut HashMap<PaymentHash, Vec<PaymentId>>, payment: &PendingPaymentDetails,
286+
) {
287+
if let Some(payment_hash) = manual_bolt11_payment_hash(&payment.details) {
288+
index.entry(payment_hash).or_default().push(payment.details.id);
289+
}
290+
}
291+
292+
fn remove_from_manual_bolt11_hash_index(
293+
index: &mut HashMap<PaymentHash, Vec<PaymentId>>, payment: &PendingPaymentDetails,
294+
) {
295+
if let Some(payment_hash) = manual_bolt11_payment_hash(&payment.details) {
296+
if let Some(ids) = index.get_mut(&payment_hash) {
297+
ids.retain(|id| *id != payment.details.id);
298+
if ids.is_empty() {
299+
index.remove(&payment_hash);
300+
}
301+
}
302+
}
303+
}
304+
}
305+
306+
fn manual_bolt11_payment_hash(payment: &PaymentDetails) -> Option<PaymentHash> {
307+
match payment.kind {
308+
PaymentKind::Bolt11 { hash, preimage: None, .. }
309+
if payment.direction == PaymentDirection::Inbound
310+
&& payment.status == PaymentStatus::Pending =>
311+
{
312+
Some(hash)
313+
},
314+
_ => None,
142315
}
143316
}
144317

src/types.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use crate::fee_estimator::OnchainFeeEstimator;
4848
use crate::ffi::maybe_wrap;
4949
use crate::logger::Logger;
5050
use crate::message_handler::NodeCustomMessageHandler;
51-
use crate::payment::{PaymentDetails, PendingPaymentDetails};
51+
use crate::payment::{PaymentDetails, PendingPaymentStore as PendingPaymentStoreImpl};
5252
use crate::runtime::RuntimeSpawner;
5353

5454
#[cfg(not(feature = "uniffi"))]
@@ -755,4 +755,4 @@ impl From<&(u64, Vec<u8>)> for CustomTlvRecord {
755755
}
756756
}
757757

758-
pub(crate) type PendingPaymentStore = DataStore<PendingPaymentDetails, Arc<Logger>>;
758+
pub(crate) type PendingPaymentStore = PendingPaymentStoreImpl<Arc<Logger>>;

0 commit comments

Comments
 (0)