Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client-side of static invoice server #3618

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
26 changes: 25 additions & 1 deletion fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use lightning::ln::peer_handler::IgnoringMessageHandler;
use lightning::ln::script::ShutdownScript;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::onion_message::async_payments::{
AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc,
AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ReleaseHeldHtlc,
ServeStaticInvoice, StaticInvoicePersisted,
};
use lightning::onion_message::messenger::{
CustomOnionMessageHandler, Destination, MessageRouter, MessageSendInstructions,
Expand Down Expand Up @@ -121,6 +122,29 @@ impl OffersMessageHandler for TestOffersMessageHandler {
struct TestAsyncPaymentsMessageHandler {}

impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
fn handle_offer_paths_request(
&self, _message: OfferPathsRequest, _context: AsyncPaymentsContext,
responder: Option<Responder>,
) -> Option<(OfferPaths, ResponseInstruction)> {
let responder = match responder {
Some(resp) => resp,
None => return None,
};
Some((OfferPaths { paths: Vec::new() }, responder.respond()))
}
fn handle_offer_paths(
&self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option<Responder>,
) -> Option<(ServeStaticInvoice, ResponseInstruction)> {
None
}
fn handle_serve_static_invoice(
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
) {
}
fn handle_static_invoice_persisted(
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,
) {
}
fn handle_held_htlc_available(
&self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext,
responder: Option<Responder>,
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/async_signer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

let closing_node = if remote_commitment { &nodes[1] } else { &nodes[0] };
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/ln/chanmon_update_fail_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2591,7 +2591,7 @@ fn test_temporary_error_during_shutdown() {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);

let (_, _, channel_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
Expand Down Expand Up @@ -2762,7 +2762,7 @@ fn do_test_outbound_reload_without_init_mon(use_0conf: bool) {
chan_config.manually_accept_inbound_channels = true;
chan_config.channel_handshake_limits.trust_own_funding_0conf = true;

let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config), Some(chan_config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config.clone()), Some(chan_config)]);
let nodes_0_deserialized;

let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
Expand Down Expand Up @@ -2853,7 +2853,7 @@ fn do_test_inbound_reload_without_init_mon(use_0conf: bool, lock_commitment: boo
chan_config.manually_accept_inbound_channels = true;
chan_config.channel_handshake_limits.trust_own_funding_0conf = true;

let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config), Some(chan_config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config.clone()), Some(chan_config)]);
let nodes_1_deserialized;

let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
Expand Down
129 changes: 86 additions & 43 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,14 @@ use crate::offers::offer::{Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::{Refund, RefundBuilder};
use crate::offers::signer;
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
use crate::onion_message::async_payments::{
AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths,
OfferPathsRequest, ReleaseHeldHtlc, ServeStaticInvoice, StaticInvoicePersisted
};
use crate::onion_message::dns_resolution::HumanReadableName;
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
use crate::onion_message::packet::OnionMessageContents;
use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::util::config::{ChannelConfig, ChannelConfigUpdate, ChannelConfigOverrides, UserConfig};
Expand Down Expand Up @@ -1500,6 +1504,22 @@ struct PendingInboundPayment {
min_value_msat: Option<u64>,
}

/// If we are an async recipient, on startup we interactively build an offer and static invoice with
/// an always-online node that will serve static invoices on our behalf. Once the offer is built and
/// the static invoice is confirmed as persisted by the server, use this struct to cache the offer
/// in `ChannelManager`.
struct AsyncReceiveOffer {
offer: Option<Offer>,
/// Used to limit the number of times we request paths for our offer from the static invoice
/// server.
offer_paths_request_attempts: u8,
}

impl_writeable_tlv_based!(AsyncReceiveOffer, {
(0, offer, option),
(2, offer_paths_request_attempts, (static_value, 0)),
});

/// [`SimpleArcChannelManager`] is useful when you need a [`ChannelManager`] with a static lifetime, e.g.
/// when you're using `lightning-net-tokio` (since `tokio::spawn` requires parameters with static
/// lifetimes). Other times you can afford a reference, which is more efficient, in which case
Expand Down Expand Up @@ -1744,7 +1764,7 @@ where
/// let default_config = UserConfig::default();
/// let channel_manager = ChannelManager::new(
/// fee_estimator, chain_monitor, tx_broadcaster, router, message_router, logger,
/// entropy_source, node_signer, signer_provider, default_config, params, current_timestamp,
/// entropy_source, node_signer, signer_provider, default_config.clone(), params, current_timestamp,
/// );
///
/// // Restart from deserialized data
Expand Down Expand Up @@ -2659,6 +2679,7 @@ where
#[cfg(any(test, feature = "_test_utils"))]
pub(crate) pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
pending_async_payments_messages: Mutex<Vec<(AsyncPaymentsMessage, MessageSendInstructions)>>,
async_receive_offer_cache: Mutex<AsyncReceiveOffer>,

/// Tracks the message events that are to be broadcasted when we are connected to some peer.
pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,
Expand Down Expand Up @@ -3613,6 +3634,7 @@ where

pending_offers_messages: Mutex::new(Vec::new()),
pending_async_payments_messages: Mutex::new(Vec::new()),
async_receive_offer_cache: Mutex::new(AsyncReceiveOffer { offer: None, offer_paths_request_attempts: 0 }),
pending_broadcast_messages: Mutex::new(Vec::new()),

last_days_feerates: Mutex::new(VecDeque::new()),
Expand Down Expand Up @@ -4867,19 +4889,10 @@ where
};

let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap();
const HTLC_AVAILABLE_LIMIT: usize = 10;
reply_paths
.iter()
.flat_map(|reply_path| invoice.message_paths().iter().map(move |invoice_path| (invoice_path, reply_path)))
.take(HTLC_AVAILABLE_LIMIT)
.for_each(|(invoice_path, reply_path)| {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::BlindedPath(invoice_path.clone()),
reply_path: reply_path.clone(),
};
let message = AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable {});
pending_async_payments_messages.push((message, instructions));
});
let message = AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable {});
queue_onion_message_with_reply_paths(
message, invoice.message_paths(), reply_paths, &mut pending_async_payments_messages
);

NotifyOption::DoPersist
});
Expand Down Expand Up @@ -10434,18 +10447,10 @@ where
) -> Result<(), Bolt12SemanticError> {
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if !invoice_request.paths().is_empty() {
reply_paths
.iter()
.flat_map(|reply_path| invoice_request.paths().iter().map(move |path| (path, reply_path)))
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
.for_each(|(path, reply_path)| {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::BlindedPath(path.clone()),
reply_path: reply_path.clone(),
};
let message = OffersMessage::InvoiceRequest(invoice_request.clone());
pending_offers_messages.push((message, instructions));
});
let message = OffersMessage::InvoiceRequest(invoice_request.clone());
queue_onion_message_with_reply_paths(
message, invoice_request.paths(), reply_paths, &mut pending_offers_messages
);
} else if let Some(node_id) = invoice_request.issuer_signing_pubkey() {
for reply_path in reply_paths {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
Expand Down Expand Up @@ -10543,18 +10548,10 @@ where
pending_offers_messages.push((message, instructions));
}
} else {
reply_paths
.iter()
.flat_map(|reply_path| refund.paths().iter().map(move |path| (path, reply_path)))
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
.for_each(|(path, reply_path)| {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::BlindedPath(path.clone()),
reply_path: reply_path.clone(),
};
let message = OffersMessage::Invoice(invoice.clone());
pending_offers_messages.push((message, instructions));
});
let message = OffersMessage::Invoice(invoice.clone());
queue_onion_message_with_reply_paths(
message, refund.paths(), reply_paths, &mut pending_offers_messages
);
}

Ok(invoice)
Expand Down Expand Up @@ -12565,6 +12562,27 @@ where
MR::Target: MessageRouter,
L::Target: Logger,
{
fn handle_offer_paths_request(
&self, _message: OfferPathsRequest, _context: AsyncPaymentsContext,
_responder: Option<Responder>,
) -> Option<(OfferPaths, ResponseInstruction)> {
None
}

fn handle_offer_paths(
&self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option<Responder>,
) -> Option<(ServeStaticInvoice, ResponseInstruction)> {
None
}

fn handle_serve_static_invoice(
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
) {}

fn handle_static_invoice_persisted(
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,
) {}

fn handle_held_htlc_available(
&self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext,
_responder: Option<Responder>
Expand Down Expand Up @@ -12689,6 +12707,27 @@ where
}
}

fn queue_onion_message_with_reply_paths<T: OnionMessageContents + Clone>(
message: T, message_paths: &[BlindedMessagePath], reply_paths: Vec<BlindedMessagePath>,
queue: &mut Vec<(T, MessageSendInstructions)>
) {
reply_paths
.iter()
.flat_map(|reply_path|
message_paths
.iter()
.map(move |path| (path.clone(), reply_path))
)
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
.for_each(|(path, reply_path)| {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::BlindedPath(path.clone()),
reply_path: reply_path.clone(),
};
queue.push((message.clone(), instructions));
});
}

/// Fetches the set of [`NodeFeatures`] flags that are provided by or required by
/// [`ChannelManager`].
pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {
Expand Down Expand Up @@ -13405,6 +13444,7 @@ where
(15, self.inbound_payment_id_secret, required),
(17, in_flight_monitor_updates, required),
(19, peer_storage_dir, optional_vec),
(21, *self.async_receive_offer_cache.lock().unwrap(), required),
});

Ok(())
Expand Down Expand Up @@ -13934,6 +13974,7 @@ where
let mut decode_update_add_htlcs: Option<HashMap<u64, Vec<msgs::UpdateAddHTLC>>> = None;
let mut inbound_payment_id_secret = None;
let mut peer_storage_dir: Option<Vec<(PublicKey, Vec<u8>)>> = None;
let mut async_receive_offer_cache = AsyncReceiveOffer { offer: None, offer_paths_request_attempts: 0 };
read_tlv_fields!(reader, {
(1, pending_outbound_payments_no_retry, option),
(2, pending_intercepted_htlcs, option),
Expand All @@ -13951,6 +13992,7 @@ where
(15, inbound_payment_id_secret, option),
(17, in_flight_monitor_updates, required),
(19, peer_storage_dir, optional_vec),
(21, async_receive_offer_cache, (default_value, AsyncReceiveOffer { offer: None, offer_paths_request_attempts: 0 })),
});
let mut decode_update_add_htlcs = decode_update_add_htlcs.unwrap_or_else(|| new_hash_map());
let peer_storage_dir: Vec<(PublicKey, Vec<u8>)> = peer_storage_dir.unwrap_or_else(Vec::new);
Expand Down Expand Up @@ -14646,6 +14688,7 @@ where

pending_offers_messages: Mutex::new(Vec::new()),
pending_async_payments_messages: Mutex::new(Vec::new()),
async_receive_offer_cache: Mutex::new(async_receive_offer_cache),

pending_broadcast_messages: Mutex::new(Vec::new()),

Expand Down Expand Up @@ -15073,8 +15116,8 @@ mod tests {
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);
// Since we do not send peer storage, we manually simulate receiving a dummy

// Since we do not send peer storage, we manually simulate receiving a dummy
// `PeerStorage` from the channel partner.
nodes[0].node.handle_peer_storage(nodes[1].node.get_our_node_id(), msgs::PeerStorage{data: vec![0; 100]});

Expand Down Expand Up @@ -16127,7 +16170,7 @@ mod tests {
let chanmon_cfg = create_chanmon_cfgs(2);
let node_cfg = create_node_cfgs(2, &chanmon_cfg);
let mut user_config = test_default_channel_config();
let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[Some(user_config), Some(user_config)]);
let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[Some(user_config.clone()), Some(user_config.clone())]);
let nodes = create_network(2, &node_cfg, &node_chanmgr);
let _ = create_announced_chan_between_nodes(&nodes, 0, 1);
let channel = &nodes[0].node.list_channels()[0];
Expand Down Expand Up @@ -16214,7 +16257,7 @@ mod tests {
let chanmon_cfg = create_chanmon_cfgs(2);
let node_cfg = create_node_cfgs(2, &chanmon_cfg);
let user_config = test_default_channel_config();
let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[Some(user_config), Some(user_config)]);
let node_chanmgr = create_node_chanmgrs(2, &node_cfg, &[Some(user_config.clone()), Some(user_config)]);
let nodes = create_network(2, &node_cfg, &node_chanmgr);
let error_message = "Channel force-closed";

Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> {
let mut w = test_utils::TestVecWriter(Vec::new());
self.node.write(&mut w).unwrap();
<(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestMessageRouter, &test_utils::TestLogger>)>::read(&mut io::Cursor::new(w.0), ChannelManagerReadArgs {
default_config: *self.node.get_current_default_configuration(),
default_config: self.node.get_current_default_configuration().clone(),
entropy_source: self.keys_manager,
node_signer: self.keys_manager,
signer_provider: self.keys_manager,
Expand Down Expand Up @@ -3971,7 +3971,7 @@ pub fn create_batch_channel_funding<'a, 'b, 'c>(
let temp_chan_id = funding_node.node.create_channel(
other_node.node.get_our_node_id(), *channel_value_satoshis, *push_msat, *user_channel_id,
None,
*override_config,
override_config.clone(),
).unwrap();
let open_channel_msg = get_event_msg!(funding_node, MessageSendEvent::SendOpenChannel, other_node.node.get_our_node_id());
other_node.node.handle_open_channel(funding_node.node.get_our_node_id(), &open_channel_msg);
Expand Down
12 changes: 6 additions & 6 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ fn test_insane_channel_opens() {
cfg.channel_handshake_limits.max_funding_satoshis = TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1;
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(cfg)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(cfg.clone())]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

// Instantiate channel parameters where we push the maximum msats given our
Expand Down Expand Up @@ -2903,7 +2903,7 @@ fn test_multiple_package_conflicts() {
user_cfg.manually_accept_inbound_channels = true;

let node_chanmgrs =
create_node_chanmgrs(3, &node_cfgs, &[Some(user_cfg), Some(user_cfg), Some(user_cfg)]);
create_node_chanmgrs(3, &node_cfgs, &[Some(user_cfg.clone()), Some(user_cfg.clone()), Some(user_cfg)]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);

// Since we're using anchor channels, make sure each node has a UTXO for paying fees.
Expand Down Expand Up @@ -10622,7 +10622,7 @@ fn test_nondust_htlc_excess_fees_are_dust() {
config.channel_handshake_config.our_htlc_minimum_msat = 1;
config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;

let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(config), Some(config), Some(config)]);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(config.clone()), Some(config.clone()), Some(config)]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);

// Leave enough on the funder side to let it pay the mining fees for a commit tx with tons of htlcs
Expand Down Expand Up @@ -11810,7 +11810,7 @@ fn do_test_funding_and_commitment_tx_confirm_same_block(confirm_remote_commitmen
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut min_depth_1_block_cfg = test_default_channel_config();
min_depth_1_block_cfg.channel_handshake_config.minimum_depth = 1;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(min_depth_1_block_cfg), Some(min_depth_1_block_cfg)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(min_depth_1_block_cfg.clone()), Some(min_depth_1_block_cfg)]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);

let funding_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 1_000_000, 0);
Expand Down Expand Up @@ -11899,7 +11899,7 @@ fn test_manual_funding_abandon() {
cfg.channel_handshake_config.minimum_depth = 1;
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg), Some(cfg)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

assert!(nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None, None).is_ok());
Expand Down Expand Up @@ -11941,7 +11941,7 @@ fn test_funding_signed_event() {
cfg.channel_handshake_config.minimum_depth = 1;
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg), Some(cfg)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(cfg.clone()), Some(cfg)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

assert!(nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None, None).is_ok());
Expand Down
Loading