Merge pull request #2127 from TheBlueMatt/2023-03-payment-metadata

Support sending `PaymentMetadata` in HTLCs
This commit is contained in:
Matt Corallo 2023-04-19 17:17:49 +00:00 committed by GitHub
commit 2e15df730f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 605 additions and 109 deletions

View file

@ -460,6 +460,8 @@ impl FromBase32 for TaggedField {
Ok(TaggedField::PrivateRoute(PrivateRoute::from_base32(field_data)?)),
constants::TAG_PAYMENT_SECRET =>
Ok(TaggedField::PaymentSecret(PaymentSecret::from_base32(field_data)?)),
constants::TAG_PAYMENT_METADATA =>
Ok(TaggedField::PaymentMetadata(Vec::<u8>::from_base32(field_data)?)),
constants::TAG_FEATURES =>
Ok(TaggedField::Features(InvoiceFeatures::from_base32(field_data)?)),
_ => {

View file

@ -218,10 +218,13 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;
/// * `D`: exactly one [`TaggedField::Description`] or [`TaggedField::DescriptionHash`]
/// * `H`: exactly one [`TaggedField::PaymentHash`]
/// * `T`: the timestamp is set
/// * `C`: the CLTV expiry is set
/// * `S`: the payment secret is set
/// * `M`: payment metadata is set
///
/// This is not exported to bindings users as we likely need to manually select one set of boolean type parameters.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
currency: Currency,
amount: Option<u64>,
si_prefix: Option<SiPrefix>,
@ -234,6 +237,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
phantom_t: core::marker::PhantomData<T>,
phantom_c: core::marker::PhantomData<C>,
phantom_s: core::marker::PhantomData<S>,
phantom_m: core::marker::PhantomData<M>,
}
/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@ -442,6 +446,7 @@ pub enum TaggedField {
Fallback(Fallback),
PrivateRoute(PrivateRoute),
PaymentSecret(PaymentSecret),
PaymentMetadata(Vec<u8>),
Features(InvoiceFeatures),
}
@ -506,15 +511,16 @@ pub mod constants {
pub const TAG_FALLBACK: u8 = 9;
pub const TAG_PRIVATE_ROUTE: u8 = 3;
pub const TAG_PAYMENT_SECRET: u8 = 16;
pub const TAG_PAYMENT_METADATA: u8 = 27;
pub const TAG_FEATURES: u8 = 5;
}
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
/// `InvoiceBuilder::build(self)` becomes available.
pub fn new(currrency: Currency) -> Self {
pub fn new(currency: Currency) -> Self {
InvoiceBuilder {
currency: currrency,
currency,
amount: None,
si_prefix: None,
timestamp: None,
@ -526,14 +532,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
phantom_t: core::marker::PhantomData,
phantom_c: core::marker::PhantomData,
phantom_s: core::marker::PhantomData,
phantom_m: core::marker::PhantomData,
}
}
}
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, S, M> {
/// Helper function to set the completeness flags.
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN> {
InvoiceBuilder::<DN, HN, TN, CN, SN> {
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool, MN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN, MN> {
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
currency: self.currency,
amount: self.amount,
si_prefix: self.si_prefix,
@ -546,6 +553,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
phantom_t: core::marker::PhantomData,
phantom_c: core::marker::PhantomData,
phantom_s: core::marker::PhantomData,
phantom_m: core::marker::PhantomData,
}
}
@ -590,7 +598,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
}
}
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S> {
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S, M> {
/// Builds a [`RawInvoice`] if no [`CreationError`] occurred while construction any of the
/// fields.
pub fn build_raw(self) -> Result<RawInvoice, CreationError> {
@ -624,9 +632,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
}
}
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
/// Set the description. This function is only available if no description (hash) was set.
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
match Description::new(description) {
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
Err(e) => self.error = Some(e),
@ -635,13 +643,13 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
}
/// Set the description hash. This function is only available if no description (hash) was set.
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S> {
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
self.set_flags()
}
/// Set the description or description hash. This function is only available if no description (hash) was set.
pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S> {
pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
match description {
InvoiceDescription::Direct(desc) => {
self.description(desc.clone().into_inner())
@ -653,18 +661,18 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
}
}
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
/// Set the payment hash. This function is only available if no payment hash was set.
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S> {
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
self.set_flags()
}
}
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
/// Sets the timestamp to a specific [`SystemTime`].
#[cfg(feature = "std")]
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
match PositiveTimestamp::from_system_time(time) {
Ok(t) => self.timestamp = Some(t),
Err(e) => self.error = Some(e),
@ -675,7 +683,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
/// Sets the timestamp to a duration since the Unix epoch, dropping the subsecond part (which
/// is not representable in BOLT 11 invoices).
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
match PositiveTimestamp::from_duration_since_epoch(time) {
Ok(t) => self.timestamp = Some(t),
Err(e) => self.error = Some(e),
@ -686,34 +694,82 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
/// Sets the timestamp to the current system time.
#[cfg(feature = "std")]
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
let now = PositiveTimestamp::from_system_time(SystemTime::now());
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
self.set_flags()
}
}
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
/// Sets `min_final_cltv_expiry_delta`.
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
self.tagged_fields.push(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta(min_final_cltv_expiry_delta)));
self.set_flags()
}
}
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
/// Sets the payment secret and relevant features.
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
let mut features = InvoiceFeatures::empty();
features.set_variable_length_onion_required();
features.set_payment_secret_required();
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
let mut found_features = false;
for field in self.tagged_fields.iter_mut() {
if let TaggedField::Features(f) = field {
found_features = true;
f.set_variable_length_onion_required();
f.set_payment_secret_required();
}
}
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
self.tagged_fields.push(TaggedField::Features(features));
if !found_features {
let mut features = InvoiceFeatures::empty();
features.set_variable_length_onion_required();
features.set_payment_secret_required();
self.tagged_fields.push(TaggedField::Features(features));
}
self.set_flags()
}
}
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
/// Sets the payment metadata.
///
/// By default features are set to *optionally* allow the sender to include the payment metadata.
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
/// they don't support payment metadata fields), you need to call
/// [`InvoiceBuilder::require_payment_metadata`] after this.
pub fn payment_metadata(mut self, payment_metadata: Vec<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
let mut found_features = false;
for field in self.tagged_fields.iter_mut() {
if let TaggedField::Features(f) = field {
found_features = true;
f.set_payment_metadata_optional();
}
}
if !found_features {
let mut features = InvoiceFeatures::empty();
features.set_payment_metadata_optional();
self.tagged_fields.push(TaggedField::Features(features));
}
self.set_flags()
}
}
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
/// Sets forwarding of payment metadata as required. A reader of the invoice which does not
/// support sending payment metadata will fail to read the invoice.
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
for field in self.tagged_fields.iter_mut() {
if let TaggedField::Features(f) = field {
f.set_payment_metadata_required();
}
}
self
}
}
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
/// Sets the `basic_mpp` feature as optional.
pub fn basic_mpp(mut self) -> Self {
for field in self.tagged_fields.iter_mut() {
@ -725,7 +781,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
}
}
impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
impl<M: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True, M> {
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
/// the included payee public key.
@ -977,6 +1033,10 @@ impl RawInvoice {
find_extract!(self.known_tagged_fields(), TaggedField::PaymentSecret(ref x), x)
}
pub fn payment_metadata(&self) -> Option<&Vec<u8>> {
find_extract!(self.known_tagged_fields(), TaggedField::PaymentMetadata(ref x), x)
}
pub fn features(&self) -> Option<&InvoiceFeatures> {
find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x)
}
@ -1248,6 +1308,11 @@ impl Invoice {
self.signed_invoice.payment_secret().expect("was checked by constructor")
}
/// Get the payment metadata blob if one was included in the invoice
pub fn payment_metadata(&self) -> Option<&Vec<u8>> {
self.signed_invoice.payment_metadata()
}
/// Get the invoice features if they were included in the invoice
pub fn features(&self) -> Option<&InvoiceFeatures> {
self.signed_invoice.features()
@ -1396,6 +1461,7 @@ impl TaggedField {
TaggedField::Fallback(_) => constants::TAG_FALLBACK,
TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE,
TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
TaggedField::PaymentMetadata(_) => constants::TAG_PAYMENT_METADATA,
TaggedField::Features(_) => constants::TAG_FEATURES,
};

View file

@ -145,8 +145,10 @@ fn pay_invoice_using_amount<P: Deref>(
payer: P
) -> Result<(), PaymentError> where P::Target: Payer {
let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
let payment_secret = Some(*invoice.payment_secret());
let recipient_onion = RecipientOnionFields { payment_secret };
let recipient_onion = RecipientOnionFields {
payment_secret: Some(*invoice.payment_secret()),
payment_metadata: invoice.payment_metadata().map(|v| v.clone()),
};
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32)
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
@ -213,6 +215,8 @@ mod tests {
use super::*;
use crate::{InvoiceBuilder, Currency};
use bitcoin_hashes::sha256::Hash as Sha256;
use lightning::events::Event;
use lightning::ln::msgs::ChannelMessageHandler;
use lightning::ln::{PaymentPreimage, PaymentSecret};
use lightning::ln::functional_test_utils::*;
use secp256k1::{SecretKey, Secp256k1};
@ -350,4 +354,52 @@ mod tests {
_ => panic!()
}
}
#[test]
#[cfg(feature = "std")]
fn payment_metadata_end_to_end() {
// Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all
// the way out through the `PaymentClaimable` event.
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(&nodes, 0, 1);
let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42];
let (payment_hash, payment_secret) =
nodes[1].node.create_inbound_payment(None, 7200, None).unwrap();
let invoice = InvoiceBuilder::new(Currency::Bitcoin)
.description("test".into())
.payment_hash(Sha256::from_slice(&payment_hash.0).unwrap())
.payment_secret(payment_secret)
.current_timestamp()
.min_final_cltv_expiry_delta(144)
.amount_milli_satoshis(50_000)
.payment_metadata(payment_metadata.clone())
.build_signed(|hash| {
Secp256k1::new().sign_ecdsa_recoverable(hash,
&nodes[1].keys_manager.backing.get_node_secret_key())
})
.unwrap();
pay_invoice(&invoice, Retry::Attempts(0), nodes[0].node).unwrap();
check_added_monitors(&nodes[0], 1);
let send_event = SendEvent::from_node(&nodes[0]);
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], &send_event.commitment_msg, false);
expect_pending_htlcs_forwardable!(nodes[1]);
let mut events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events.pop().unwrap() {
Event::PaymentClaimable { onion_fields, .. } => {
assert_eq!(Some(payment_metadata), onion_fields.unwrap().payment_metadata);
},
_ => panic!("Unexpected event")
}
}
}

View file

@ -446,6 +446,9 @@ impl ToBase32 for TaggedField {
TaggedField::PaymentSecret(ref payment_secret) => {
write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret)
},
TaggedField::PaymentMetadata(ref payment_metadata) => {
write_tagged_field(writer, constants::TAG_PAYMENT_METADATA, payment_metadata)
},
TaggedField::Features(ref features) => {
write_tagged_field(writer, constants::TAG_FEATURES, features)
},

View file

@ -332,6 +332,56 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> {
true, // Different features than set in InvoiceBuilder
true, // Some unknown fields
),
( // Older version of the payment metadata test with a payment_pubkey set
"lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgqy9gw6ymamd20jumvdgpfphkhp8fzhhdhycw36egcmla5vlrtrmhs9t7psfy3hkkdqzm9eq64fjg558znccds5nhsfmxveha5xe0dykgpspdha0".to_owned(),
InvoiceBuilder::new(Currency::Bitcoin)
.amount_milli_satoshis(1_000_000_000)
.duration_since_epoch(Duration::from_secs(1496314658))
.payment_hash(sha256::Hash::from_hex(
"0001020304050607080900010203040506070809000102030405060708090102"
).unwrap())
.description("payment metadata inside".to_owned())
.payment_metadata(hex::decode("01fafaf0").unwrap())
.require_payment_metadata()
.payee_pub_key(PublicKey::from_slice(&hex::decode(
"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"
).unwrap()).unwrap())
.payment_secret(PaymentSecret([0x11; 32]))
.build_raw()
.unwrap()
.sign(|_| {
RecoverableSignature::from_compact(
&hex::decode("2150ed137ddb54f9736c6a0290ded709d22bddb7261d1d6518dffb467c6b1eef02afc182491bdacd00b65c83554c914a1c53c61b0a4ef04eccccdfb4365ed259").unwrap(),
RecoveryId::from_i32(1).unwrap()
)
}).unwrap(),
false, // Different features than set in InvoiceBuilder
true, // Some unknown fields
),
(
"lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc".to_owned(),
InvoiceBuilder::new(Currency::Bitcoin)
.amount_milli_satoshis(1_000_000_000)
.duration_since_epoch(Duration::from_secs(1496314658))
.payment_hash(sha256::Hash::from_hex(
"0001020304050607080900010203040506070809000102030405060708090102"
).unwrap())
.description("payment metadata inside".to_owned())
.payment_metadata(hex::decode("01fafaf0").unwrap())
.require_payment_metadata()
.payment_secret(PaymentSecret([0x11; 32]))
.build_raw()
.unwrap()
.sign(|_| {
RecoverableSignature::from_compact(
&hex::decode("f5d27be7d9c27d3aa521bc35d77cabd6bda18f1f61716445b19e27e4e17a887508ea8de5a8e1d94f561248f65434e61a221160dac1f1991b9c0f1057b269d898").unwrap(),
RecoveryId::from_i32(1).unwrap()
)
}).unwrap(),
false, // Different features than set in InvoiceBuilder
true, // Some unknown fields
),
]
}

View file

@ -21,7 +21,7 @@ pub mod bump_transaction;
pub use bump_transaction::BumpTransactionEvent;
use crate::chain::keysinterface::SpendableOutputDescriptor;
use crate::ln::channelmanager::{InterceptId, PaymentId};
use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields};
use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS;
use crate::ln::features::ChannelTypeFeatures;
use crate::ln::msgs;
@ -232,8 +232,11 @@ pub enum HTLCDestination {
///
/// Some of the reasons may include:
/// * HTLC Timeouts
/// * Expected MPP amount has already been reached
/// * Claimable amount does not match expected amount
/// * Excess HTLCs for a payment that we have already fully received, over-paying for the
/// payment,
/// * The counterparty node modified the HTLC in transit,
/// * A probing attack where an intermediary node is trying to detect if we are the ultimate
/// recipient for a payment.
FailedPayment {
/// The payment hash of the payment we attempted to process.
payment_hash: PaymentHash
@ -379,6 +382,11 @@ pub enum Event {
/// The hash for which the preimage should be handed to the ChannelManager. Note that LDK will
/// not stop you from registering duplicate payment hashes for inbound payments.
payment_hash: PaymentHash,
/// The fields in the onion which were received with each HTLC. Only fields which were
/// identical in each HTLC involved in the payment will be included here.
///
/// Payments received on LDK versions prior to 0.0.115 will have this field unset.
onion_fields: Option<RecipientOnionFields>,
/// The value, in thousandths of a satoshi, that this payment is for.
amount_msat: u64,
/// Information for claiming this received payment, based on whether the purpose of the
@ -820,7 +828,10 @@ impl Writeable for Event {
// We never write out FundingGenerationReady events as, upon disconnection, peers
// drop any channels which have not yet exchanged funding_signed.
},
&Event::PaymentClaimable { ref payment_hash, ref amount_msat, ref purpose, ref receiver_node_id, ref via_channel_id, ref via_user_channel_id, ref claim_deadline } => {
&Event::PaymentClaimable { ref payment_hash, ref amount_msat, ref purpose,
ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
ref claim_deadline, ref onion_fields
} => {
1u8.write(writer)?;
let mut payment_secret = None;
let payment_preimage;
@ -843,6 +854,7 @@ impl Writeable for Event {
(6, 0u64, required), // user_payment_id required for compatibility with 0.0.103 and earlier
(7, claim_deadline, option),
(8, payment_preimage, option),
(9, onion_fields, option),
});
},
&Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
@ -1043,6 +1055,7 @@ impl MaybeReadable for Event {
let mut via_channel_id = None;
let mut claim_deadline = None;
let mut via_user_channel_id = None;
let mut onion_fields = None;
read_tlv_fields!(reader, {
(0, payment_hash, required),
(1, receiver_node_id, option),
@ -1053,6 +1066,7 @@ impl MaybeReadable for Event {
(6, _user_payment_id, option),
(7, claim_deadline, option),
(8, payment_preimage, option),
(9, onion_fields, option),
});
let purpose = match payment_secret {
Some(secret) => PaymentPurpose::InvoicePayment {
@ -1070,6 +1084,7 @@ impl MaybeReadable for Event {
via_channel_id,
via_user_channel_id,
claim_deadline,
onion_fields,
}))
};
f()

View file

@ -106,11 +106,13 @@ pub(super) enum PendingHTLCRouting {
},
Receive {
payment_data: msgs::FinalOnionHopData,
payment_metadata: Option<Vec<u8>>,
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
phantom_shared_secret: Option<[u8; 32]>,
},
ReceiveKeysend {
payment_preimage: PaymentPreimage,
payment_metadata: Option<Vec<u8>>,
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
},
}
@ -470,6 +472,12 @@ impl_writeable_tlv_based!(ClaimingPayment, {
(4, receiver_node_id, required),
});
struct ClaimablePayment {
purpose: events::PaymentPurpose,
onion_fields: Option<RecipientOnionFields>,
htlcs: Vec<ClaimableHTLC>,
}
/// Information about claimable or being-claimed payments
struct ClaimablePayments {
/// Map from payment hash to the payment data and any HTLCs which are to us and can be
@ -480,7 +488,7 @@ struct ClaimablePayments {
///
/// When adding to the map, [`Self::pending_claiming_payments`] must also be checked to ensure
/// we don't get a duplicate payment.
claimable_htlcs: HashMap<PaymentHash, (events::PaymentPurpose, Vec<ClaimableHTLC>)>,
claimable_payments: HashMap<PaymentHash, ClaimablePayment>,
/// Map from payment hash to the payment data for HTLCs which we have begun claiming, but which
/// are waiting on a [`ChannelMonitorUpdate`] to complete in order to be surfaced to the user
@ -1761,7 +1769,7 @@ where
pending_inbound_payments: Mutex::new(HashMap::new()),
pending_outbound_payments: OutboundPayments::new(),
forward_htlcs: Mutex::new(HashMap::new()),
claimable_payments: Mutex::new(ClaimablePayments { claimable_htlcs: HashMap::new(), pending_claiming_payments: HashMap::new() }),
claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: HashMap::new(), pending_claiming_payments: HashMap::new() }),
pending_intercepted_htlcs: Mutex::new(HashMap::new()),
id_to_peer: Mutex::new(HashMap::new()),
short_to_chan_info: FairRwLock::new(HashMap::new()),
@ -2256,7 +2264,7 @@ where
msg: "Got non final data with an HMAC of 0",
});
},
msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage } => {
msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage, payment_metadata } => {
if payment_data.is_some() && keysend_preimage.is_some() {
return Err(ReceiveError {
err_code: 0x4000|22,
@ -2266,6 +2274,7 @@ where
} else if let Some(data) = payment_data {
PendingHTLCRouting::Receive {
payment_data: data,
payment_metadata,
incoming_cltv_expiry: hop_data.outgoing_cltv_value,
phantom_shared_secret,
}
@ -2286,6 +2295,7 @@ where
PendingHTLCRouting::ReceiveKeysend {
payment_preimage,
payment_metadata,
incoming_cltv_expiry: hop_data.outgoing_cltv_value,
}
} else {
@ -2799,6 +2809,11 @@ where
self.pending_outbound_payments.test_add_new_pending_payment(payment_hash, recipient_onion, payment_id, route, None, &self.entropy_source, best_block_height)
}
#[cfg(test)]
pub(crate) fn test_set_payment_metadata(&self, payment_id: PaymentId, new_payment_metadata: Option<Vec<u8>>) {
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
}
/// Signals that no further retries for the given payment should occur. Useful if you have a
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
@ -3383,7 +3398,7 @@ where
}
}
} else {
for forward_info in pending_forwards.drain(..) {
'next_forwardable_htlc: for forward_info in pending_forwards.drain(..) {
match forward_info {
HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo {
prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
@ -3391,13 +3406,19 @@ where
routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, ..
}
}) => {
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret) = match routing {
PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry, phantom_shared_secret } => {
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret } => {
let _legacy_hop_data = Some(payment_data.clone());
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data }, Some(payment_data), phantom_shared_secret)
let onion_fields =
RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), payment_metadata };
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
Some(payment_data), phantom_shared_secret, onion_fields)
},
PendingHTLCRouting::ReceiveKeysend { payment_preimage, payment_metadata, incoming_cltv_expiry } => {
let onion_fields = RecipientOnionFields { payment_secret: None, payment_metadata };
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
None, None, onion_fields)
},
PendingHTLCRouting::ReceiveKeysend { payment_preimage, incoming_cltv_expiry } =>
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), None, None),
_ => {
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
}
@ -3422,8 +3443,11 @@ where
onion_payload,
};
let mut committed_to_claimable = false;
macro_rules! fail_htlc {
($htlc: expr, $payment_hash: expr) => {
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(),
@ -3438,6 +3462,7 @@ where
HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data),
HTLCDestination::FailedPayment { payment_hash: $payment_hash },
));
continue 'next_forwardable_htlc;
}
}
let phantom_shared_secret = claimable_htlc.prev_hop.phantom_shared_secret;
@ -3459,15 +3484,28 @@ where
let mut claimable_payments = self.claimable_payments.lock().unwrap();
if claimable_payments.pending_claiming_payments.contains_key(&payment_hash) {
fail_htlc!(claimable_htlc, payment_hash);
continue
}
let (_, ref mut htlcs) = claimable_payments.claimable_htlcs.entry(payment_hash)
.or_insert_with(|| (purpose(), Vec::new()));
let ref mut claimable_payment = claimable_payments.claimable_payments
.entry(payment_hash)
// Note that if we insert here we MUST NOT fail_htlc!()
.or_insert_with(|| {
committed_to_claimable = true;
ClaimablePayment {
purpose: purpose(), htlcs: Vec::new(), onion_fields: None,
}
});
if let Some(earlier_fields) = &mut claimable_payment.onion_fields {
if earlier_fields.check_merge(&mut onion_fields).is_err() {
fail_htlc!(claimable_htlc, payment_hash);
}
} else {
claimable_payment.onion_fields = Some(onion_fields);
}
let ref mut htlcs = &mut claimable_payment.htlcs;
if htlcs.len() == 1 {
if let OnionPayload::Spontaneous(_) = htlcs[0].onion_payload {
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we already had an existing keysend HTLC with the same payment hash", log_bytes!(payment_hash.0));
fail_htlc!(claimable_htlc, payment_hash);
continue
}
}
let mut total_value = claimable_htlc.sender_intended_value;
@ -3496,6 +3534,9 @@ where
log_bytes!(payment_hash.0));
fail_htlc!(claimable_htlc, payment_hash);
} else if total_value >= $payment_data.total_msat {
#[allow(unused_assignments)] {
committed_to_claimable = true;
}
let prev_channel_id = prev_funding_outpoint.to_channel_id();
htlcs.push(claimable_htlc);
let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
@ -3508,6 +3549,7 @@ where
via_channel_id: Some(prev_channel_id),
via_user_channel_id: Some(prev_user_channel_id),
claim_deadline: Some(earliest_expiry - HTLC_FAIL_BACK_BUFFER),
onion_fields: claimable_payment.onion_fields.clone(),
});
payment_claimable_generated = true;
} else {
@ -3515,6 +3557,9 @@ where
// payment value yet, wait until we receive more
// MPP parts.
htlcs.push(claimable_htlc);
#[allow(unused_assignments)] {
committed_to_claimable = true;
}
}
payment_claimable_generated
}}
@ -3537,7 +3582,6 @@ where
Err(()) => {
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as payment verification failed", log_bytes!(payment_hash.0));
fail_htlc!(claimable_htlc, payment_hash);
continue
}
};
if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
@ -3546,7 +3590,6 @@ where
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as its CLTV expiry was too soon (had {}, earliest expected {})",
log_bytes!(payment_hash.0), cltv_expiry, expected_min_expiry_height);
fail_htlc!(claimable_htlc, payment_hash);
continue;
}
}
check_total_value!(payment_data, payment_preimage);
@ -3555,15 +3598,18 @@ where
let mut claimable_payments = self.claimable_payments.lock().unwrap();
if claimable_payments.pending_claiming_payments.contains_key(&payment_hash) {
fail_htlc!(claimable_htlc, payment_hash);
continue
}
match claimable_payments.claimable_htlcs.entry(payment_hash) {
match claimable_payments.claimable_payments.entry(payment_hash) {
hash_map::Entry::Vacant(e) => {
let amount_msat = claimable_htlc.value;
claimable_htlc.total_value_received = Some(amount_msat);
let claim_deadline = Some(claimable_htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER);
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
e.insert((purpose.clone(), vec![claimable_htlc]));
e.insert(ClaimablePayment {
purpose: purpose.clone(),
onion_fields: Some(onion_fields.clone()),
htlcs: vec![claimable_htlc],
});
let prev_channel_id = prev_funding_outpoint.to_channel_id();
new_events.push(events::Event::PaymentClaimable {
receiver_node_id: Some(receiver_node_id),
@ -3573,6 +3619,7 @@ where
via_channel_id: Some(prev_channel_id),
via_user_channel_id: Some(prev_user_channel_id),
claim_deadline,
onion_fields: Some(onion_fields),
});
},
hash_map::Entry::Occupied(_) => {
@ -3587,7 +3634,6 @@ where
if payment_data.is_none() {
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", log_bytes!(payment_hash.0));
fail_htlc!(claimable_htlc, payment_hash);
continue
};
let payment_data = payment_data.unwrap();
if inbound_payment.get().payment_secret != payment_data.payment_secret {
@ -3832,24 +3878,27 @@ where
}
}
self.claimable_payments.lock().unwrap().claimable_htlcs.retain(|payment_hash, (_, htlcs)| {
if htlcs.is_empty() {
self.claimable_payments.lock().unwrap().claimable_payments.retain(|payment_hash, payment| {
if payment.htlcs.is_empty() {
// This should be unreachable
debug_assert!(false);
return false;
}
if let OnionPayload::Invoice { .. } = htlcs[0].onion_payload {
if let OnionPayload::Invoice { .. } = payment.htlcs[0].onion_payload {
// Check if we've received all the parts we need for an MPP (the value of the parts adds to total_msat).
// In this case we're not going to handle any timeouts of the parts here.
// This condition determining whether the MPP is complete here must match
// exactly the condition used in `process_pending_htlc_forwards`.
if htlcs[0].total_msat <= htlcs.iter().fold(0, |total, htlc| total + htlc.sender_intended_value) {
if payment.htlcs[0].total_msat <= payment.htlcs.iter()
.fold(0, |total, htlc| total + htlc.sender_intended_value)
{
return true;
} else if htlcs.into_iter().any(|htlc| {
} else if payment.htlcs.iter_mut().any(|htlc| {
htlc.timer_ticks += 1;
return htlc.timer_ticks >= MPP_TIMEOUT_TICKS
}) {
timed_out_mpp_htlcs.extend(htlcs.drain(..).map(|htlc: ClaimableHTLC| (htlc.prev_hop, *payment_hash)));
timed_out_mpp_htlcs.extend(payment.htlcs.drain(..)
.map(|htlc: ClaimableHTLC| (htlc.prev_hop, *payment_hash)));
return false;
}
}
@ -3904,9 +3953,9 @@ where
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: FailureCode) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let removed_source = self.claimable_payments.lock().unwrap().claimable_payments.remove(payment_hash);
if let Some(payment) = removed_source {
for htlc in payment.htlcs {
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
@ -4083,9 +4132,9 @@ where
let mut sources = {
let mut claimable_payments = self.claimable_payments.lock().unwrap();
if let Some((payment_purpose, sources)) = claimable_payments.claimable_htlcs.remove(&payment_hash) {
if let Some(payment) = claimable_payments.claimable_payments.remove(&payment_hash) {
let mut receiver_node_id = self.our_network_pubkey;
for htlc in sources.iter() {
for htlc in payment.htlcs.iter() {
if htlc.prev_hop.phantom_shared_secret.is_some() {
let phantom_pubkey = self.node_signer.get_node_id(Recipient::PhantomNode)
.expect("Failed to get node_id for phantom node recipient");
@ -4095,15 +4144,15 @@ where
}
let dup_purpose = claimable_payments.pending_claiming_payments.insert(payment_hash,
ClaimingPayment { amount_msat: sources.iter().map(|source| source.value).sum(),
payment_purpose, receiver_node_id,
ClaimingPayment { amount_msat: payment.htlcs.iter().map(|source| source.value).sum(),
payment_purpose: payment.purpose, receiver_node_id,
});
if dup_purpose.is_some() {
debug_assert!(false, "Shouldn't get a duplicate pending claim event ever");
log_error!(self.logger, "Got a duplicate pending claimable event on payment hash {}! Please report this bug",
log_bytes!(payment_hash.0));
}
sources
payment.htlcs
} else { return; }
};
debug_assert!(!sources.is_empty());
@ -6175,8 +6224,8 @@ where
}
if let Some(height) = height_opt {
self.claimable_payments.lock().unwrap().claimable_htlcs.retain(|payment_hash, (_, htlcs)| {
htlcs.retain(|htlc| {
self.claimable_payments.lock().unwrap().claimable_payments.retain(|payment_hash, payment| {
payment.htlcs.retain(|htlc| {
// If height is approaching the number of blocks we think it takes us to get
// our commitment transaction confirmed before the HTLC expires, plus the
// number of blocks we generally consider it to take to do a commitment update,
@ -6191,7 +6240,7 @@ where
false
} else { true }
});
!htlcs.is_empty() // Only retain this entry if htlcs has at least one entry.
!payment.htlcs.is_empty() // Only retain this entry if htlcs has at least one entry.
});
let mut intercepted_htlcs = self.pending_intercepted_htlcs.lock().unwrap();
@ -6776,10 +6825,12 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
(0, payment_data, required),
(1, phantom_shared_secret, option),
(2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
},
(2, ReceiveKeysend) => {
(0, payment_preimage, required),
(2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
},
;);
@ -7112,14 +7163,16 @@ where
let pending_outbound_payments = self.pending_outbound_payments.pending_outbound_payments.lock().unwrap();
let mut htlc_purposes: Vec<&events::PaymentPurpose> = Vec::new();
(claimable_payments.claimable_htlcs.len() as u64).write(writer)?;
for (payment_hash, (purpose, previous_hops)) in claimable_payments.claimable_htlcs.iter() {
let mut htlc_onion_fields: Vec<&_> = Vec::new();
(claimable_payments.claimable_payments.len() as u64).write(writer)?;
for (payment_hash, payment) in claimable_payments.claimable_payments.iter() {
payment_hash.write(writer)?;
(previous_hops.len() as u64).write(writer)?;
for htlc in previous_hops.iter() {
(payment.htlcs.len() as u64).write(writer)?;
for htlc in payment.htlcs.iter() {
htlc.write(writer)?;
}
htlc_purposes.push(purpose);
htlc_purposes.push(&payment.purpose);
htlc_onion_fields.push(&payment.onion_fields);
}
let mut monitor_update_blocked_actions_per_peer = None;
@ -7234,6 +7287,7 @@ where
(7, self.fake_scid_rand_bytes, required),
(9, htlc_purposes, vec_type),
(11, self.probing_cookie_secret, required),
(13, htlc_onion_fields, optional_vec),
});
Ok(())
@ -7612,6 +7666,7 @@ where
let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
let mut probing_cookie_secret: Option<[u8; 32]> = None;
let mut claimable_htlc_purposes = None;
let mut claimable_htlc_onion_fields = None;
let mut pending_claiming_payments = Some(HashMap::new());
let mut monitor_update_blocked_actions_per_peer = Some(Vec::new());
read_tlv_fields!(reader, {
@ -7624,6 +7679,7 @@ where
(7, fake_scid_rand_bytes, option),
(9, claimable_htlc_purposes, vec_type),
(11, probing_cookie_secret, option),
(13, claimable_htlc_onion_fields, optional_vec),
});
if fake_scid_rand_bytes.is_none() {
fake_scid_rand_bytes = Some(args.entropy_source.get_secure_random_bytes());
@ -7687,6 +7743,7 @@ where
session_privs: [session_priv_bytes].iter().map(|a| *a).collect(),
payment_hash: htlc.payment_hash,
payment_secret: None, // only used for retries, and we'll never retry on startup
payment_metadata: None, // only used for retries, and we'll never retry on startup
keysend_preimage: None, // only used for retries, and we'll never retry on startup
pending_amt_msat: path_amt,
pending_fee_msat: Some(path_fee),
@ -7771,22 +7828,39 @@ where
let inbound_pmt_key_material = args.node_signer.get_inbound_payment_key_material();
let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material);
let mut claimable_htlcs = HashMap::with_capacity(claimable_htlcs_list.len());
if let Some(mut purposes) = claimable_htlc_purposes {
let mut claimable_payments = HashMap::with_capacity(claimable_htlcs_list.len());
if let Some(purposes) = claimable_htlc_purposes {
if purposes.len() != claimable_htlcs_list.len() {
return Err(DecodeError::InvalidValue);
}
for (purpose, (payment_hash, previous_hops)) in purposes.drain(..).zip(claimable_htlcs_list.drain(..)) {
claimable_htlcs.insert(payment_hash, (purpose, previous_hops));
if let Some(onion_fields) = claimable_htlc_onion_fields {
if onion_fields.len() != claimable_htlcs_list.len() {
return Err(DecodeError::InvalidValue);
}
for (purpose, (onion, (payment_hash, htlcs))) in
purposes.into_iter().zip(onion_fields.into_iter().zip(claimable_htlcs_list.into_iter()))
{
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: onion,
});
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
}
} else {
for (purpose, (payment_hash, htlcs)) in purposes.into_iter().zip(claimable_htlcs_list.into_iter()) {
let existing_payment = claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: None,
});
if existing_payment.is_some() { return Err(DecodeError::InvalidValue); }
}
}
} else {
// LDK versions prior to 0.0.107 did not write a `pending_htlc_purposes`, but do
// include a `_legacy_hop_data` in the `OnionPayload`.
for (payment_hash, previous_hops) in claimable_htlcs_list.drain(..) {
if previous_hops.is_empty() {
for (payment_hash, htlcs) in claimable_htlcs_list.drain(..) {
if htlcs.is_empty() {
return Err(DecodeError::InvalidValue);
}
let purpose = match &previous_hops[0].onion_payload {
let purpose = match &htlcs[0].onion_payload {
OnionPayload::Invoice { _legacy_hop_data } => {
if let Some(hop_data) = _legacy_hop_data {
events::PaymentPurpose::InvoicePayment {
@ -7807,7 +7881,9 @@ where
OnionPayload::Spontaneous(payment_preimage) =>
events::PaymentPurpose::SpontaneousPayment(*payment_preimage),
};
claimable_htlcs.insert(payment_hash, (purpose, previous_hops));
claimable_payments.insert(payment_hash, ClaimablePayment {
purpose, htlcs, onion_fields: None,
});
}
}
@ -7859,17 +7935,17 @@ where
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some((payment_purpose, claimable_htlcs)) = claimable_htlcs.remove(&payment_hash) {
if let Some(payment) = claimable_payments.remove(&payment_hash) {
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));
let mut claimable_amt_msat = 0;
let mut receiver_node_id = Some(our_network_pubkey);
let phantom_shared_secret = claimable_htlcs[0].prev_hop.phantom_shared_secret;
let phantom_shared_secret = payment.htlcs[0].prev_hop.phantom_shared_secret;
if phantom_shared_secret.is_some() {
let phantom_pubkey = args.node_signer.get_node_id(Recipient::PhantomNode)
.expect("Failed to get node_id for phantom node recipient");
receiver_node_id = Some(phantom_pubkey)
}
for claimable_htlc in claimable_htlcs {
for claimable_htlc in payment.htlcs {
claimable_amt_msat += claimable_htlc.value;
// Add a holding-cell claim of the payment to the Channel, which should be
@ -7903,7 +7979,7 @@ where
pending_events_read.push(events::Event::PaymentClaimed {
receiver_node_id,
payment_hash,
purpose: payment_purpose,
purpose: payment.purpose,
amount_msat: claimable_amt_msat,
});
}
@ -7934,7 +8010,7 @@ where
pending_intercepted_htlcs: Mutex::new(pending_intercepted_htlcs.unwrap()),
forward_htlcs: Mutex::new(forward_htlcs),
claimable_payments: Mutex::new(ClaimablePayments { claimable_htlcs, pending_claiming_payments: pending_claiming_payments.unwrap() }),
claimable_payments: Mutex::new(ClaimablePayments { claimable_payments, pending_claiming_payments: pending_claiming_payments.unwrap() }),
outbound_scid_aliases: Mutex::new(outbound_scid_aliases),
id_to_peer: Mutex::new(id_to_peer),
short_to_chan_info: FairRwLock::new(short_to_chan_info),

View file

@ -45,17 +45,25 @@
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information).
//! - `OnionMessages` - requires/supports forwarding onion messages
//! (see [BOLT-7](https://github.com/lightning/bolts/pull/759/files) for more information).
//! TODO: update link
// TODO: update link
//! - `ChannelType` - node supports the channel_type field in open/accept
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information).
//! - `SCIDPrivacy` - supply channel aliases for routing
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information).
//! - `PaymentMetadata` - include additional data in invoices which is passed to recipients in the
//! onion.
//! (see [BOLT-11](https://github.com/lightning/bolts/blob/master/11-payment-encoding.md) for
//! more).
//! - `ZeroConf` - supports accepting HTLCs and using channels prior to funding confirmation
//! (see
//! [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-channel_ready-message)
//! for more info).
//! - `Keysend` - send funds to a node without an invoice
//! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information).
//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs
//! and HTLC transactions are pre-signed with zero fee (see
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
//! information).
//! and HTLC transactions are pre-signed with zero fee (see
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
//! information).
//!
//! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md
//! [messages]: crate::ln::msgs
@ -160,6 +168,14 @@ mod sealed {
VariableLengthOnion | PaymentSecret,
// Byte 2
BasicMPP,
// Byte 3
,
// Byte 4
,
// Byte 5
,
// Byte 6
PaymentMetadata,
]);
define_context!(OfferContext, []);
define_context!(InvoiceRequestContext, []);
@ -259,6 +275,7 @@ mod sealed {
}
flags[Self::BYTE_OFFSET] |= Self::REQUIRED_MASK;
flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK;
}
/// Sets the feature's optional (odd) bit in the given flags.
@ -376,12 +393,16 @@ mod sealed {
define_feature!(47, SCIDPrivacy, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for only forwarding with SCID aliasing. Called `option_scid_alias` in the BOLTs",
set_scid_privacy_optional, set_scid_privacy_required, supports_scid_privacy, requires_scid_privacy);
define_feature!(49, PaymentMetadata, [InvoiceContext],
"Feature flags for payment metadata in invoices.", set_payment_metadata_optional,
set_payment_metadata_required, supports_payment_metadata, requires_payment_metadata);
define_feature!(51, ZeroConf, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for accepting channels with zero confirmations. Called `option_zeroconf` in the BOLTs",
set_zero_conf_optional, set_zero_conf_required, supports_zero_conf, requires_zero_conf);
define_feature!(55, Keysend, [NodeContext],
"Feature flags for keysend payments.", set_keysend_optional, set_keysend_required,
supports_keysend, requires_keysend);
// Note: update the module-level docs when a new feature bit is added!
#[cfg(test)]
define_feature!(123456789, UnknownFeature,
@ -885,13 +906,13 @@ mod tests {
#[test]
fn convert_to_context_with_unknown_flags() {
// Ensure the `from` context has fewer known feature bytes than the `to` context.
assert!(<sealed::InvoiceContext as sealed::Context>::KNOWN_FEATURE_MASK.len() <
<sealed::NodeContext as sealed::Context>::KNOWN_FEATURE_MASK.len());
let mut invoice_features = InvoiceFeatures::empty();
invoice_features.set_unknown_feature_optional();
assert!(invoice_features.supports_unknown_bits());
let node_features: NodeFeatures = invoice_features.to_context();
assert!(!node_features.supports_unknown_bits());
assert!(<sealed::ChannelContext as sealed::Context>::KNOWN_FEATURE_MASK.len() <
<sealed::InvoiceContext as sealed::Context>::KNOWN_FEATURE_MASK.len());
let mut channel_features = ChannelFeatures::empty();
channel_features.set_unknown_feature_optional();
assert!(channel_features.supports_unknown_bits());
let invoice_features: InvoiceFeatures = channel_features.to_context_internal();
assert!(!invoice_features.supports_unknown_bits());
}
#[test]

View file

@ -2034,21 +2034,26 @@ pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_p
let events_2 = node.node.get_and_clear_pending_events();
if payment_claimable_expected {
assert_eq!(events_2.len(), 1);
match events_2[0] {
Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat, receiver_node_id, ref via_channel_id, ref via_user_channel_id, claim_deadline } => {
match &events_2[0] {
Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat,
receiver_node_id, ref via_channel_id, ref via_user_channel_id,
claim_deadline, onion_fields,
} => {
assert_eq!(our_payment_hash, *payment_hash);
assert_eq!(node.node.get_our_node_id(), receiver_node_id.unwrap());
assert!(onion_fields.is_some());
match &purpose {
PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
assert_eq!(expected_preimage, *payment_preimage);
assert_eq!(our_payment_secret.unwrap(), *payment_secret);
assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret);
},
PaymentPurpose::SpontaneousPayment(payment_preimage) => {
assert_eq!(expected_preimage.unwrap(), *payment_preimage);
assert!(our_payment_secret.is_none());
},
}
assert_eq!(amount_msat, recv_value);
assert_eq!(*amount_msat, recv_value);
assert!(node.node.list_channels().iter().any(|details| details.channel_id == via_channel_id.unwrap()));
assert!(node.node.list_channels().iter().any(|details| details.user_channel_id == via_user_channel_id.unwrap()));
assert!(claim_deadline.unwrap() > node.best_block_info().1);

View file

@ -42,7 +42,7 @@ use crate::io_extras::read_to_end;
use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
use crate::util::logger;
use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname};
use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
@ -1168,6 +1168,7 @@ mod fuzzy_internal_msgs {
},
FinalNode {
payment_data: Option<FinalOnionHopData>,
payment_metadata: Option<Vec<u8>>,
keysend_preimage: Option<PaymentPreimage>,
},
}
@ -1661,11 +1662,12 @@ impl Writeable for OnionHopData {
(6, short_channel_id, required)
});
},
OnionHopDataFormat::FinalNode { ref payment_data, ref keysend_preimage } => {
OnionHopDataFormat::FinalNode { ref payment_data, ref payment_metadata, ref keysend_preimage } => {
_encode_varint_length_prefixed_tlv!(w, {
(2, HighZeroBytesDroppedBigSize(self.amt_to_forward), required),
(4, HighZeroBytesDroppedBigSize(self.outgoing_cltv_value), required),
(8, payment_data, option),
(16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option),
(5482373484, keysend_preimage, option)
});
},
@ -1680,29 +1682,33 @@ impl Readable for OnionHopData {
let mut cltv_value = HighZeroBytesDroppedBigSize(0u32);
let mut short_id: Option<u64> = None;
let mut payment_data: Option<FinalOnionHopData> = None;
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
read_tlv_fields!(r, {
(2, amt, required),
(4, cltv_value, required),
(6, short_id, option),
(8, payment_data, option),
(16, payment_metadata, option),
// See https://github.com/lightning/blips/blob/master/blip-0003.md
(5482373484, keysend_preimage, option)
});
let format = if let Some(short_channel_id) = short_id {
if payment_data.is_some() { return Err(DecodeError::InvalidValue); }
if payment_metadata.is_some() { return Err(DecodeError::InvalidValue); }
OnionHopDataFormat::NonFinalNode {
short_channel_id,
}
} else {
if let &Some(ref data) = &payment_data {
if let Some(data) = &payment_data {
if data.total_msat > MAX_VALUE_MSAT {
return Err(DecodeError::InvalidValue);
}
}
OnionHopDataFormat::FinalNode {
payment_data,
payment_metadata: payment_metadata.map(|w| w.0),
keysend_preimage,
}
};
@ -2880,6 +2886,7 @@ mod tests {
let mut msg = msgs::OnionHopData {
format: OnionHopDataFormat::FinalNode {
payment_data: None,
payment_metadata: None,
keysend_preimage: None,
},
amt_to_forward: 0x0badf00d01020304,
@ -2903,6 +2910,7 @@ mod tests {
payment_secret: expected_payment_secret,
total_msat: 0x1badca1f
}),
payment_metadata: None,
keysend_preimage: None,
},
amt_to_forward: 0x0badf00d01020304,
@ -2917,6 +2925,7 @@ mod tests {
payment_secret,
total_msat: 0x1badca1f
}),
payment_metadata: None,
keysend_preimage: None,
} = msg.format {
assert_eq!(payment_secret, expected_payment_secret);

View file

@ -170,6 +170,7 @@ pub(super) fn build_onion_payloads(path: &Vec<RouteHop>, total_msat: u64, mut re
total_msat,
})
} else { None },
payment_metadata: recipient_onion.payment_metadata.take(),
keysend_preimage: *keysend_preimage,
}
} else {

View file

@ -46,6 +46,7 @@ pub(crate) enum PendingOutboundPayment {
session_privs: HashSet<[u8; 32]>,
payment_hash: PaymentHash,
payment_secret: Option<PaymentSecret>,
payment_metadata: Option<Vec<u8>>,
keysend_preimage: Option<PaymentPreimage>,
pending_amt_msat: u64,
/// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+.
@ -405,7 +406,7 @@ pub enum PaymentSendFailure {
///
/// This should generally be constructed with data communicated to us from the recipient (via a
/// BOLT11 or BOLT12 invoice).
#[derive(Clone)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RecipientOnionFields {
/// The [`PaymentSecret`] is an arbitrary 32 bytes provided by the recipient for us to repeat
/// in the onion. It is unrelated to `payment_hash` (or [`PaymentPreimage`]) and exists to
@ -419,14 +420,32 @@ pub struct RecipientOnionFields {
/// receives, thus you should generally never be providing a secret here for spontaneous
/// payments.
pub payment_secret: Option<PaymentSecret>,
/// The payment metadata serves a similar purpose as [`Self::payment_secret`] but is of
/// arbitrary length. This gives recipients substantially more flexibility to receive
/// additional data.
///
/// In LDK, while the [`Self::payment_secret`] is fixed based on an internal authentication
/// scheme to authenticate received payments against expected payments and invoices, this field
/// is not used in LDK for received payments, and can be used to store arbitrary data in
/// invoices which will be received with the payment.
///
/// Note that this field was added to the lightning specification more recently than
/// [`Self::payment_secret`] and while nearly all lightning senders support secrets, metadata
/// may not be supported as universally.
pub payment_metadata: Option<Vec<u8>>,
}
impl_writeable_tlv_based!(RecipientOnionFields, {
(0, payment_secret, option),
(2, payment_metadata, option),
});
impl RecipientOnionFields {
/// Creates a [`RecipientOnionFields`] from only a [`PaymentSecret`]. This is the most common
/// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`]
/// but do not require or provide any further data.
pub fn secret_only(payment_secret: PaymentSecret) -> Self {
Self { payment_secret: Some(payment_secret) }
Self { payment_secret: Some(payment_secret), payment_metadata: None }
}
/// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create
@ -435,7 +454,21 @@ impl RecipientOnionFields {
///
/// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment
pub fn spontaneous_empty() -> Self {
Self { payment_secret: None }
Self { payment_secret: None, payment_metadata: None }
}
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
/// have to make sure that some fields match exactly across the parts. For those that aren't
/// required to match, if they don't match we should remove them so as to not expose data
/// that's dependent on the HTLC receive order to users.
///
/// Here we implement this, first checking compatibility then mutating two objects and then
/// dropping any remaining non-matching fields from both.
pub(super) fn check_merge(&mut self, further_htlc_fields: &mut Self) -> Result<(), ()> {
if self.payment_secret != further_htlc_fields.payment_secret { return Err(()); }
if self.payment_metadata != further_htlc_fields.payment_metadata { return Err(()); }
// For custom TLVs we should just drop non-matching ones, but not reject the payment.
Ok(())
}
}
@ -722,7 +755,7 @@ impl OutboundPayments {
hash_map::Entry::Occupied(mut payment) => {
let res = match payment.get() {
PendingOutboundPayment::Retryable {
total_msat, keysend_preimage, payment_secret, pending_amt_msat, ..
total_msat, keysend_preimage, payment_secret, payment_metadata, pending_amt_msat, ..
} => {
let retry_amt_msat: u64 = route.paths.iter().map(|path| path.last().unwrap().fee_msat).sum();
if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 {
@ -732,6 +765,7 @@ impl OutboundPayments {
}
(*total_msat, RecipientOnionFields {
payment_secret: *payment_secret,
payment_metadata: payment_metadata.clone(),
}, *keysend_preimage)
},
PendingOutboundPayment::Legacy { .. } => {
@ -886,6 +920,18 @@ impl OutboundPayments {
}
}
#[cfg(test)]
pub(super) fn test_set_payment_metadata(
&self, payment_id: PaymentId, new_payment_metadata: Option<Vec<u8>>
) {
match self.pending_outbound_payments.lock().unwrap().get_mut(&payment_id).unwrap() {
PendingOutboundPayment::Retryable { payment_metadata, .. } => {
*payment_metadata = new_payment_metadata;
},
_ => panic!("Need a retryable payment to update metadata on"),
}
}
#[cfg(test)]
pub(super) fn test_add_new_pending_payment<ES: Deref>(
&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId,
@ -917,6 +963,7 @@ impl OutboundPayments {
pending_fee_msat: Some(0),
payment_hash,
payment_secret: recipient_onion.payment_secret,
payment_metadata: recipient_onion.payment_metadata,
keysend_preimage,
starting_block_height: best_block_height,
total_msat: route.get_total_amount(),
@ -1358,6 +1405,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
(4, payment_secret, option),
(5, keysend_preimage, option),
(6, total_msat, required),
(7, payment_metadata, option),
(8, pending_amt_msat, required),
(10, starting_block_height, required),
(not_written, retry_strategy, (static_value, None)),

View file

@ -3017,3 +3017,151 @@ fn claim_from_closed_chan() {
do_claim_from_closed_chan(true);
do_claim_from_closed_chan(false);
}
fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) {
// Check that a payment metadata received on one HTLC that doesn't match the one received on
// another results in the HTLC being rejected.
//
// We first set up a diamond shaped network, allowing us to split a payment into two HTLCs, the
// first of which we'll deliver and the second of which we'll fail and then re-send with
// modified payment metadata, which will in turn result in it being failed by the recipient.
let chanmon_cfgs = create_chanmon_cfgs(4);
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
let mut config = test_default_channel_config();
config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 50;
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, Some(config), Some(config), Some(config)]);
let persister;
let new_chain_monitor;
let nodes_0_deserialized;
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_id_bd = create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 1_000_000, 0).2;
create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1_000_000, 0);
let chan_id_cd = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).2;
// Pay more than half of each channel's max, requiring MPP
let amt_msat = 750_000_000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[3], Some(amt_msat));
let payment_id = PaymentId(payment_hash.0);
let payment_metadata = vec![44, 49, 52, 142];
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_features(nodes[1].node.invoice_features());
let mut route_params = RouteParameters {
payment_params,
final_value_msat: amt_msat,
};
// Send the MPP payment, delivering the updated commitment state to nodes[1].
nodes[0].node.send_payment(payment_hash, RecipientOnionFields {
payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata),
}, payment_id, route_params.clone(), Retry::Attempts(1)).unwrap();
check_added_monitors!(nodes[0], 2);
let mut send_events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(send_events.len(), 2);
let first_send = SendEvent::from_event(send_events.pop().unwrap());
let second_send = SendEvent::from_event(send_events.pop().unwrap());
let (b_recv_ev, c_recv_ev) = if first_send.node_id == nodes[1].node.get_our_node_id() {
(&first_send, &second_send)
} else {
(&second_send, &first_send)
};
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &b_recv_ev.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], b_recv_ev.commitment_msg, false, true);
expect_pending_htlcs_forwardable!(nodes[1]);
check_added_monitors(&nodes[1], 1);
let b_forward_ev = SendEvent::from_node(&nodes[1]);
nodes[3].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &b_forward_ev.msgs[0]);
commitment_signed_dance!(nodes[3], nodes[1], b_forward_ev.commitment_msg, false, true);
expect_pending_htlcs_forwardable!(nodes[3]);
// Before delivering the second MPP HTLC to nodes[2], disconnect nodes[2] and nodes[3], which
// will result in nodes[2] failing the HTLC back.
nodes[2].node.peer_disconnected(&nodes[3].node.get_our_node_id());
nodes[3].node.peer_disconnected(&nodes[2].node.get_our_node_id());
nodes[2].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &c_recv_ev.msgs[0]);
commitment_signed_dance!(nodes[2], nodes[0], c_recv_ev.commitment_msg, false, true);
let cs_fail = get_htlc_update_msgs(&nodes[2], &nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(), &cs_fail.update_fail_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_fail.commitment_signed, false, true);
let payment_fail_retryable_evs = nodes[0].node.get_and_clear_pending_events();
assert_eq!(payment_fail_retryable_evs.len(), 2);
if let Event::PaymentPathFailed { .. } = payment_fail_retryable_evs[0] {} else { panic!(); }
if let Event::PendingHTLCsForwardable { .. } = payment_fail_retryable_evs[1] {} else { panic!(); }
// Before we allow the HTLC to be retried, optionally change the payment_metadata we have
// stored for our payment.
if do_modify {
nodes[0].node.test_set_payment_metadata(payment_id, Some(Vec::new()));
}
// Optionally reload nodes[3] to check that the payment_metadata is properly serialized with
// the payment state.
if do_reload {
let mon_bd = get_monitor!(nodes[3], chan_id_bd).encode();
let mon_cd = get_monitor!(nodes[3], chan_id_cd).encode();
reload_node!(nodes[3], config, &nodes[3].node.encode(), &[&mon_bd, &mon_cd],
persister, new_chain_monitor, nodes_0_deserialized);
nodes[1].node.peer_disconnected(&nodes[3].node.get_our_node_id());
reconnect_nodes(&nodes[1], &nodes[3], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
}
reconnect_nodes(&nodes[2], &nodes[3], (true, true), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false));
// Create a new channel between C and D as A will refuse to retry on the existing one because
// it just failed.
let chan_id_cd_2 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).2;
// Now retry the failed HTLC.
nodes[0].node.process_pending_htlc_forwards();
check_added_monitors(&nodes[0], 1);
let as_resend = SendEvent::from_node(&nodes[0]);
nodes[2].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &as_resend.msgs[0]);
commitment_signed_dance!(nodes[2], nodes[0], as_resend.commitment_msg, false, true);
expect_pending_htlcs_forwardable!(nodes[2]);
check_added_monitors(&nodes[2], 1);
let cs_forward = SendEvent::from_node(&nodes[2]);
nodes[3].node.handle_update_add_htlc(&nodes[2].node.get_our_node_id(), &cs_forward.msgs[0]);
commitment_signed_dance!(nodes[3], nodes[2], cs_forward.commitment_msg, false, true);
// Finally, check that nodes[3] does the correct thing - either accepting the payment or, if
// the payment metadata was modified, failing only the one modified HTLC and retaining the
// other.
if do_modify {
expect_pending_htlcs_forwardable_ignore!(nodes[3]);
nodes[3].node.process_pending_htlc_forwards();
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 ds_fail = get_htlc_update_msgs(&nodes[3], &nodes[2].node.get_our_node_id());
nodes[2].node.handle_update_fail_htlc(&nodes[3].node.get_our_node_id(), &ds_fail.update_fail_htlcs[0]);
commitment_signed_dance!(nodes[2], nodes[3], ds_fail.commitment_signed, false, true);
expect_pending_htlcs_forwardable_conditions(nodes[2].node.get_and_clear_pending_events(),
&[HTLCDestination::NextHopChannel { node_id: Some(nodes[3].node.get_our_node_id()), channel_id: chan_id_cd_2 }]);
} else {
expect_pending_htlcs_forwardable!(nodes[3]);
expect_payment_claimable!(nodes[3], payment_hash, payment_secret, amt_msat);
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_preimage);
}
}
#[test]
fn test_payment_metadata_consistency() {
do_test_payment_metadata_consistency(true, true);
do_test_payment_metadata_consistency(true, false);
do_test_payment_metadata_consistency(false, true);
do_test_payment_metadata_consistency(false, false);
}