Skip to content

Commit b7598ae

Browse files
committed
Add onion mailbox for async receivers
This introduces an in-memory mailbox to hold onion messages until the receiver comes online. This is required for async payment `held_htlc_available` messages. The mailbox is bounded by a maximum number of peers and a maximum number of messages per peer.
1 parent f3dea63 commit b7598ae

File tree

10 files changed

+298
-48
lines changed

10 files changed

+298
-48
lines changed

bindings/ldk_node.udl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ dictionary Config {
1313
u64 probing_liquidity_limit_multiplier;
1414
AnchorChannelsConfig? anchor_channels_config;
1515
RouteParametersConfig? route_parameters;
16-
boolean async_payment_services_enabled;
1716
};
1817

1918
dictionary AnchorChannelsConfig {
@@ -96,6 +95,8 @@ interface Builder {
9695
[Throws=BuildError]
9796
void set_node_alias(string node_alias);
9897
[Throws=BuildError]
98+
void set_async_payments_role(AsyncPaymentsRole? role);
99+
[Throws=BuildError]
99100
Node build();
100101
[Throws=BuildError]
101102
Node build_with_fs_store();
@@ -356,6 +357,7 @@ enum BuildError {
356357
"WalletSetupFailed",
357358
"LoggerSetupFailed",
358359
"NetworkMismatch",
360+
"AsyncPaymentsConfigMismatch",
359361
};
360362

361363
[Trait]
@@ -720,6 +722,11 @@ enum Currency {
720722
"Signet",
721723
};
722724

725+
enum AsyncPaymentsRole {
726+
"Client",
727+
"Server",
728+
};
729+
723730
dictionary RouteHintHop {
724731
PublicKey src_node_id;
725732
u64 short_channel_id;

src/builder.rs

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
use crate::chain::ChainSource;
99
use crate::config::{
10-
default_user_config, may_announce_channel, AnnounceError, BitcoindRestClientConfig, Config,
11-
ElectrumSyncConfig, EsploraSyncConfig, DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME,
12-
DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN,
10+
default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole,
11+
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig,
12+
DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN,
1313
};
1414

1515
use crate::connection::ConnectionManager;
@@ -27,6 +27,7 @@ use crate::liquidity::{
2727
};
2828
use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger};
2929
use crate::message_handler::NodeCustomMessageHandler;
30+
use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox;
3031
use crate::peer_store::PeerStore;
3132
use crate::runtime::Runtime;
3233
use crate::tx_broadcaster::TransactionBroadcaster;
@@ -191,6 +192,8 @@ pub enum BuildError {
191192
LoggerSetupFailed,
192193
/// The given network does not match the node's previously configured network.
193194
NetworkMismatch,
195+
/// The role of the node in an asynchronous payments context is not compatible with the current configuration.
196+
AsyncPaymentsConfigMismatch,
194197
}
195198

196199
impl fmt::Display for BuildError {
@@ -219,6 +222,12 @@ impl fmt::Display for BuildError {
219222
Self::NetworkMismatch => {
220223
write!(f, "Given network does not match the node's previously configured network.")
221224
},
225+
Self::AsyncPaymentsConfigMismatch => {
226+
write!(
227+
f,
228+
"The async payments role is not compatible with the current configuration."
229+
)
230+
},
222231
}
223232
}
224233
}
@@ -240,6 +249,7 @@ pub struct NodeBuilder {
240249
gossip_source_config: Option<GossipSourceConfig>,
241250
liquidity_source_config: Option<LiquiditySourceConfig>,
242251
log_writer_config: Option<LogWriterConfig>,
252+
async_payments_role: Option<AsyncPaymentsRole>,
243253
runtime_handle: Option<tokio::runtime::Handle>,
244254
}
245255

@@ -266,6 +276,7 @@ impl NodeBuilder {
266276
liquidity_source_config,
267277
log_writer_config,
268278
runtime_handle,
279+
async_payments_role: None,
269280
}
270281
}
271282

@@ -544,6 +555,21 @@ impl NodeBuilder {
544555
Ok(self)
545556
}
546557

558+
/// Sets the role of the node in an asynchronous payments context.
559+
///
560+
/// See <https://github.com/lightning/bolts/pull/1149> for more information about the async payments protocol.
561+
pub fn set_async_payments_role(
562+
&mut self, role: Option<AsyncPaymentsRole>,
563+
) -> Result<&mut Self, BuildError> {
564+
if let Some(AsyncPaymentsRole::Server) = role {
565+
may_announce_channel(&self.config)
566+
.map_err(|_| BuildError::AsyncPaymentsConfigMismatch)?;
567+
}
568+
569+
self.async_payments_role = role;
570+
Ok(self)
571+
}
572+
547573
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
548574
/// previously configured.
549575
pub fn build(&self) -> Result<Node, BuildError> {
@@ -700,6 +726,7 @@ impl NodeBuilder {
700726
self.chain_data_source_config.as_ref(),
701727
self.gossip_source_config.as_ref(),
702728
self.liquidity_source_config.as_ref(),
729+
self.async_payments_role,
703730
seed_bytes,
704731
runtime,
705732
logger,
@@ -732,6 +759,7 @@ impl NodeBuilder {
732759
self.chain_data_source_config.as_ref(),
733760
self.gossip_source_config.as_ref(),
734761
self.liquidity_source_config.as_ref(),
762+
self.async_payments_role,
735763
seed_bytes,
736764
runtime,
737765
logger,
@@ -989,6 +1017,13 @@ impl ArcedNodeBuilder {
9891017
self.inner.write().unwrap().set_node_alias(node_alias).map(|_| ())
9901018
}
9911019

1020+
/// Sets the role of the node in an asynchronous payments context.
1021+
pub fn set_async_payments_role(
1022+
&self, role: Option<AsyncPaymentsRole>,
1023+
) -> Result<(), BuildError> {
1024+
self.inner.write().unwrap().set_async_payments_role(role).map(|_| ())
1025+
}
1026+
9921027
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
9931028
/// previously configured.
9941029
pub fn build(&self) -> Result<Arc<Node>, BuildError> {
@@ -1082,8 +1117,9 @@ impl ArcedNodeBuilder {
10821117
fn build_with_store_internal(
10831118
config: Arc<Config>, chain_data_source_config: Option<&ChainDataSourceConfig>,
10841119
gossip_source_config: Option<&GossipSourceConfig>,
1085-
liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64],
1086-
runtime: Arc<Runtime>, logger: Arc<Logger>, kv_store: Arc<DynStore>,
1120+
liquidity_source_config: Option<&LiquiditySourceConfig>,
1121+
async_payments_role: Option<AsyncPaymentsRole>, seed_bytes: [u8; 64], runtime: Arc<Runtime>,
1122+
logger: Arc<Logger>, kv_store: Arc<DynStore>,
10871123
) -> Result<Node, BuildError> {
10881124
optionally_install_rustls_cryptoprovider();
10891125

@@ -1378,8 +1414,14 @@ fn build_with_store_internal(
13781414
100;
13791415
}
13801416

1381-
if config.async_payment_services_enabled {
1382-
user_config.accept_forwards_to_priv_channels = true;
1417+
if let Some(role) = async_payments_role {
1418+
match role {
1419+
AsyncPaymentsRole::Server => {
1420+
user_config.accept_forwards_to_priv_channels = true;
1421+
user_config.enable_htlc_hold = true;
1422+
},
1423+
AsyncPaymentsRole::Client => user_config.hold_outbound_htlcs_at_next_hop = true,
1424+
}
13831425
}
13841426

13851427
let message_router =
@@ -1452,17 +1494,32 @@ fn build_with_store_internal(
14521494
}
14531495

14541496
// Initialize the PeerManager
1455-
let onion_messenger: Arc<OnionMessenger> = Arc::new(OnionMessenger::new(
1456-
Arc::clone(&keys_manager),
1457-
Arc::clone(&keys_manager),
1458-
Arc::clone(&logger),
1459-
Arc::clone(&channel_manager),
1460-
message_router,
1461-
Arc::clone(&channel_manager),
1462-
Arc::clone(&channel_manager),
1463-
IgnoringMessageHandler {},
1464-
IgnoringMessageHandler {},
1465-
));
1497+
let onion_messenger: Arc<OnionMessenger> =
1498+
if let Some(AsyncPaymentsRole::Server) = async_payments_role {
1499+
Arc::new(OnionMessenger::new_with_offline_peer_interception(
1500+
Arc::clone(&keys_manager),
1501+
Arc::clone(&keys_manager),
1502+
Arc::clone(&logger),
1503+
Arc::clone(&channel_manager),
1504+
message_router,
1505+
Arc::clone(&channel_manager),
1506+
Arc::clone(&channel_manager),
1507+
IgnoringMessageHandler {},
1508+
IgnoringMessageHandler {},
1509+
))
1510+
} else {
1511+
Arc::new(OnionMessenger::new(
1512+
Arc::clone(&keys_manager),
1513+
Arc::clone(&keys_manager),
1514+
Arc::clone(&logger),
1515+
Arc::clone(&channel_manager),
1516+
message_router,
1517+
Arc::clone(&channel_manager),
1518+
Arc::clone(&channel_manager),
1519+
IgnoringMessageHandler {},
1520+
IgnoringMessageHandler {},
1521+
))
1522+
};
14661523
let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes();
14671524

14681525
// Initialize the GossipSource
@@ -1649,6 +1706,12 @@ fn build_with_store_internal(
16491706
},
16501707
};
16511708

1709+
let om_mailbox = if let Some(AsyncPaymentsRole::Server) = async_payments_role {
1710+
Some(Arc::new(OnionMessageMailbox::new()))
1711+
} else {
1712+
None
1713+
};
1714+
16521715
let (stop_sender, _) = tokio::sync::watch::channel(());
16531716
let (background_processor_stop_sender, _) = tokio::sync::watch::channel(());
16541717
let is_running = Arc::new(RwLock::new(false));
@@ -1681,6 +1744,8 @@ fn build_with_store_internal(
16811744
is_running,
16821745
is_listening,
16831746
node_metrics,
1747+
om_mailbox,
1748+
async_payments_role,
16841749
})
16851750
}
16861751

src/config.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,6 @@ pub struct Config {
179179
/// **Note:** If unset, default parameters will be used, and you will be able to override the
180180
/// parameters on a per-payment basis in the corresponding method calls.
181181
pub route_parameters: Option<RouteParametersConfig>,
182-
/// Whether to enable the static invoice service to support async payment reception for clients.
183-
pub async_payment_services_enabled: bool,
184182
}
185183

186184
impl Default for Config {
@@ -195,7 +193,6 @@ impl Default for Config {
195193
anchor_channels_config: Some(AnchorChannelsConfig::default()),
196194
route_parameters: None,
197195
node_alias: None,
198-
async_payment_services_enabled: false,
199196
}
200197
}
201198
}
@@ -537,6 +534,19 @@ impl From<MaxDustHTLCExposure> for LdkMaxDustHTLCExposure {
537534
}
538535
}
539536

537+
#[derive(Debug, Clone, Copy)]
538+
/// The role of the node in an asynchronous payments context.
539+
///
540+
/// See <https://github.com/lightning/bolts/pull/1149> for more information about the async payments protocol.
541+
pub enum AsyncPaymentsRole {
542+
/// Node acts a client in an async payments context. This means that if possible, it will instruct its peers to hold
543+
/// HTLCs for it, so that it can go offline.
544+
Client,
545+
/// Node acts as a server in an async payments context. This means that it will hold async payments HTLCs and onion
546+
/// messages for its peers.
547+
Server,
548+
}
549+
540550
#[cfg(test)]
541551
mod tests {
542552
use std::str::FromStr;

src/event.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
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 crate::types::{CustomTlvRecord, DynStore, PaymentStore, Sweeper, Wallet};
8+
use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox;
9+
use crate::types::{CustomTlvRecord, DynStore, OnionMessenger, PaymentStore, Sweeper, Wallet};
910
use crate::{
1011
hex_utils, BumpTransactionEventHandler, ChannelManager, Error, Graph, PeerInfo, PeerStore,
1112
UserChannelId,
@@ -459,6 +460,8 @@ where
459460
logger: L,
460461
config: Arc<Config>,
461462
static_invoice_store: Option<StaticInvoiceStore>,
463+
onion_messenger: Arc<OnionMessenger>,
464+
om_mailbox: Option<Arc<OnionMessageMailbox>>,
462465
}
463466

464467
impl<L: Deref + Clone + Sync + Send + 'static> EventHandler<L>
@@ -472,7 +475,8 @@ where
472475
output_sweeper: Arc<Sweeper>, network_graph: Arc<Graph>,
473476
liquidity_source: Option<Arc<LiquiditySource<Arc<Logger>>>>,
474477
payment_store: Arc<PaymentStore>, peer_store: Arc<PeerStore<L>>,
475-
static_invoice_store: Option<StaticInvoiceStore>, runtime: Arc<Runtime>, logger: L,
478+
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
479+
om_mailbox: Option<Arc<OnionMessageMailbox>>, runtime: Arc<Runtime>, logger: L,
476480
config: Arc<Config>,
477481
) -> Self {
478482
Self {
@@ -490,6 +494,8 @@ where
490494
runtime,
491495
config,
492496
static_invoice_store,
497+
onion_messenger,
498+
om_mailbox,
493499
}
494500
}
495501

@@ -1491,11 +1497,33 @@ where
14911497

14921498
self.bump_tx_event_handler.handle_event(&bte).await;
14931499
},
1494-
LdkEvent::OnionMessageIntercepted { .. } => {
1495-
debug_assert!(false, "We currently don't support onion message interception, so this event should never be emitted.");
1500+
LdkEvent::OnionMessageIntercepted { peer_node_id, message } => {
1501+
if let Some(om_mailbox) = self.om_mailbox.as_ref() {
1502+
om_mailbox.onion_message_intercepted(peer_node_id, message);
1503+
} else {
1504+
log_trace!(
1505+
self.logger,
1506+
"Onion message intercepted, but no onion message mailbox available"
1507+
);
1508+
}
14961509
},
1497-
LdkEvent::OnionMessagePeerConnected { .. } => {
1498-
debug_assert!(false, "We currently don't support onion message interception, so this event should never be emitted.");
1510+
LdkEvent::OnionMessagePeerConnected { peer_node_id } => {
1511+
if let Some(om_mailbox) = self.om_mailbox.as_ref() {
1512+
let messages = om_mailbox.onion_message_peer_connected(peer_node_id);
1513+
1514+
for message in messages {
1515+
if let Err(e) =
1516+
self.onion_messenger.forward_onion_message(message, &peer_node_id)
1517+
{
1518+
log_trace!(
1519+
self.logger,
1520+
"Failed to forward onion message to peer {}: {:?}",
1521+
peer_node_id,
1522+
e
1523+
);
1524+
}
1525+
}
1526+
}
14991527
},
15001528

15011529
LdkEvent::PersistStaticInvoice {

0 commit comments

Comments
 (0)