Skip to content

Commit fae2746

Browse files
authored
Merge pull request #733 from vincenzopalazzo/macros/bolt12-pop
[RFC] event: expose BOLT12 invoice in PaymentSuccessful for proof of payment
2 parents cb99e84 + 3c724a9 commit fae2746

File tree

4 files changed

+140
-7
lines changed

4 files changed

+140
-7
lines changed

src/event.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use bitcoin::blockdata::locktime::absolute::LockTime;
1515
use bitcoin::secp256k1::PublicKey;
1616
use bitcoin::{Amount, OutPoint};
1717
use lightning::events::bump_transaction::BumpTransactionEvent;
18+
#[cfg(not(feature = "uniffi"))]
19+
use lightning::events::PaidBolt12Invoice;
1820
use lightning::events::{
1921
ClosureReason, Event as LdkEvent, FundingInfo, PaymentFailureReason, PaymentPurpose,
2022
ReplayEvent,
@@ -37,6 +39,8 @@ use crate::config::{may_announce_channel, Config};
3739
use crate::connection::ConnectionManager;
3840
use crate::data_store::DataStoreUpdateResult;
3941
use crate::fee_estimator::ConfirmationTarget;
42+
#[cfg(feature = "uniffi")]
43+
use crate::ffi::PaidBolt12Invoice;
4044
use crate::io::{
4145
EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE,
4246
EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE,
@@ -79,6 +83,17 @@ pub enum Event {
7983
payment_preimage: Option<PaymentPreimage>,
8084
/// The total fee which was spent at intermediate hops in this payment.
8185
fee_paid_msat: Option<u64>,
86+
/// The BOLT12 invoice that was paid.
87+
///
88+
/// This is useful for proof of payment. A third party can verify that the payment was made
89+
/// by checking that the `payment_hash` in the invoice matches `sha256(payment_preimage)`.
90+
///
91+
/// Will be `None` for non-BOLT12 payments.
92+
///
93+
/// Note that static invoices (indicated by [`PaidBolt12Invoice::StaticInvoice`], used for
94+
/// async payments) do not support proof of payment as the payment hash is not derived
95+
/// from a preimage known only to the recipient.
96+
bolt12_invoice: Option<PaidBolt12Invoice>,
8297
},
8398
/// A sent payment has failed.
8499
PaymentFailed {
@@ -268,6 +283,7 @@ impl_writeable_tlv_based_enum!(Event,
268283
(1, fee_paid_msat, option),
269284
(3, payment_id, option),
270285
(5, payment_preimage, option),
286+
(7, bolt12_invoice, option),
271287
},
272288
(1, PaymentFailed) => {
273289
(0, payment_hash, option),
@@ -1028,6 +1044,7 @@ where
10281044
payment_preimage,
10291045
payment_hash,
10301046
fee_paid_msat,
1047+
bolt12_invoice,
10311048
..
10321049
} => {
10331050
let payment_id = if let Some(id) = payment_id {
@@ -1073,6 +1090,7 @@ where
10731090
payment_hash,
10741091
payment_preimage: Some(payment_preimage),
10751092
fee_paid_msat,
1093+
bolt12_invoice: bolt12_invoice.map(Into::into),
10761094
};
10771095

10781096
match self.event_queue.add_event(event).await {

src/ffi/types.rs

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
// Make sure to add any re-exported items that need to be used in uniffi below.
1212

13+
use std::collections::HashMap;
1314
use std::convert::TryInto;
1415
use std::ops::Deref;
1516
use std::str::FromStr;
@@ -22,17 +23,20 @@ use bitcoin::hashes::Hash;
2223
use bitcoin::secp256k1::PublicKey;
2324
pub use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, ScriptBuf, Txid};
2425
pub use lightning::chain::channelmonitor::BalanceSource;
26+
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
2527
pub use lightning::events::{ClosureReason, PaymentFailureReason};
2628
use lightning::ln::channelmanager::PaymentId;
29+
use lightning::ln::msgs::DecodeError;
2730
pub use lightning::ln::types::ChannelId;
2831
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
2932
pub use lightning::offers::offer::OfferId;
3033
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
3134
use lightning::offers::refund::Refund as LdkRefund;
35+
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
3236
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
3337
pub use lightning::routing::gossip::{NodeAlias, NodeId, RoutingFees};
3438
pub use lightning::routing::router::RouteParametersConfig;
35-
use lightning::util::ser::Writeable;
39+
use lightning::util::ser::{Readable, Writeable, Writer};
3640
use lightning_invoice::{Bolt11Invoice as LdkBolt11Invoice, Bolt11InvoiceDescriptionRef};
3741
pub use lightning_invoice::{Description, SignedRawBolt11Invoice};
3842
pub use lightning_liquidity::lsps0::ser::LSPSDateTime;
@@ -41,10 +45,10 @@ pub use lightning_liquidity::lsps1::msgs::{
4145
};
4246
pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
4347
pub use lightning_types::string::UntrustedString;
44-
use std::collections::HashMap;
45-
46-
use vss_client::headers::VssHeaderProvider as VssClientHeaderProvider;
47-
use vss_client::headers::VssHeaderProviderError as VssClientHeaderProviderError;
48+
use vss_client::headers::{
49+
VssHeaderProvider as VssClientHeaderProvider,
50+
VssHeaderProviderError as VssClientHeaderProviderError,
51+
};
4852

4953
/// Errors around providing headers for each VSS request.
5054
#[derive(Debug, uniffi::Error)]
@@ -775,6 +779,95 @@ impl AsRef<LdkBolt12Invoice> for Bolt12Invoice {
775779
}
776780
}
777781

782+
/// A static invoice used for async payments.
783+
///
784+
/// Static invoices are a special type of BOLT12 invoice where proof of payment is not possible,
785+
/// as the payment hash is not derived from a preimage known only to the recipient.
786+
#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)]
787+
pub struct StaticInvoice {
788+
pub(crate) inner: LdkStaticInvoice,
789+
}
790+
791+
#[uniffi::export]
792+
impl StaticInvoice {
793+
/// The amount for a successful payment of the invoice, if specified.
794+
pub fn amount(&self) -> Option<OfferAmount> {
795+
self.inner.amount().map(|amount| amount.into())
796+
}
797+
}
798+
799+
impl From<LdkStaticInvoice> for StaticInvoice {
800+
fn from(invoice: LdkStaticInvoice) -> Self {
801+
StaticInvoice { inner: invoice }
802+
}
803+
}
804+
805+
impl Deref for StaticInvoice {
806+
type Target = LdkStaticInvoice;
807+
fn deref(&self) -> &Self::Target {
808+
&self.inner
809+
}
810+
}
811+
812+
impl AsRef<LdkStaticInvoice> for StaticInvoice {
813+
fn as_ref(&self) -> &LdkStaticInvoice {
814+
self.deref()
815+
}
816+
}
817+
818+
/// The BOLT12 invoice that was paid, surfaced in [`Event::PaymentSuccessful`].
819+
///
820+
/// [`Event::PaymentSuccessful`]: crate::Event::PaymentSuccessful
821+
#[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum)]
822+
pub enum PaidBolt12Invoice {
823+
/// The BOLT12 invoice, allowing the user to perform proof of payment.
824+
Bolt12(Arc<Bolt12Invoice>),
825+
/// The static invoice, used in async payments, where the user cannot perform proof of
826+
/// payment.
827+
Static(Arc<StaticInvoice>),
828+
}
829+
830+
impl From<LdkPaidBolt12Invoice> for PaidBolt12Invoice {
831+
fn from(ldk: LdkPaidBolt12Invoice) -> Self {
832+
match ldk {
833+
LdkPaidBolt12Invoice::Bolt12Invoice(invoice) => {
834+
PaidBolt12Invoice::Bolt12(Arc::new(Bolt12Invoice::from(invoice)))
835+
},
836+
LdkPaidBolt12Invoice::StaticInvoice(invoice) => {
837+
PaidBolt12Invoice::Static(Arc::new(StaticInvoice::from(invoice)))
838+
},
839+
}
840+
}
841+
}
842+
843+
impl From<PaidBolt12Invoice> for LdkPaidBolt12Invoice {
844+
fn from(wrapper: PaidBolt12Invoice) -> Self {
845+
match wrapper {
846+
PaidBolt12Invoice::Bolt12(invoice) => {
847+
LdkPaidBolt12Invoice::Bolt12Invoice(invoice.inner.clone())
848+
},
849+
PaidBolt12Invoice::Static(invoice) => {
850+
LdkPaidBolt12Invoice::StaticInvoice(invoice.inner.clone())
851+
},
852+
}
853+
}
854+
}
855+
856+
impl Writeable for PaidBolt12Invoice {
857+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), lightning::io::Error> {
858+
// TODO: Find way to avoid cloning invoice data.
859+
let ldk_type: LdkPaidBolt12Invoice = self.clone().into();
860+
ldk_type.write(w)
861+
}
862+
}
863+
864+
impl Readable for PaidBolt12Invoice {
865+
fn read<R: lightning::io::Read>(r: &mut R) -> Result<Self, DecodeError> {
866+
let ldk_type = LdkPaidBolt12Invoice::read(r)?;
867+
Ok(ldk_type.into())
868+
}
869+
}
870+
778871
uniffi::custom_type!(OfferId, String, {
779872
remote,
780873
try_lift: |val| {

tests/common/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,17 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
953953
});
954954
assert_eq!(inbound_payments_b.len(), 1);
955955

956-
expect_event!(node_a, PaymentSuccessful);
956+
// Verify bolt12_invoice is None for BOLT11 payments
957+
match node_a.next_event_async().await {
958+
ref e @ Event::PaymentSuccessful { ref bolt12_invoice, .. } => {
959+
println!("{} got event {:?}", node_a.node_id(), e);
960+
assert!(bolt12_invoice.is_none(), "bolt12_invoice should be None for BOLT11 payments");
961+
node_a.event_handled().unwrap();
962+
},
963+
ref e => {
964+
panic!("{} got unexpected event!: {:?}", std::stringify!(node_a), e);
965+
},
966+
}
957967
expect_event!(node_b, PaymentReceived);
958968
assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded);
959969
assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound);

tests/integration_tests_rust.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,19 @@ async fn simple_bolt12_send_receive() {
10891089
.send(&offer, expected_quantity, expected_payer_note.clone(), None)
10901090
.unwrap();
10911091

1092-
expect_payment_successful_event!(node_a, Some(payment_id), None);
1092+
let event = node_a.next_event_async().await;
1093+
match event {
1094+
ref e @ Event::PaymentSuccessful { payment_id: ref evt_id, ref bolt12_invoice, .. } => {
1095+
println!("{} got event {:?}", node_a.node_id(), e);
1096+
assert_eq!(*evt_id, Some(payment_id));
1097+
assert!(
1098+
bolt12_invoice.is_some(),
1099+
"bolt12_invoice should be present for BOLT12 payments"
1100+
);
1101+
node_a.event_handled().unwrap();
1102+
},
1103+
ref e => panic!("{} got unexpected event!: {:?}", "node_a", e),
1104+
}
10931105
let node_a_payments =
10941106
node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. }));
10951107
assert_eq!(node_a_payments.len(), 1);

0 commit comments

Comments
 (0)