Skip to content

Commit 20877bd

Browse files
committed
Add storage for forwarded payments
Routing nodes and LSPs want to track forwarded payments so they can run accounting on fees earned and track profitability across time. We now store these to make it easier to track and allows for future accounting utils in the future. This shouldn't effect edge user nodes as they should never be forwarding payments. Implementation is mostly just copied how we currently handle normal payments and adapted for forwarded payments.
1 parent 7537b62 commit 20877bd

File tree

10 files changed

+314
-32
lines changed

10 files changed

+314
-32
lines changed

bindings/ldk_node.udl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ interface Node {
179179
void remove_payment([ByRef]PaymentId payment_id);
180180
BalanceDetails list_balances();
181181
sequence<PaymentDetails> list_payments();
182+
ForwardedPaymentDetails? forwarded_payment([ByRef]ForwardedPaymentId forwarded_payment_id);
183+
sequence<ForwardedPaymentDetails> list_forwarded_payments();
182184
sequence<PeerDetails> list_peers();
183185
sequence<ChannelDetails> list_channels();
184186
NetworkGraph network_graph();
@@ -497,6 +499,21 @@ dictionary PaymentDetails {
497499
u64 latest_update_timestamp;
498500
};
499501

502+
dictionary ForwardedPaymentDetails {
503+
ForwardedPaymentId id;
504+
ChannelId prev_channel_id;
505+
ChannelId next_channel_id;
506+
UserChannelId? prev_user_channel_id;
507+
UserChannelId? next_user_channel_id;
508+
PublicKey? prev_node_id;
509+
PublicKey? next_node_id;
510+
u64? total_fee_earned_msat;
511+
u64? skimmed_fee_msat;
512+
boolean claim_from_onchain_tx;
513+
u64? outbound_amount_forwarded_msat;
514+
u64 forwarded_at_timestamp;
515+
};
516+
500517
dictionary RouteParametersConfig {
501518
u64? max_total_routing_fee_msat;
502519
u32 max_total_cltv_expiry_delta;
@@ -884,6 +901,9 @@ typedef string OfferId;
884901
[Custom]
885902
typedef string PaymentId;
886903

904+
[Custom]
905+
typedef string ForwardedPaymentId;
906+
887907
[Custom]
888908
typedef string PaymentHash;
889909

src/builder.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@ use crate::fee_estimator::OnchainFeeEstimator;
5454
use crate::gossip::GossipSource;
5555
use crate::io::sqlite_store::SqliteStore;
5656
use crate::io::utils::{
57-
read_event_queue, read_external_pathfinding_scores_from_cache, read_network_graph,
58-
read_node_metrics, read_output_sweeper, read_payments, read_peer_info, read_pending_payments,
59-
read_scorer, write_node_metrics,
57+
read_event_queue, read_external_pathfinding_scores_from_cache, read_forwarded_payments,
58+
read_network_graph, read_node_metrics, read_output_sweeper, read_payments, read_peer_info,
59+
read_pending_payments, read_scorer, write_node_metrics,
6060
};
6161
use crate::io::vss_store::VssStoreBuilder;
6262
use crate::io::{
63-
self, PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
63+
self, FORWARDED_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
64+
FORWARDED_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
65+
PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
6466
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
6567
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
6668
};
@@ -74,9 +76,9 @@ use crate::peer_store::PeerStore;
7476
use crate::runtime::{Runtime, RuntimeSpawner};
7577
use crate::tx_broadcaster::TransactionBroadcaster;
7678
use crate::types::{
77-
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreWrapper, GossipSync, Graph,
78-
KeysManager, MessageRouter, OnionMessenger, PaymentStore, PeerManager, PendingPaymentStore,
79-
Persister, SyncAndAsyncKVStore,
79+
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreWrapper, ForwardedPaymentStore,
80+
GossipSync, Graph, KeysManager, MessageRouter, OnionMessenger, PaymentStore, PeerManager,
81+
PendingPaymentStore, Persister, SyncAndAsyncKVStore,
8082
};
8183
use crate::wallet::persist::KVStoreWalletPersister;
8284
use crate::wallet::Wallet;
@@ -1059,14 +1061,19 @@ fn build_with_store_internal(
10591061

10601062
let kv_store_ref = Arc::clone(&kv_store);
10611063
let logger_ref = Arc::clone(&logger);
1062-
let (payment_store_res, node_metris_res, pending_payment_store_res) =
1063-
runtime.block_on(async move {
1064-
tokio::join!(
1065-
read_payments(&*kv_store_ref, Arc::clone(&logger_ref)),
1066-
read_node_metrics(&*kv_store_ref, Arc::clone(&logger_ref)),
1067-
read_pending_payments(&*kv_store_ref, Arc::clone(&logger_ref))
1068-
)
1069-
});
1064+
let (
1065+
payment_store_res,
1066+
forwarded_payment_store_res,
1067+
node_metris_res,
1068+
pending_payment_store_res,
1069+
) = runtime.block_on(async move {
1070+
tokio::join!(
1071+
read_payments(&*kv_store_ref, Arc::clone(&logger_ref)),
1072+
read_forwarded_payments(&*kv_store_ref, Arc::clone(&logger_ref)),
1073+
read_node_metrics(&*kv_store_ref, Arc::clone(&logger_ref)),
1074+
read_pending_payments(&*kv_store_ref, Arc::clone(&logger_ref))
1075+
)
1076+
});
10701077

10711078
// Initialize the status fields.
10721079
let node_metrics = match node_metris_res {
@@ -1095,6 +1102,20 @@ fn build_with_store_internal(
10951102
},
10961103
};
10971104

1105+
let forwarded_payment_store = match forwarded_payment_store_res {
1106+
Ok(forwarded_payments) => Arc::new(ForwardedPaymentStore::new(
1107+
forwarded_payments,
1108+
FORWARDED_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE.to_string(),
1109+
FORWARDED_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE.to_string(),
1110+
Arc::clone(&kv_store),
1111+
Arc::clone(&logger),
1112+
)),
1113+
Err(e) => {
1114+
log_error!(logger, "Failed to read forwarded payment data from store: {}", e);
1115+
return Err(BuildError::ReadFailed);
1116+
},
1117+
};
1118+
10981119
let (chain_source, chain_tip_opt) = match chain_data_source_config {
10991120
Some(ChainDataSourceConfig::Esplora { server_url, headers, sync_config }) => {
11001121
let sync_config = sync_config.unwrap_or(EsploraSyncConfig::default());
@@ -1781,6 +1802,7 @@ fn build_with_store_internal(
17811802
scorer,
17821803
peer_store,
17831804
payment_store,
1805+
forwarded_payment_store,
17841806
is_running,
17851807
node_metrics,
17861808
om_mailbox,

src/event.rs

Lines changed: 49 additions & 8 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::{SystemTime, UNIX_EPOCH};
1314

1415
use bitcoin::blockdata::locktime::absolute::LockTime;
1516
use bitcoin::secp256k1::PublicKey;
@@ -45,10 +46,13 @@ use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger
4546
use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox;
4647
use crate::payment::asynchronous::static_invoice_store::StaticInvoiceStore;
4748
use crate::payment::store::{
48-
PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus,
49+
ForwardedPaymentDetails, ForwardedPaymentId, PaymentDetails, PaymentDetailsUpdate,
50+
PaymentDirection, PaymentKind, PaymentStatus,
4951
};
5052
use crate::runtime::Runtime;
51-
use crate::types::{CustomTlvRecord, DynStore, OnionMessenger, PaymentStore, Sweeper, Wallet};
53+
use crate::types::{
54+
CustomTlvRecord, DynStore, ForwardedPaymentStore, OnionMessenger, PaymentStore, Sweeper, Wallet,
55+
};
5256
use crate::{
5357
hex_utils, BumpTransactionEventHandler, ChannelManager, Error, Graph, PeerInfo, PeerStore,
5458
UserChannelId,
@@ -487,6 +491,7 @@ where
487491
network_graph: Arc<Graph>,
488492
liquidity_source: Option<Arc<LiquiditySource<Arc<Logger>>>>,
489493
payment_store: Arc<PaymentStore>,
494+
forwarded_payment_store: Arc<ForwardedPaymentStore>,
490495
peer_store: Arc<PeerStore<L>>,
491496
runtime: Arc<Runtime>,
492497
logger: L,
@@ -506,10 +511,10 @@ where
506511
channel_manager: Arc<ChannelManager>, connection_manager: Arc<ConnectionManager<L>>,
507512
output_sweeper: Arc<Sweeper>, network_graph: Arc<Graph>,
508513
liquidity_source: Option<Arc<LiquiditySource<Arc<Logger>>>>,
509-
payment_store: Arc<PaymentStore>, peer_store: Arc<PeerStore<L>>,
510-
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
511-
om_mailbox: Option<Arc<OnionMessageMailbox>>, runtime: Arc<Runtime>, logger: L,
512-
config: Arc<Config>,
514+
payment_store: Arc<PaymentStore>, forwarded_payment_store: Arc<ForwardedPaymentStore>,
515+
peer_store: Arc<PeerStore<L>>, static_invoice_store: Option<StaticInvoiceStore>,
516+
onion_messenger: Arc<OnionMessenger>, om_mailbox: Option<Arc<OnionMessageMailbox>>,
517+
runtime: Arc<Runtime>, logger: L, config: Arc<Config>,
513518
) -> Self {
514519
Self {
515520
event_queue,
@@ -521,6 +526,7 @@ where
521526
network_graph,
522527
liquidity_source,
523528
payment_store,
529+
forwarded_payment_store,
524530
peer_store,
525531
logger,
526532
runtime,
@@ -1364,9 +1370,44 @@ where
13641370
.await;
13651371
}
13661372

1373+
// Store the forwarded payment details
1374+
let prev_channel_id_value = prev_channel_id
1375+
.expect("prev_channel_id expected for events generated by LDK versions greater than 0.0.107.");
1376+
let next_channel_id_value = next_channel_id
1377+
.expect("next_channel_id expected for events generated by LDK versions greater than 0.0.107.");
1378+
1379+
// PaymentForwarded does not have a unique id, so we generate a random one here.
1380+
let mut id_bytes = [0u8; 32];
1381+
rng().fill(&mut id_bytes);
1382+
1383+
let forwarded_at_timestamp = SystemTime::now()
1384+
.duration_since(UNIX_EPOCH)
1385+
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH")
1386+
.as_secs();
1387+
1388+
let forwarded_payment = ForwardedPaymentDetails {
1389+
id: ForwardedPaymentId(id_bytes),
1390+
prev_channel_id: prev_channel_id_value,
1391+
next_channel_id: next_channel_id_value,
1392+
prev_user_channel_id: prev_user_channel_id.map(UserChannelId),
1393+
next_user_channel_id: next_user_channel_id.map(UserChannelId),
1394+
prev_node_id,
1395+
next_node_id,
1396+
total_fee_earned_msat,
1397+
skimmed_fee_msat,
1398+
claim_from_onchain_tx,
1399+
outbound_amount_forwarded_msat,
1400+
forwarded_at_timestamp,
1401+
};
1402+
1403+
self.forwarded_payment_store.insert(forwarded_payment).map_err(|e| {
1404+
log_error!(self.logger, "Failed to store forwarded payment: {e}");
1405+
ReplayEvent()
1406+
})?;
1407+
13671408
let event = Event::PaymentForwarded {
1368-
prev_channel_id: prev_channel_id.expect("prev_channel_id expected for events generated by LDK versions greater than 0.0.107."),
1369-
next_channel_id: next_channel_id.expect("next_channel_id expected for events generated by LDK versions greater than 0.0.107."),
1409+
prev_channel_id: prev_channel_id_value,
1410+
next_channel_id: next_channel_id_value,
13701411
prev_user_channel_id: prev_user_channel_id.map(UserChannelId),
13711412
next_user_channel_id: next_user_channel_id.map(UserChannelId),
13721413
prev_node_id,

src/ffi/types.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, Nod
5454
pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig};
5555
pub use crate::logger::{LogLevel, LogRecord, LogWriter};
5656
pub use crate::payment::store::{
57-
ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus,
57+
ConfirmationStatus, ForwardedPaymentId, LSPFeeLimits, PaymentDirection, PaymentKind,
58+
PaymentStatus,
5859
};
59-
pub use crate::payment::UnifiedPaymentResult;
60+
pub use crate::payment::{ForwardedPaymentDetails, UnifiedPaymentResult};
6061
use crate::{hex_utils, SocketAddress, UniffiCustomTypeConverter, UserChannelId};
6162

6263
impl UniffiCustomTypeConverter for PublicKey {
@@ -722,6 +723,24 @@ impl UniffiCustomTypeConverter for PaymentId {
722723
}
723724
}
724725

726+
impl UniffiCustomTypeConverter for ForwardedPaymentId {
727+
type Builtin = String;
728+
729+
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
730+
if let Some(bytes_vec) = hex_utils::to_vec(&val) {
731+
let bytes_res = bytes_vec.try_into();
732+
if let Ok(bytes) = bytes_res {
733+
return Ok(ForwardedPaymentId(bytes));
734+
}
735+
}
736+
Err(Error::InvalidPaymentId.into())
737+
}
738+
739+
fn from_custom(obj: Self) -> Self::Builtin {
740+
hex_utils::to_string(&obj.0)
741+
}
742+
}
743+
725744
impl UniffiCustomTypeConverter for PaymentHash {
726745
type Builtin = String;
727746

src/io/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ pub(crate) const PEER_INFO_PERSISTENCE_KEY: &str = "peers";
2727
pub(crate) const PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = "payments";
2828
pub(crate) const PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";
2929

30+
/// The forwarded payment information will be persisted under this prefix.
31+
pub(crate) const FORWARDED_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = "forwarded_payments";
32+
pub(crate) const FORWARDED_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";
33+
3034
/// The node metrics will be persisted under this key.
3135
pub(crate) const NODE_METRICS_PRIMARY_NAMESPACE: &str = "";
3236
pub(crate) const NODE_METRICS_SECONDARY_NAMESPACE: &str = "";

src/io/utils.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use crate::io::{
4646
NODE_METRICS_KEY, NODE_METRICS_PRIMARY_NAMESPACE, NODE_METRICS_SECONDARY_NAMESPACE,
4747
};
4848
use crate::logger::{log_error, LdkLogger, Logger};
49-
use crate::payment::PendingPaymentDetails;
49+
use crate::payment::{ForwardedPaymentDetails, PendingPaymentDetails};
5050
use crate::peer_store::PeerStore;
5151
use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper};
5252
use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper};
@@ -304,6 +304,22 @@ where
304304
.await
305305
}
306306

307+
/// Read previously persisted forwarded payments information from the store.
308+
pub(crate) async fn read_forwarded_payments<L: Deref>(
309+
kv_store: &DynStore, logger: L,
310+
) -> Result<Vec<ForwardedPaymentDetails>, std::io::Error>
311+
where
312+
L::Target: LdkLogger,
313+
{
314+
read_objects_from_store(
315+
kv_store,
316+
logger,
317+
FORWARDED_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
318+
FORWARDED_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
319+
)
320+
.await
321+
}
322+
307323
/// Read `OutputSweeper` state from the store.
308324
pub(crate) async fn read_output_sweeper(
309325
broadcaster: Arc<Broadcaster>, fee_estimator: Arc<OnchainFeeEstimator>,

src/lib.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,16 @@ use logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger};
153153
use payment::asynchronous::om_mailbox::OnionMessageMailbox;
154154
use payment::asynchronous::static_invoice_store::StaticInvoiceStore;
155155
use payment::{
156-
Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment,
157-
UnifiedPayment,
156+
Bolt11Payment, Bolt12Payment, ForwardedPaymentDetails, ForwardedPaymentId, OnchainPayment,
157+
PaymentDetails, SpontaneousPayment, UnifiedPayment,
158158
};
159159
use peer_store::{PeerInfo, PeerStore};
160160
use rand::Rng;
161161
use runtime::Runtime;
162162
use types::{
163-
Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph,
164-
HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper,
165-
Wallet,
163+
Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore,
164+
ForwardedPaymentStore, Graph, HRNResolver, KeysManager, OnionMessenger, PaymentStore,
165+
PeerManager, Router, Scorer, Sweeper, Wallet,
166166
};
167167
pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, SyncAndAsyncKVStore, UserChannelId};
168168
pub use {
@@ -222,6 +222,7 @@ pub struct Node {
222222
scorer: Arc<Mutex<Scorer>>,
223223
peer_store: Arc<PeerStore<Arc<Logger>>>,
224224
payment_store: Arc<PaymentStore>,
225+
forwarded_payment_store: Arc<ForwardedPaymentStore>,
225226
is_running: Arc<RwLock<bool>>,
226227
node_metrics: Arc<RwLock<NodeMetrics>>,
227228
om_mailbox: Option<Arc<OnionMessageMailbox>>,
@@ -573,6 +574,7 @@ impl Node {
573574
Arc::clone(&self.network_graph),
574575
self.liquidity_source.clone(),
575576
Arc::clone(&self.payment_store),
577+
Arc::clone(&self.forwarded_payment_store),
576578
Arc::clone(&self.peer_store),
577579
static_invoice_store,
578580
Arc::clone(&self.onion_messenger),
@@ -1692,6 +1694,34 @@ impl Node {
16921694
self.payment_store.list_filter(|_| true)
16931695
}
16941696

1697+
/// Retrieve the details of a specific forwarded payment with the given id.
1698+
///
1699+
/// Returns `Some` if the forwarded payment was known and `None` otherwise.
1700+
pub fn forwarded_payment(
1701+
&self, forwarded_payment_id: &ForwardedPaymentId,
1702+
) -> Option<ForwardedPaymentDetails> {
1703+
self.forwarded_payment_store.get(forwarded_payment_id)
1704+
}
1705+
1706+
/// Retrieves all forwarded payments that match the given predicate.
1707+
///
1708+
/// For example, to list all forwarded payments that earned at least 1000 msat in fees:
1709+
/// ```ignore
1710+
/// node.list_forwarded_payments_with_filter(|p| {
1711+
/// p.total_fee_earned_msat.unwrap_or(0) >= 1000
1712+
/// });
1713+
/// ```
1714+
pub fn list_forwarded_payments_with_filter<F: FnMut(&&ForwardedPaymentDetails) -> bool>(
1715+
&self, f: F,
1716+
) -> Vec<ForwardedPaymentDetails> {
1717+
self.forwarded_payment_store.list_filter(f)
1718+
}
1719+
1720+
/// Retrieves all forwarded payments.
1721+
pub fn list_forwarded_payments(&self) -> Vec<ForwardedPaymentDetails> {
1722+
self.forwarded_payment_store.list_filter(|_| true)
1723+
}
1724+
16951725
/// Retrieves a list of known peers.
16961726
pub fn list_peers(&self) -> Vec<PeerDetails> {
16971727
let mut peers = Vec::new();

src/payment/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub use onchain::OnchainPayment;
2222
pub use pending_payment_store::PendingPaymentDetails;
2323
pub use spontaneous::SpontaneousPayment;
2424
pub use store::{
25-
ConfirmationStatus, LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus,
25+
ConfirmationStatus, ForwardedPaymentDetails, ForwardedPaymentId, LSPFeeLimits, PaymentDetails,
26+
PaymentDirection, PaymentKind, PaymentStatus,
2627
};
2728
pub use unified::{UnifiedPayment, UnifiedPaymentResult};

0 commit comments

Comments
 (0)