|
| 1 | +/// Convenient utilities to create an invoice. |
| 2 | +use {Currency, Invoice, InvoiceBuilder, SignOrCreationError, SignedRawInvoice}; |
| 3 | +use bech32::{FromBase32, ToBase32, u5}; |
| 4 | +use bitcoin_hashes::Hash; |
| 5 | +use lightning::chain; |
| 6 | +use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; |
| 7 | +use lightning::chain::keysinterface::{Sign, KeysInterface}; |
| 8 | +use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY}; |
| 9 | +use lightning::ln::features::InvoiceFeatures; |
| 10 | +use lightning::ln::msgs::DecodeError; |
| 11 | +use lightning::routing::network_graph::RoutingFees; |
| 12 | +use lightning::routing::router::RouteHintHop; |
| 13 | +use lightning::util::logger::Logger; |
| 14 | +use std::ops::Deref; |
| 15 | + |
| 16 | +/// Utility to construct an invoice. Generally, unless you want to do something like a custom |
| 17 | +/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this |
| 18 | +/// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user |
| 19 | +/// doesn't have to store preimage/payment secret information and (b) `ChannelManager` can verify |
| 20 | +/// that the payment secret is valid when the invoice is paid. |
| 21 | +pub fn create_invoice_from_channelmanager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>( |
| 22 | + channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, amt_msat: Option<u64>, |
| 23 | + description: String, network: Currency |
| 24 | +) -> Result<Invoice, SignOrCreationError<DecodeError>> |
| 25 | +where |
| 26 | + M::Target: chain::Watch<Signer>, |
| 27 | + T::Target: BroadcasterInterface, |
| 28 | + K::Target: KeysInterface<Signer = Signer>, |
| 29 | + F::Target: FeeEstimator, |
| 30 | + L::Target: Logger, |
| 31 | +{ |
| 32 | + // Marshall route hints. |
| 33 | + let our_channels = channelmanager.list_usable_channels(); |
| 34 | + let mut route_hints = vec![]; |
| 35 | + for channel in our_channels { |
| 36 | + let short_channel_id = match channel.short_channel_id { |
| 37 | + Some(id) => id, |
| 38 | + None => continue, |
| 39 | + }; |
| 40 | + let forwarding_info = match channel.counterparty_forwarding_info { |
| 41 | + Some(info) => info, |
| 42 | + None => continue, |
| 43 | + }; |
| 44 | + route_hints.push(vec![RouteHintHop { |
| 45 | + src_node_id: channel.remote_network_id, |
| 46 | + short_channel_id, |
| 47 | + fees: RoutingFees { |
| 48 | + base_msat: forwarding_info.fee_base_msat, |
| 49 | + proportional_millionths: forwarding_info.fee_proportional_millionths, |
| 50 | + }, |
| 51 | + cltv_expiry_delta: forwarding_info.cltv_expiry_delta, |
| 52 | + htlc_minimum_msat: None, |
| 53 | + htlc_maximum_msat: None, |
| 54 | + }]); |
| 55 | + } |
| 56 | + |
| 57 | + let (payment_hash, payment_secret) = channelmanager.create_inbound_payment( |
| 58 | + amt_msat, |
| 59 | + 7200, // default invoice expiry is 2 hours |
| 60 | + 0, |
| 61 | + ); |
| 62 | + let our_node_pubkey = channelmanager.get_our_node_id(); |
| 63 | + let mut invoice = InvoiceBuilder::new(network) |
| 64 | + .description(description) |
| 65 | + .current_timestamp() |
| 66 | + .payee_pub_key(our_node_pubkey) |
| 67 | + .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) |
| 68 | + .payment_secret(payment_secret) |
| 69 | + .features(InvoiceFeatures::known()) |
| 70 | + .min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into()); |
| 71 | + if let Some(amt) = amt_msat { |
| 72 | + invoice = invoice.amount_pico_btc(amt * 10); |
| 73 | + } |
| 74 | + for hint in route_hints.drain(..) { |
| 75 | + invoice = invoice.route(hint); |
| 76 | + } |
| 77 | + |
| 78 | + let raw_invoice = match invoice.build_raw() { |
| 79 | + Ok(inv) => inv, |
| 80 | + Err(e) => return Err(SignOrCreationError::CreationError(e)) |
| 81 | + }; |
| 82 | + let hrp_str = raw_invoice.hrp.to_string(); |
| 83 | + let hrp_bytes = hrp_str.as_bytes(); |
| 84 | + let data_without_signature = raw_invoice.data.to_base32(); |
| 85 | + let mut invoice_preimage = Vec::<u8>::from(hrp_bytes); |
| 86 | + |
| 87 | + let mut data_part = Vec::from(data_without_signature); |
| 88 | + let overhang = (data_part.len() * 5) % 8; |
| 89 | + if overhang > 0 { |
| 90 | + // add padding if data does not end at a byte boundary |
| 91 | + data_part.push(u5::try_from_u8(0).unwrap()); |
| 92 | + |
| 93 | + // if overhang is in (1..3) we need to add u5(0) padding two times |
| 94 | + if overhang < 3 { |
| 95 | + data_part.push(u5::try_from_u8(0).unwrap()); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + invoice_preimage.extend_from_slice(&Vec::<u8>::from_base32(&data_part) |
| 100 | + .expect("No padding error may occur due to appended zero above.")); |
| 101 | + let signed_raw_invoice: Result<SignedRawInvoice, DecodeError> = raw_invoice.sign(|_| keys_manager.sign_invoice(invoice_preimage)); |
| 102 | + match signed_raw_invoice { |
| 103 | + Ok(inv) => return Ok(Invoice::from_signed(inv).unwrap()), |
| 104 | + Err(e) => return Err(SignOrCreationError::SignError(e)) |
| 105 | + }; |
| 106 | +} |
| 107 | + |
| 108 | +#[cfg(test)] |
| 109 | +mod test { |
| 110 | + use {Currency, Description, InvoiceDescription}; |
| 111 | + use lightning::ln::PaymentHash; |
| 112 | + use lightning::ln::functional_test_utils::*; |
| 113 | + use lightning::ln::features::InitFeatures; |
| 114 | + use lightning::ln::msgs::ChannelMessageHandler; |
| 115 | + use lightning::routing::router; |
| 116 | + use lightning::util::events::MessageSendEventsProvider; |
| 117 | + use lightning::util::test_utils; |
| 118 | + #[test] |
| 119 | + fn test_from_channelmanager() { |
| 120 | + let chanmon_cfgs = create_chanmon_cfgs(2); |
| 121 | + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); |
| 122 | + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); |
| 123 | + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); |
| 124 | + let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()); |
| 125 | + let invoice = ::utils::create_invoice_from_channelmanager(&nodes[1].node, Some(10_000), "test".to_string(), Currency::BitcoinTestnet, nodes[1].keys_manager).unwrap(); |
| 126 | + assert_eq!(invoice.amount_pico_btc(), Some(100_000)); |
| 127 | + assert_eq!(invoice.min_final_cltv_expiry(), Some(&9)); |
| 128 | + assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string()))); |
| 129 | + |
| 130 | + let mut route_hints = invoice.routes().clone(); |
| 131 | + let mut last_hops = Vec::new(); |
| 132 | + for hint in route_hints.drain(..) { |
| 133 | + last_hops.push(hint[hint.len() - 1].clone()); |
| 134 | + } |
| 135 | + let amt_msat = invoice.amount_pico_btc().unwrap() / 10; |
| 136 | + |
| 137 | + let first_hops = nodes[0].node.list_usable_channels(); |
| 138 | + let network_graph = nodes[0].net_graph_msg_handler.network_graph.read().unwrap(); |
| 139 | + let logger = test_utils::TestLogger::new(); |
| 140 | + let route = router::get_route( |
| 141 | + &nodes[0].node.get_our_node_id(), |
| 142 | + &network_graph, |
| 143 | + &invoice.recover_payee_pub_key(), |
| 144 | + Some(invoice.features().unwrap().clone()), |
| 145 | + Some(&first_hops.iter().collect::<Vec<_>>()), |
| 146 | + &last_hops.iter().collect::<Vec<_>>(), |
| 147 | + amt_msat, |
| 148 | + *invoice.min_final_cltv_expiry().unwrap() as u32, |
| 149 | + &logger, |
| 150 | + ).unwrap(); |
| 151 | + |
| 152 | + let payment_event = { |
| 153 | + let mut payment_hash = PaymentHash([0; 32]); |
| 154 | + payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); |
| 155 | + nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().unwrap().clone())).unwrap(); |
| 156 | + let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); |
| 157 | + assert_eq!(added_monitors.len(), 1); |
| 158 | + added_monitors.clear(); |
| 159 | + |
| 160 | + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); |
| 161 | + assert_eq!(events.len(), 1); |
| 162 | + SendEvent::from_event(events.remove(0)) |
| 163 | + |
| 164 | + }; |
| 165 | + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); |
| 166 | + nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &payment_event.commitment_msg); |
| 167 | + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); |
| 168 | + assert_eq!(added_monitors.len(), 1); |
| 169 | + added_monitors.clear(); |
| 170 | + let events = nodes[1].node.get_and_clear_pending_msg_events(); |
| 171 | + assert_eq!(events.len(), 2); |
| 172 | + } |
| 173 | +} |
0 commit comments