-
Notifications
You must be signed in to change notification settings - Fork 411
Support sending PaymentMetadata
in HTLCs
#2127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6ce239c
35b597a
1b29a55
928c9b8
8ed6e64
a90a35b
ee9afd3
f57221b
c1e6a74
3dd05ab
9c55ada
e1e7903
a41d75f
934a534
ef8e377
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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. | ||||||
|
@@ -419,6 +423,7 @@ pub enum TaggedField { | |||||
Fallback(Fallback), | ||||||
PrivateRoute(PrivateRoute), | ||||||
PaymentSecret(PaymentSecret), | ||||||
PaymentMetadata(Vec<u8>), | ||||||
Features(InvoiceFeatures), | ||||||
} | ||||||
|
||||||
|
@@ -483,15 +488,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, | ||||||
|
@@ -503,14 +509,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, | ||||||
|
@@ -523,6 +530,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, | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -567,7 +575,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> { | ||||||
|
@@ -601,9 +609,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), | ||||||
|
@@ -612,13 +620,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()) | ||||||
|
@@ -630,18 +638,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), | ||||||
|
@@ -652,7 +660,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), | ||||||
|
@@ -663,34 +671,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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But there's only one sender? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, then it should probably be "...that the sender includes the metadata (and fails 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)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a follow-up we might want to look into: Do we need/want to limit the size of the payment metadata field? How can we communicate the influence it might have on the maximum path length? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll need to rework all that code for blinded paths too, indeed let's do it in a followup - #2201 |
||||||
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() { | ||||||
|
@@ -702,7 +758,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. | ||||||
|
@@ -954,6 +1010,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) | ||||||
} | ||||||
|
@@ -1225,6 +1285,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() | ||||||
|
@@ -1374,6 +1439,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, | ||||||
}; | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woah, this is the first time I'm really looking at this part of the repo, this is so cool. Just to make sure I'm getting things right, these type parameters are here to basically conditionally implement/expose functions to the user based on the state of the invoice (what fields have been added) (such that a user's error will be caught at compile time), and then the
PhantomData
is there to just use the types because rust doesn't allow unused type params?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep!