echo -e "\n\nTest cfg-flag builds"
RUSTFLAGS="--cfg=taproot" cargo test --verbose --color always -p lightning
+[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
RUSTFLAGS="--cfg=async_signing" cargo test --verbose --color always -p lightning
+[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean
RUSTFLAGS="--cfg=dual_funding" cargo test --verbose --color always -p lightning
use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self};
use crate::utils::test_logger;
-use core::convert::{Infallible, TryFrom};
+use core::convert::TryFrom;
use lightning::blinded_path::BlindedPath;
use lightning::sign::EntropySource;
use lightning::ln::PaymentHash;
let even_pubkey = x_only_pubkey.public_key(Parity::Even);
if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey {
unsigned_invoice
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ .sign(|message: &UnsignedBolt12Invoice|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap()
.write(&mut buffer)
.unwrap();
} else {
unsigned_invoice
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ .sign(|message: &UnsignedBolt12Invoice|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap_err();
}
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
use crate::utils::test_logger;
-use core::convert::{Infallible, TryFrom};
+use core::convert::TryFrom;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::offers::offer::{Amount, Offer, Quantity};
use lightning::offers::parse::Bolt12SemanticError;
if let Ok(invoice_request) = build_response(&offer, pubkey) {
invoice_request
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap()
.write(&mut buffer)
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self};
use crate::utils::test_logger;
-use core::convert::{Infallible, TryFrom};
+use core::convert::TryFrom;
use lightning::blinded_path::BlindedPath;
use lightning::sign::EntropySource;
use lightning::ln::PaymentHash;
if let Ok(invoice) = build_response(&refund, pubkey, &secp_ctx) {
invoice
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ .sign(|message: &UnsignedBolt12Invoice|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap()
.write(&mut buffer)
fn confirm_transaction_depth(node: &mut Node, tx: &Transaction, depth: u32) {
for i in 1..=depth {
- let prev_blockhash = node.best_block.block_hash();
- let height = node.best_block.height() + 1;
+ let prev_blockhash = node.best_block.block_hash;
+ let height = node.best_block.height + 1;
let header = create_dummy_header(prev_blockhash, height);
let txdata = vec![(0, tx)];
node.best_block = BestBlock::new(header.block_hash(), height);
}
fn has_reached_confirmation_threshold(&self, best_block: &BestBlock) -> bool {
- best_block.height() >= self.confirmation_threshold()
+ best_block.height >= self.confirmation_threshold()
}
}
event.write(writer)?;
}
- self.best_block.block_hash().write(writer)?;
- writer.write_all(&self.best_block.height().to_be_bytes())?;
+ self.best_block.block_hash.write(writer)?;
+ writer.write_all(&self.best_block.height.to_be_bytes())?;
writer.write_all(&(self.onchain_events_awaiting_threshold_conf.len() as u64).to_be_bytes())?;
for ref entry in self.onchain_events_awaiting_threshold_conf.iter() {
// before considering it "no longer pending" - this matches when we
// provide the ChannelManager an HTLC failure event.
Some(commitment_tx_output_idx) == htlc.transaction_output_index &&
- us.best_block.height() >= event.height + ANTI_REORG_DELAY - 1
+ us.best_block.height >= event.height + ANTI_REORG_DELAY - 1
} else if let OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, .. } = event.event {
// If the HTLC was fulfilled with a preimage, we consider the HTLC
// immediately non-pending, matching when we provide ChannelManager
macro_rules! claim_htlcs {
($commitment_number: expr, $txid: expr) => {
let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None);
- self.onchain_tx_handler.update_claims_view_from_requests(htlc_claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
+ self.onchain_tx_handler.update_claims_view_from_requests(htlc_claim_reqs, self.best_block.height, self.best_block.height, broadcaster, fee_estimator, logger);
}
}
if let Some(txid) = self.current_counterparty_commitment_txid {
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC
// transactions.
- let (claim_reqs, _) = self.get_broadcasted_holder_claims(&holder_commitment_tx, self.best_block.height());
- self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
+ let (claim_reqs, _) = self.get_broadcasted_holder_claims(&holder_commitment_tx, self.best_block.height);
+ self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height, self.best_block.height, broadcaster, fee_estimator, logger);
}
}
}
let commitment_package = PackageTemplate::build_package(
self.funding_info.0.txid.clone(), self.funding_info.0.index as u32,
PackageSolvingData::HolderFundingOutput(funding_outp),
- self.best_block.height(), self.best_block.height()
+ self.best_block.height, self.best_block.height
);
let mut claimable_outpoints = vec![commitment_package];
self.pending_monitor_events.push(MonitorEvent::HolderForceClosed(self.funding_info.0));
// assuming it gets confirmed in the next block. Sadly, we have code which considers
// "not yet confirmed" things as discardable, so we cannot do that here.
let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(
- &self.current_holder_commitment_tx, self.best_block.height()
+ &self.current_holder_commitment_tx, self.best_block.height
);
let unsigned_commitment_tx = self.onchain_tx_handler.get_unsigned_holder_commitment_tx();
let new_outputs = self.get_broadcasted_holder_watch_outputs(
{
let (claimable_outpoints, _) = self.generate_claimable_outpoints_and_watch_outputs();
self.onchain_tx_handler.update_claims_view_from_requests(
- claimable_outpoints, self.best_block.height(), self.best_block.height(), broadcaster,
+ claimable_outpoints, self.best_block.height, self.best_block.height, broadcaster,
fee_estimator, logger
);
}
{
let block_hash = header.block_hash();
- if height > self.best_block.height() {
+ if height > self.best_block.height {
self.best_block = BestBlock::new(block_hash, height);
log_trace!(logger, "Connecting new block {} at height {}", block_hash, height);
self.block_confirmed(height, block_hash, vec![], vec![], vec![], &broadcaster, &fee_estimator, logger)
- } else if block_hash != self.best_block.block_hash() {
+ } else if block_hash != self.best_block.block_hash {
self.best_block = BestBlock::new(block_hash, height);
log_trace!(logger, "Best block re-orged, replaced with new block {} at height {}", block_hash, height);
self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height <= height);
}
}
- if height > self.best_block.height() {
+ if height > self.best_block.height {
self.best_block = BestBlock::new(block_hash, height);
}
L::Target: Logger,
{
log_trace!(logger, "Processing {} matched transactions for block at height {}.", txn_matched.len(), conf_height);
- debug_assert!(self.best_block.height() >= conf_height);
+ debug_assert!(self.best_block.height >= conf_height);
let should_broadcast = self.should_broadcast_holder_commitment_txn(logger);
if should_broadcast {
}
}
- self.onchain_tx_handler.update_claims_view_from_requests(claimable_outpoints, conf_height, self.best_block.height(), broadcaster, fee_estimator, logger);
- self.onchain_tx_handler.update_claims_view_from_matched_txn(&txn_matched, conf_height, conf_hash, self.best_block.height(), broadcaster, fee_estimator, logger);
+ self.onchain_tx_handler.update_claims_view_from_requests(claimable_outpoints, conf_height, self.best_block.height, broadcaster, fee_estimator, logger);
+ self.onchain_tx_handler.update_claims_view_from_matched_txn(&txn_matched, conf_height, conf_hash, self.best_block.height, broadcaster, fee_estimator, logger);
// Determine new outputs to watch by comparing against previously known outputs to watch,
// updating the latter in the process.
// to the source, and if we don't fail the channel we will have to ensure that the next
// updates that peer sends us are update_fails, failing the channel if not. It's probably
// easier to just fail the channel as this case should be rare enough anyway.
- let height = self.best_block.height();
+ let height = self.best_block.height;
macro_rules! scan_commitment {
($htlcs: expr, $holder_tx: expr) => {
for ref htlc in $htlcs {
chan_utils::get_to_countersignatory_with_anchors_redeemscript(&payment_point).to_v0_p2wsh();
}
- Ok((best_block.block_hash(), ChannelMonitor::from_impl(ChannelMonitorImpl {
+ Ok((best_block.block_hash, ChannelMonitor::from_impl(ChannelMonitorImpl {
latest_update_id,
commitment_transaction_number_obscure_factor,
pub(crate) mod package;
/// The best known block as identified by its hash and height.
-#[derive(Clone, Copy, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct BestBlock {
- block_hash: BlockHash,
- height: u32,
+ /// The block's hash
+ pub block_hash: BlockHash,
+ /// The height at which the block was confirmed.
+ pub height: u32,
}
impl BestBlock {
pub fn new(block_hash: BlockHash, height: u32) -> Self {
BestBlock { block_hash, height }
}
-
- /// Returns the best block hash.
- pub fn block_hash(&self) -> BlockHash { self.block_hash }
-
- /// Returns the best block height.
- pub fn height(&self) -> u32 { self.height }
}
+#[cfg(not(fuzzing))]
use bitcoin::hashes::cmp::fixed_time_eq;
pub(crate) mod chacha20;
/// [`PaymentParameters::expiry_time`]: crate::routing::router::PaymentParameters::expiry_time
PaymentExpired,
/// We failed to find a route while retrying the payment.
+ ///
+ /// Note that this generally indicates that we've exhausted the available set of possible
+ /// routes - we tried the payment over a few routes but were not able to find any further
+ /// candidate routes beyond those.
RouteNotFound,
/// This error should generally never happen. This likely means that there is a problem with
/// your router.
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::onion_utils;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
-use crate::ln::outbound_payment::Retry;
+use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
use crate::offers::invoice::BlindedPayInfo;
use crate::prelude::*;
use crate::routing::router::{Payee, PaymentParameters, RouteParameters};
use crate::util::test_utils;
fn blinded_payment_path(
- payment_secret: PaymentSecret, node_ids: Vec<PublicKey>,
- channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface
+ payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
+ node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
+ keys_manager: &test_utils::TestKeysInterface
) -> (BlindedPayInfo, BlindedPath) {
let mut intermediate_nodes = Vec::new();
- for (node_id, chan_upd) in node_ids.iter().zip(channel_upds) {
+ let mut intro_node_min_htlc_opt = Some(intro_node_min_htlc);
+ let mut intro_node_max_htlc_opt = Some(intro_node_max_htlc);
+ for (idx, (node_id, chan_upd)) in node_ids.iter().zip(channel_upds).enumerate() {
intermediate_nodes.push(ForwardNode {
node_id: *node_id,
tlvs: ForwardTlvs {
},
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
- htlc_minimum_msat: chan_upd.htlc_minimum_msat,
+ htlc_minimum_msat: intro_node_min_htlc_opt.take()
+ .unwrap_or_else(|| channel_upds[idx - 1].htlc_minimum_msat),
},
features: BlindedHopFeatures::empty(),
},
- htlc_maximum_msat: chan_upd.htlc_maximum_msat,
+ htlc_maximum_msat: intro_node_max_htlc_opt.take()
+ .unwrap_or_else(|| channel_upds[idx - 1].htlc_maximum_msat),
});
}
let payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
- htlc_minimum_msat: channel_upds.last().unwrap().htlc_minimum_msat,
+ htlc_minimum_msat:
+ intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat),
},
};
let mut secp_ctx = Secp256k1::new();
BlindedPath::new_for_payment(
&intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs,
- channel_upds.last().unwrap().htlc_maximum_msat, TEST_FINAL_CLTV as u16, keys_manager, &secp_ctx
+ intro_node_max_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_maximum_msat),
+ TEST_FINAL_CLTV as u16, keys_manager, &secp_ctx
).unwrap()
}
pub fn get_blinded_route_parameters(
- amt_msat: u64, payment_secret: PaymentSecret, node_ids: Vec<PublicKey>,
- channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface
+ amt_msat: u64, payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
+ node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
+ keys_manager: &test_utils::TestKeysInterface
) -> RouteParameters {
RouteParameters::from_payment_params_and_value(
PaymentParameters::blinded(vec![
- blinded_payment_path(payment_secret, node_ids, channel_upds, keys_manager)
+ blinded_payment_path(
+ payment_secret, intro_node_min_htlc, intro_node_max_htlc, node_ids, channel_upds,
+ keys_manager
+ )
]), amt_msat
)
}
claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage);
}
+#[test]
+fn mpp_to_three_hop_blinded_paths() {
+ let chanmon_cfgs = create_chanmon_cfgs(6);
+ let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]);
+ let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
+
+ // Create this network topology so node 0 MPP's over 2 3-hop blinded paths:
+ // n1 -- n3
+ // / \
+ // n0 n5
+ // \ /
+ // n2 -- n4
+ create_announced_chan_between_nodes(&nodes, 0, 1);
+ create_announced_chan_between_nodes(&nodes, 0, 2);
+ let chan_upd_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3).0.contents;
+ let chan_upd_2_4 = create_announced_chan_between_nodes(&nodes, 2, 4).0.contents;
+ let chan_upd_3_5 = create_announced_chan_between_nodes(&nodes, 3, 5).0.contents;
+ let chan_upd_4_5 = create_announced_chan_between_nodes(&nodes, 4, 5).0.contents;
+
+ let amt_msat = 15_000_000;
+ let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[5], Some(amt_msat), None);
+ let route_params = {
+ let path_1_params = get_blinded_route_parameters(
+ amt_msat, payment_secret, 1, 1_0000_0000, vec![
+ nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id(),
+ nodes[5].node.get_our_node_id()
+ ], &[&chan_upd_1_3, &chan_upd_3_5], &chanmon_cfgs[5].keys_manager
+ );
+ let path_2_params = get_blinded_route_parameters(
+ amt_msat, payment_secret, 1, 1_0000_0000, vec![
+ nodes[2].node.get_our_node_id(), nodes[4].node.get_our_node_id(),
+ nodes[5].node.get_our_node_id()
+ ], &[&chan_upd_2_4, &chan_upd_4_5], &chanmon_cfgs[5].keys_manager
+ );
+ let pay_params = PaymentParameters::blinded(
+ vec![
+ path_1_params.payment_params.payee.blinded_route_hints()[0].clone(),
+ path_2_params.payment_params.payee.blinded_route_hints()[0].clone()
+ ]
+ )
+ .with_bolt12_features(channelmanager::provided_bolt12_invoice_features(&UserConfig::default()))
+ .unwrap();
+ RouteParameters::from_payment_params_and_value(pay_params, amt_msat)
+ };
+
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(),
+ PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+ check_added_monitors(&nodes[0], 2);
+
+ let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3], &nodes[5]], &[&nodes[2], &nodes[4], &nodes[5]]];
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 2);
+
+ let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
+ pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash.clone(),
+ Some(payment_secret), ev.clone(), false, None);
+
+ let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
+ pass_along_path(&nodes[0], expected_route[1], amt_msat, payment_hash.clone(),
+ Some(payment_secret), ev.clone(), true, None);
+ claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage);
+}
+
enum ForwardCheckFail {
// Fail a check on the inbound onion payload. In this case, we underflow when calculating the
// outgoing cltv_expiry.
#[test]
fn forward_checks_failure() {
- do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck);
- do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive);
- do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck);
+ do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck, true);
+ do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck, false);
+ do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive, true);
+ do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive, false);
+ do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck, true);
+ do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck, false);
}
-fn do_forward_checks_failure(check: ForwardCheckFail) {
+fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) {
// Ensure we'll fail backwards properly if a forwarding check fails on initial update_add
// receipt.
- let chanmon_cfgs = create_chanmon_cfgs(3);
- let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
- let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
+ let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
// We need the session priv to construct a bogus onion packet later.
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([3; 32]);
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
+ let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents;
let amt_msat = 5000;
- let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
- let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
- nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
- &chanmon_cfgs[2].keys_manager);
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
+ let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
+ nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(),
+ &[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager);
let route = get_route(&nodes[0], &route_params).unwrap();
node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
check_added_monitors(&nodes[0], 1);
- let mut events = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(events.len(), 1);
- let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
- let mut payment_event = SendEvent::from_event(ev);
+ macro_rules! cause_error {
+ ($src_node_idx: expr, $target_node_idx: expr, $update_add: expr) => {
+ match check {
+ ForwardCheckFail::InboundOnionCheck => {
+ $update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow
+ },
+ ForwardCheckFail::ForwardPayloadEncodedAsReceive => {
+ let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
+ let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let cur_height = nodes[0].best_block_info().1;
+ let (mut onion_payloads, ..) = onion_utils::build_onion_payloads(
+ &route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
+ // Remove the receive payload so the blinded forward payload is encoded as a final payload
+ // (i.e. next_hop_hmac == [0; 32])
+ onion_payloads.pop();
+ if $target_node_idx + 1 < nodes.len() {
+ onion_payloads.pop();
+ }
+ $update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
+ },
+ ForwardCheckFail::OutboundChannelCheck => {
+ // The intro node will see that the next-hop peer is disconnected and fail the HTLC backwards.
+ nodes[$src_node_idx].node.peer_disconnected(&nodes[$target_node_idx].node.get_our_node_id());
+ }
+ }
+ }
+ }
- let mut update_add = &mut payment_event.msgs[0];
- match check {
- ForwardCheckFail::InboundOnionCheck => {
- update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow
- },
- ForwardCheckFail::ForwardPayloadEncodedAsReceive => {
- let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
- let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
- let cur_height = nodes[0].best_block_info().1;
- let (mut onion_payloads, ..) = onion_utils::build_onion_payloads(
- &route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
- // Remove the receive payload so the blinded forward payload is encoded as a final payload
- // (i.e. next_hop_hmac == [0; 32])
- onion_payloads.pop();
- update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap();
- },
- ForwardCheckFail::OutboundChannelCheck => {
- // The intro node will see that the next-hop peer is disconnected and fail the HTLC backwards.
- nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id());
- },
+ let mut updates_0_1 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let update_add = &mut updates_0_1.update_add_htlcs[0];
+
+ if intro_fails {
+ cause_error!(1, 2, update_add);
}
- nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
check_added_monitors!(nodes[1], 0);
- do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, true, true);
+ do_commitment_signed_dance(&nodes[1], &nodes[0], &updates_0_1.commitment_signed, true, true);
+
+ if intro_fails {
+ let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+ do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
+ expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+ PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+ return
+ }
+
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ check_added_monitors!(nodes[1], 1);
+
+ let mut updates_1_2 = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+ let mut update_add = &mut updates_1_2.update_add_htlcs[0];
+
+ cause_error!(2, 3, update_add);
+
+ nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add);
+ check_added_monitors!(nodes[2], 0);
+ do_commitment_signed_dance(&nodes[2], &nodes[1], &updates_1_2.commitment_signed, true, true);
+
+ let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+ let update_malformed = &mut updates.update_fail_malformed_htlcs[0];
+ assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+ assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+
+ // Ensure the intro node will properly blind the error if its downstream node failed to do so.
+ update_malformed.sha256_of_onion = [1; 32];
+ update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1;
+ nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed);
+ do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false);
let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
let amt_msat = 5000;
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
- let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+ let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
&chanmon_cfgs[2].keys_manager);
#[test]
fn forward_fail_in_process_pending_htlc_fwds() {
- do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected);
- do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed);
+ do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected, true);
+ do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected, false);
+ do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed, true);
+ do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed, false);
}
-fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck) {
+fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, intro_fails: bool) {
// Ensure the intro node will error backwards properly if the HTLC fails in
// process_pending_htlc_forwards.
- let chanmon_cfgs = create_chanmon_cfgs(3);
- let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
- let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
+ let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
- let (chan_upd_1_2, channel_id) = {
+ let (chan_upd_1_2, chan_id_1_2) = {
let chan = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
(chan.0.contents, chan.2)
};
+ let (chan_upd_2_3, chan_id_2_3) = {
+ let chan = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
+ (chan.0.contents, chan.2)
+ };
let amt_msat = 5000;
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
- let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
- nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
+ let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
+ nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2, &chan_upd_2_3],
&chanmon_cfgs[2].keys_manager);
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
check_added_monitors!(nodes[1], 0);
do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, false, false);
- match check {
- ProcessPendingHTLCsCheck::FwdPeerDisconnected => {
- // Disconnect the next-hop peer so when we go to forward in process_pending_htlc_forwards, the
- // intro node will error backwards.
- nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id());
- expect_pending_htlcs_forwardable!(nodes[1]);
- expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1],
- vec![HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id }]);
- },
- ProcessPendingHTLCsCheck::FwdChannelClosed => {
- // Force close the next-hop channel so when we go to forward in process_pending_htlc_forwards,
- // the intro node will error backwards.
- nodes[1].node.force_close_broadcasting_latest_txn(&channel_id, &nodes[2].node.get_our_node_id()).unwrap();
- let events = nodes[1].node.get_and_clear_pending_events();
- match events[0] {
- crate::events::Event::PendingHTLCsForwardable { .. } => {},
- _ => panic!("Unexpected event {:?}", events),
- };
- match events[1] {
- crate::events::Event::ChannelClosed { .. } => {},
- _ => panic!("Unexpected event {:?}", events),
+ macro_rules! cause_error {
+ ($prev_node: expr, $curr_node: expr, $next_node: expr, $failed_chan_id: expr, $failed_scid: expr) => {
+ match check {
+ ProcessPendingHTLCsCheck::FwdPeerDisconnected => {
+ // Disconnect the next-hop peer so when we go to forward in process_pending_htlc_forwards, the
+ // intro node will error backwards.
+ $curr_node.node.peer_disconnected(&$next_node.node.get_our_node_id());
+ expect_pending_htlcs_forwardable!($curr_node);
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node,
+ vec![HTLCDestination::NextHopChannel { node_id: Some($next_node.node.get_our_node_id()), channel_id: $failed_chan_id }]);
+ },
+ ProcessPendingHTLCsCheck::FwdChannelClosed => {
+ // Force close the next-hop channel so when we go to forward in process_pending_htlc_forwards,
+ // the intro node will error backwards.
+ $curr_node.node.force_close_broadcasting_latest_txn(&$failed_chan_id, &$next_node.node.get_our_node_id()).unwrap();
+ let events = $curr_node.node.get_and_clear_pending_events();
+ match events[0] {
+ crate::events::Event::PendingHTLCsForwardable { .. } => {},
+ _ => panic!("Unexpected event {:?}", events),
+ };
+ match events[1] {
+ crate::events::Event::ChannelClosed { .. } => {},
+ _ => panic!("Unexpected event {:?}", events),
+ }
+
+ $curr_node.node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node,
+ vec![HTLCDestination::UnknownNextHop { requested_forward_scid: $failed_scid }]);
+ check_closed_broadcast(&$curr_node, 1, true);
+ check_added_monitors!($curr_node, 1);
+ $curr_node.node.process_pending_htlc_forwards();
+ },
}
+ }
+ }
- nodes[1].node.process_pending_htlc_forwards();
- expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1],
- vec![HTLCDestination::UnknownNextHop { requested_forward_scid: chan_upd_1_2.short_channel_id }]);
- check_closed_broadcast(&nodes[1], 1, true);
- check_added_monitors!(nodes[1], 1);
- nodes[1].node.process_pending_htlc_forwards();
- },
+ if intro_fails {
+ cause_error!(nodes[0], nodes[1], nodes[2], chan_id_1_2, chan_upd_1_2.short_channel_id);
+ let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+ check_added_monitors!(nodes[1], 1);
+ do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
+
+ expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+ PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+ return
}
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ check_added_monitors!(nodes[1], 1);
+
+ let mut updates_1_2 = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id());
+ let mut update_add = &mut updates_1_2.update_add_htlcs[0];
+ nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add);
+ check_added_monitors!(nodes[2], 0);
+ do_commitment_signed_dance(&nodes[2], &nodes[1], &updates_1_2.commitment_signed, true, true);
+
+ cause_error!(nodes[1], nodes[2], nodes[3], chan_id_2_3, chan_upd_2_3.short_channel_id);
+ check_added_monitors!(nodes[2], 1);
+
+ let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+ let update_malformed = &mut updates.update_fail_malformed_htlcs[0];
+ assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+ assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+
+ // Ensure the intro node will properly blind the error if its downstream node failed to do so.
+ update_malformed.sha256_of_onion = [1; 32];
+ update_malformed.failure_code = INVALID_ONION_BLINDING ^ 1;
+ nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed);
+ do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false);
+
let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
- check_added_monitors!(nodes[1], 1);
do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
-
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
}
let intercept_scid = nodes[1].node.get_intercept_scid();
let mut intercept_chan_upd = chan_upd;
intercept_chan_upd.short_channel_id = intercept_scid;
- let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+ let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&intercept_chan_upd],
&chanmon_cfgs[2].keys_manager);
let amt_msat = 5000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
- let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+ let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
&chanmon_cfgs[2].keys_manager);
let amt_msat = 5000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None);
- let route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+ let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(2).map(|n| n.node.get_our_node_id()).collect(),
&[&chan_upd_2_3, &chan_upd_3_4], &chanmon_cfgs[4].keys_manager);
claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]], payment_preimage);
}
+#[test]
+fn three_hop_blinded_path_fail() {
+ // Test that an intermediate blinded forwarding node gets failed back to with
+ // malformed and also fails back themselves with malformed.
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
+ let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
+ let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents;
+
+ let amt_msat = 5000;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
+ let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
+ nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(),
+ &[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager);
+
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+ check_added_monitors(&nodes[0], 1);
+ pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3]]], amt_msat, payment_hash, payment_secret);
+
+ nodes[3].node.fail_htlc_backwards(&payment_hash);
+ expect_pending_htlcs_forwardable_conditions(
+ nodes[3].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]
+ );
+ nodes[3].node.process_pending_htlc_forwards();
+ check_added_monitors!(nodes[3], 1);
+
+ let updates_3_2 = get_htlc_update_msgs!(nodes[3], nodes[2].node.get_our_node_id());
+ assert_eq!(updates_3_2.update_fail_malformed_htlcs.len(), 1);
+ let update_malformed = &updates_3_2.update_fail_malformed_htlcs[0];
+ assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+ assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+ nodes[2].node.handle_update_fail_malformed_htlc(&nodes[3].node.get_our_node_id(), update_malformed);
+ do_commitment_signed_dance(&nodes[2], &nodes[3], &updates_3_2.commitment_signed, true, false);
+
+ let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
+ assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1);
+ let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0];
+ assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
+ assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
+ nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed);
+ do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false);
+
+ let updates_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ assert_eq!(updates_1_0.update_fail_htlcs.len(), 1);
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]);
+ do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false);
+ expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+ PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+}
+
#[derive(PartialEq)]
enum ReceiveCheckFail {
// The recipient fails the payment upon `PaymentClaimable`.
Some(TEST_FINAL_CLTV as u16 - 2)
} else { None };
let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), excess_final_cltv_delta_opt);
- let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+ let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
&chanmon_cfgs[2].keys_manager);
let high_htlc_min_bp = {
let mut high_htlc_minimum_upd = chan_upd_1_2.clone();
high_htlc_minimum_upd.htlc_minimum_msat = amt_msat + 1000;
- let high_htlc_min_params = get_blinded_route_parameters(amt_msat, payment_secret,
+ let high_htlc_min_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&high_htlc_minimum_upd],
&chanmon_cfgs[2].keys_manager);
if let Payee::Blinded { route_hints, .. } = high_htlc_min_params.payment_params.payee {
let route_params = {
let pay_params = PaymentParameters::blinded(
vec![
- blinded_payment_path(payment_secret,
+ blinded_payment_path(payment_secret, 1, 1_0000_0000,
vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_1_3.0.contents],
&chanmon_cfgs[3].keys_manager
),
- blinded_payment_path(payment_secret,
+ blinded_payment_path(payment_secret, 1, 1_0000_0000,
vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_2_3.0.contents],
&chanmon_cfgs[3].keys_manager
),
_ => panic!()
}
}
+
+#[test]
+fn min_htlc() {
+ // The min htlc of a blinded path is the max (htlc_min - following_fees) along the path. Make sure
+ // the payment succeeds when we calculate the min htlc this way.
+ let chanmon_cfgs = create_chanmon_cfgs(4);
+ let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+ let mut node_1_cfg = test_default_channel_config();
+ node_1_cfg.channel_handshake_config.our_htlc_minimum_msat = 2000;
+ node_1_cfg.channel_config.forwarding_fee_base_msat = 1000;
+ node_1_cfg.channel_config.forwarding_fee_proportional_millionths = 100_000;
+ let mut node_2_cfg = test_default_channel_config();
+ node_2_cfg.channel_handshake_config.our_htlc_minimum_msat = 5000;
+ node_2_cfg.channel_config.forwarding_fee_base_msat = 200;
+ node_2_cfg.channel_config.forwarding_fee_proportional_millionths = 150_000;
+ let mut node_3_cfg = test_default_channel_config();
+ node_3_cfg.channel_handshake_config.our_htlc_minimum_msat = 2000;
+ let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, Some(node_1_cfg), Some(node_2_cfg), Some(node_3_cfg)]);
+ let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ let chan_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
+ let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
+
+ let min_htlc_msat = {
+ // The min htlc for this setup is nodes[2]'s htlc_minimum_msat minus the
+ // following fees.
+ let post_base_fee = chan_2_3.1.contents.htlc_minimum_msat - chan_2_3.0.contents.fee_base_msat as u64;
+ let prop_fee = chan_2_3.0.contents.fee_proportional_millionths as u64;
+ (post_base_fee * 1_000_000 + 1_000_000 + prop_fee - 1) / (prop_fee + 1_000_000)
+ };
+ let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(min_htlc_msat), None);
+ let mut route_params = get_blinded_route_parameters(
+ min_htlc_msat, payment_secret, chan_1_2.1.contents.htlc_minimum_msat,
+ chan_1_2.1.contents.htlc_maximum_msat, vec![nodes[1].node.get_our_node_id(),
+ nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()],
+ &[&chan_1_2.0.contents, &chan_2_3.0.contents], &chanmon_cfgs[3].keys_manager);
+ assert_eq!(min_htlc_msat,
+ route_params.payment_params.payee.blinded_route_hints()[0].0.htlc_minimum_msat);
+
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap();
+ check_added_monitors(&nodes[0], 1);
+ pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3]]], min_htlc_msat, payment_hash, payment_secret);
+ claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3]], payment_preimage);
+
+ // Paying 1 less than the min fails.
+ for _ in 0..IDEMPOTENCY_TIMEOUT_TICKS + 1 {
+ nodes[0].node.timer_tick_occurred();
+ }
+ if let Payee::Blinded { ref mut route_hints, .. } = route_params.payment_params.payee {
+ route_hints[0].0.htlc_minimum_msat -= 1;
+ } else { panic!() }
+ route_params.final_value_msat -= 1;
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+ check_added_monitors(&nodes[0], 1);
+
+ let mut payment_event_0_1 = {
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
+ SendEvent::from_event(ev)
+ };
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event_0_1.msgs[0]);
+ check_added_monitors!(nodes[1], 0);
+ do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event_0_1.commitment_msg, true, true);
+ let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
+ do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
+ expect_payment_failed_conditions(&nodes[0], payment_hash, false,
+ PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
+}
+
+#[test]
+fn conditionally_round_fwd_amt() {
+ // Previously, the (rng-found) feerates below caught a bug where an intermediate node would
+ // calculate an amt_to_forward that underpaid them by 1 msat, caused by rounding up the outbound
+ // amount on top of an already rounded-up total routing fee. Ensure that we'll conditionally round
+ // down intermediate nodes' outbound amounts based on whether rounding up will result in
+ // undercharging for relay.
+ let chanmon_cfgs = create_chanmon_cfgs(5);
+ let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
+
+ let mut node_1_cfg = test_default_channel_config();
+ node_1_cfg.channel_config.forwarding_fee_base_msat = 247371;
+ node_1_cfg.channel_config.forwarding_fee_proportional_millionths = 86552;
+
+ let mut node_2_cfg = test_default_channel_config();
+ node_2_cfg.channel_config.forwarding_fee_base_msat = 198921;
+ node_2_cfg.channel_config.forwarding_fee_proportional_millionths = 681759;
+
+ let mut node_3_cfg = test_default_channel_config();
+ node_3_cfg.channel_config.forwarding_fee_base_msat = 132845;
+ node_3_cfg.channel_config.forwarding_fee_proportional_millionths = 552561;
+
+ let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, Some(node_1_cfg), Some(node_2_cfg), Some(node_3_cfg), None]);
+ let nodes = create_network(5, &node_cfgs, &node_chanmgrs);
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ let chan_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
+ let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
+ let chan_3_4 = create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0);
+
+ let amt_msat = 100_000;
+ let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[4], Some(amt_msat), None);
+ let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret,
+ chan_1_2.1.contents.htlc_minimum_msat, chan_1_2.1.contents.htlc_maximum_msat,
+ vec![nodes[1].node.get_our_node_id(), nodes[2].node.get_our_node_id(),
+ nodes[3].node.get_our_node_id(), nodes[4].node.get_our_node_id()],
+ &[&chan_1_2.0.contents, &chan_2_3.0.contents, &chan_3_4.0.contents],
+ &chanmon_cfgs[4].keys_manager);
+ route_params.max_total_routing_fee_msat = None;
+
+ nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+ check_added_monitors(&nodes[0], 1);
+ pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3], &nodes[4]]], amt_msat, payment_hash, payment_secret);
+ nodes[4].node.claim_funds(payment_preimage);
+ let expected_path = &[&nodes[1], &nodes[2], &nodes[3], &nodes[4]];
+ let expected_route = &[&expected_path[..]];
+ let mut args = ClaimAlongRouteArgs::new(&nodes[0], &expected_route[..], payment_preimage)
+ .allow_1_msat_fee_overpay();
+ let expected_fee = pass_claimed_payment_along_route(args);
+ expect_payment_sent(&nodes[0], payment_preimage, Some(Some(expected_fee)), true, true);
+}
let mut events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), if close_during_reload { 2 } else { 1 });
expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000),
- None, close_during_reload, false);
+ None, close_during_reload, false, false);
if close_during_reload {
match events[0] {
Event::ChannelClosed { .. } => {},
log_info!(logger, "Received channel_ready from peer for channel {}", &self.context.channel_id());
- Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height(), logger))
+ Ok(self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height, logger))
}
pub fn update_add_htlc<F, FE: Deref, L: Deref>(
let shutdown_msg = self.get_outbound_shutdown();
- let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height(), logger);
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, best_block.height, logger);
if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(_)) {
// If we're waiting on a monitor update, we shouldn't re-send any channel_ready's.
use crate::ln::outbound_payment;
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
use crate::ln::wire::Encode;
-use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder};
+use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
use crate::offers::invoice_error::InvoiceError;
+use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder};
use crate::offers::merkle::SignError;
-use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
+use crate::offers::offer::{Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::{Refund, RefundBuilder};
use crate::onion_message::messenger::{Destination, MessageRouter, PendingOnionMessage, new_pending_onion_message};
use crate::util::errors::APIError;
#[cfg(not(c_bindings))]
use {
+ crate::offers::offer::DerivedMetadata,
crate::routing::router::DefaultRouter,
crate::routing::gossip::NetworkGraph,
crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters},
crate::sign::KeysManager,
};
+#[cfg(c_bindings)]
+use {
+ crate::offers::offer::OfferWithDerivedMetadataBuilder,
+ crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder,
+};
use alloc::collections::{btree_map, BTreeMap};
let logger = WithChannelContext::from(&$self.logger, &$chan.context);
let mut updates = $chan.monitor_updating_restored(&&logger,
&$self.node_signer, $self.chain_hash, &$self.default_configuration,
- $self.best_block.read().unwrap().height());
+ $self.best_block.read().unwrap().height);
let counterparty_node_id = $chan.context.get_counterparty_node_id();
let channel_update = if updates.channel_ready.is_some() && $chan.context.is_usable() {
// We only send a channel_update in the case where we are just now sending a
}
fn create_and_insert_outbound_scid_alias(&self) -> u64 {
- let height = self.best_block.read().unwrap().height();
+ let height = self.best_block.read().unwrap().height;
let mut outbound_scid_alias = 0;
let mut i = 0;
loop {
let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration };
match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key,
their_features, channel_value_satoshis, push_msat, user_channel_id, config,
- self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id)
+ self.best_block.read().unwrap().height, outbound_scid_alias, temporary_channel_id)
{
Ok(res) => res,
Err(e) => {
// the same channel.
let mut res = Vec::with_capacity(self.short_to_chan_info.read().unwrap().len());
{
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let per_peer_state = self.per_peer_state.read().unwrap();
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
// the same channel.
let mut res = Vec::with_capacity(self.short_to_chan_info.read().unwrap().len());
{
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let per_peer_state = self.per_peer_state.read().unwrap();
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
/// Gets the list of channels we have with a given counterparty, in random order.
pub fn list_channels_with_counterparty(&self, counterparty_node_id: &PublicKey) -> Vec<ChannelDetails> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let per_peer_state = self.per_peer_state.read().unwrap();
if let Some(peer_state_mutex) = per_peer_state.get(counterparty_node_id) {
None
};
- let cur_height = self.best_block.read().unwrap().height() + 1;
+ let cur_height = self.best_block.read().unwrap().height + 1;
if let Err((err_msg, code)) = check_incoming_htlc_cltv(
cur_height, outgoing_cltv_value, msg.cltv_expiry
match decoded_hop {
onion_utils::Hop::Receive(next_hop_data) => {
// OUR PAYMENT!
- let current_height: u32 = self.best_block.read().unwrap().height();
+ let current_height: u32 = self.best_block.read().unwrap().height;
match create_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash,
msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat,
current_height, self.default_configuration.accept_mpp_keysend)
/// [`PeerManager::process_events`]: crate::ln::peer_handler::PeerManager::process_events
/// [`ChannelMonitorUpdateStatus::InProgress`]: crate::chain::ChannelMonitorUpdateStatus::InProgress
pub fn send_payment_with_route(&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId) -> Result<(), PaymentSendFailure> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments
.send_payment_with_route(route, payment_hash, recipient_onion, payment_id,
/// Similar to [`ChannelManager::send_payment_with_route`], but will automatically find a route based on
/// `route_params` and retry failed payment paths based on `retry_strategy`.
pub fn send_payment(&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<(), RetryableSendFailure> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments
.send_payment(payment_hash, recipient_onion, payment_id, retry_strategy, route_params,
#[cfg(test)]
pub(super) fn test_send_payment_internal(&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>, onion_session_privs: Vec<[u8; 32]>) -> Result<(), PaymentSendFailure> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.test_send_payment_internal(route, payment_hash, recipient_onion,
keysend_preimage, payment_id, recv_value_msat, onion_session_privs, &self.node_signer,
#[cfg(test)]
pub(crate) fn test_add_new_pending_payment(&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route: &Route) -> Result<Vec<[u8; 32]>, PaymentSendFailure> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
self.pending_outbound_payments.test_add_new_pending_payment(payment_hash, recipient_onion, payment_id, route, None, &self.entropy_source, best_block_height)
}
}
pub(super) fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments
.send_payment_for_bolt12_invoice(
///
/// [`send_payment`]: Self::send_payment
pub fn send_spontaneous_payment(&self, route: &Route, payment_preimage: Option<PaymentPreimage>, recipient_onion: RecipientOnionFields, payment_id: PaymentId) -> Result<PaymentHash, PaymentSendFailure> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.send_spontaneous_payment_with_route(
route, payment_preimage, recipient_onion, payment_id, &self.entropy_source,
///
/// [`PaymentParameters::for_keysend`]: crate::routing::router::PaymentParameters::for_keysend
pub fn send_spontaneous_payment_with_retry(&self, payment_preimage: Option<PaymentPreimage>, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<PaymentHash, RetryableSendFailure> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.send_spontaneous_payment(payment_preimage, recipient_onion,
payment_id, retry_strategy, route_params, &self.router, self.list_usable_channels(),
/// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
/// us to easily discern them from real payments.
pub fn send_probe(&self, path: Path) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments.send_probe(path, self.probing_cookie_secret,
&self.entropy_source, &self.node_signer, best_block_height,
}));
}
{
- let height = self.best_block.read().unwrap().height();
+ let height = self.best_block.read().unwrap().height;
// Transactions are evaluated as final by network mempools if their locktime is strictly
// lower than the next block height. However, the modules constituting our Lightning
// node might not have perfect sync about their blockchain views. Thus, if the wallet
};
match next_hop {
onion_utils::Hop::Receive(hop_data) => {
- let current_height: u32 = self.best_block.read().unwrap().height();
+ let current_height: u32 = self.best_block.read().unwrap().height;
match create_recv_pending_htlc_info(hop_data,
incoming_shared_secret, payment_hash, outgoing_amt_msat,
outgoing_cltv_value, Some(phantom_shared_secret), false, None,
debug_assert!(!committed_to_claimable);
let mut htlc_msat_height_data = $htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(
- &self.best_block.read().unwrap().height().to_be_bytes(),
+ &self.best_block.read().unwrap().height.to_be_bytes(),
);
failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData {
short_channel_id: $htlc.prev_hop.short_channel_id,
}
};
if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
- let expected_min_expiry_height = (self.current_best_block().height() + min_final_cltv_expiry_delta as u32) as u64;
+ let expected_min_expiry_height = (self.current_best_block().height + min_final_cltv_expiry_delta as u32) as u64;
if (cltv_expiry as u64) < expected_min_expiry_height {
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as its CLTV expiry was too soon (had {}, earliest expected {})",
&payment_hash, cltv_expiry, expected_min_expiry_height);
}
}
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
self.pending_outbound_payments.check_retry_payments(&self.router, || self.list_usable_channels(),
|| self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, best_block_height,
&self.pending_events, &self.logger, |args| self.send_payment_along_path(args));
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(failure_code.into()),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
- htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
+ htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height.to_be_bytes());
HTLCFailReason::reason(failure_code.into(), htlc_msat_height_data)
},
FailureCode::InvalidOnionPayload(data) => {
if !valid_mpp {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
- htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
+ htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height.to_be_bytes());
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash };
// succeed.
let mut channel = match peer_state.inbound_channel_request_by_id.remove(temporary_channel_id) {
Some(unaccepted_channel) => {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider,
counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features,
&unaccepted_channel.open_channel_msg, user_channel_id, &self.default_configuration, best_block_height,
fn peers_without_funded_channels<Filter>(&self, maybe_count_peer: Filter) -> usize
where Filter: Fn(&PeerState<SP>) -> bool {
let mut peers_without_funded_channels = 0;
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
{
let peer_state_lock = self.per_peer_state.read().unwrap();
for (_, peer_mtx) in peer_state_lock.iter() {
msg.common_fields.temporary_channel_id.clone()));
}
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
if Self::unfunded_channel_count(peer_state, best_block_height) >= MAX_UNFUNDED_CHANS_PER_PEER {
return Err(MsgHandleErrInternal::send_err_msg_no_close(
format!("Refusing more than {} unfunded channels.", MAX_UNFUNDED_CHANS_PER_PEER),
peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
msg: try_chan_phase_entry!(self, chan.announcement_signatures(
- &self.node_signer, self.chain_hash, self.best_block.read().unwrap().height(),
+ &self.node_signer, self.chain_hash, self.best_block.read().unwrap().height,
msg, &self.default_configuration
), chan_phase_entry),
// Note that announcement_signatures fails if the channel cannot be announced,
self.finish_close_channel(failure);
}
}
+}
+macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
/// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
/// not have an expiration unless otherwise set on the builder.
/// [`Offer`]: crate::offers::offer::Offer
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn create_offer_builder(
- &self, description: String
- ) -> Result<OfferBuilder<DerivedMetadata, secp256k1::All>, Bolt12SemanticError> {
- let node_id = self.get_our_node_id();
- let expanded_key = &self.inbound_payment_key;
- let entropy = &*self.entropy_source;
- let secp_ctx = &self.secp_ctx;
-
- let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+ &$self, description: String
+ ) -> Result<$builder, Bolt12SemanticError> {
+ let node_id = $self.get_our_node_id();
+ let expanded_key = &$self.inbound_payment_key;
+ let entropy = &*$self.entropy_source;
+ let secp_ctx = &$self.secp_ctx;
+
+ let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let builder = OfferBuilder::deriving_signing_pubkey(
description, node_id, expanded_key, entropy, secp_ctx
)
- .chain_hash(self.chain_hash)
+ .chain_hash($self.chain_hash)
.path(path);
- Ok(builder)
+ Ok(builder.into())
}
+} }
+macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
/// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund.
///
/// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
pub fn create_refund_builder(
- &self, description: String, amount_msats: u64, absolute_expiry: Duration,
+ &$self, description: String, amount_msats: u64, absolute_expiry: Duration,
payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
- ) -> Result<RefundBuilder<secp256k1::All>, Bolt12SemanticError> {
- let node_id = self.get_our_node_id();
- let expanded_key = &self.inbound_payment_key;
- let entropy = &*self.entropy_source;
- let secp_ctx = &self.secp_ctx;
+ ) -> Result<$builder, Bolt12SemanticError> {
+ let node_id = $self.get_our_node_id();
+ let expanded_key = &$self.inbound_payment_key;
+ let entropy = &*$self.entropy_source;
+ let secp_ctx = &$self.secp_ctx;
- let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+ let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let builder = RefundBuilder::deriving_payer_id(
description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
)?
- .chain_hash(self.chain_hash)
+ .chain_hash($self.chain_hash)
.absolute_expiry(absolute_expiry)
.path(path);
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop($self);
+
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
- self.pending_outbound_payments
+ $self.pending_outbound_payments
.add_new_awaiting_invoice(
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
)
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
- Ok(builder)
+ Ok(builder.into())
}
+} }
+
+impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<M, T, ES, NS, SP, F, R, L>
+where
+ M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
+ T::Target: BroadcasterInterface,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ SP::Target: SignerProvider,
+ F::Target: FeeEstimator,
+ R::Target: Router,
+ L::Target: Logger,
+{
+ #[cfg(not(c_bindings))]
+ create_offer_builder!(self, OfferBuilder<DerivedMetadata, secp256k1::All>);
+ #[cfg(not(c_bindings))]
+ create_refund_builder!(self, RefundBuilder<secp256k1::All>);
+
+ #[cfg(c_bindings)]
+ create_offer_builder!(self, OfferWithDerivedMetadataBuilder);
+ #[cfg(c_bindings)]
+ create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
/// Errors if:
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
/// - the provided parameters are invalid for the offer,
+ /// - the offer is for an unsupported chain, or
/// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice
/// request.
///
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
- let builder = offer
+ let builder: InvoiceRequestBuilder<DerivedPayerId, secp256k1::All> = offer
.request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)?
- .chain_hash(self.chain_hash)?;
+ .into();
+ let builder = builder.chain_hash(self.chain_hash)?;
+
let builder = match quantity {
None => builder,
Some(quantity) => builder.quantity(quantity)?,
let invoice_request = builder.build_and_sign()?;
let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
let expiration = StaleExpiration::TimerTicks(1);
self.pending_outbound_payments
.add_new_awaiting_invoice(
///
/// # Errors
///
- /// Errors if the parameterized [`Router`] is unable to create a blinded payment path or reply
- /// path for the invoice.
+ /// Errors if:
+ /// - the refund is for an unsupported chain, or
+ /// - the parameterized [`Router`] is unable to create a blinded payment path or reply path for
+ /// the invoice.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> {
let amount_msats = refund.amount_msats();
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
+ if refund.chain() != self.chain_hash {
+ return Err(Bolt12SemanticError::UnsupportedChain);
+ }
+
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
Ok((payment_hash, payment_secret)) => {
let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
let builder = refund.respond_using_derived_keys_no_std(
payment_paths, payment_hash, created_at, expanded_key, entropy
)?;
+ let builder: InvoiceBuilder<DerivedSigningPubkey> = builder.into();
let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
let reply_path = self.create_blinded_path()
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
let first_hops = self.list_usable_channels();
let payee_node_id = self.get_our_node_id();
- let max_cltv_expiry = self.best_block.read().unwrap().height() + CLTV_FAR_FAR_AWAY
+ let max_cltv_expiry = self.best_block.read().unwrap().height + CLTV_FAR_FAR_AWAY
+ LATENCY_GRACE_PERIOD_BLOCKS;
let payee_tlvs = ReceiveTlvs {
payment_secret,
///
/// [phantom node payments]: crate::sign::PhantomKeysManager
pub fn get_phantom_scid(&self) -> u64 {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let short_to_chan_info = self.short_to_chan_info.read().unwrap();
loop {
let scid_candidate = fake_scid::Namespace::Phantom.get_fake_scid(best_block_height, &self.chain_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
/// Note that this method is not guaranteed to return unique values, you may need to call it a few
/// times to get a unique scid.
pub fn get_intercept_scid(&self) -> u64 {
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
let short_to_chan_info = self.short_to_chan_info.read().unwrap();
loop {
let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block_height, &self.chain_hash, &self.fake_scid_rand_bytes, &self.entropy_source);
fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) {
{
let best_block = self.best_block.read().unwrap();
- assert_eq!(best_block.block_hash(), header.prev_blockhash,
+ assert_eq!(best_block.block_hash, header.prev_blockhash,
"Blocks must be connected in chain-order - the connected header must build on the last connected header");
- assert_eq!(best_block.height(), height - 1,
+ assert_eq!(best_block.height, height - 1,
"Blocks must be connected in chain-order - the connected block height must be one greater than the previous height");
}
let new_height = height - 1;
{
let mut best_block = self.best_block.write().unwrap();
- assert_eq!(best_block.block_hash(), header.block_hash(),
+ assert_eq!(best_block.block_hash, header.block_hash(),
"Blocks must be disconnected in chain-order - the disconnected header must be the last connected header");
- assert_eq!(best_block.height(), height,
+ assert_eq!(best_block.height, height,
"Blocks must be disconnected in chain-order - the disconnected block must have the correct height");
*best_block = BestBlock::new(header.prev_blockhash, new_height)
}
self.do_chain_event(Some(height), |channel| channel.transactions_confirmed(&block_hash, height, txdata, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context))
.map(|(a, b)| (a, Vec::new(), b)));
- let last_best_block_height = self.best_block.read().unwrap().height();
+ let last_best_block_height = self.best_block.read().unwrap().height;
if height < last_best_block_height {
let timestamp = self.highest_seen_timestamp.load(Ordering::Acquire);
self.do_chain_event(Some(last_best_block_height), |channel| channel.best_block_updated(last_best_block_height, timestamp as u32, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context)));
let mut peer_state = e.get().lock().unwrap();
peer_state.latest_features = init_msg.features.clone();
- let best_block_height = self.best_block.read().unwrap().height();
+ let best_block_height = self.best_block.read().unwrap().height;
if inbound_peer_limited &&
Self::unfunded_channel_count(&*peer_state, best_block_height) ==
peer_state.channel_by_id.len()
}
fn handle_error(&self, counterparty_node_id: &PublicKey, msg: &msgs::ErrorMessage) {
- let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
-
match &msg.data as &str {
"cannot co-op close channel w/ active htlcs"|
"link failed to shutdown" =>
// We're not going to bother handling this in a sensible way, instead simply
// repeating the Shutdown message on repeat until morale improves.
if !msg.channel_id.is_zero() {
- let per_peer_state = self.per_peer_state.read().unwrap();
- let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
- if peer_state_mutex_opt.is_none() { return; }
- let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap();
- if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get(&msg.channel_id) {
- if let Some(msg) = chan.get_outbound_shutdown() {
- peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
- node_id: *counterparty_node_id,
- msg,
- });
- }
- peer_state.pending_msg_events.push(events::MessageSendEvent::HandleError {
- node_id: *counterparty_node_id,
- action: msgs::ErrorAction::SendWarningMessage {
- msg: msgs::WarningMessage {
- channel_id: msg.channel_id,
- data: "You appear to be exhibiting LND bug 6039, we'll keep sending you shutdown messages until you handle them correctly".to_owned()
- },
- log_level: Level::Trace,
+ PersistenceNotifierGuard::optionally_notify(
+ self,
+ || -> NotifyOption {
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
+ if peer_state_mutex_opt.is_none() { return NotifyOption::SkipPersistNoEvents; }
+ let mut peer_state = peer_state_mutex_opt.unwrap().lock().unwrap();
+ if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get(&msg.channel_id) {
+ if let Some(msg) = chan.get_outbound_shutdown() {
+ peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
+ node_id: *counterparty_node_id,
+ msg,
+ });
+ }
+ peer_state.pending_msg_events.push(events::MessageSendEvent::HandleError {
+ node_id: *counterparty_node_id,
+ action: msgs::ErrorAction::SendWarningMessage {
+ msg: msgs::WarningMessage {
+ channel_id: msg.channel_id,
+ data: "You appear to be exhibiting LND bug 6039, we'll keep sending you shutdown messages until you handle them correctly".to_owned()
+ },
+ log_level: Level::Trace,
+ }
+ });
+ // This can happen in a fairly tight loop, so we absolutely cannot trigger
+ // a `ChannelManager` write here.
+ return NotifyOption::SkipPersistHandleEvents;
}
- });
- }
+ NotifyOption::SkipPersistNoEvents
+ }
+ );
}
return;
}
_ => {}
}
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
if msg.channel_id.is_zero() {
let channel_ids: Vec<ChannelId> = {
let per_peer_state = self.per_peer_state.read().unwrap();
let builder = invoice_request.respond_using_derived_keys_no_std(
payment_paths, payment_hash, created_at
);
+ let builder: Result<InvoiceBuilder<DerivedSigningPubkey>, _> =
+ builder.map(|b| b.into());
match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
let builder = invoice_request.respond_with_no_std(
payment_paths, payment_hash, created_at
);
+ let builder: Result<InvoiceBuilder<ExplicitSigningPubkey>, _> =
+ builder.map(|b| b.into());
let response = builder.and_then(|builder| builder.allow_mpp().build())
.map_err(|e| OffersMessage::InvoiceError(e.into()))
- .and_then(|invoice|
- match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) {
+ .and_then(|invoice| {
+ #[cfg(c_bindings)]
+ let mut invoice = invoice;
+ match invoice.sign(|invoice: &UnsignedBolt12Invoice|
+ self.node_signer.sign_bolt12_invoice(invoice)
+ ) {
Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
- Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
+ Err(SignError::Signing) => Err(OffersMessage::InvoiceError(
InvoiceError::from_string("Failed signing invoice".to_string())
)),
Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
InvoiceError::from_string("Failed invoice signature verification".to_string())
)),
- });
+ }
+ });
match response {
Ok(invoice) => Some(invoice),
Err(error) => Some(error),
self.chain_hash.write(writer)?;
{
let best_block = self.best_block.read().unwrap();
- best_block.height().write(writer)?;
- best_block.block_hash().write(writer)?;
+ best_block.height.write(writer)?;
+ best_block.block_hash.write(writer)?;
}
let mut serializable_peer_count: u64 = 0;
};
// Check that if the amount we received + the penultimate hop extra fee is less than the sender
// intended amount, we fail the payment.
- let current_height: u32 = node[0].node.best_block.read().unwrap().height();
+ let current_height: u32 = node[0].node.best_block.read().unwrap().height;
if let Err(crate::ln::channelmanager::InboundHTLCErr { err_code, .. }) =
create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat),
}),
custom_tlvs: Vec::new(),
};
- let current_height: u32 = node[0].node.best_block.read().unwrap().height();
+ let current_height: u32 = node[0].node.best_block.read().unwrap().height;
assert!(create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat),
current_height, node[0].node.default_configuration.accept_mpp_keysend).is_ok());
let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]);
let node = create_network(1, &node_cfg, &node_chanmgr);
- let current_height: u32 = node[0].node.best_block.read().unwrap().height();
+ let current_height: u32 = node[0].node.best_block.read().unwrap().height;
let result = create_recv_pending_htlc_info(msgs::InboundOnionPayload::Receive {
sender_intended_htlc_amt_msat: 100,
cltv_expiry_height: 22,
assert_eq!(&tx_broadcaster.txn_broadcasted.lock().unwrap()[..], &[tx.clone()]);
- let block = create_dummy_block(BestBlock::from_network(network).block_hash(), 42, vec![tx]);
+ let block = create_dummy_block(BestBlock::from_network(network).block_hash, 42, vec![tx]);
Listen::block_connected(&node_a, &block, 1);
Listen::block_connected(&node_b, &block, 1);
}
}
+/// Returns the total fee earned by this HTLC forward, in msat.
pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option<u64>,
expected_extra_fees_msat: Option<u64>, upstream_force_closed: bool,
- downstream_force_closed: bool
-) {
+ downstream_force_closed: bool, allow_1_msat_fee_overpay: bool,
+) -> Option<u64> {
match event {
Event::PaymentForwarded {
total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
outbound_amount_forwarded_msat: _, skimmed_fee_msat
} => {
- assert_eq!(total_fee_earned_msat, expected_fee);
+ if allow_1_msat_fee_overpay {
+ // Aggregating fees for blinded paths may result in a rounding error, causing slight
+ // overpayment in fees.
+ let actual_fee = total_fee_earned_msat.unwrap();
+ let expected_fee = expected_fee.unwrap();
+ assert!(actual_fee == expected_fee || actual_fee == expected_fee + 1);
+ } else {
+ assert_eq!(total_fee_earned_msat, expected_fee);
+ }
// Check that the (knowingly) withheld amount is always less or equal to the expected
// overpaid amount.
assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == next_node.node().get_our_node_id() && x.channel_id == next_channel_id.unwrap()));
}
assert_eq!(claim_from_onchain_tx, downstream_force_closed);
+ total_fee_earned_msat
},
_ => panic!("Unexpected event"),
}
assert_eq!(events.len(), 1);
$crate::ln::functional_test_utils::expect_payment_forwarded(
events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee, None,
- $upstream_force_closed, $downstream_force_closed
+ $upstream_force_closed, $downstream_force_closed, false
);
}
}
}
}
-pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_claimable_expected: bool, clear_recipient_events: bool, expected_preimage: Option<PaymentPreimage>, is_probe: bool) -> Option<Event> {
+pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> {
+ pub origin_node: &'a Node<'b, 'c, 'd>,
+ pub expected_path: &'a [&'a Node<'b, 'c, 'd>],
+ pub recv_value: u64,
+ pub payment_hash: PaymentHash,
+ pub payment_secret: Option<PaymentSecret>,
+ pub event: MessageSendEvent,
+ pub payment_claimable_expected: bool,
+ pub clear_recipient_events: bool,
+ pub expected_preimage: Option<PaymentPreimage>,
+ pub is_probe: bool,
+}
+
+impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
+ pub fn new(
+ origin_node: &'a Node<'b, 'c, 'd>, expected_path: &'a [&'a Node<'b, 'c, 'd>], recv_value: u64,
+ payment_hash: PaymentHash, event: MessageSendEvent,
+ ) -> Self {
+ Self {
+ origin_node, expected_path, recv_value, payment_hash, payment_secret: None, event,
+ payment_claimable_expected: true, clear_recipient_events: true, expected_preimage: None,
+ is_probe: false,
+ }
+ }
+ pub fn without_clearing_recipient_events(mut self) -> Self {
+ self.clear_recipient_events = false;
+ self
+ }
+ pub fn is_probe(mut self) -> Self {
+ self.payment_claimable_expected = false;
+ self.is_probe = true;
+ self
+ }
+ pub fn without_claimable_event(mut self) -> Self {
+ self.payment_claimable_expected = false;
+ self
+ }
+ pub fn with_payment_secret(mut self, payment_secret: PaymentSecret) -> Self {
+ self.payment_secret = Some(payment_secret);
+ self
+ }
+ pub fn with_payment_preimage(mut self, payment_preimage: PaymentPreimage) -> Self {
+ self.expected_preimage = Some(payment_preimage);
+ self
+ }
+}
+
+pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event> {
+ let PassAlongPathArgs {
+ origin_node, expected_path, recv_value, payment_hash: our_payment_hash,
+ payment_secret: our_payment_secret, event: ev, payment_claimable_expected,
+ clear_recipient_events, expected_preimage, is_probe
+ } = args;
+
let mut payment_event = SendEvent::from_event(ev);
let mut prev_node = origin_node;
let mut event = None;
}
pub fn pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_claimable_expected: bool, expected_preimage: Option<PaymentPreimage>) -> Option<Event> {
- do_pass_along_path(origin_node, expected_path, recv_value, our_payment_hash, our_payment_secret, ev, payment_claimable_expected, true, expected_preimage, false)
+ let mut args = PassAlongPathArgs::new(origin_node, expected_path, recv_value, our_payment_hash, ev);
+ if !payment_claimable_expected {
+ args = args.without_claimable_event();
+ }
+ if let Some(payment_secret) = our_payment_secret {
+ args = args.with_payment_secret(payment_secret);
+ }
+ if let Some(payment_preimage) = expected_preimage {
+ args = args.with_payment_preimage(payment_preimage);
+ }
+ do_pass_along_path(args)
}
pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&[&Node<'a, 'b, 'c>]]) {
for path in expected_route.iter() {
let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events);
- do_pass_along_path(origin_node, path, 0, PaymentHash([0_u8; 32]), None, ev, false, false, None, true);
+ do_pass_along_path(PassAlongPathArgs::new(origin_node, path, 0, PaymentHash([0_u8; 32]), ev)
+ .is_probe()
+ .without_clearing_recipient_events());
+
let nodes_to_fail_payment: Vec<_> = vec![origin_node].into_iter().chain(path.iter().cloned()).collect();
fail_payment_along_path(nodes_to_fail_payment.as_slice());
pub expected_min_htlc_overpay: Vec<u32>,
pub skip_last: bool,
pub payment_preimage: PaymentPreimage,
+ // Allow forwarding nodes to have taken 1 msat more fee than expected based on the downstream
+ // fulfill amount.
+ //
+ // Necessary because our test utils calculate the expected fee for an intermediate node based on
+ // the amount was claimed in their downstream peer's fulfill, but blinded intermediate nodes
+ // calculate their fee based on the inbound amount from their upstream peer, causing a difference
+ // in rounding.
+ pub allow_1_msat_fee_overpay: bool,
}
impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
Self {
origin_node, expected_paths, expected_extra_fees: vec![0; expected_paths.len()],
expected_min_htlc_overpay: vec![0; expected_paths.len()], skip_last: false, payment_preimage,
+ allow_1_msat_fee_overpay: false,
}
}
pub fn skip_last(mut self, skip_last: bool) -> Self {
self.expected_min_htlc_overpay = extra_fees;
self
}
+ pub fn allow_1_msat_fee_overpay(mut self) -> Self {
+ self.allow_1_msat_fee_overpay = true;
+ self
+ }
}
pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArgs) -> u64 {
let ClaimAlongRouteArgs {
origin_node, expected_paths, expected_extra_fees, expected_min_htlc_overpay, skip_last,
- payment_preimage: our_payment_preimage
+ payment_preimage: our_payment_preimage, allow_1_msat_fee_overpay,
} = args;
let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events();
assert_eq!(claim_event.len(), 1);
+ #[allow(unused)]
+ let mut fwd_amt_msat = 0;
match claim_event[0] {
Event::PaymentClaimed {
purpose: PaymentPurpose::SpontaneousPayment(preimage),
assert_eq!(htlcs.len(), expected_paths.len()); // One per path.
assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::<u64>(), amount_msat);
expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc));
+ fwd_amt_msat = amount_msat;
},
Event::PaymentClaimed {
purpose: PaymentPurpose::InvoicePayment { .. },
assert_eq!(htlcs.len(), expected_paths.len()); // One per path.
assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::<u64>(), amount_msat);
expected_paths.iter().zip(htlcs).for_each(|(path, htlc)| check_claimed_htlc_channel(origin_node, path, htlc));
+ fwd_amt_msat = amount_msat;
}
_ => panic!(),
}
per_path_msgs.push(msgs_from_ev!(&events[0]));
} else {
for expected_path in expected_paths.iter() {
- // For MPP payments, we always want the message to the first node in the path.
- let ev = remove_first_msg_event_to_node(&expected_path[0].node.get_our_node_id(), &mut events);
+ // For MPP payments, we want the fulfill message from the payee to the penultimate hop in the
+ // path.
+ let penultimate_hop_node_id = expected_path.iter().rev().skip(1).next()
+ .map(|n| n.node.get_our_node_id())
+ .unwrap_or(origin_node.node.get_our_node_id());
+ let ev = remove_first_msg_event_to_node(&penultimate_hop_node_id, &mut events);
per_path_msgs.push(msgs_from_ev!(&ev));
}
}
{
$node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
let mut fee = {
- let per_peer_state = $node.node.per_peer_state.read().unwrap();
- let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
- .unwrap().lock().unwrap();
- let channel = peer_state.channel_by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
- if let Some(prev_config) = channel.context().prev_config() {
- prev_config.forwarding_fee_base_msat
- } else {
- channel.context().config().forwarding_fee_base_msat
- }
+ let (base_fee, prop_fee) = {
+ let per_peer_state = $node.node.per_peer_state.read().unwrap();
+ let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
+ .unwrap().lock().unwrap();
+ let channel = peer_state.channel_by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
+ if let Some(prev_config) = channel.context().prev_config() {
+ (prev_config.forwarding_fee_base_msat as u64,
+ prev_config.forwarding_fee_proportional_millionths as u64)
+ } else {
+ (channel.context().config().forwarding_fee_base_msat as u64,
+ channel.context().config().forwarding_fee_proportional_millionths as u64)
+ }
+ };
+ ((fwd_amt_msat * prop_fee / 1_000_000) + base_fee) as u32
};
let mut expected_extra_fee = None;
}
let mut events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
- Some(fee as u64), expected_extra_fee, false, false);
- expected_total_fee_msat += fee as u64;
+ let actual_fee = expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
+ Some(fee as u64), expected_extra_fee, false, false, allow_1_msat_fee_overpay);
+ expected_total_fee_msat += actual_fee.unwrap();
+ fwd_amt_msat += actual_fee.unwrap();
check_added_monitors!($node, 1);
let new_next_msgs = if $new_msgs {
let events = $node.node.get_and_clear_pending_msg_events();
let secp_ctx = Secp256k1::new();
let session_priv = SecretKey::from_slice(&[42; 32]).expect("RNG is bad!");
- let cur_height = nodes[1].node.best_block.read().unwrap().height() + 1;
+ let cur_height = nodes[1].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0],
// Need to manually create the update_add_htlc message to go around the channel reserve check in send_htlc()
let secp_ctx = Secp256k1::new();
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
- let cur_height = nodes[1].node.best_block.read().unwrap().height() + 1;
+ let cur_height = nodes[1].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0],
700_000, RecipientOnionFields::secret_only(payment_secret), cur_height, &None).unwrap();
// Need to manually create the update_add_htlc message to go around the channel reserve check in send_htlc()
let secp_ctx = Secp256k1::new();
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
- let cur_height = nodes[0].node.best_block.read().unwrap().height() + 1;
+ let cur_height = nodes[0].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route_2.paths[0], &session_priv).unwrap();
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
&route_2.paths[0], recv_value_2, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap();
let secp_ctx = Secp256k1::new();
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
- let current_height = nodes[1].node.best_block.read().unwrap().height() + 1;
+ let current_height = nodes[1].node.best_block.read().unwrap().height + 1;
let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(
&route.paths[0], 50_000, RecipientOnionFields::secret_only(payment_secret), current_height, &None).unwrap();
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap();
get_route_and_payment_hash!(nodes[0], nodes[1], 1000);
route.paths[0].hops[0].fee_msat = send_amt;
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
- let cur_height = nodes[0].node.best_block.read().unwrap().height() + 1;
+ let cur_height = nodes[0].node.best_block.read().unwrap().height + 1;
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv).unwrap();
let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
&route.paths[0], send_amt, RecipientOnionFields::secret_only(our_payment_secret), cur_height, &None).unwrap();
let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
- let best_height = nodes[0].node.best_block.read().unwrap().height();
+ let best_height = nodes[0].node.best_block.read().unwrap().height;
let chan_id = *nodes[0].network_chan_count.borrow();
let events = nodes[0].node.get_and_clear_pending_events();
let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
- let best_height = nodes[0].node.best_block.read().unwrap().height();
+ let best_height = nodes[0].node.best_block.read().unwrap().height;
let chan_id = *nodes[0].network_chan_count.borrow();
let events = nodes[0].node.get_and_clear_pending_events();
pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN;
+use bitcoin::hashes::{sha256::Hash as Sha256, Hash};
+
/// payment_hash type, use to cross-lock hop
///
/// This is not exported to bindings users as we just use [u8; 32] directly
}
}
+/// Converts a `PaymentPreimage` into a `PaymentHash` by hashing the preimage with SHA256.
+impl From<PaymentPreimage> for PaymentHash {
+ fn from(value: PaymentPreimage) -> Self {
+ PaymentHash(Sha256::hash(&value.0).to_byte_array())
+ }
+}
+
/// payment_secret type, use to authenticate sender to the receiver and tie MPP HTLCs together
///
/// This is not exported to bindings users as we just use [u8; 32] directly
/// This maximum length is reached by a hostname address descriptor:
/// a hostname with a maximum length of 255, its 1-byte length and a 2-byte port.
pub(crate) const MAX_LEN: u16 = 258;
+
+ pub(crate) fn is_tor(&self) -> bool {
+ match self {
+ &SocketAddress::TcpIpV4 {..} => false,
+ &SocketAddress::TcpIpV6 {..} => false,
+ &SocketAddress::OnionV2(_) => true,
+ &SocketAddress::OnionV3 {..} => true,
+ &SocketAddress::Hostname {..} => false,
+ }
+ }
}
impl Writeable for SocketAddress {
//! Nodes without channels are disconnected and connected as needed to ensure that deterministic
//! blinded paths are used.
+use bitcoin::network::constants::Network;
use core::time::Duration;
use crate::blinded_path::BlindedPath;
use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose};
use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self};
use crate::ln::functional_test_utils::*;
-use crate::ln::msgs::{ChannelMessageHandler, Init, OnionMessage, OnionMessageHandler};
+use crate::ln::msgs::{ChannelMessageHandler, Init, NodeAnnouncement, OnionMessage, OnionMessageHandler, RoutingMessageHandler, SocketAddress, UnsignedGossipMessage, UnsignedNodeAnnouncement};
use crate::offers::invoice::Bolt12Invoice;
use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::InvoiceRequest;
use crate::onion_message::messenger::PeeledOnion;
use crate::onion_message::offers::OffersMessage;
use crate::onion_message::packet::ParsedOnionMessageContents;
+use crate::routing::gossip::{NodeAlias, NodeId};
+use crate::sign::{NodeSigner, Recipient};
use crate::prelude::*;
}
}
+fn announce_node_address<'a, 'b, 'c>(
+ node: &Node<'a, 'b, 'c>, peers: &[&Node<'a, 'b, 'c>], address: SocketAddress,
+) {
+ let features = node.onion_messenger.provided_node_features()
+ | node.gossip_sync.provided_node_features();
+ let rgb = [0u8; 3];
+ let announcement = UnsignedNodeAnnouncement {
+ features,
+ timestamp: 1000,
+ node_id: NodeId::from_pubkey(&node.keys_manager.get_node_id(Recipient::Node).unwrap()),
+ rgb,
+ alias: NodeAlias([0u8; 32]),
+ addresses: vec![address],
+ excess_address_data: Vec::new(),
+ excess_data: Vec::new(),
+ };
+ let signature = node.keys_manager.sign_gossip_message(
+ UnsignedGossipMessage::NodeAnnouncement(&announcement)
+ ).unwrap();
+
+ let msg = NodeAnnouncement {
+ signature,
+ contents: announcement
+ };
+
+ node.gossip_sync.handle_node_announcement(&msg).unwrap();
+ for peer in peers {
+ peer.gossip_sync.handle_node_announcement(&msg).unwrap();
+ }
+}
+
fn route_bolt12_payment<'a, 'b, 'c>(
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice
) {
// invoice contains the payment_hash but it was encrypted inside an onion message.
let amount_msats = invoice.amount_msats();
let payment_hash = invoice.payment_hash();
- do_pass_along_path(
- node, path, amount_msats, payment_hash, None, ev, false, false, None, false
- );
+ let args = PassAlongPathArgs::new(node, path, amount_msats, payment_hash, ev)
+ .without_clearing_recipient_events();
+ do_pass_along_path(args);
}
fn claim_bolt12_payment<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>]) {
}
}
+/// Checks that blinded paths without Tor-only nodes are preferred when constructing an offer.
+#[test]
+fn prefers_non_tor_nodes_in_blinded_paths() {
+ let mut accept_forward_cfg = test_default_channel_config();
+ accept_forward_cfg.accept_forwards_to_priv_channels = true;
+
+ let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
+ features.set_onion_messages_optional();
+ features.set_route_blinding_optional();
+
+ let chanmon_cfgs = create_chanmon_cfgs(6);
+ let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
+
+ *node_cfgs[1].override_init_features.borrow_mut() = Some(features);
+
+ let node_chanmgrs = create_node_chanmgrs(
+ 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
+ );
+ let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
+
+ create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
+ create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
+
+ // Add an extra channel so that more than one of Bob's peers have MIN_PEER_CHANNELS.
+ create_announced_chan_between_nodes_with_value(&nodes, 4, 5, 10_000_000, 1_000_000_000);
+
+ let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
+ let bob_id = bob.node.get_our_node_id();
+ let charlie_id = charlie.node.get_our_node_id();
+
+ disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
+ disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
+
+ let tor = SocketAddress::OnionV2([255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]);
+ announce_node_address(charlie, &[alice, bob, david, &nodes[4], &nodes[5]], tor.clone());
+
+ let offer = bob.node
+ .create_offer_builder("coffee".to_string()).unwrap()
+ .amount_msats(10_000_000)
+ .build().unwrap();
+ assert_ne!(offer.signing_pubkey(), bob_id);
+ assert!(!offer.paths().is_empty());
+ for path in offer.paths() {
+ assert_ne!(path.introduction_node_id, bob_id);
+ assert_ne!(path.introduction_node_id, charlie_id);
+ }
+
+ // Use a one-hop blinded path when Bob is announced and all his peers are Tor-only.
+ announce_node_address(&nodes[4], &[alice, bob, charlie, david, &nodes[5]], tor.clone());
+ announce_node_address(&nodes[5], &[alice, bob, charlie, david, &nodes[4]], tor.clone());
+
+ let offer = bob.node
+ .create_offer_builder("coffee".to_string()).unwrap()
+ .amount_msats(10_000_000)
+ .build().unwrap();
+ assert_ne!(offer.signing_pubkey(), bob_id);
+ assert!(!offer.paths().is_empty());
+ for path in offer.paths() {
+ assert_eq!(path.introduction_node_id, bob_id);
+ }
+}
+
+/// Checks that blinded paths prefer an introduction node that is the most connected.
+#[test]
+fn prefers_more_connected_nodes_in_blinded_paths() {
+ let mut accept_forward_cfg = test_default_channel_config();
+ accept_forward_cfg.accept_forwards_to_priv_channels = true;
+
+ let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
+ features.set_onion_messages_optional();
+ features.set_route_blinding_optional();
+
+ let chanmon_cfgs = create_chanmon_cfgs(6);
+ let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
+
+ *node_cfgs[1].override_init_features.borrow_mut() = Some(features);
+
+ let node_chanmgrs = create_node_chanmgrs(
+ 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
+ );
+ let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
+
+ create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
+ create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
+
+ // Add extra channels so that more than one of Bob's peers have MIN_PEER_CHANNELS and one has
+ // more than the others.
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 4, 10_000_000, 1_000_000_000);
+ create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 10_000_000, 1_000_000_000);
+
+ let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
+ let bob_id = bob.node.get_our_node_id();
+
+ disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
+ disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
+
+ let offer = bob.node
+ .create_offer_builder("coffee".to_string()).unwrap()
+ .amount_msats(10_000_000)
+ .build().unwrap();
+ assert_ne!(offer.signing_pubkey(), bob_id);
+ assert!(!offer.paths().is_empty());
+ for path in offer.paths() {
+ assert_eq!(path.introduction_node_id, nodes[4].node.get_our_node_id());
+ }
+}
+
/// Checks that an offer can be paid through blinded paths and that ephemeral pubkeys are used
/// rather than exposing a node's pubkey.
#[test]
assert!(nodes[0].node.list_recent_payments().is_empty());
}
+/// Fails creating an invoice request when the offer contains an unsupported chain.
+#[test]
+fn fails_creating_invoice_request_for_unsupported_chain() {
+ 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, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
+
+ let alice = &nodes[0];
+ let bob = &nodes[1];
+
+ let offer = alice.node
+ .create_offer_builder("coffee".to_string()).unwrap()
+ .clear_chains()
+ .chain(Network::Signet)
+ .build().unwrap();
+
+ let payment_id = PaymentId([1; 32]);
+ match bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) {
+ Ok(_) => panic!("Expected error"),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
+ }
+}
+
+/// Fails requesting a payment when the refund contains an unsupported chain.
+#[test]
+fn fails_sending_invoice_with_unsupported_chain_for_refund() {
+ 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, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
+
+ let alice = &nodes[0];
+ let bob = &nodes[1];
+
+ let absolute_expiry = Duration::from_secs(u64::MAX);
+ let payment_id = PaymentId([1; 32]);
+ let refund = bob.node
+ .create_refund_builder(
+ "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
+ )
+ .unwrap()
+ .chain(Network::Signet)
+ .build().unwrap();
+
+ match alice.node.request_refund_payment(&refund) {
+ Ok(_) => panic!("Expected error"),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
+ }
+}
+
/// Fails creating an invoice request when a blinded reply path cannot be created without exposing
/// the node's id.
#[test]
// Ensure the payment fails with the expected error.
let mut error_data = recv_value_msat.to_be_bytes().to_vec();
error_data.extend_from_slice(
- &nodes[0].node.best_block.read().unwrap().height().to_be_bytes(),
+ &nodes[0].node.best_block.read().unwrap().height.to_be_bytes(),
);
let mut fail_conditions = PaymentFailedConditions::new()
.blamed_scid(phantom_scid)
// Ensure the payment fails with the expected error.
let mut error_data = bad_recv_amt_msat.to_be_bytes().to_vec();
- error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height().to_be_bytes());
+ error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height.to_be_bytes());
let mut fail_conditions = PaymentFailedConditions::new()
.blamed_scid(phantom_scid)
.expected_htlc_error_data(0x4000 | 15, &error_data);
// Ensure the payment fails with the expected error.
let mut error_data = recv_amt_msat.to_be_bytes().to_vec();
- error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height().to_be_bytes());
+ error_data.extend_from_slice(&nodes[1].node.best_block.read().unwrap().height.to_be_bytes());
let mut fail_conditions = PaymentFailedConditions::new()
.blamed_scid(phantom_scid)
.expected_htlc_error_data(0x4000 | 15, &error_data);
assert_eq!(send_events.len(), 2);
let node_1_msgs = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut send_events);
let node_2_msgs = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut send_events);
- do_pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_1_msgs, true, false, None, false);
- do_pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_2_msgs, true, false, None, false);
+ do_pass_along_path(PassAlongPathArgs::new(&nodes[0],&[&nodes[1], &nodes[3]], 15_000_000, payment_hash, node_1_msgs)
+ .with_payment_secret(payment_secret)
+ .without_clearing_recipient_events());
+ do_pass_along_path(PassAlongPathArgs::new(&nodes[0], &[&nodes[2], &nodes[3]], 15_000_000, payment_hash, node_2_msgs)
+ .with_payment_secret(payment_secret)
+ .without_clearing_recipient_events());
// Now that we have an MPP payment pending, get the latest encoded copies of nodes[3]'s
// monitors and ChannelManager, for use later, if we don't want to persist both monitors.
let (_, our_payment_hash, our_payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
let route_params = if blinded_recipient {
crate::ln::blinded_payment_tests::get_blinded_route_parameters(
- amt_msat, our_payment_secret,
+ amt_msat, our_payment_secret, 1, 100000000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_2.0.contents],
&chanmon_cfgs[2].keys_manager)
} else {
//!
//! use bitcoin::hashes::Hash;
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
-//! use core::convert::{Infallible, TryFrom};
+//! use core::convert::TryFrom;
+//! use lightning::offers::invoice::UnsignedBolt12Invoice;
//! use lightning::offers::invoice_request::InvoiceRequest;
//! use lightning::offers::refund::Refund;
//! use lightning::util::ser::Writeable;
//!
//! # use lightning::ln::PaymentHash;
-//! # use lightning::offers::invoice::BlindedPayInfo;
+//! # use lightning::offers::invoice::{BlindedPayInfo, ExplicitSigningPubkey, InvoiceBuilder};
//! # use lightning::blinded_path::BlindedPath;
//! #
//! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() }
//! let mut buffer = Vec::new();
//!
//! // Invoice for the "offer to be paid" flow.
+//! # <InvoiceBuilder<ExplicitSigningPubkey>>::from(
//! InvoiceRequest::try_from(bytes)?
#![cfg_attr(feature = "std", doc = "
.respond_with(payment_paths, payment_hash)?
#![cfg_attr(not(feature = "std"), doc = "
.respond_with_no_std(payment_paths, payment_hash, core::time::Duration::from_secs(0))?
")]
+//! # )
//! .relative_expiry(3600)
//! .allow_mpp()
//! .fallback_v0_p2wpkh(&wpubkey_hash)
//! .build()?
-//! .sign::<_, Infallible>(
-//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//! .sign(|message: &UnsignedBolt12Invoice|
+//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
//! )
//! .expect("failed verifying signature")
//! .write(&mut buffer)
//! # let mut buffer = Vec::new();
//!
//! // Invoice for the "offer for money" flow.
+//! # <InvoiceBuilder<ExplicitSigningPubkey>>::from(
//! "lnr1qcp4256ypq"
//! .parse::<Refund>()?
#![cfg_attr(feature = "std", doc = "
#![cfg_attr(not(feature = "std"), doc = "
.respond_with_no_std(payment_paths, payment_hash, pubkey, core::time::Duration::from_secs(0))?
")]
+//! # )
//! .relative_expiry(3600)
//! .allow_mpp()
//! .fallback_v0_p2wpkh(&wpubkey_hash)
//! .build()?
-//! .sign::<_, Infallible>(
-//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//! .sign(|message: &UnsignedBolt12Invoice|
+//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
//! )
//! .expect("failed verifying signature")
//! .write(&mut buffer)
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion};
use bitcoin::key::TweakedPublicKey;
-use core::convert::{AsRef, Infallible, TryFrom};
+use core::convert::{AsRef, TryFrom};
use core::time::Duration;
use crate::io;
use crate::blinded_path::BlindedPath;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
+use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
signing_pubkey_strategy: S,
}
+/// Builds a [`Bolt12Invoice`] from either:
+/// - an [`InvoiceRequest`] for the "offer to be paid" flow or
+/// - a [`Refund`] for the "offer for money" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Refund`]: crate::offers::refund::Refund
+/// [module-level documentation]: self
+#[cfg(c_bindings)]
+pub struct InvoiceWithExplicitSigningPubkeyBuilder<'a> {
+ invreq_bytes: &'a Vec<u8>,
+ invoice: InvoiceContents,
+ signing_pubkey_strategy: ExplicitSigningPubkey,
+}
+
+/// Builds a [`Bolt12Invoice`] from either:
+/// - an [`InvoiceRequest`] for the "offer to be paid" flow or
+/// - a [`Refund`] for the "offer for money" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Refund`]: crate::offers::refund::Refund
+/// [module-level documentation]: self
+#[cfg(c_bindings)]
+pub struct InvoiceWithDerivedSigningPubkeyBuilder<'a> {
+ invreq_bytes: &'a Vec<u8>,
+ invoice: InvoiceContents,
+ signing_pubkey_strategy: DerivedSigningPubkey,
+}
+
/// Indicates how [`Bolt12Invoice::signing_pubkey`] was set.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
impl SigningPubkeyStrategy for ExplicitSigningPubkey {}
impl SigningPubkeyStrategy for DerivedSigningPubkey {}
-impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
+macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn for_offer(
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash
Self::new(&invoice_request.bytes, contents, ExplicitSigningPubkey {})
}
+ #[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn for_refund(
refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, signing_pubkey: PublicKey
Self::new(&refund.bytes, contents, ExplicitSigningPubkey {})
}
-}
-impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+ /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by
+ /// [`UnsignedBolt12Invoice::sign`].
+ pub fn build($self: $self_type) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
+ #[cfg(feature = "std")] {
+ if $self.invoice.is_offer_or_refund_expired() {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
+ #[cfg(not(feature = "std"))] {
+ if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
+ let Self { invreq_bytes, invoice, .. } = $self;
+ #[cfg(not(c_bindings))] {
+ Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
+ }
+ #[cfg(c_bindings)] {
+ Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone()))
+ }
+ }
+} }
+
+macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn for_offer_using_keys(
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
Self::new(&invoice_request.bytes, contents, DerivedSigningPubkey(keys))
}
+ #[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn for_refund_using_keys(
refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, keys: KeyPair,
Self::new(&refund.bytes, contents, DerivedSigningPubkey(keys))
}
-}
-impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
+ /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics.
+ pub fn build_and_sign<T: secp256k1::Signing>(
+ $self: $self_type, secp_ctx: &Secp256k1<T>
+ ) -> Result<Bolt12Invoice, Bolt12SemanticError> {
+ #[cfg(feature = "std")] {
+ if $self.invoice.is_offer_or_refund_expired() {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
+ #[cfg(not(feature = "std"))] {
+ if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) {
+ return Err(Bolt12SemanticError::AlreadyExpired);
+ }
+ }
+
+ let Self {
+ invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys)
+ } = $self;
+ #[cfg(not(c_bindings))]
+ let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice);
+ #[cfg(c_bindings)]
+ let mut unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone());
+
+ let invoice = unsigned_invoice
+ .sign(|message: &UnsignedBolt12Invoice|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ )
+ .unwrap();
+ Ok(invoice)
+ }
+} }
+
+macro_rules! invoice_builder_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $type_param: ty $(, $self_mut: tt)?
+) => {
pub(crate) fn amount_msats(
invoice_request: &InvoiceRequest
) -> Result<u64, Bolt12SemanticError> {
}
}
+ #[cfg_attr(c_bindings, allow(dead_code))]
fn fields(
payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
payment_hash: PaymentHash, amount_msats: u64, signing_pubkey: PublicKey
}
}
+ #[cfg_attr(c_bindings, allow(dead_code))]
fn new(
- invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, signing_pubkey_strategy: S
+ invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, signing_pubkey_strategy: $type_param
) -> Result<Self, Bolt12SemanticError> {
if contents.fields().payment_paths.is_empty() {
return Err(Bolt12SemanticError::MissingPaths);
/// [`Bolt12Invoice::is_expired`].
///
/// Successive calls to this method will override the previous setting.
- pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self {
+ pub fn relative_expiry($($self_mut)* $self: $self_type, relative_expiry_secs: u32) -> $return_type {
let relative_expiry = Duration::from_secs(relative_expiry_secs as u64);
- self.invoice.fields_mut().relative_expiry = Some(relative_expiry);
- self
+ $self.invoice.fields_mut().relative_expiry = Some(relative_expiry);
+ $return_value
}
/// Adds a P2WSH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses.
- pub fn fallback_v0_p2wsh(mut self, script_hash: &WScriptHash) -> Self {
+ pub fn fallback_v0_p2wsh($($self_mut)* $self: $self_type, script_hash: &WScriptHash) -> $return_type {
let address = FallbackAddress {
version: WitnessVersion::V0.to_num(),
program: Vec::from(script_hash.to_byte_array()),
};
- self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
- self
+ $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
+ $return_value
}
/// Adds a P2WPKH address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses.
- pub fn fallback_v0_p2wpkh(mut self, pubkey_hash: &WPubkeyHash) -> Self {
+ pub fn fallback_v0_p2wpkh($($self_mut)* $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type {
let address = FallbackAddress {
version: WitnessVersion::V0.to_num(),
program: Vec::from(pubkey_hash.to_byte_array()),
};
- self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
- self
+ $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
+ $return_value
}
/// Adds a P2TR address to [`Bolt12Invoice::fallbacks`].
///
/// Successive calls to this method will add another address. Caller is responsible for not
/// adding duplicate addresses and only calling if capable of receiving to P2TR addresses.
- pub fn fallback_v1_p2tr_tweaked(mut self, output_key: &TweakedPublicKey) -> Self {
+ pub fn fallback_v1_p2tr_tweaked($($self_mut)* $self: $self_type, output_key: &TweakedPublicKey) -> $return_type {
let address = FallbackAddress {
version: WitnessVersion::V1.to_num(),
program: Vec::from(&output_key.serialize()[..]),
};
- self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
- self
+ $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
+ $return_value
}
/// Sets [`Bolt12Invoice::invoice_features`] to indicate MPP may be used. Otherwise, MPP is
/// disallowed.
- pub fn allow_mpp(mut self) -> Self {
- self.invoice.fields_mut().features.set_basic_mpp_optional();
- self
+ pub fn allow_mpp($($self_mut)* $self: $self_type) -> $return_type {
+ $self.invoice.fields_mut().features.set_basic_mpp_optional();
+ $return_value
}
-}
+} }
impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
- /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by
- /// [`UnsignedBolt12Invoice::sign`].
- pub fn build(self) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
- #[cfg(feature = "std")] {
- if self.invoice.is_offer_or_refund_expired() {
- return Err(Bolt12SemanticError::AlreadyExpired);
- }
- }
+ invoice_explicit_signing_pubkey_builder_methods!(self, Self);
+}
- #[cfg(not(feature = "std"))] {
- if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
- return Err(Bolt12SemanticError::AlreadyExpired);
- }
- }
+impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+ invoice_derived_signing_pubkey_builder_methods!(self, Self);
+}
- let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
- Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
- }
+impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
+ invoice_builder_methods!(self, Self, Self, self, S, mut);
}
-impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
- /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics.
- pub fn build_and_sign<T: secp256k1::Signing>(
- self, secp_ctx: &Secp256k1<T>
- ) -> Result<Bolt12Invoice, Bolt12SemanticError> {
- #[cfg(feature = "std")] {
- if self.invoice.is_offer_or_refund_expired() {
- return Err(Bolt12SemanticError::AlreadyExpired);
- }
- }
+#[cfg(all(c_bindings, not(test)))]
+impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> {
+ invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self);
+ invoice_builder_methods!(self, &mut Self, (), (), ExplicitSigningPubkey);
+}
- #[cfg(not(feature = "std"))] {
- if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
- return Err(Bolt12SemanticError::AlreadyExpired);
- }
+#[cfg(all(c_bindings, test))]
+impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> {
+ invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self);
+ invoice_builder_methods!(self, &mut Self, &mut Self, self, ExplicitSigningPubkey);
+}
+
+#[cfg(all(c_bindings, not(test)))]
+impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> {
+ invoice_derived_signing_pubkey_builder_methods!(self, &mut Self);
+ invoice_builder_methods!(self, &mut Self, (), (), DerivedSigningPubkey);
+}
+
+#[cfg(all(c_bindings, test))]
+impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> {
+ invoice_derived_signing_pubkey_builder_methods!(self, &mut Self);
+ invoice_builder_methods!(self, &mut Self, &mut Self, self, DerivedSigningPubkey);
+}
+
+#[cfg(c_bindings)]
+impl<'a> From<InvoiceWithExplicitSigningPubkeyBuilder<'a>>
+for InvoiceBuilder<'a, ExplicitSigningPubkey> {
+ fn from(builder: InvoiceWithExplicitSigningPubkeyBuilder<'a>) -> Self {
+ let InvoiceWithExplicitSigningPubkeyBuilder {
+ invreq_bytes, invoice, signing_pubkey_strategy,
+ } = builder;
+
+ Self {
+ invreq_bytes, invoice, signing_pubkey_strategy,
}
+ }
+}
- let InvoiceBuilder {
- invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys)
- } = self;
- let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice);
+#[cfg(c_bindings)]
+impl<'a> From<InvoiceWithDerivedSigningPubkeyBuilder<'a>>
+for InvoiceBuilder<'a, DerivedSigningPubkey> {
+ fn from(builder: InvoiceWithDerivedSigningPubkeyBuilder<'a>) -> Self {
+ let InvoiceWithDerivedSigningPubkeyBuilder {
+ invreq_bytes, invoice, signing_pubkey_strategy,
+ } = builder;
- let invoice = unsigned_invoice
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
- )
- .unwrap();
- Ok(invoice)
+ Self {
+ invreq_bytes, invoice, signing_pubkey_strategy,
+ }
}
}
tagged_hash: TaggedHash,
}
+/// A function for signing an [`UnsignedBolt12Invoice`].
+pub trait SignBolt12InvoiceFn {
+ /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
+ fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result<Signature, ()>;
+}
+
+impl<F> SignBolt12InvoiceFn for F
+where
+ F: Fn(&UnsignedBolt12Invoice) -> Result<Signature, ()>,
+{
+ fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result<Signature, ()> {
+ self(message)
+ }
+}
+
+impl<F> SignFn<UnsignedBolt12Invoice> for F
+where
+ F: SignBolt12InvoiceFn,
+{
+ fn sign(&self, message: &UnsignedBolt12Invoice) -> Result<Signature, ()> {
+ self.sign_invoice(message)
+ }
+}
+
impl UnsignedBolt12Invoice {
fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self {
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
pub fn tagged_hash(&self) -> &TaggedHash {
&self.tagged_hash
}
+}
+macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $self_mut: tt)?) => {
/// Signs the [`TaggedHash`] of the invoice using the given function.
///
/// Note: The hash computation may have included unknown, odd TLV records.
- ///
- /// This is not exported to bindings users as functions aren't currently mapped.
- pub fn sign<F, E>(mut self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
- where
- F: FnOnce(&Self) -> Result<Signature, E>
- {
- let pubkey = self.contents.fields().signing_pubkey;
- let signature = merkle::sign_message(sign, &self, pubkey)?;
+ pub fn sign<F: SignBolt12InvoiceFn>(
+ $($self_mut)* $self: $self_type, sign: F
+ ) -> Result<Bolt12Invoice, SignError> {
+ let pubkey = $self.contents.fields().signing_pubkey;
+ let signature = merkle::sign_message(sign, &$self, pubkey)?;
// Append the signature TLV record to the bytes.
let signature_tlv_stream = SignatureTlvStreamRef {
signature: Some(&signature),
};
- signature_tlv_stream.write(&mut self.bytes).unwrap();
+ signature_tlv_stream.write(&mut $self.bytes).unwrap();
Ok(Bolt12Invoice {
- bytes: self.bytes,
- contents: self.contents,
+ #[cfg(not(c_bindings))]
+ bytes: $self.bytes,
+ #[cfg(c_bindings)]
+ bytes: $self.bytes.clone(),
+ #[cfg(not(c_bindings))]
+ contents: $self.contents,
+ #[cfg(c_bindings)]
+ contents: $self.contents.clone(),
signature,
- tagged_hash: self.tagged_hash,
+ #[cfg(not(c_bindings))]
+ tagged_hash: $self.tagged_hash,
+ #[cfg(c_bindings)]
+ tagged_hash: $self.tagged_hash.clone(),
})
}
+} }
+
+#[cfg(not(c_bindings))]
+impl UnsignedBolt12Invoice {
+ unsigned_invoice_sign_method!(self, Self, mut);
+}
+
+#[cfg(c_bindings)]
+impl UnsignedBolt12Invoice {
+ unsigned_invoice_sign_method!(self, &mut Self);
}
impl AsRef<TaggedHash> for UnsignedBolt12Invoice {
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
- use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
+ use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
+ #[cfg(not(c_bindings))]
+ use {
+ crate::offers::offer::OfferBuilder,
+ crate::offers::refund::RefundBuilder,
+ };
+ #[cfg(c_bindings)]
+ use {
+ crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder,
+ crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder as RefundBuilder,
+ };
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
- use crate::offers::refund::RefundBuilder;
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Iterable, Writeable};
use crate::util::string::PrintableString;
},
}
+ #[cfg(c_bindings)]
+ let mut unsigned_invoice = unsigned_invoice;
let invoice = unsigned_invoice.sign(recipient_sign).unwrap();
let mut buffer = Vec::new();
],
};
+ #[cfg(c_bindings)]
+ use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder;
let offer = OfferBuilder
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
.amount_msats(1000)
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
- .sign(|_| Err(()))
+ .sign(fail_sign)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SignError::Signing(())),
+ Err(e) => assert_eq!(e, SignError::Signing),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
+ #[cfg(not(c_bindings))]
+ let invoice_builder = invoice_request
+ .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap();
+ #[cfg(c_bindings)]
let mut invoice_builder = invoice_request
- .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
+ .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap();
+ let invoice_builder = invoice_builder
.fallback_v0_p2wsh(&script.wscript_hash())
.fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap())
.fallback_v1_p2tr_tweaked(&tweaked_pubkey);
+ #[cfg(not(c_bindings))]
+ let mut invoice_builder = invoice_builder;
// Only standard addresses will be included.
let fallbacks = invoice_builder.invoice.fields_mut().fallbacks.as_mut().unwrap();
//!
//! use bitcoin::network::constants::Network;
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
-//! use core::convert::Infallible;
//! use lightning::ln::features::OfferFeatures;
+//! use lightning::offers::invoice_request::UnsignedInvoiceRequest;
//! use lightning::offers::offer::Offer;
//! use lightning::util::ser::Writeable;
//!
//! let pubkey = PublicKey::from(keys);
//! let mut buffer = Vec::new();
//!
+//! # use lightning::offers::invoice_request::{ExplicitPayerId, InvoiceRequestBuilder};
+//! # <InvoiceRequestBuilder<ExplicitPayerId, _>>::from(
//! "lno1qcp4256ypq"
//! .parse::<Offer>()?
//! .request_invoice(vec![42; 64], pubkey)?
+//! # )
//! .chain(Network::Testnet)?
//! .amount_msats(1000)?
//! .quantity(5)?
//! .payer_note("foo".to_string())
//! .build()?
-//! .sign::<_, Infallible>(
-//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+//! .sign(|message: &UnsignedInvoiceRequest|
+//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
//! )
//! .expect("failed verifying signature")
//! .write(&mut buffer)
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
-use core::convert::{AsRef, Infallible, TryFrom};
+use core::convert::{AsRef, TryFrom};
use core::ops::Deref;
use crate::sign::EntropySource;
use crate::io;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::DecodeError;
-use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self};
+use crate::offers::invoice::BlindedPayInfo;
+use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self};
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
+#[cfg(not(c_bindings))]
+use {
+ crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder},
+};
+#[cfg(c_bindings)]
+use {
+ crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder},
+};
+
use crate::prelude::*;
/// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root.
secp_ctx: Option<&'b Secp256k1<T>>,
}
+/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// [module-level documentation]: self
+#[cfg(c_bindings)]
+pub struct InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> {
+ offer: &'a Offer,
+ invoice_request: InvoiceRequestContentsWithoutPayerId,
+ payer_id: Option<PublicKey>,
+ payer_id_strategy: core::marker::PhantomData<ExplicitPayerId>,
+ secp_ctx: Option<&'b Secp256k1<secp256k1::All>>,
+}
+
+/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// [module-level documentation]: self
+#[cfg(c_bindings)]
+pub struct InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> {
+ offer: &'a Offer,
+ invoice_request: InvoiceRequestContentsWithoutPayerId,
+ payer_id: Option<PublicKey>,
+ payer_id_strategy: core::marker::PhantomData<DerivedPayerId>,
+ secp_ctx: Option<&'b Secp256k1<secp256k1::All>>,
+}
+
/// Indicates how [`InvoiceRequest::payer_id`] will be set.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
impl PayerIdStrategy for ExplicitPayerId {}
impl PayerIdStrategy for DerivedPayerId {}
-impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
+macro_rules! invoice_request_explicit_payer_id_builder_methods { ($self: ident, $self_type: ty) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn new(offer: &'a Offer, metadata: Vec<u8>, payer_id: PublicKey) -> Self {
Self {
offer,
}
}
+ #[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn deriving_metadata<ES: Deref>(
offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
payment_id: PaymentId,
secp_ctx: None,
}
}
-}
-impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
+ /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
+ /// by [`UnsignedInvoiceRequest::sign`].
+ pub fn build($self: $self_type) -> Result<UnsignedInvoiceRequest, Bolt12SemanticError> {
+ let (unsigned_invoice_request, keys, _) = $self.build_with_checks()?;
+ debug_assert!(keys.is_none());
+ Ok(unsigned_invoice_request)
+ }
+} }
+
+macro_rules! invoice_request_derived_payer_id_builder_methods { (
+ $self: ident, $self_type: ty, $secp_context: ty
+) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn deriving_payer_id<ES: Deref>(
offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES,
- secp_ctx: &'b Secp256k1<T>, payment_id: PaymentId
+ secp_ctx: &'b Secp256k1<$secp_context>, payment_id: PaymentId
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
let payment_id = Some(payment_id);
secp_ctx: Some(secp_ctx),
}
}
-}
-impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> {
+ /// Builds a signed [`InvoiceRequest`] after checking for valid semantics.
+ pub fn build_and_sign($self: $self_type) -> Result<InvoiceRequest, Bolt12SemanticError> {
+ let (unsigned_invoice_request, keys, secp_ctx) = $self.build_with_checks()?;
+ #[cfg(c_bindings)]
+ let mut unsigned_invoice_request = unsigned_invoice_request;
+ debug_assert!(keys.is_some());
+
+ let secp_ctx = secp_ctx.unwrap();
+ let keys = keys.unwrap();
+ let invoice_request = unsigned_invoice_request
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ )
+ .unwrap();
+ Ok(invoice_request)
+ }
+} }
+
+macro_rules! invoice_request_builder_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $secp_context: ty $(, $self_mut: tt)?
+) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
fn create_contents(offer: &Offer, metadata: Metadata) -> InvoiceRequestContentsWithoutPayerId {
let offer = offer.contents.clone();
InvoiceRequestContentsWithoutPayerId {
/// by the offer.
///
/// Successive calls to this method will override the previous setting.
- pub fn chain(self, network: Network) -> Result<Self, Bolt12SemanticError> {
- self.chain_hash(ChainHash::using_genesis_block(network))
+ pub fn chain($self: $self_type, network: Network) -> Result<$return_type, Bolt12SemanticError> {
+ $self.chain_hash(ChainHash::using_genesis_block(network))
}
/// Sets the [`InvoiceRequest::chain`] for paying an invoice. If not called, the chain hash of
/// offer.
///
/// Successive calls to this method will override the previous setting.
- pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Result<Self, Bolt12SemanticError> {
- if !self.offer.supports_chain(chain) {
+ pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> Result<$return_type, Bolt12SemanticError> {
+ if !$self.offer.supports_chain(chain) {
return Err(Bolt12SemanticError::UnsupportedChain);
}
- self.invoice_request.chain = Some(chain);
- Ok(self)
+ $self.invoice_request.chain = Some(chain);
+ Ok($return_value)
}
/// Sets the [`InvoiceRequest::amount_msats`] for paying an invoice. Errors if `amount_msats` is
/// Successive calls to this method will override the previous setting.
///
/// [`quantity`]: Self::quantity
- pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, Bolt12SemanticError> {
- self.invoice_request.offer.check_amount_msats_for_quantity(
- Some(amount_msats), self.invoice_request.quantity
+ pub fn amount_msats($($self_mut)* $self: $self_type, amount_msats: u64) -> Result<$return_type, Bolt12SemanticError> {
+ $self.invoice_request.offer.check_amount_msats_for_quantity(
+ Some(amount_msats), $self.invoice_request.quantity
)?;
- self.invoice_request.amount_msats = Some(amount_msats);
- Ok(self)
+ $self.invoice_request.amount_msats = Some(amount_msats);
+ Ok($return_value)
}
/// Sets [`InvoiceRequest::quantity`] of items. If not set, `1` is assumed. Errors if `quantity`
/// does not conform to [`Offer::is_valid_quantity`].
///
/// Successive calls to this method will override the previous setting.
- pub fn quantity(mut self, quantity: u64) -> Result<Self, Bolt12SemanticError> {
- self.invoice_request.offer.check_quantity(Some(quantity))?;
- self.invoice_request.quantity = Some(quantity);
- Ok(self)
+ pub fn quantity($($self_mut)* $self: $self_type, quantity: u64) -> Result<$return_type, Bolt12SemanticError> {
+ $self.invoice_request.offer.check_quantity(Some(quantity))?;
+ $self.invoice_request.quantity = Some(quantity);
+ Ok($return_value)
}
/// Sets the [`InvoiceRequest::payer_note`].
///
/// Successive calls to this method will override the previous setting.
- pub fn payer_note(mut self, payer_note: String) -> Self {
- self.invoice_request.payer_note = Some(payer_note);
- self
+ pub fn payer_note($($self_mut)* $self: $self_type, payer_note: String) -> $return_type {
+ $self.invoice_request.payer_note = Some(payer_note);
+ $return_value
}
- fn build_with_checks(mut self) -> Result<
- (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<T>>),
+ fn build_with_checks($($self_mut)* $self: $self_type) -> Result<
+ (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<$secp_context>>),
Bolt12SemanticError
> {
#[cfg(feature = "std")] {
- if self.offer.is_expired() {
+ if $self.offer.is_expired() {
return Err(Bolt12SemanticError::AlreadyExpired);
}
}
- let chain = self.invoice_request.chain();
- if !self.offer.supports_chain(chain) {
+ let chain = $self.invoice_request.chain();
+ if !$self.offer.supports_chain(chain) {
return Err(Bolt12SemanticError::UnsupportedChain);
}
- if chain == self.offer.implied_chain() {
- self.invoice_request.chain = None;
+ if chain == $self.offer.implied_chain() {
+ $self.invoice_request.chain = None;
}
- if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() {
+ if $self.offer.amount().is_none() && $self.invoice_request.amount_msats.is_none() {
return Err(Bolt12SemanticError::MissingAmount);
}
- self.invoice_request.offer.check_quantity(self.invoice_request.quantity)?;
- self.invoice_request.offer.check_amount_msats_for_quantity(
- self.invoice_request.amount_msats, self.invoice_request.quantity
+ $self.invoice_request.offer.check_quantity($self.invoice_request.quantity)?;
+ $self.invoice_request.offer.check_amount_msats_for_quantity(
+ $self.invoice_request.amount_msats, $self.invoice_request.quantity
)?;
- Ok(self.build_without_checks())
+ Ok($self.build_without_checks())
}
- fn build_without_checks(mut self) ->
- (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<T>>)
+ fn build_without_checks($($self_mut)* $self: $self_type) ->
+ (UnsignedInvoiceRequest, Option<KeyPair>, Option<&'b Secp256k1<$secp_context>>)
{
// Create the metadata for stateless verification of a Bolt12Invoice.
let mut keys = None;
- let secp_ctx = self.secp_ctx.clone();
- if self.invoice_request.payer.0.has_derivation_material() {
- let mut metadata = core::mem::take(&mut self.invoice_request.payer.0);
+ let secp_ctx = $self.secp_ctx.clone();
+ if $self.invoice_request.payer.0.has_derivation_material() {
+ let mut metadata = core::mem::take(&mut $self.invoice_request.payer.0);
- let mut tlv_stream = self.invoice_request.as_tlv_stream();
+ let mut tlv_stream = $self.invoice_request.as_tlv_stream();
debug_assert!(tlv_stream.2.payer_id.is_none());
tlv_stream.0.metadata = None;
if !metadata.derives_payer_keys() {
- tlv_stream.2.payer_id = self.payer_id.as_ref();
+ tlv_stream.2.payer_id = $self.payer_id.as_ref();
}
- let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, self.secp_ctx);
+ let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, $self.secp_ctx);
metadata = derived_metadata;
keys = derived_keys;
if let Some(keys) = keys {
- debug_assert!(self.payer_id.is_none());
- self.payer_id = Some(keys.public_key());
+ debug_assert!($self.payer_id.is_none());
+ $self.payer_id = Some(keys.public_key());
}
- self.invoice_request.payer.0 = metadata;
+ $self.invoice_request.payer.0 = metadata;
}
- debug_assert!(self.invoice_request.payer.0.as_bytes().is_some());
- debug_assert!(self.payer_id.is_some());
- let payer_id = self.payer_id.unwrap();
+ debug_assert!($self.invoice_request.payer.0.as_bytes().is_some());
+ debug_assert!($self.payer_id.is_some());
+ let payer_id = $self.payer_id.unwrap();
let invoice_request = InvoiceRequestContents {
- inner: self.invoice_request,
+ #[cfg(not(c_bindings))]
+ inner: $self.invoice_request,
+ #[cfg(c_bindings)]
+ inner: $self.invoice_request.clone(),
payer_id,
};
- let unsigned_invoice_request = UnsignedInvoiceRequest::new(self.offer, invoice_request);
+ let unsigned_invoice_request = UnsignedInvoiceRequest::new($self.offer, invoice_request);
(unsigned_invoice_request, keys, secp_ctx)
}
-}
+} }
-impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
- /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
- /// by [`UnsignedInvoiceRequest::sign`].
- pub fn build(self) -> Result<UnsignedInvoiceRequest, Bolt12SemanticError> {
- let (unsigned_invoice_request, keys, _) = self.build_with_checks()?;
- debug_assert!(keys.is_none());
- Ok(unsigned_invoice_request)
+#[cfg(test)]
+macro_rules! invoice_request_builder_test_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)?
+) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ fn chain_unchecked($($self_mut)* $self: $self_type, network: Network) -> $return_type {
+ let chain = ChainHash::using_genesis_block(network);
+ $self.invoice_request.chain = Some(chain);
+ $return_value
}
-}
-impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
- /// Builds a signed [`InvoiceRequest`] after checking for valid semantics.
- pub fn build_and_sign(self) -> Result<InvoiceRequest, Bolt12SemanticError> {
- let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?;
- debug_assert!(keys.is_some());
-
- let secp_ctx = secp_ctx.unwrap();
- let keys = keys.unwrap();
- let invoice_request = unsigned_invoice_request
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
- )
- .unwrap();
- Ok(invoice_request)
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ fn amount_msats_unchecked($($self_mut)* $self: $self_type, amount_msats: u64) -> $return_type {
+ $self.invoice_request.amount_msats = Some(amount_msats);
+ $return_value
}
-}
-#[cfg(test)]
-impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> {
- fn chain_unchecked(mut self, network: Network) -> Self {
- let chain = ChainHash::using_genesis_block(network);
- self.invoice_request.chain = Some(chain);
- self
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ fn features_unchecked($($self_mut)* $self: $self_type, features: InvoiceRequestFeatures) -> $return_type {
+ $self.invoice_request.features = features;
+ $return_value
}
- fn amount_msats_unchecked(mut self, amount_msats: u64) -> Self {
- self.invoice_request.amount_msats = Some(amount_msats);
- self
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ fn quantity_unchecked($($self_mut)* $self: $self_type, quantity: u64) -> $return_type {
+ $self.invoice_request.quantity = Some(quantity);
+ $return_value
}
- fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
- self.invoice_request.features = features;
- self
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ pub(super) fn build_unchecked($self: $self_type) -> UnsignedInvoiceRequest {
+ $self.build_without_checks().0
}
+} }
+
+impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
+ invoice_request_explicit_payer_id_builder_methods!(self, Self);
+}
+
+impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
+ invoice_request_derived_payer_id_builder_methods!(self, Self, T);
+}
+
+impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> {
+ invoice_request_builder_methods!(self, Self, Self, self, T, mut);
+
+ #[cfg(test)]
+ invoice_request_builder_test_methods!(self, Self, Self, self, mut);
+}
+
+#[cfg(all(c_bindings, not(test)))]
+impl<'a, 'b> InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> {
+ invoice_request_explicit_payer_id_builder_methods!(self, &mut Self);
+ invoice_request_builder_methods!(self, &mut Self, (), (), secp256k1::All);
+}
+
+#[cfg(all(c_bindings, test))]
+impl<'a, 'b> InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> {
+ invoice_request_explicit_payer_id_builder_methods!(self, &mut Self);
+ invoice_request_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All);
+ invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self);
+}
+
+#[cfg(all(c_bindings, not(test)))]
+impl<'a, 'b> InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> {
+ invoice_request_derived_payer_id_builder_methods!(self, &mut Self, secp256k1::All);
+ invoice_request_builder_methods!(self, &mut Self, (), (), secp256k1::All);
+}
+
+#[cfg(all(c_bindings, test))]
+impl<'a, 'b> InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> {
+ invoice_request_derived_payer_id_builder_methods!(self, &mut Self, secp256k1::All);
+ invoice_request_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All);
+ invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self);
+}
- fn quantity_unchecked(mut self, quantity: u64) -> Self {
- self.invoice_request.quantity = Some(quantity);
- self
+#[cfg(c_bindings)]
+impl<'a, 'b> From<InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b>>
+for InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, secp256k1::All> {
+ fn from(builder: InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b>) -> Self {
+ let InvoiceRequestWithExplicitPayerIdBuilder {
+ offer, invoice_request, payer_id, payer_id_strategy, secp_ctx,
+ } = builder;
+
+ Self {
+ offer, invoice_request, payer_id, payer_id_strategy, secp_ctx,
+ }
}
+}
+
+#[cfg(c_bindings)]
+impl<'a, 'b> From<InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b>>
+for InvoiceRequestBuilder<'a, 'b, DerivedPayerId, secp256k1::All> {
+ fn from(builder: InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b>) -> Self {
+ let InvoiceRequestWithDerivedPayerIdBuilder {
+ offer, invoice_request, payer_id, payer_id_strategy, secp_ctx,
+ } = builder;
- pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest {
- self.build_without_checks().0
+ Self {
+ offer, invoice_request, payer_id, payer_id_strategy, secp_ctx,
+ }
}
}
tagged_hash: TaggedHash,
}
+/// A function for signing an [`UnsignedInvoiceRequest`].
+pub trait SignInvoiceRequestFn {
+ /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
+ fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result<Signature, ()>;
+}
+
+impl<F> SignInvoiceRequestFn for F
+where
+ F: Fn(&UnsignedInvoiceRequest) -> Result<Signature, ()>,
+{
+ fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result<Signature, ()> {
+ self(message)
+ }
+}
+
+impl<F> SignFn<UnsignedInvoiceRequest> for F
+where
+ F: SignInvoiceRequestFn,
+{
+ fn sign(&self, message: &UnsignedInvoiceRequest) -> Result<Signature, ()> {
+ self.sign_invoice_request(message)
+ }
+}
+
impl UnsignedInvoiceRequest {
fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self {
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
pub fn tagged_hash(&self) -> &TaggedHash {
&self.tagged_hash
}
+}
+macro_rules! unsigned_invoice_request_sign_method { (
+ $self: ident, $self_type: ty $(, $self_mut: tt)?
+) => {
/// Signs the [`TaggedHash`] of the invoice request using the given function.
///
/// Note: The hash computation may have included unknown, odd TLV records.
- ///
- /// This is not exported to bindings users as functions are not yet mapped.
- pub fn sign<F, E>(mut self, sign: F) -> Result<InvoiceRequest, SignError<E>>
- where
- F: FnOnce(&Self) -> Result<Signature, E>
- {
- let pubkey = self.contents.payer_id;
- let signature = merkle::sign_message(sign, &self, pubkey)?;
+ pub fn sign<F: SignInvoiceRequestFn>(
+ $($self_mut)* $self: $self_type, sign: F
+ ) -> Result<InvoiceRequest, SignError> {
+ let pubkey = $self.contents.payer_id;
+ let signature = merkle::sign_message(sign, &$self, pubkey)?;
// Append the signature TLV record to the bytes.
let signature_tlv_stream = SignatureTlvStreamRef {
signature: Some(&signature),
};
- signature_tlv_stream.write(&mut self.bytes).unwrap();
+ signature_tlv_stream.write(&mut $self.bytes).unwrap();
Ok(InvoiceRequest {
- bytes: self.bytes,
- contents: self.contents,
+ #[cfg(not(c_bindings))]
+ bytes: $self.bytes,
+ #[cfg(c_bindings)]
+ bytes: $self.bytes.clone(),
+ #[cfg(not(c_bindings))]
+ contents: $self.contents,
+ #[cfg(c_bindings)]
+ contents: $self.contents.clone(),
signature,
})
}
+} }
+
+#[cfg(not(c_bindings))]
+impl UnsignedInvoiceRequest {
+ unsigned_invoice_request_sign_method!(self, Self, mut);
+}
+
+#[cfg(c_bindings)]
+impl UnsignedInvoiceRequest {
+ unsigned_invoice_request_sign_method!(self, &mut Self);
}
impl AsRef<TaggedHash> for UnsignedInvoiceRequest {
invoice_request_accessors!(self, self.contents);
}
-impl InvoiceRequest {
- offer_accessors!(self, self.contents.inner.offer);
- invoice_request_accessors!(self, self.contents);
-
- /// Signature of the invoice request using [`payer_id`].
- ///
- /// [`payer_id`]: Self::payer_id
- pub fn signature(&self) -> Signature {
- self.signature
- }
-
+macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
+ $self: ident, $contents: expr, $builder: ty
+) => {
/// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
/// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
///
/// See [`InvoiceRequest::respond_with_no_std`] for further details where the aforementioned
/// creation time is used for the `created_at` parameter.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`Duration`]: core::time::Duration
#[cfg(feature = "std")]
pub fn respond_with(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
+ ) -> Result<$builder, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
- self.respond_with_no_std(payment_paths, payment_hash, created_at)
+ $contents.respond_with_no_std(payment_paths, payment_hash, created_at)
}
/// Creates an [`InvoiceBuilder`] for the request with the given required fields.
/// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`],
/// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
/// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey
pub fn respond_with_no_std(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
- if self.invoice_request_features().requires_unknown_bits() {
+ ) -> Result<$builder, Bolt12SemanticError> {
+ if $contents.invoice_request_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
+ <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash)
}
+} }
+macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => {
/// Verifies that the request was for an offer created using the given key. Returns the verified
/// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request
/// if they could be extracted from the metadata.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
- pub fn verify<T: secp256k1::Signing>(
- self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ pub fn verify<
+ #[cfg(not(c_bindings))]
+ T: secp256k1::Signing
+ >(
+ $self: $self_type, key: &ExpandedKey,
+ #[cfg(not(c_bindings))]
+ secp_ctx: &Secp256k1<T>,
+ #[cfg(c_bindings)]
+ secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<VerifiedInvoiceRequest, ()> {
- let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?;
+ let keys = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?;
Ok(VerifiedInvoiceRequest {
- inner: self,
+ #[cfg(not(c_bindings))]
+ inner: $self,
+ #[cfg(c_bindings)]
+ inner: $self.clone(),
keys,
})
}
+} }
+
+#[cfg(not(c_bindings))]
+impl InvoiceRequest {
+ offer_accessors!(self, self.contents.inner.offer);
+ invoice_request_accessors!(self, self.contents);
+ invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceBuilder<ExplicitSigningPubkey>);
+ invoice_request_verify_method!(self, Self);
+}
+
+#[cfg(c_bindings)]
+impl InvoiceRequest {
+ offer_accessors!(self, self.contents.inner.offer);
+ invoice_request_accessors!(self, self.contents);
+ invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceWithExplicitSigningPubkeyBuilder);
+ invoice_request_verify_method!(self, &Self);
+}
+
+impl InvoiceRequest {
+ /// Signature of the invoice request using [`payer_id`].
+ ///
+ /// [`payer_id`]: Self::payer_id
+ pub fn signature(&self) -> Signature {
+ self.signature
+ }
+
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
self.contents.as_tlv_stream();
}
}
-impl VerifiedInvoiceRequest {
- offer_accessors!(self, self.inner.contents.inner.offer);
- invoice_request_accessors!(self, self.inner.contents);
-
- /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the
- /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
- ///
- /// See [`InvoiceRequest::respond_with_no_std`] for further details.
- ///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
- /// [`Duration`]: core::time::Duration
- #[cfg(feature = "std")]
- pub fn respond_with(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
- self.inner.respond_with(payment_paths, payment_hash)
- }
-
- /// Creates an [`InvoiceBuilder`] for the request with the given required fields.
- ///
- /// See [`InvoiceRequest::respond_with_no_std`] for further details.
- ///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- pub fn respond_with_no_std(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
- created_at: core::time::Duration
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
- self.inner.respond_with_no_std(payment_paths, payment_hash, created_at)
- }
-
+macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { (
+ $self: ident, $contents: expr, $builder: ty
+) => {
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
/// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use
/// the same [`ExpandedKey`] as the one used to create the offer.
///
/// See [`InvoiceRequest::respond_with`] for further details.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
pub fn respond_using_derived_keys(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
+ ) -> Result<$builder, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
- self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
+ $self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at)
}
/// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
///
/// See [`InvoiceRequest::respond_with_no_std`] for further details.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn respond_using_derived_keys_no_std(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
- if self.inner.invoice_request_features().requires_unknown_bits() {
+ ) -> Result<$builder, Bolt12SemanticError> {
+ if $self.inner.invoice_request_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- let keys = match self.keys {
+ let keys = match $self.keys {
None => return Err(Bolt12SemanticError::InvalidMetadata),
Some(keys) => keys,
};
- InvoiceBuilder::for_offer_using_keys(
- &self.inner, payment_paths, created_at, payment_hash, keys
+ <$builder>::for_offer_using_keys(
+ &$self.inner, payment_paths, created_at, payment_hash, keys
)
}
+} }
+
+impl VerifiedInvoiceRequest {
+ offer_accessors!(self, self.inner.contents.inner.offer);
+ invoice_request_accessors!(self, self.inner.contents);
+ #[cfg(not(c_bindings))]
+ invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceBuilder<ExplicitSigningPubkey>);
+ #[cfg(c_bindings)]
+ invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceWithExplicitSigningPubkeyBuilder);
+ #[cfg(not(c_bindings))]
+ invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceBuilder<DerivedSigningPubkey>);
+ #[cfg(c_bindings)]
+ invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceWithDerivedSigningPubkeyBuilder);
}
impl InvoiceRequestContents {
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self};
- use core::convert::{Infallible, TryFrom};
+ use core::convert::TryFrom;
use core::num::NonZeroU64;
#[cfg(feature = "std")]
use core::time::Duration;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
- use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
+ use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
+ #[cfg(not(c_bindings))]
+ use {
+ crate::offers::offer::OfferBuilder,
+ };
+ #[cfg(c_bindings)]
+ use {
+ crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder,
+ };
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::test_utils::*;
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap();
+ #[cfg(c_bindings)]
+ let mut unsigned_invoice_request = unsigned_invoice_request;
let mut buffer = Vec::new();
unsigned_invoice_request.write(&mut buffer).unwrap();
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
- .sign(|_| Err(()))
+ .sign(fail_sign)
{
Ok(_) => panic!("expected error"),
- Err(e) => assert_eq!(e, SignError::Signing(())),
+ Err(e) => assert_eq!(e, SignError::Signing),
}
match OfferBuilder::new("foo".into(), recipient_pubkey())
.build().unwrap()
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
.build().unwrap()
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
/// Error when signing messages.
#[derive(Debug, PartialEq)]
-pub enum SignError<E> {
+pub enum SignError {
/// User-defined error when signing the message.
- Signing(E),
+ Signing,
/// Error when verifying the produced signature using the given pubkey.
Verification(secp256k1::Error),
}
+/// A function for signing a [`TaggedHash`].
+pub(super) trait SignFn<T: AsRef<TaggedHash>> {
+ /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
+ fn sign(&self, message: &T) -> Result<Signature, ()>;
+}
+
+impl<F> SignFn<TaggedHash> for F
+where
+ F: Fn(&TaggedHash) -> Result<Signature, ()>,
+{
+ fn sign(&self, message: &TaggedHash) -> Result<Signature, ()> {
+ self(message)
+ }
+}
+
/// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream, checking if it
/// can be verified with the supplied `pubkey`.
///
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-pub(super) fn sign_message<F, E, T>(
- sign: F, message: &T, pubkey: PublicKey,
-) -> Result<Signature, SignError<E>>
+pub(super) fn sign_message<F, T>(
+ f: F, message: &T, pubkey: PublicKey,
+) -> Result<Signature, SignError>
where
- F: FnOnce(&T) -> Result<Signature, E>,
+ F: SignFn<T>,
T: AsRef<TaggedHash>,
{
- let signature = sign(message).map_err(|e| SignError::Signing(e))?;
+ let signature = f.sign(message).map_err(|()| SignError::Signing)?;
let digest = message.as_ref().as_digest();
let pubkey = pubkey.into();
use bitcoin::hashes::hex::FromHex;
use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey};
use bitcoin::secp256k1::schnorr::Signature;
- use core::convert::Infallible;
use crate::offers::offer::{Amount, OfferBuilder};
- use crate::offers::invoice_request::InvoiceRequest;
+ use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest};
use crate::offers::parse::Bech32Encode;
use crate::offers::test_utils::{payer_pubkey, recipient_pubkey};
use crate::util::ser::Writeable;
.build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
.build_unchecked()
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
)
.unwrap();
assert_eq!(
.build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
.build_unchecked()
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
)
.unwrap();
.build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
.build_unchecked()
- .sign::<_, Infallible>(
- |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
)
.unwrap();
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
use core::convert::TryFrom;
+use core::hash::{Hash, Hasher};
use core::num::NonZeroU64;
use core::ops::Deref;
use core::str::FromStr;
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::MAX_VALUE_MSAT;
-use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
use crate::offers::merkle::TlvStream;
use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
+#[cfg(not(c_bindings))]
+use {
+ crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder},
+};
+#[cfg(c_bindings)]
+use {
+ crate::offers::invoice_request::{InvoiceRequestWithDerivedPayerIdBuilder, InvoiceRequestWithExplicitPayerIdBuilder},
+};
+
use crate::prelude::*;
#[cfg(feature = "std")]
secp_ctx: Option<&'a Secp256k1<T>>,
}
+/// Builds an [`Offer`] for the "offer to be paid" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+///
+/// [module-level documentation]: self
+#[cfg(c_bindings)]
+pub struct OfferWithExplicitMetadataBuilder<'a> {
+ offer: OfferContents,
+ metadata_strategy: core::marker::PhantomData<ExplicitMetadata>,
+ secp_ctx: Option<&'a Secp256k1<secp256k1::All>>,
+}
+
+/// Builds an [`Offer`] for the "offer to be paid" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+///
+/// [module-level documentation]: self
+#[cfg(c_bindings)]
+pub struct OfferWithDerivedMetadataBuilder<'a> {
+ offer: OfferContents,
+ metadata_strategy: core::marker::PhantomData<DerivedMetadata>,
+ secp_ctx: Option<&'a Secp256k1<secp256k1::All>>,
+}
+
/// Indicates how [`Offer::metadata`] may be set.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
pub struct DerivedMetadata {}
impl MetadataStrategy for ExplicitMetadata {}
+
impl MetadataStrategy for DerivedMetadata {}
-impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> {
+macro_rules! offer_explicit_metadata_builder_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr
+) => {
/// Creates a new builder for an offer setting the [`Offer::description`] and using the
/// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
/// while the offer is valid.
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
/// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
- OfferBuilder {
+ Self {
offer: OfferContents {
chains: None, metadata: None, amount: None, description,
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
/// Sets the [`Offer::metadata`] to the given bytes.
///
/// Successive calls to this method will override the previous setting.
- pub fn metadata(mut self, metadata: Vec<u8>) -> Result<Self, Bolt12SemanticError> {
- self.offer.metadata = Some(Metadata::Bytes(metadata));
- Ok(self)
+ pub fn metadata(mut $self: $self_type, metadata: Vec<u8>) -> Result<$return_type, Bolt12SemanticError> {
+ $self.offer.metadata = Some(Metadata::Bytes(metadata));
+ Ok($return_value)
}
-}
+} }
-impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
+macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => {
/// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing
/// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides
/// recipient privacy by using a different signing pubkey for each offer. Otherwise, the
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn deriving_signing_pubkey<ES: Deref>(
description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
- secp_ctx: &'a Secp256k1<T>
+ secp_ctx: &'a Secp256k1<$secp_context>
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
- OfferBuilder {
+ Self {
offer: OfferContents {
chains: None, metadata: Some(metadata), amount: None, description,
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
secp_ctx: Some(secp_ctx),
}
}
-}
+} }
-impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
+macro_rules! offer_builder_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)?
+) => {
/// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
/// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported.
///
/// See [`Offer::chains`] on how this relates to the payment currency.
///
/// Successive calls to this method will add another chain hash.
- pub fn chain(self, network: Network) -> Self {
- self.chain_hash(ChainHash::using_genesis_block(network))
+ pub fn chain($self: $self_type, network: Network) -> $return_type {
+ $self.chain_hash(ChainHash::using_genesis_block(network))
}
/// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of
/// See [`Offer::chains`] on how this relates to the payment currency.
///
/// Successive calls to this method will add another chain hash.
- pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
- let chains = self.offer.chains.get_or_insert_with(Vec::new);
+ pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type {
+ let chains = $self.offer.chains.get_or_insert_with(Vec::new);
if !chains.contains(&chain) {
chains.push(chain);
}
- self
+ $return_value
}
/// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`].
///
/// Successive calls to this method will override the previous setting.
- pub fn amount_msats(self, amount_msats: u64) -> Self {
- self.amount(Amount::Bitcoin { amount_msats })
+ pub fn amount_msats($self: $self_type, amount_msats: u64) -> $return_type {
+ $self.amount(Amount::Bitcoin { amount_msats })
}
/// Sets the [`Offer::amount`].
///
/// Successive calls to this method will override the previous setting.
- pub(super) fn amount(mut self, amount: Amount) -> Self {
- self.offer.amount = Some(amount);
- self
+ pub(super) fn amount($($self_mut)* $self: $self_type, amount: Amount) -> $return_type {
+ $self.offer.amount = Some(amount);
+ $return_value
}
/// Sets the [`Offer::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
/// already passed is valid and can be checked for using [`Offer::is_expired`].
///
/// Successive calls to this method will override the previous setting.
- pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self {
- self.offer.absolute_expiry = Some(absolute_expiry);
- self
+ pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type {
+ $self.offer.absolute_expiry = Some(absolute_expiry);
+ $return_value
}
/// Sets the [`Offer::issuer`].
///
/// Successive calls to this method will override the previous setting.
- pub fn issuer(mut self, issuer: String) -> Self {
- self.offer.issuer = Some(issuer);
- self
+ pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type {
+ $self.offer.issuer = Some(issuer);
+ $return_value
}
/// Adds a blinded path to [`Offer::paths`]. Must include at least one path if only connected by
///
/// Successive calls to this method will add another blinded path. Caller is responsible for not
/// adding duplicate paths.
- pub fn path(mut self, path: BlindedPath) -> Self {
- self.offer.paths.get_or_insert_with(Vec::new).push(path);
- self
+ pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type {
+ $self.offer.paths.get_or_insert_with(Vec::new).push(path);
+ $return_value
}
/// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to
/// [`Quantity::One`].
///
/// Successive calls to this method will override the previous setting.
- pub fn supported_quantity(mut self, quantity: Quantity) -> Self {
- self.offer.supported_quantity = quantity;
- self
+ pub fn supported_quantity($($self_mut)* $self: $self_type, quantity: Quantity) -> $return_type {
+ $self.offer.supported_quantity = quantity;
+ $return_value
}
/// Builds an [`Offer`] from the builder's settings.
- pub fn build(mut self) -> Result<Offer, Bolt12SemanticError> {
- match self.offer.amount {
+ pub fn build($($self_mut)* $self: $self_type) -> Result<Offer, Bolt12SemanticError> {
+ match $self.offer.amount {
Some(Amount::Bitcoin { amount_msats }) => {
if amount_msats > MAX_VALUE_MSAT {
return Err(Bolt12SemanticError::InvalidAmount);
None => {},
}
- if let Some(chains) = &self.offer.chains {
- if chains.len() == 1 && chains[0] == self.offer.implied_chain() {
- self.offer.chains = None;
+ if let Some(chains) = &$self.offer.chains {
+ if chains.len() == 1 && chains[0] == $self.offer.implied_chain() {
+ $self.offer.chains = None;
}
}
- Ok(self.build_without_checks())
+ Ok($self.build_without_checks())
}
- fn build_without_checks(mut self) -> Offer {
+ fn build_without_checks($($self_mut)* $self: $self_type) -> Offer {
// Create the metadata for stateless verification of an InvoiceRequest.
- if let Some(mut metadata) = self.offer.metadata.take() {
+ if let Some(mut metadata) = $self.offer.metadata.take() {
if metadata.has_derivation_material() {
- if self.offer.paths.is_none() {
+ if $self.offer.paths.is_none() {
metadata = metadata.without_keys();
}
- let mut tlv_stream = self.offer.as_tlv_stream();
+ let mut tlv_stream = $self.offer.as_tlv_stream();
debug_assert_eq!(tlv_stream.metadata, None);
tlv_stream.metadata = None;
if metadata.derives_recipient_keys() {
tlv_stream.node_id = None;
}
- let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx);
+ let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx);
metadata = derived_metadata;
if let Some(keys) = keys {
- self.offer.signing_pubkey = keys.public_key();
+ $self.offer.signing_pubkey = keys.public_key();
}
}
- self.offer.metadata = Some(metadata);
+ $self.offer.metadata = Some(metadata);
}
let mut bytes = Vec::new();
- self.offer.write(&mut bytes).unwrap();
-
- Offer { bytes, contents: self.offer }
+ $self.offer.write(&mut bytes).unwrap();
+
+ Offer {
+ bytes,
+ #[cfg(not(c_bindings))]
+ contents: $self.offer,
+ #[cfg(c_bindings)]
+ contents: $self.offer.clone()
+ }
}
-}
+} }
#[cfg(test)]
-impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
- fn features_unchecked(mut self, features: OfferFeatures) -> Self {
- self.offer.features = features;
- self
+macro_rules! offer_builder_test_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)?
+) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ fn features_unchecked($($self_mut)* $self: $self_type, features: OfferFeatures) -> $return_type {
+ $self.offer.features = features;
+ $return_value
}
- pub(crate) fn clear_paths(mut self) -> Self {
- self.offer.paths = None;
- self
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ pub(crate) fn clear_chains($($self_mut)* $self: $self_type) -> $return_type {
+ $self.offer.chains = None;
+ $return_value
}
- pub(super) fn build_unchecked(self) -> Offer {
- self.build_without_checks()
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type {
+ $self.offer.paths = None;
+ $return_value
+ }
+
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ pub(super) fn build_unchecked($self: $self_type) -> Offer {
+ $self.build_without_checks()
+ }
+} }
+
+impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
+ offer_builder_methods!(self, Self, Self, self, mut);
+
+ #[cfg(test)]
+ offer_builder_test_methods!(self, Self, Self, self, mut);
+}
+
+impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> {
+ offer_explicit_metadata_builder_methods!(self, Self, Self, self);
+}
+
+impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
+ offer_derived_metadata_builder_methods!(T);
+}
+
+#[cfg(all(c_bindings, not(test)))]
+impl<'a> OfferWithExplicitMetadataBuilder<'a> {
+ offer_explicit_metadata_builder_methods!(self, &mut Self, (), ());
+ offer_builder_methods!(self, &mut Self, (), ());
+}
+
+#[cfg(all(c_bindings, test))]
+impl<'a> OfferWithExplicitMetadataBuilder<'a> {
+ offer_explicit_metadata_builder_methods!(self, &mut Self, &mut Self, self);
+ offer_builder_methods!(self, &mut Self, &mut Self, self);
+ offer_builder_test_methods!(self, &mut Self, &mut Self, self);
+}
+
+#[cfg(all(c_bindings, not(test)))]
+impl<'a> OfferWithDerivedMetadataBuilder<'a> {
+ offer_derived_metadata_builder_methods!(secp256k1::All);
+ offer_builder_methods!(self, &mut Self, (), ());
+}
+
+#[cfg(all(c_bindings, test))]
+impl<'a> OfferWithDerivedMetadataBuilder<'a> {
+ offer_derived_metadata_builder_methods!(secp256k1::All);
+ offer_builder_methods!(self, &mut Self, &mut Self, self);
+ offer_builder_test_methods!(self, &mut Self, &mut Self, self);
+}
+
+#[cfg(c_bindings)]
+impl<'a> From<OfferBuilder<'a, DerivedMetadata, secp256k1::All>>
+for OfferWithDerivedMetadataBuilder<'a> {
+ fn from(builder: OfferBuilder<'a, DerivedMetadata, secp256k1::All>) -> Self {
+ let OfferBuilder { offer, metadata_strategy, secp_ctx } = builder;
+
+ Self { offer, metadata_strategy, secp_ctx }
}
}
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(PartialEq))]
pub struct Offer {
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
// fields.
pub fn expects_quantity(&self) -> bool {
self.contents.expects_quantity()
}
+}
+macro_rules! request_invoice_derived_payer_id { ($self: ident, $builder: ty) => {
/// Similar to [`Offer::request_invoice`] except it:
/// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
/// request,
///
/// Useful to protect the sender's privacy.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
/// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata
/// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::verify
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
- pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
- &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>,
+ pub fn request_invoice_deriving_payer_id<
+ 'a, 'b, ES: Deref,
+ #[cfg(not(c_bindings))]
+ T: secp256k1::Signing
+ >(
+ &'a $self, expanded_key: &ExpandedKey, entropy_source: ES,
+ #[cfg(not(c_bindings))]
+ secp_ctx: &'b Secp256k1<T>,
+ #[cfg(c_bindings)]
+ secp_ctx: &'b Secp256k1<secp256k1::All>,
payment_id: PaymentId
- ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
+ ) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
- if self.offer_features().requires_unknown_bits() {
+ if $self.offer_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_payer_id(
- self, expanded_key, entropy_source, secp_ctx, payment_id
- ))
+ Ok(<$builder>::deriving_payer_id($self, expanded_key, entropy_source, secp_ctx, payment_id))
}
+} }
+macro_rules! request_invoice_explicit_payer_id { ($self: ident, $builder: ty) => {
/// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
/// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request.
///
/// Useful for recurring payments using the same `payer_id` with different invoices.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
pub fn request_invoice_deriving_metadata<ES: Deref>(
- &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+ &$self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
payment_id: PaymentId
- ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
+ ) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
- if self.offer_features().requires_unknown_bits() {
+ if $self.offer_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_metadata(
- self, payer_id, expanded_key, entropy_source, payment_id
- ))
+ Ok(<$builder>::deriving_metadata($self, payer_id, expanded_key, entropy_source, payment_id))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
///
/// Errors if the offer contains unknown required features.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn request_invoice(
- &self, metadata: Vec<u8>, payer_id: PublicKey
- ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError> {
- if self.offer_features().requires_unknown_bits() {
+ &$self, metadata: Vec<u8>, payer_id: PublicKey
+ ) -> Result<$builder, Bolt12SemanticError> {
+ if $self.offer_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::new(self, metadata, payer_id))
+ Ok(<$builder>::new($self, metadata, payer_id))
}
+} }
- #[cfg(test)]
+#[cfg(not(c_bindings))]
+impl Offer {
+ request_invoice_derived_payer_id!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>);
+ request_invoice_explicit_payer_id!(self, InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>);
+}
+
+#[cfg(c_bindings)]
+impl Offer {
+ request_invoice_derived_payer_id!(self, InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b>);
+ request_invoice_explicit_payer_id!(self, InvoiceRequestWithExplicitPayerIdBuilder);
+}
+
+#[cfg(test)]
+impl Offer {
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
self.contents.as_tlv_stream()
}
}
}
+impl PartialEq for Offer {
+ fn eq(&self, other: &Self) -> bool {
+ self.bytes.eq(&other.bytes)
+ }
+}
+
+impl Eq for Offer {}
+
+impl Hash for Offer {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.bytes.hash(state);
+ }
+}
+
impl OfferContents {
pub fn chains(&self) -> Vec<ChainHash> {
self.chains.as_ref().cloned().unwrap_or_else(|| vec![self.implied_chain()])
#[cfg(test)]
mod tests {
- use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
+ use super::{Amount, Offer, OfferTlvStreamRef, Quantity};
+ #[cfg(not(c_bindings))]
+ use {
+ super::OfferBuilder,
+ };
+ #[cfg(c_bindings)]
+ use {
+ super::OfferWithExplicitMetadataBuilder as OfferBuilder,
+ };
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();
+ #[cfg(c_bindings)]
+ use super::OfferWithDerivedMetadataBuilder as OfferBuilder;
let offer = OfferBuilder
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
.amount_msats(1000)
],
};
+ #[cfg(c_bindings)]
+ use super::OfferWithDerivedMetadataBuilder as OfferBuilder;
let offer = OfferBuilder
::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
.amount_msats(1000)
assert_eq!(tlv_stream.amount, Some(1000));
assert_eq!(tlv_stream.currency, None);
+ #[cfg(not(c_bindings))]
let builder = OfferBuilder::new("foo".into(), pubkey(42))
.amount(currency_amount.clone());
+ #[cfg(c_bindings)]
+ let mut builder = OfferBuilder::new("foo".into(), pubkey(42));
+ #[cfg(c_bindings)]
+ builder.amount(currency_amount.clone());
let tlv_stream = builder.offer.as_tlv_stream();
assert_eq!(builder.offer.amount, Some(currency_amount.clone()));
assert_eq!(tlv_stream.amount, Some(10));
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
use core::convert::TryFrom;
+use core::hash::{Hash, Hasher};
use core::ops::Deref;
use core::str::FromStr;
use core::time::Duration;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
-use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
+use crate::offers::invoice::BlindedPayInfo;
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
+#[cfg(not(c_bindings))]
+use {
+ crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder},
+};
+#[cfg(c_bindings)]
+use {
+ crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder},
+};
+
use crate::prelude::*;
#[cfg(feature = "std")]
secp_ctx: Option<&'a Secp256k1<T>>,
}
-impl<'a> RefundBuilder<'a, secp256k1::SignOnly> {
+/// Builds a [`Refund`] for the "offer for money" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// [module-level documentation]: self
+#[cfg(c_bindings)]
+pub struct RefundMaybeWithDerivedMetadataBuilder<'a> {
+ refund: RefundContents,
+ secp_ctx: Option<&'a Secp256k1<secp256k1::All>>,
+}
+
+macro_rules! refund_explicit_metadata_builder_methods { () => {
/// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to
/// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey.
///
secp_ctx: None,
})
}
-}
+} }
-impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
+macro_rules! refund_builder_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $secp_context: ty $(, $self_mut: tt)?
+) => {
/// Similar to [`RefundBuilder::new`] except, if [`RefundBuilder::path`] is called, the payer id
/// is derived from the given [`ExpandedKey`] and nonce. This provides sender privacy by using a
/// different payer id for each refund, assuming a different nonce is used. Otherwise, the
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn deriving_payer_id<ES: Deref>(
description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
- secp_ctx: &'a Secp256k1<T>, amount_msats: u64, payment_id: PaymentId
+ secp_ctx: &'a Secp256k1<$secp_context>, amount_msats: u64, payment_id: PaymentId
) -> Result<Self, Bolt12SemanticError> where ES::Target: EntropySource {
if amount_msats > MAX_VALUE_MSAT {
return Err(Bolt12SemanticError::InvalidAmount);
/// already passed is valid and can be checked for using [`Refund::is_expired`].
///
/// Successive calls to this method will override the previous setting.
- pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self {
- self.refund.absolute_expiry = Some(absolute_expiry);
- self
+ pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type {
+ $self.refund.absolute_expiry = Some(absolute_expiry);
+ $return_value
}
/// Sets the [`Refund::issuer`].
///
/// Successive calls to this method will override the previous setting.
- pub fn issuer(mut self, issuer: String) -> Self {
- self.refund.issuer = Some(issuer);
- self
+ pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type {
+ $self.refund.issuer = Some(issuer);
+ $return_value
}
/// Adds a blinded path to [`Refund::paths`]. Must include at least one path if only connected
///
/// Successive calls to this method will add another blinded path. Caller is responsible for not
/// adding duplicate paths.
- pub fn path(mut self, path: BlindedPath) -> Self {
- self.refund.paths.get_or_insert_with(Vec::new).push(path);
- self
+ pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type {
+ $self.refund.paths.get_or_insert_with(Vec::new).push(path);
+ $return_value
}
/// Sets the [`Refund::chain`] of the given [`Network`] for paying an invoice. If not
/// called, [`Network::Bitcoin`] is assumed.
///
/// Successive calls to this method will override the previous setting.
- pub fn chain(self, network: Network) -> Self {
- self.chain_hash(ChainHash::using_genesis_block(network))
+ pub fn chain($self: $self_type, network: Network) -> $return_type {
+ $self.chain_hash(ChainHash::using_genesis_block(network))
}
/// Sets the [`Refund::chain`] of the given [`ChainHash`] for paying an invoice. If not called,
/// [`Network::Bitcoin`] is assumed.
///
/// Successive calls to this method will override the previous setting.
- pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
- self.refund.chain = Some(chain);
- self
+ pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type {
+ $self.refund.chain = Some(chain);
+ $return_value
}
/// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
/// [`Offer`]: crate::offers::offer::Offer
- pub fn quantity(mut self, quantity: u64) -> Self {
- self.refund.quantity = Some(quantity);
- self
+ pub fn quantity($($self_mut)* $self: $self_type, quantity: u64) -> $return_type {
+ $self.refund.quantity = Some(quantity);
+ $return_value
}
/// Sets the [`Refund::payer_note`].
///
/// Successive calls to this method will override the previous setting.
- pub fn payer_note(mut self, payer_note: String) -> Self {
- self.refund.payer_note = Some(payer_note);
- self
+ pub fn payer_note($($self_mut)* $self: $self_type, payer_note: String) -> $return_type {
+ $self.refund.payer_note = Some(payer_note);
+ $return_value
}
/// Builds a [`Refund`] after checking for valid semantics.
- pub fn build(mut self) -> Result<Refund, Bolt12SemanticError> {
- if self.refund.chain() == self.refund.implied_chain() {
- self.refund.chain = None;
+ pub fn build($($self_mut)* $self: $self_type) -> Result<Refund, Bolt12SemanticError> {
+ if $self.refund.chain() == $self.refund.implied_chain() {
+ $self.refund.chain = None;
}
// Create the metadata for stateless verification of a Bolt12Invoice.
- if self.refund.payer.0.has_derivation_material() {
- let mut metadata = core::mem::take(&mut self.refund.payer.0);
+ if $self.refund.payer.0.has_derivation_material() {
+ let mut metadata = core::mem::take(&mut $self.refund.payer.0);
- if self.refund.paths.is_none() {
+ if $self.refund.paths.is_none() {
metadata = metadata.without_keys();
}
- let mut tlv_stream = self.refund.as_tlv_stream();
+ let mut tlv_stream = $self.refund.as_tlv_stream();
tlv_stream.0.metadata = None;
if metadata.derives_payer_keys() {
tlv_stream.2.payer_id = None;
}
- let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx);
+ let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx);
metadata = derived_metadata;
if let Some(keys) = keys {
- self.refund.payer_id = keys.public_key();
+ $self.refund.payer_id = keys.public_key();
}
- self.refund.payer.0 = metadata;
+ $self.refund.payer.0 = metadata;
}
let mut bytes = Vec::new();
- self.refund.write(&mut bytes).unwrap();
+ $self.refund.write(&mut bytes).unwrap();
+
+ Ok(Refund {
+ bytes,
+ #[cfg(not(c_bindings))]
+ contents: $self.refund,
+ #[cfg(c_bindings)]
+ contents: $self.refund.clone(),
+ })
+ }
+} }
+
+#[cfg(test)]
+macro_rules! refund_builder_test_methods { (
+ $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)?
+) => {
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type {
+ $self.refund.paths = None;
+ $return_value
+ }
- Ok(Refund { bytes, contents: self.refund })
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ fn features_unchecked($($self_mut)* $self: $self_type, features: InvoiceRequestFeatures) -> $return_type {
+ $self.refund.features = features;
+ $return_value
}
+} }
+
+impl<'a> RefundBuilder<'a, secp256k1::SignOnly> {
+ refund_explicit_metadata_builder_methods!();
}
-#[cfg(test)]
impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
- pub(crate) fn clear_paths(mut self) -> Self {
- self.refund.paths = None;
- self
- }
+ refund_builder_methods!(self, Self, Self, self, T, mut);
+
+ #[cfg(test)]
+ refund_builder_test_methods!(self, Self, Self, self, mut);
+}
- fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
- self.refund.features = features;
- self
+#[cfg(all(c_bindings, not(test)))]
+impl<'a> RefundMaybeWithDerivedMetadataBuilder<'a> {
+ refund_explicit_metadata_builder_methods!();
+ refund_builder_methods!(self, &mut Self, (), (), secp256k1::All);
+}
+
+#[cfg(all(c_bindings, test))]
+impl<'a> RefundMaybeWithDerivedMetadataBuilder<'a> {
+ refund_explicit_metadata_builder_methods!();
+ refund_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All);
+ refund_builder_test_methods!(self, &mut Self, &mut Self, self);
+}
+
+#[cfg(c_bindings)]
+impl<'a> From<RefundBuilder<'a, secp256k1::All>>
+for RefundMaybeWithDerivedMetadataBuilder<'a> {
+ fn from(builder: RefundBuilder<'a, secp256k1::All>) -> Self {
+ let RefundBuilder { refund, secp_ctx } = builder;
+
+ Self { refund, secp_ctx }
}
}
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(PartialEq))]
pub struct Refund {
pub(super) bytes: Vec<u8>,
pub(super) contents: RefundContents,
pub fn payer_note(&self) -> Option<PrintableString> {
self.contents.payer_note()
}
+}
+macro_rules! respond_with_explicit_signing_pubkey_methods { ($self: ident, $builder: ty) => {
/// Creates an [`InvoiceBuilder`] for the refund with the given required fields and using the
/// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
///
/// [`Duration`]: core::time::Duration
#[cfg(feature = "std")]
pub fn respond_with(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey,
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
+ ) -> Result<$builder, Bolt12SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
- self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at)
+ $self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at)
}
/// Creates an [`InvoiceBuilder`] for the refund with the given required fields.
///
/// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
pub fn respond_with_no_std(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey, created_at: Duration
- ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
- if self.features().requires_unknown_bits() {
+ ) -> Result<$builder, Bolt12SemanticError> {
+ if $self.features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
+ <$builder>::for_refund($self, payment_paths, created_at, payment_hash, signing_pubkey)
}
+} }
+macro_rules! respond_with_derived_signing_pubkey_methods { ($self: ident, $builder: ty) => {
/// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses
/// derived signing keys to sign the [`Bolt12Invoice`].
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[cfg(feature = "std")]
pub fn respond_using_derived_keys<ES: Deref>(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
+ ) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
- self.respond_using_derived_keys_no_std(
+ $self.respond_using_derived_keys_no_std(
payment_paths, payment_hash, created_at, expanded_key, entropy_source
)
}
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn respond_using_derived_keys_no_std<ES: Deref>(
- &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
+ &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
created_at: core::time::Duration, expanded_key: &ExpandedKey, entropy_source: ES
- ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError>
+ ) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
- if self.features().requires_unknown_bits() {
+ if $self.features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
let nonce = Nonce::from_entropy_source(entropy_source);
let keys = signer::derive_keys(nonce, expanded_key);
- InvoiceBuilder::for_refund_using_keys(self, payment_paths, created_at, payment_hash, keys)
+ <$builder>::for_refund_using_keys($self, payment_paths, created_at, payment_hash, keys)
}
+} }
- #[cfg(test)]
+#[cfg(not(c_bindings))]
+impl Refund {
+ respond_with_explicit_signing_pubkey_methods!(self, InvoiceBuilder<ExplicitSigningPubkey>);
+ respond_with_derived_signing_pubkey_methods!(self, InvoiceBuilder<DerivedSigningPubkey>);
+}
+
+#[cfg(c_bindings)]
+impl Refund {
+ respond_with_explicit_signing_pubkey_methods!(self, InvoiceWithExplicitSigningPubkeyBuilder);
+ respond_with_derived_signing_pubkey_methods!(self, InvoiceWithDerivedSigningPubkeyBuilder);
+}
+
+#[cfg(test)]
+impl Refund {
fn as_tlv_stream(&self) -> RefundTlvStreamRef {
self.contents.as_tlv_stream()
}
}
}
+impl PartialEq for Refund {
+ fn eq(&self, other: &Self) -> bool {
+ self.bytes.eq(&other.bytes)
+ }
+}
+
+impl Eq for Refund {}
+
+impl Hash for Refund {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.bytes.hash(state);
+ }
+}
+
impl RefundContents {
pub fn description(&self) -> PrintableString {
PrintableString(&self.description)
#[cfg(test)]
mod tests {
- use super::{Refund, RefundBuilder, RefundTlvStreamRef};
+ use super::{Refund, RefundTlvStreamRef};
+ #[cfg(not(c_bindings))]
+ use {
+ super::RefundBuilder,
+ };
+ #[cfg(c_bindings)]
+ use {
+ super::RefundMaybeWithDerivedMetadataBuilder as RefundBuilder,
+ };
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
use bitcoin::secp256k1::schnorr::Signature;
-use core::convert::{AsRef, Infallible};
+use core::convert::AsRef;
use core::time::Duration;
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::sign::EntropySource;
use crate::offers::invoice::BlindedPayInfo;
use crate::offers::merkle::TaggedHash;
+pub(crate) fn fail_sign<T: AsRef<TaggedHash>>(_message: &T) -> Result<Signature, ()> {
+ Err(())
+}
+
pub(crate) fn payer_keys() -> KeyPair {
let secp_ctx = Secp256k1::new();
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
}
-pub(crate) fn payer_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, Infallible> {
+pub(crate) fn payer_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, ()> {
let secp_ctx = Secp256k1::new();
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
}
-pub(crate) fn recipient_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, Infallible> {
+pub(crate) fn recipient_sign<T: AsRef<TaggedHash>>(message: &T) -> Result<Signature, ()> {
let secp_ctx = Secp256k1::new();
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
use super::packet::ParsedOnionMessageContents;
use super::offers::OffersMessageHandler;
use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
-use crate::util::logger::Logger;
+use crate::util::logger::{Logger, WithContext};
use crate::util::ser::Writeable;
use core::fmt;
const MIN_PEER_CHANNELS: usize = 3;
let network_graph = self.network_graph.deref().read_only();
- let paths = peers.iter()
+ let is_recipient_announced =
+ network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient));
+
+ let mut peer_info = peers.iter()
// Limit to peers with announced channels
- .filter(|pubkey|
+ .filter_map(|pubkey|
network_graph
.node(&NodeId::from_pubkey(pubkey))
- .map(|info| &info.channels[..])
- .map(|channels| channels.len() >= MIN_PEER_CHANNELS)
- .unwrap_or(false)
+ .filter(|info| info.channels.len() >= MIN_PEER_CHANNELS)
+ .map(|info| (*pubkey, info.is_tor_only(), info.channels.len()))
)
- .map(|pubkey| vec![*pubkey, recipient])
+ // Exclude Tor-only nodes when the recipient is announced.
+ .filter(|(_, is_tor_only, _)| !(*is_tor_only && is_recipient_announced))
+ .collect::<Vec<_>>();
+
+ // Prefer using non-Tor nodes with the most channels as the introduction node.
+ peer_info.sort_unstable_by(|(_, a_tor_only, a_channels), (_, b_tor_only, b_channels)| {
+ a_tor_only.cmp(b_tor_only).then(a_channels.cmp(b_channels).reverse())
+ });
+
+ let paths = peer_info.into_iter()
+ .map(|(pubkey, _, _)| vec![pubkey, recipient])
.map(|node_pks| BlindedPath::new_for_message(&node_pks, &*self.entropy_source, secp_ctx))
.take(MAX_PATHS)
.collect::<Result<Vec<_>, _>>();
match paths {
Ok(paths) if !paths.is_empty() => Ok(paths),
_ => {
- if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) {
+ if is_recipient_announced {
BlindedPath::one_hop_for_message(recipient, &*self.entropy_source, secp_ctx)
.map(|path| vec![path])
} else {
&self, contents: T, destination: Destination, reply_path: Option<BlindedPath>,
log_suffix: fmt::Arguments
) -> Result<SendSuccess, SendError> {
+ let mut logger = WithContext::from(&self.logger, None, None);
let result = self.find_path(destination)
- .and_then(|path| self.enqueue_onion_message(path, contents, reply_path, log_suffix));
+ .and_then(|path| {
+ let first_hop = path.intermediate_nodes.get(0).map(|p| *p);
+ logger = WithContext::from(&self.logger, first_hop, None);
+ self.enqueue_onion_message(path, contents, reply_path, log_suffix)
+ });
match result.as_ref() {
Err(SendError::GetNodeIdFailed) => {
- log_warn!(self.logger, "Unable to retrieve node id {}", log_suffix);
+ log_warn!(logger, "Unable to retrieve node id {}", log_suffix);
},
Err(SendError::PathNotFound) => {
- log_trace!(self.logger, "Failed to find path {}", log_suffix);
+ log_trace!(logger, "Failed to find path {}", log_suffix);
},
Err(e) => {
- log_trace!(self.logger, "Failed sending onion message {}: {:?}", log_suffix, e);
+ log_trace!(logger, "Failed sending onion message {}: {:?}", log_suffix, e);
},
Ok(SendSuccess::Buffered) => {
- log_trace!(self.logger, "Buffered onion message {}", log_suffix);
+ log_trace!(logger, "Buffered onion message {}", log_suffix);
},
Ok(SendSuccess::BufferedAwaitingConnection(node_id)) => {
log_trace!(
- self.logger, "Buffered onion message waiting on peer connection {}: {:?}",
+ logger,
+ "Buffered onion message waiting on peer connection {}: {}",
log_suffix, node_id
);
},
OMH::Target: OffersMessageHandler,
CMH::Target: CustomOnionMessageHandler,
{
- fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) {
+ fn handle_onion_message(&self, peer_node_id: &PublicKey, msg: &OnionMessage) {
+ let logger = WithContext::from(&self.logger, Some(*peer_node_id), None);
match self.peel_onion_message(msg) {
Ok(PeeledOnion::Receive(message, path_id, reply_path)) => {
log_trace!(
- self.logger,
- "Received an onion message with path_id {:02x?} and {} reply_path: {:?}",
+ logger,
+ "Received an onion message with path_id {:02x?} and {} reply_path: {:?}",
path_id, if reply_path.is_some() { "a" } else { "no" }, message);
match message {
Ok(PeeledOnion::Forward(next_node_id, onion_message)) => {
let mut message_recipients = self.message_recipients.lock().unwrap();
if outbound_buffer_full(&next_node_id, &message_recipients) {
- log_trace!(self.logger, "Dropping forwarded onion message to peer {:?}: outbound buffer full", next_node_id);
+ log_trace!(
+ logger,
+ "Dropping forwarded onion message to peer {}: outbound buffer full",
+ next_node_id);
return
}
e.get(), OnionMessageRecipient::ConnectedPeer(..)
) => {
e.get_mut().enqueue_message(onion_message);
- log_trace!(self.logger, "Forwarding an onion message to peer {}", next_node_id);
+ log_trace!(logger, "Forwarding an onion message to peer {}", next_node_id);
},
_ => {
- log_trace!(self.logger, "Dropping forwarded onion message to disconnected peer {:?}", next_node_id);
+ log_trace!(
+ logger,
+ "Dropping forwarded onion message to disconnected peer {}",
+ next_node_id);
return
},
}
},
Err(e) => {
- log_error!(self.logger, "Failed to process onion message {:?}", e);
+ log_error!(logger, "Failed to process onion message {:?}", e);
}
}
}
pub announcement_info: Option<NodeAnnouncementInfo>
}
+impl NodeInfo {
+ /// Returns whether the node has only announced Tor addresses.
+ pub fn is_tor_only(&self) -> bool {
+ self.announcement_info
+ .as_ref()
+ .map(|info| info.addresses())
+ .and_then(|addresses| (!addresses.is_empty()).then(|| addresses))
+ .map(|addresses| addresses.iter().all(|address| address.is_tor()))
+ .unwrap_or(false)
+ }
+}
+
impl fmt::Display for NodeInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, " channels: {:?}, announcement_info: {:?}",
use crate::ln::chan_utils::make_funding_redeemscript;
#[cfg(feature = "std")]
use crate::ln::features::InitFeatures;
+ use crate::ln::msgs::SocketAddress;
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, NodeAlias, MAX_EXCESS_BYTES_FOR_RELAY, NodeId, RoutingFees, ChannelUpdateInfo, ChannelInfo, NodeAnnouncementInfo, NodeInfo};
use crate::routing::utxo::{UtxoLookupError, UtxoResult};
use crate::ln::msgs::{RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement,
ReplyChannelRange, QueryChannelRange, QueryShortChannelIds, MAX_VALUE_MSAT};
use crate::util::config::UserConfig;
use crate::util::test_utils;
- use crate::util::ser::{ReadableArgs, Readable, Writeable};
+ use crate::util::ser::{Hostname, ReadableArgs, Readable, Writeable};
use crate::util::scid_utils::scid_from_parts;
use crate::routing::gossip::REMOVED_ENTRIES_TRACKING_AGE_LIMIT_SECS;
let node_id = NodeId([42; 33]);
assert_eq!(format!("{}", &node_id), "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
}
+
+ #[test]
+ fn is_tor_only_node() {
+ let network_graph = create_network_graph();
+ let (secp_ctx, gossip_sync) = create_gossip_sync(&network_graph);
+
+ let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap();
+ let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap();
+ let node_1_id = NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, node_1_privkey));
+
+ let announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx);
+ gossip_sync.handle_channel_announcement(&announcement).unwrap();
+
+ let tcp_ip_v4 = SocketAddress::TcpIpV4 {
+ addr: [255, 254, 253, 252],
+ port: 9735
+ };
+ let tcp_ip_v6 = SocketAddress::TcpIpV6 {
+ addr: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240],
+ port: 9735
+ };
+ let onion_v2 = SocketAddress::OnionV2([255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]);
+ let onion_v3 = SocketAddress::OnionV3 {
+ ed25519_pubkey: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224],
+ checksum: 32,
+ version: 16,
+ port: 9735
+ };
+ let hostname = SocketAddress::Hostname {
+ hostname: Hostname::try_from(String::from("host")).unwrap(),
+ port: 9735,
+ };
+
+ assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+
+ let announcement = get_signed_node_announcement(|_| {}, node_1_privkey, &secp_ctx);
+ gossip_sync.handle_node_announcement(&announcement).unwrap();
+ assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+
+ let announcement = get_signed_node_announcement(
+ |announcement| {
+ announcement.addresses = vec![
+ tcp_ip_v4.clone(), tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone(),
+ hostname.clone()
+ ];
+ announcement.timestamp += 1000;
+ },
+ node_1_privkey, &secp_ctx
+ );
+ gossip_sync.handle_node_announcement(&announcement).unwrap();
+ assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+
+ let announcement = get_signed_node_announcement(
+ |announcement| {
+ announcement.addresses = vec![
+ tcp_ip_v4.clone(), tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone()
+ ];
+ announcement.timestamp += 2000;
+ },
+ node_1_privkey, &secp_ctx
+ );
+ gossip_sync.handle_node_announcement(&announcement).unwrap();
+ assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+
+ let announcement = get_signed_node_announcement(
+ |announcement| {
+ announcement.addresses = vec![
+ tcp_ip_v6.clone(), onion_v2.clone(), onion_v3.clone()
+ ];
+ announcement.timestamp += 3000;
+ },
+ node_1_privkey, &secp_ctx
+ );
+ gossip_sync.handle_node_announcement(&announcement).unwrap();
+ assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+
+ let announcement = get_signed_node_announcement(
+ |announcement| {
+ announcement.addresses = vec![onion_v2.clone(), onion_v3.clone()];
+ announcement.timestamp += 4000;
+ },
+ node_1_privkey, &secp_ctx
+ );
+ gossip_sync.handle_node_announcement(&announcement).unwrap();
+ assert!(network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+
+ let announcement = get_signed_node_announcement(
+ |announcement| {
+ announcement.addresses = vec![onion_v2.clone()];
+ announcement.timestamp += 5000;
+ },
+ node_1_privkey, &secp_ctx
+ );
+ gossip_sync.handle_node_announcement(&announcement).unwrap();
+ assert!(network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+
+ let announcement = get_signed_node_announcement(
+ |announcement| {
+ announcement.addresses = vec![tcp_ip_v4.clone()];
+ announcement.timestamp += 6000;
+ },
+ node_1_privkey, &secp_ctx
+ );
+ gossip_sync.handle_node_announcement(&announcement).unwrap();
+ assert!(!network_graph.read_only().node(&node_1_id).unwrap().is_tor_only());
+ }
}
#[cfg(ldk_bench)]
_ => None,
}
}
- fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] {
+ pub(crate) fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] {
match self {
Self::Blinded { route_hints, .. } => &route_hints[..],
Self::Clear { .. } => &[]
#[cfg(not(feature = "hashbrown"))]
mod std_hashtables {
- pub use std::collections::HashMap;
pub use std::collections::hash_map::RandomState;
+ pub use std::collections::HashMap;
- pub(crate) use std::collections::{HashSet, hash_map};
+ pub(crate) use std::collections::{hash_map, HashSet};
pub(crate) type OccupiedHashMapEntry<'a, K, V> =
std::collections::hash_map::OccupiedEntry<'a, K, V>;
std::collections::hash_map::VacantEntry<'a, K, V>;
/// Builds a new [`HashMap`].
- pub fn new_hash_map<K, V>() -> HashMap<K, V> { HashMap::new() }
+ pub fn new_hash_map<K, V>() -> HashMap<K, V> {
+ HashMap::new()
+ }
/// Builds a new [`HashMap`] with the given capacity.
pub fn hash_map_with_capacity<K, V>(cap: usize) -> HashMap<K, V> {
HashMap::with_capacity(cap)
}
- pub(crate) fn hash_map_from_iter<K: core::hash::Hash + Eq, V, I: IntoIterator<Item=(K, V)>>(iter: I) -> HashMap<K, V> {
+ pub(crate) fn hash_map_from_iter<
+ K: core::hash::Hash + Eq,
+ V,
+ I: IntoIterator<Item = (K, V)>,
+ >(
+ iter: I,
+ ) -> HashMap<K, V> {
HashMap::from_iter(iter)
}
- pub(crate) fn new_hash_set<K>() -> HashSet<K> { HashSet::new() }
+ pub(crate) fn new_hash_set<K>() -> HashSet<K> {
+ HashSet::new()
+ }
pub(crate) fn hash_set_with_capacity<K>(cap: usize) -> HashSet<K> {
HashSet::with_capacity(cap)
}
- pub(crate) fn hash_set_from_iter<K: core::hash::Hash + Eq, I: IntoIterator<Item=K>>(iter: I) -> HashSet<K> {
+ pub(crate) fn hash_set_from_iter<K: core::hash::Hash + Eq, I: IntoIterator<Item = K>>(
+ iter: I,
+ ) -> HashSet<K> {
HashSet::from_iter(iter)
}
}
/// A simple implementation of [`BuildHasher`] that uses `getrandom` to opportunistically
/// randomize, if the platform supports it.
pub struct RandomState {
- k0: u64, k1: u64,
+ k0: u64,
+ k1: u64,
}
impl RandomState {
/// target platform.
pub fn new() -> RandomState {
let (k0, k1);
- #[cfg(all(not(fuzzing), feature = "possiblyrandom"))] {
+ #[cfg(all(not(fuzzing), feature = "possiblyrandom"))]
+ {
let mut keys = [0; 16];
possiblyrandom::getpossiblyrandom(&mut keys);
k0 = u64::from_le_bytes(k0_bytes);
k1 = u64::from_le_bytes(k1_bytes);
}
- #[cfg(any(fuzzing, not(feature = "possiblyrandom")))] {
+ #[cfg(any(fuzzing, not(feature = "possiblyrandom")))]
+ {
k0 = 0;
k1 = 0;
}
}
impl Default for RandomState {
- fn default() -> RandomState { RandomState::new() }
+ fn default() -> RandomState {
+ RandomState::new()
+ }
}
impl BuildHasher for RandomState {
}
}
- pub use hasher::*;
use super::*;
+ pub use hasher::*;
/// The HashMap type used in LDK.
pub type HashMap<K, V> = hashbrown::HashMap<K, V, RandomState>;
pub fn hash_map_with_capacity<K, V>(cap: usize) -> HashMap<K, V> {
HashMap::with_capacity_and_hasher(cap, RandomState::new())
}
- pub(crate) fn hash_map_from_iter<K: core::hash::Hash + Eq, V, I: IntoIterator<Item=(K, V)>>(iter: I) -> HashMap<K, V> {
+ pub(crate) fn hash_map_from_iter<
+ K: core::hash::Hash + Eq,
+ V,
+ I: IntoIterator<Item = (K, V)>,
+ >(
+ iter: I,
+ ) -> HashMap<K, V> {
let iter = iter.into_iter();
let min_size = iter.size_hint().0;
let mut res = HashMap::with_capacity_and_hasher(min_size, RandomState::new());
pub(crate) fn hash_set_with_capacity<K>(cap: usize) -> HashSet<K> {
HashSet::with_capacity_and_hasher(cap, RandomState::new())
}
- pub(crate) fn hash_set_from_iter<K: core::hash::Hash + Eq, I: IntoIterator<Item=K>>(iter: I) -> HashSet<K> {
+ pub(crate) fn hash_set_from_iter<K: core::hash::Hash + Eq, I: IntoIterator<Item = K>>(
+ iter: I,
+ ) -> HashSet<K> {
let iter = iter.into_iter();
let min_size = iter.size_hint().0;
let mut res = HashSet::with_capacity_and_hasher(min_size, RandomState::new());
impl_for_vec!(ecdsa::Signature);
impl_for_vec!(crate::chain::channelmonitor::ChannelMonitorUpdate);
impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction);
+impl_for_vec!(crate::ln::msgs::SocketAddress);
impl_for_vec!((A, B), A, B);
impl_writeable_for_vec!(&crate::routing::router::BlindedTail);
impl_readable_for_vec!(crate::routing::router::BlindedTail);