mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 15:02:20 +01:00
Merge pull request #3204 from valentinewallace/2024-07-rb-test-vectors
Implement route blinding test vectors
This commit is contained in:
commit
fb4403f8ae
13 changed files with 334 additions and 43 deletions
|
@ -113,6 +113,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
|
|||
htlc_minimum_msat: 100,
|
||||
},
|
||||
features: BlindedHopFeatures::empty(),
|
||||
next_blinding_override: None,
|
||||
},
|
||||
node_id: pubkey(43),
|
||||
htlc_maximum_msat: 1_000_000_000_000,
|
||||
|
|
|
@ -21,7 +21,7 @@ pub fn onion_hop_data_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
|
|||
let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
|
||||
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<(
|
||||
Option<PublicKey>,
|
||||
&&test_utils::TestNodeSigner,
|
||||
&test_utils::TestNodeSigner,
|
||||
)>>::read(&mut r, (None, &&node_signer));
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,6 @@ pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) {
|
|||
let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
|
||||
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<(
|
||||
Option<PublicKey>,
|
||||
&&test_utils::TestNodeSigner,
|
||||
&test_utils::TestNodeSigner,
|
||||
)>>::read(&mut r, (None, &&node_signer));
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
|
|||
htlc_minimum_msat: 100,
|
||||
},
|
||||
features: BlindedHopFeatures::empty(),
|
||||
next_blinding_override: None,
|
||||
},
|
||||
node_id: pubkey(43),
|
||||
htlc_maximum_msat: 1_000_000_000_000,
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::offers::invoice_request::InvoiceRequestFields;
|
|||
use crate::offers::offer::OfferId;
|
||||
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
|
||||
use crate::sign::{EntropySource, NodeSigner, Recipient};
|
||||
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, HighZeroBytesDroppedBigSize, Readable, Writeable, Writer};
|
||||
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
|
||||
|
||||
use core::mem;
|
||||
use core::ops::Deref;
|
||||
|
@ -201,6 +201,9 @@ pub struct ForwardTlvs {
|
|||
///
|
||||
/// [`BlindedHop::encrypted_payload`]: crate::blinded_path::BlindedHop::encrypted_payload
|
||||
pub features: BlindedHopFeatures,
|
||||
/// Set if this [`BlindedPaymentPath`] is concatenated to another, to indicate the
|
||||
/// [`BlindedPaymentPath::blinding_point`] of the appended blinded path.
|
||||
pub next_blinding_override: Option<PublicKey>,
|
||||
}
|
||||
|
||||
/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and
|
||||
|
@ -234,7 +237,7 @@ enum BlindedPaymentTlvsRef<'a> {
|
|||
/// Parameters for relaying over a given [`BlindedHop`].
|
||||
///
|
||||
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PaymentRelay {
|
||||
/// Number of blocks subtracted from an incoming HTLC's `cltv_expiry` for this [`BlindedHop`].
|
||||
pub cltv_expiry_delta: u16,
|
||||
|
@ -248,7 +251,7 @@ pub struct PaymentRelay {
|
|||
/// Constraints for relaying over a given [`BlindedHop`].
|
||||
///
|
||||
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PaymentConstraints {
|
||||
/// The maximum total CLTV that is acceptable when relaying a payment over this [`BlindedHop`].
|
||||
pub max_cltv_expiry: u32,
|
||||
|
@ -341,7 +344,7 @@ impl Writeable for ForwardTlvs {
|
|||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||
let features_opt =
|
||||
if self.features == BlindedHopFeatures::empty() { None }
|
||||
else { Some(&self.features) };
|
||||
else { Some(WithoutLength(&self.features)) };
|
||||
encode_tlv_stream!(w, {
|
||||
(2, self.short_channel_id, required),
|
||||
(10, self.payment_relay, required),
|
||||
|
@ -379,9 +382,10 @@ impl Readable for BlindedPaymentTlvs {
|
|||
_init_and_read_tlv_stream!(r, {
|
||||
(1, _padding, option),
|
||||
(2, scid, option),
|
||||
(8, next_blinding_override, option),
|
||||
(10, payment_relay, option),
|
||||
(12, payment_constraints, required),
|
||||
(14, features, option),
|
||||
(14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))),
|
||||
(65536, payment_secret, option),
|
||||
(65537, payment_context, (default_value, PaymentContext::unknown())),
|
||||
});
|
||||
|
@ -395,6 +399,7 @@ impl Readable for BlindedPaymentTlvs {
|
|||
short_channel_id,
|
||||
payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?,
|
||||
payment_constraints: payment_constraints.0.unwrap(),
|
||||
next_blinding_override,
|
||||
features: features.unwrap_or_else(BlindedHopFeatures::empty),
|
||||
}))
|
||||
} else {
|
||||
|
@ -602,6 +607,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 100,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: u64::max_value(),
|
||||
|
@ -618,6 +624,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 1_000,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: u64::max_value(),
|
||||
|
@ -675,6 +682,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 1,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: u64::max_value()
|
||||
|
@ -691,6 +699,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 2_000,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: u64::max_value()
|
||||
|
@ -726,6 +735,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 5_000,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: u64::max_value()
|
||||
|
@ -742,6 +752,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 2_000,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: u64::max_value()
|
||||
|
@ -781,6 +792,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 1,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: 5_000,
|
||||
|
@ -797,6 +809,7 @@ mod tests {
|
|||
max_cltv_expiry: 0,
|
||||
htlc_minimum_msat: 1,
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: 10_000
|
||||
|
|
|
@ -109,7 +109,7 @@ where
|
|||
}
|
||||
|
||||
// Panics if `unblinded_tlvs` length is less than `unblinded_pks` length
|
||||
pub(super) fn construct_blinded_hops<'a, T, I1, I2>(
|
||||
pub(crate) fn construct_blinded_hops<'a, T, I1, I2>(
|
||||
secp_ctx: &Secp256k1<T>, unblinded_pks: I1, mut unblinded_tlvs: I2, session_priv: &SecretKey
|
||||
) -> Result<Vec<BlindedHop>, secp256k1::Error>
|
||||
where
|
||||
|
|
|
@ -7,24 +7,33 @@
|
|||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
|
||||
use crate::blinded_path;
|
||||
use crate::blinded_path::payment::{BlindedPaymentPath, ForwardNode, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentRelay, ReceiveTlvs};
|
||||
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason};
|
||||
use crate::ln::types::PaymentSecret;
|
||||
use crate::ln::types::{ChannelId, PaymentHash, PaymentSecret};
|
||||
use crate::ln::channelmanager;
|
||||
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
|
||||
use crate::ln::features::BlindedHopFeatures;
|
||||
use crate::ln::channelmanager::{HTLCFailureMsg, PaymentId, RecipientOnionFields};
|
||||
use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, NodeFeatures};
|
||||
use crate::ln::functional_test_utils::*;
|
||||
use crate::ln::msgs;
|
||||
use crate::ln::msgs::ChannelMessageHandler;
|
||||
use crate::ln::msgs::{ChannelMessageHandler, UnsignedGossipMessage};
|
||||
use crate::ln::onion_payment;
|
||||
use crate::ln::onion_utils;
|
||||
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
|
||||
use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
|
||||
use crate::offers::invoice::BlindedPayInfo;
|
||||
use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
|
||||
use crate::offers::invoice_request::UnsignedInvoiceRequest;
|
||||
use crate::prelude::*;
|
||||
use crate::routing::router::{Payee, PaymentParameters, RouteParameters};
|
||||
use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters};
|
||||
use crate::sign::{KeyMaterial, NodeSigner, Recipient};
|
||||
use crate::util::config::UserConfig;
|
||||
use crate::util::ser::WithoutLength;
|
||||
use crate::util::test_utils;
|
||||
use lightning_invoice::RawBolt11Invoice;
|
||||
|
||||
fn blinded_payment_path(
|
||||
payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
|
||||
|
@ -49,6 +58,7 @@ fn blinded_payment_path(
|
|||
htlc_minimum_msat: intro_node_min_htlc_opt.take()
|
||||
.unwrap_or_else(|| channel_upds[idx - 1].htlc_minimum_msat),
|
||||
},
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
htlc_maximum_msat: intro_node_max_htlc_opt.take()
|
||||
|
@ -1331,3 +1341,254 @@ fn custom_tlvs_to_blinded_path() {
|
|||
.with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone())
|
||||
);
|
||||
}
|
||||
|
||||
fn secret_from_hex(hex: &str) -> SecretKey {
|
||||
SecretKey::from_slice(&<Vec<u8>>::from_hex(hex).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
fn bytes_from_hex(hex: &str) -> Vec<u8> {
|
||||
<Vec<u8>>::from_hex(hex).unwrap()
|
||||
}
|
||||
|
||||
fn pubkey_from_hex(hex: &str) -> PublicKey {
|
||||
PublicKey::from_slice(&<Vec<u8>>::from_hex(hex).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
fn update_add_msg(
|
||||
amount_msat: u64, cltv_expiry: u32, blinding_point: Option<PublicKey>,
|
||||
onion_routing_packet: msgs::OnionPacket
|
||||
) -> msgs::UpdateAddHTLC {
|
||||
msgs::UpdateAddHTLC {
|
||||
channel_id: ChannelId::from_bytes([0; 32]),
|
||||
htlc_id: 0,
|
||||
amount_msat,
|
||||
cltv_expiry,
|
||||
payment_hash: PaymentHash([0; 32]),
|
||||
onion_routing_packet,
|
||||
skimmed_fee_msat: None,
|
||||
blinding_point,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn route_blinding_spec_test_vector() {
|
||||
let mut secp_ctx = Secp256k1::new();
|
||||
let bob_secret = secret_from_hex("4242424242424242424242424242424242424242424242424242424242424242");
|
||||
let bob_node_id = PublicKey::from_secret_key(&secp_ctx, &bob_secret);
|
||||
let bob_unblinded_tlvs = bytes_from_hex("011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456");
|
||||
let carol_secret = secret_from_hex("4343434343434343434343434343434343434343434343434343434343434343");
|
||||
let carol_node_id = PublicKey::from_secret_key(&secp_ctx, &carol_secret);
|
||||
let carol_unblinded_tlvs = bytes_from_hex("020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00");
|
||||
let dave_secret = secret_from_hex("4444444444444444444444444444444444444444444444444444444444444444");
|
||||
let dave_node_id = PublicKey::from_secret_key(&secp_ctx, &dave_secret);
|
||||
let dave_unblinded_tlvs = bytes_from_hex("01230000000000000000000000000000000000000000000000000000000000000000000000020800000000000002310a060090000000fa0c06000b699105dc0e00");
|
||||
let eve_secret = secret_from_hex("4545454545454545454545454545454545454545454545454545454545454545");
|
||||
let eve_node_id = PublicKey::from_secret_key(&secp_ctx, &eve_secret);
|
||||
let eve_unblinded_tlvs = bytes_from_hex("011a00000000000000000000000000000000000000000000000000000604deadbeef0c06000b690105dc0e0f020000000000000000000000000000fdffff0206c1");
|
||||
|
||||
// Eve creates a blinded path to herself through Dave:
|
||||
let dave_eve_session_priv = secret_from_hex("0101010101010101010101010101010101010101010101010101010101010101");
|
||||
let blinding_override = PublicKey::from_secret_key(&secp_ctx, &dave_eve_session_priv);
|
||||
assert_eq!(blinding_override, pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"));
|
||||
// Can't use the public API here as the encrypted payloads contain unknown TLVs.
|
||||
let mut dave_eve_blinded_hops = blinded_path::utils::construct_blinded_hops(
|
||||
&secp_ctx, [dave_node_id, eve_node_id].iter(),
|
||||
&mut [WithoutLength(&dave_unblinded_tlvs), WithoutLength(&eve_unblinded_tlvs)].iter(),
|
||||
&dave_eve_session_priv
|
||||
).unwrap();
|
||||
|
||||
// Concatenate an additional Bob -> Carol blinded path to the Eve -> Dave blinded path.
|
||||
let bob_carol_session_priv = secret_from_hex("0202020202020202020202020202020202020202020202020202020202020202");
|
||||
let bob_blinding_point = PublicKey::from_secret_key(&secp_ctx, &bob_carol_session_priv);
|
||||
let bob_carol_blinded_hops = blinded_path::utils::construct_blinded_hops(
|
||||
&secp_ctx, [bob_node_id, carol_node_id].iter(),
|
||||
&mut [WithoutLength(&bob_unblinded_tlvs), WithoutLength(&carol_unblinded_tlvs)].iter(),
|
||||
&bob_carol_session_priv
|
||||
).unwrap();
|
||||
|
||||
let mut blinded_hops = bob_carol_blinded_hops;
|
||||
blinded_hops.append(&mut dave_eve_blinded_hops);
|
||||
assert_eq!(
|
||||
vec![
|
||||
pubkey_from_hex("03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25"),
|
||||
pubkey_from_hex("02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7"),
|
||||
pubkey_from_hex("036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf"),
|
||||
pubkey_from_hex("021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae")
|
||||
],
|
||||
blinded_hops.iter().map(|bh| bh.blinded_node_id).collect::<Vec<PublicKey>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![
|
||||
bytes_from_hex("cd4100ff9c09ed28102b210ac73aa12d63e90852cebc496c49f57c49982088b49f2e70b99287fdee0aa58aa39913ab405813b999f66783aa2fe637b3cda91ffc0913c30324e2c6ce327e045183e4bffecb"),
|
||||
bytes_from_hex("cc0f16524fd7f8bb0b1d8d40ad71709ef140174c76faa574cac401bb8992fef76c4d004aa485dd599ed1cf2715f57ff62da5aaec5d7b10d59b04d8a9d77e472b9b3ecc2179334e411be22fa4c02b467c7e"),
|
||||
bytes_from_hex("0fa0a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86561adee1f679ee8db3e561d9c43815fd4bcebf6f58c546da0cd8a9bf5cebd0d554802f6c0255e28e4a27343f761fe518cd897463187991105"),
|
||||
bytes_from_hex("da1a7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60724a2e4d3f0489ad884f7f3f77149209f0df51efd6b276294a02e3949c7254fbc8b5cab58212d9a78983e1cf86fe218b30c4ca8f6d8")
|
||||
],
|
||||
blinded_hops.iter().map(|bh| bh.encrypted_payload.clone()).collect::<Vec<Vec<u8>>>()
|
||||
);
|
||||
|
||||
let mut amt_msat = 100_000;
|
||||
let session_priv = secret_from_hex("0303030303030303030303030303030303030303030303030303030303030303");
|
||||
let path = Path {
|
||||
hops: vec![RouteHop {
|
||||
pubkey: bob_node_id,
|
||||
node_features: NodeFeatures::empty(),
|
||||
short_channel_id: 42,
|
||||
channel_features: ChannelFeatures::empty(),
|
||||
fee_msat: 100,
|
||||
cltv_expiry_delta: 42,
|
||||
maybe_announced_channel: false,
|
||||
}],
|
||||
blinded_tail: Some(BlindedTail {
|
||||
hops: blinded_hops,
|
||||
blinding_point: bob_blinding_point,
|
||||
excess_final_cltv_expiry_delta: 0,
|
||||
final_value_msat: amt_msat
|
||||
}),
|
||||
};
|
||||
let cur_height = 747_000;
|
||||
let (bob_onion, _, _) = onion_utils::create_payment_onion(&secp_ctx, &path, &session_priv, amt_msat, &RecipientOnionFields::spontaneous_empty(), cur_height, &PaymentHash([0; 32]), &None, [0; 32]).unwrap();
|
||||
|
||||
struct TestEcdhSigner {
|
||||
node_secret: SecretKey,
|
||||
}
|
||||
impl NodeSigner for TestEcdhSigner {
|
||||
fn ecdh(
|
||||
&self, _recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>,
|
||||
) -> Result<SharedSecret, ()> {
|
||||
let mut node_secret = self.node_secret.clone();
|
||||
if let Some(tweak) = tweak {
|
||||
node_secret = self.node_secret.mul_tweak(tweak).map_err(|_| ())?;
|
||||
}
|
||||
Ok(SharedSecret::new(other_key, &node_secret))
|
||||
}
|
||||
fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!() }
|
||||
fn get_node_id(&self, _recipient: Recipient) -> Result<PublicKey, ()> { unreachable!() }
|
||||
fn sign_invoice(
|
||||
&self, _invoice: &RawBolt11Invoice, _recipient: Recipient,
|
||||
) -> Result<RecoverableSignature, ()> { unreachable!() }
|
||||
fn sign_bolt12_invoice_request(
|
||||
&self, _invoice_request: &UnsignedInvoiceRequest,
|
||||
) -> Result<schnorr::Signature, ()> { unreachable!() }
|
||||
fn sign_bolt12_invoice(
|
||||
&self, _invoice: &UnsignedBolt12Invoice,
|
||||
) -> Result<schnorr::Signature, ()> { unreachable!() }
|
||||
fn sign_gossip_message(&self, _msg: UnsignedGossipMessage) -> Result<Signature, ()> { unreachable!() }
|
||||
}
|
||||
let logger = test_utils::TestLogger::with_id("".to_owned());
|
||||
|
||||
let bob_update_add = update_add_msg(110_000, 747_500, None, bob_onion);
|
||||
let bob_node_signer = TestEcdhSigner { node_secret: bob_secret };
|
||||
// Can't use the public API here as we need to avoid the CLTV delta checks (test vector uses
|
||||
// < MIN_CLTV_EXPIRY_DELTA).
|
||||
let (bob_peeled_onion, _, next_packet_details_opt) =
|
||||
match onion_payment::decode_incoming_update_add_htlc_onion(
|
||||
&bob_update_add, &bob_node_signer, &logger, &secp_ctx
|
||||
) {
|
||||
Ok(res) => res,
|
||||
_ => panic!("Unexpected error")
|
||||
};
|
||||
let (carol_packet_bytes, carol_hmac) = if let onion_utils::Hop::Forward {
|
||||
next_hop_data: msgs::InboundOnionPayload::BlindedForward {
|
||||
short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override
|
||||
}, next_hop_hmac, new_packet_bytes
|
||||
} = bob_peeled_onion {
|
||||
assert_eq!(short_channel_id, 1729);
|
||||
assert!(next_blinding_override.is_none());
|
||||
assert_eq!(intro_node_blinding_point, Some(bob_blinding_point));
|
||||
assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 36, fee_proportional_millionths: 150, fee_base_msat: 10_000 });
|
||||
assert_eq!(features, BlindedHopFeatures::empty());
|
||||
assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 748_005, htlc_minimum_msat: 1500 });
|
||||
(new_packet_bytes, next_hop_hmac)
|
||||
} else { panic!() };
|
||||
|
||||
let carol_packet_details = next_packet_details_opt.unwrap();
|
||||
let carol_onion = msgs::OnionPacket {
|
||||
version: 0,
|
||||
public_key: carol_packet_details.next_packet_pubkey,
|
||||
hop_data: carol_packet_bytes,
|
||||
hmac: carol_hmac,
|
||||
};
|
||||
let carol_update_add = update_add_msg(
|
||||
carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value,
|
||||
Some(pubkey_from_hex("034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")),
|
||||
carol_onion
|
||||
);
|
||||
let carol_node_signer = TestEcdhSigner { node_secret: carol_secret };
|
||||
let (carol_peeled_onion, _, next_packet_details_opt) =
|
||||
match onion_payment::decode_incoming_update_add_htlc_onion(
|
||||
&carol_update_add, &carol_node_signer, &logger, &secp_ctx
|
||||
) {
|
||||
Ok(res) => res,
|
||||
_ => panic!("Unexpected error")
|
||||
};
|
||||
let (dave_packet_bytes, dave_hmac) = if let onion_utils::Hop::Forward {
|
||||
next_hop_data: msgs::InboundOnionPayload::BlindedForward {
|
||||
short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override
|
||||
}, next_hop_hmac, new_packet_bytes
|
||||
} = carol_peeled_onion {
|
||||
assert_eq!(short_channel_id, 1105);
|
||||
assert_eq!(next_blinding_override, Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")));
|
||||
assert!(intro_node_blinding_point.is_none());
|
||||
assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 48, fee_proportional_millionths: 100, fee_base_msat: 500 });
|
||||
assert_eq!(features, BlindedHopFeatures::empty());
|
||||
assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_969, htlc_minimum_msat: 1500 });
|
||||
(new_packet_bytes, next_hop_hmac)
|
||||
} else { panic!() };
|
||||
|
||||
let dave_packet_details = next_packet_details_opt.unwrap();
|
||||
let dave_onion = msgs::OnionPacket {
|
||||
version: 0,
|
||||
public_key: dave_packet_details.next_packet_pubkey,
|
||||
hop_data: dave_packet_bytes,
|
||||
hmac: dave_hmac,
|
||||
};
|
||||
let dave_update_add = update_add_msg(
|
||||
dave_packet_details.outgoing_amt_msat, dave_packet_details.outgoing_cltv_value,
|
||||
Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")),
|
||||
dave_onion
|
||||
);
|
||||
let dave_node_signer = TestEcdhSigner { node_secret: dave_secret };
|
||||
let (dave_peeled_onion, _, next_packet_details_opt) =
|
||||
match onion_payment::decode_incoming_update_add_htlc_onion(
|
||||
&dave_update_add, &dave_node_signer, &logger, &secp_ctx
|
||||
) {
|
||||
Ok(res) => res,
|
||||
_ => panic!("Unexpected error")
|
||||
};
|
||||
let (eve_packet_bytes, eve_hmac) = if let onion_utils::Hop::Forward {
|
||||
next_hop_data: msgs::InboundOnionPayload::BlindedForward {
|
||||
short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override
|
||||
}, next_hop_hmac, new_packet_bytes
|
||||
} = dave_peeled_onion {
|
||||
assert_eq!(short_channel_id, 561);
|
||||
assert!(next_blinding_override.is_none());
|
||||
assert!(intro_node_blinding_point.is_none());
|
||||
assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 144, fee_proportional_millionths: 250, fee_base_msat: 0 });
|
||||
assert_eq!(features, BlindedHopFeatures::empty());
|
||||
assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_921, htlc_minimum_msat: 1500 });
|
||||
(new_packet_bytes, next_hop_hmac)
|
||||
} else { panic!() };
|
||||
|
||||
let eve_packet_details = next_packet_details_opt.unwrap();
|
||||
let eve_onion = msgs::OnionPacket {
|
||||
version: 0,
|
||||
public_key: eve_packet_details.next_packet_pubkey,
|
||||
hop_data: eve_packet_bytes,
|
||||
hmac: eve_hmac,
|
||||
};
|
||||
let eve_update_add = update_add_msg(
|
||||
eve_packet_details.outgoing_amt_msat, eve_packet_details.outgoing_cltv_value,
|
||||
Some(pubkey_from_hex("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")),
|
||||
eve_onion
|
||||
);
|
||||
let eve_node_signer = TestEcdhSigner { node_secret: eve_secret };
|
||||
// We can't decode the final payload because it contains a path_id and is missing some LDK
|
||||
// specific fields.
|
||||
match onion_payment::decode_incoming_update_add_htlc_onion(
|
||||
&eve_update_add, &eve_node_signer, &logger, &secp_ctx
|
||||
) {
|
||||
Err(HTLCFailureMsg::Malformed(msg)) => assert_eq!(msg.failure_code, INVALID_ONION_BLINDING),
|
||||
_ => panic!("Unexpected error")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,6 +227,10 @@ pub struct BlindedForward {
|
|||
/// If needed, this determines how this HTLC should be failed backwards, based on whether we are
|
||||
/// the introduction node.
|
||||
pub failure: BlindedFailure,
|
||||
/// Overrides the next hop's [`msgs::UpdateAddHTLC::blinding_point`]. Set if this HTLC is being
|
||||
/// forwarded within a [`BlindedPaymentPath`] that was concatenated to another blinded path that
|
||||
/// starts at the next hop.
|
||||
pub next_blinding_override: Option<PublicKey>,
|
||||
}
|
||||
|
||||
impl PendingHTLCRouting {
|
||||
|
@ -3845,7 +3849,7 @@ where
|
|||
(onion_utils::Hop, [u8; 32], Option<Result<PublicKey, secp256k1::Error>>), HTLCFailureMsg
|
||||
> {
|
||||
let (next_hop, shared_secret, next_packet_details_opt) = decode_incoming_update_add_htlc_onion(
|
||||
msg, &self.node_signer, &self.logger, &self.secp_ctx
|
||||
msg, &*self.node_signer, &*self.logger, &self.secp_ctx
|
||||
)?;
|
||||
|
||||
let next_packet_details = match next_packet_details_opt {
|
||||
|
@ -5062,7 +5066,7 @@ where
|
|||
let mut htlc_fails = Vec::new();
|
||||
for update_add_htlc in &update_add_htlcs {
|
||||
let (next_hop, shared_secret, next_packet_details_opt) = match decode_incoming_update_add_htlc_onion(
|
||||
&update_add_htlc, &self.node_signer, &self.logger, &self.secp_ctx
|
||||
&update_add_htlc, &*self.node_signer, &*self.logger, &self.secp_ctx
|
||||
) {
|
||||
Ok(decoded_onion) => decoded_onion,
|
||||
Err(htlc_fail) => {
|
||||
|
@ -5239,7 +5243,7 @@ where
|
|||
let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
|
||||
let next_hop = match onion_utils::decode_next_payment_hop(
|
||||
phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac,
|
||||
payment_hash, None, &self.node_signer
|
||||
payment_hash, None, &*self.node_signer
|
||||
) {
|
||||
Ok(res) => res,
|
||||
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
|
||||
|
@ -5330,12 +5334,14 @@ where
|
|||
blinded_failure: blinded.map(|b| b.failure),
|
||||
});
|
||||
let next_blinding_point = blinded.and_then(|b| {
|
||||
b.next_blinding_override.or_else(|| {
|
||||
let encrypted_tlvs_ss = self.node_signer.ecdh(
|
||||
Recipient::Node, &b.inbound_blinding_point, None
|
||||
).unwrap().secret_bytes();
|
||||
onion_utils::next_hop_pubkey(
|
||||
&self.secp_ctx, b.inbound_blinding_point, &encrypted_tlvs_ss
|
||||
).ok()
|
||||
})
|
||||
});
|
||||
|
||||
// Forward the HTLC over the most appropriate channel with the corresponding peer,
|
||||
|
@ -11051,6 +11057,7 @@ impl_writeable_tlv_based!(PhantomRouteHints, {
|
|||
impl_writeable_tlv_based!(BlindedForward, {
|
||||
(0, inbound_blinding_point, required),
|
||||
(1, failure, (default_value, BlindedFailure::FromIntroductionNode)),
|
||||
(3, next_blinding_override, option),
|
||||
});
|
||||
|
||||
impl_writeable_tlv_based_enum!(PendingHTLCRouting,
|
||||
|
|
|
@ -98,6 +98,7 @@ impl_feature_write_without_length!(Bolt12InvoiceFeatures);
|
|||
impl_feature_write_without_length!(ChannelTypeFeatures);
|
||||
impl_feature_write_without_length!(InvoiceRequestFeatures);
|
||||
impl_feature_write_without_length!(OfferFeatures);
|
||||
impl_feature_write_without_length!(BlindedHopFeatures);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -1764,6 +1764,7 @@ mod fuzzy_internal_msgs {
|
|||
payment_constraints: PaymentConstraints,
|
||||
features: BlindedHopFeatures,
|
||||
intro_node_blinding_point: Option<PublicKey>,
|
||||
next_blinding_override: Option<PublicKey>,
|
||||
},
|
||||
BlindedReceive {
|
||||
sender_intended_htlc_amt_msat: u64,
|
||||
|
@ -2755,8 +2756,8 @@ impl Writeable for OutboundTrampolinePayload {
|
|||
}
|
||||
|
||||
|
||||
impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload where NS::Target: NodeSigner {
|
||||
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, &NS)) -> Result<Self, DecodeError> {
|
||||
impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload where NS::Target: NodeSigner {
|
||||
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
|
||||
let (update_add_blinding_point, node_signer) = args;
|
||||
|
||||
let mut amt = None;
|
||||
|
@ -2808,7 +2809,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
|
|||
let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64);
|
||||
match ChaChaPolyReadAdapter::read(&mut reader, rho)? {
|
||||
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs {
|
||||
short_channel_id, payment_relay, payment_constraints, features
|
||||
short_channel_id, payment_relay, payment_constraints, features, next_blinding_override
|
||||
})} => {
|
||||
if amt.is_some() || cltv_value.is_some() || total_msat.is_some() ||
|
||||
keysend_preimage.is_some()
|
||||
|
@ -2821,6 +2822,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload w
|
|||
payment_constraints,
|
||||
features,
|
||||
intro_node_blinding_point,
|
||||
next_blinding_override,
|
||||
})
|
||||
},
|
||||
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
|
||||
|
@ -4454,7 +4456,7 @@ mod tests {
|
|||
assert_eq!(encoded_value, target_value);
|
||||
|
||||
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap();
|
||||
if let msgs::InboundOnionPayload::Forward {
|
||||
short_channel_id, amt_to_forward, outgoing_cltv_value
|
||||
} = inbound_msg {
|
||||
|
@ -4479,7 +4481,7 @@ mod tests {
|
|||
assert_eq!(encoded_value, target_value);
|
||||
|
||||
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap();
|
||||
if let msgs::InboundOnionPayload::Receive {
|
||||
payment_data: None, sender_intended_htlc_amt_msat, cltv_expiry_height, ..
|
||||
} = inbound_msg {
|
||||
|
@ -4507,7 +4509,7 @@ mod tests {
|
|||
assert_eq!(encoded_value, target_value);
|
||||
|
||||
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap();
|
||||
if let msgs::InboundOnionPayload::Receive {
|
||||
payment_data: Some(FinalOnionHopData {
|
||||
payment_secret,
|
||||
|
@ -4543,7 +4545,7 @@ mod tests {
|
|||
};
|
||||
let encoded_value = msg.encode();
|
||||
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||
assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).is_err());
|
||||
assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), (None, &node_signer)).is_err());
|
||||
let good_type_range_tlvs = vec![
|
||||
((1 << 16) - 3, vec![42]),
|
||||
((1 << 16) - 1, vec![42; 32]),
|
||||
|
@ -4552,7 +4554,7 @@ mod tests {
|
|||
*custom_tlvs = &good_type_range_tlvs;
|
||||
}
|
||||
let encoded_value = msg.encode();
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).unwrap();
|
||||
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &node_signer)).unwrap();
|
||||
match inbound_msg {
|
||||
msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()),
|
||||
_ => panic!(),
|
||||
|
@ -4577,7 +4579,7 @@ mod tests {
|
|||
let target_value = <Vec<u8>>::from_hex("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap();
|
||||
assert_eq!(encoded_value, target_value);
|
||||
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||
let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap();
|
||||
let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap();
|
||||
if let msgs::InboundOnionPayload::Receive {
|
||||
payment_data: None,
|
||||
payment_metadata: None,
|
||||
|
@ -4807,7 +4809,7 @@ mod tests {
|
|||
let mut rd = Cursor::new(&big_payload[..]);
|
||||
|
||||
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||
<msgs::InboundOnionPayload as ReadableArgs<(Option<PublicKey>, &&test_utils::TestKeysInterface)>>
|
||||
<msgs::InboundOnionPayload as ReadableArgs<(Option<PublicKey>, &test_utils::TestKeysInterface)>>
|
||||
::read(&mut rd, (None, &&node_signer)).unwrap();
|
||||
}
|
||||
// see above test, needs to be a separate method for use of the serialization macros.
|
||||
|
|
|
@ -75,12 +75,14 @@ pub(super) fn create_fwd_pending_htlc_info(
|
|||
};
|
||||
|
||||
let (
|
||||
short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point
|
||||
short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point,
|
||||
next_blinding_override
|
||||
) = match hop_data {
|
||||
msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
|
||||
(short_channel_id, amt_to_forward, outgoing_cltv_value, None),
|
||||
(short_channel_id, amt_to_forward, outgoing_cltv_value, None, None),
|
||||
msgs::InboundOnionPayload::BlindedForward {
|
||||
short_channel_id, payment_relay, payment_constraints, intro_node_blinding_point, features,
|
||||
next_blinding_override,
|
||||
} => {
|
||||
let (amt_to_forward, outgoing_cltv_value) = check_blinded_forward(
|
||||
msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &features
|
||||
|
@ -93,7 +95,8 @@ pub(super) fn create_fwd_pending_htlc_info(
|
|||
err_data: vec![0; 32],
|
||||
}
|
||||
})?;
|
||||
(short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point)
|
||||
(short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point,
|
||||
next_blinding_override)
|
||||
},
|
||||
msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } =>
|
||||
return Err(InboundHTLCErr {
|
||||
|
@ -110,6 +113,7 @@ pub(super) fn create_fwd_pending_htlc_info(
|
|||
blinded: intro_node_blinding_point.or(msg.blinding_point)
|
||||
.map(|bp| BlindedForward {
|
||||
inbound_blinding_point: bp,
|
||||
next_blinding_override,
|
||||
failure: intro_node_blinding_point
|
||||
.map(|_| BlindedFailure::FromIntroductionNode)
|
||||
.unwrap_or(BlindedFailure::FromBlindedNode),
|
||||
|
@ -276,7 +280,7 @@ pub(super) fn create_recv_pending_htlc_info(
|
|||
///
|
||||
/// [`Event::PaymentClaimable`]: crate::events::Event::PaymentClaimable
|
||||
pub fn peel_payment_onion<NS: Deref, L: Deref, T: secp256k1::Verification>(
|
||||
msg: &msgs::UpdateAddHTLC, node_signer: &NS, logger: &L, secp_ctx: &Secp256k1<T>,
|
||||
msg: &msgs::UpdateAddHTLC, node_signer: NS, logger: L, secp_ctx: &Secp256k1<T>,
|
||||
cur_height: u32, accept_mpp_keysend: bool, allow_skimmed_fees: bool,
|
||||
) -> Result<PendingHTLCInfo, InboundHTLCErr>
|
||||
where
|
||||
|
@ -342,7 +346,7 @@ pub(super) struct NextPacketDetails {
|
|||
}
|
||||
|
||||
pub(super) fn decode_incoming_update_add_htlc_onion<NS: Deref, L: Deref, T: secp256k1::Verification>(
|
||||
msg: &msgs::UpdateAddHTLC, node_signer: &NS, logger: &L, secp_ctx: &Secp256k1<T>,
|
||||
msg: &msgs::UpdateAddHTLC, node_signer: NS, logger: L, secp_ctx: &Secp256k1<T>,
|
||||
) -> Result<(onion_utils::Hop, [u8; 32], Option<NextPacketDetails>), HTLCFailureMsg>
|
||||
where
|
||||
NS::Target: NodeSigner,
|
||||
|
@ -570,7 +574,7 @@ mod tests {
|
|||
let msg = make_update_add_msg(amount_msat, cltv_expiry, payment_hash, onion);
|
||||
let logger = test_utils::TestLogger::with_id("bob".to_string());
|
||||
|
||||
let peeled = peel_payment_onion(&msg, &&bob, &&logger, &secp_ctx, cur_height, true, false)
|
||||
let peeled = peel_payment_onion(&msg, &bob, &logger, &secp_ctx, cur_height, true, false)
|
||||
.map_err(|e| e.msg).unwrap();
|
||||
|
||||
let next_onion = match peeled.routing {
|
||||
|
@ -581,7 +585,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let msg2 = make_update_add_msg(amount_msat, cltv_expiry, payment_hash, next_onion);
|
||||
let peeled2 = peel_payment_onion(&msg2, &&charlie, &&logger, &secp_ctx, cur_height, true, false)
|
||||
let peeled2 = peel_payment_onion(&msg2, &charlie, &logger, &secp_ctx, cur_height, true, false)
|
||||
.map_err(|e| e.msg).unwrap();
|
||||
|
||||
match peeled2.routing {
|
||||
|
|
|
@ -1133,7 +1133,7 @@ pub(crate) enum OnionDecodeErr {
|
|||
|
||||
pub(crate) fn decode_next_payment_hop<NS: Deref>(
|
||||
shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash,
|
||||
blinding_point: Option<PublicKey>, node_signer: &NS,
|
||||
blinding_point: Option<PublicKey>, node_signer: NS,
|
||||
) -> Result<Hop, OnionDecodeErr>
|
||||
where
|
||||
NS::Target: NodeSigner,
|
||||
|
|
|
@ -4284,7 +4284,7 @@ fn peel_payment_onion_custom_tlvs() {
|
|||
blinding_point: None,
|
||||
};
|
||||
let peeled_onion = crate::ln::onion_payment::peel_payment_onion(
|
||||
&update_add, &&chanmon_cfgs[1].keys_manager, &&chanmon_cfgs[1].logger, &secp_ctx,
|
||||
&update_add, &chanmon_cfgs[1].keys_manager, &chanmon_cfgs[1].logger, &secp_ctx,
|
||||
nodes[1].best_block_info().1, true, false
|
||||
).unwrap();
|
||||
assert_eq!(peeled_onion.incoming_amt_msat, Some(amt_msat));
|
||||
|
|
|
@ -151,6 +151,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, ES: Deref, S: Deref, SP: Size
|
|||
short_channel_id,
|
||||
payment_relay,
|
||||
payment_constraints,
|
||||
next_blinding_override: None,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
node_id: details.counterparty.node_id,
|
||||
|
|
Loading…
Add table
Reference in a new issue