|
16 | 16 | //! * For serializing invoices use the `Display`/`ToString` traits
|
17 | 17 |
|
18 | 18 | extern crate bech32;
|
| 19 | +extern crate bitcoin; |
19 | 20 | extern crate bitcoin_hashes;
|
20 | 21 | extern crate lightning;
|
21 | 22 | extern crate num_traits;
|
22 | 23 | extern crate secp256k1;
|
23 | 24 |
|
24 | 25 | use bech32::u5;
|
| 26 | +use bitcoin::network::constants::Network; |
25 | 27 | use bitcoin_hashes::Hash;
|
26 | 28 | use bitcoin_hashes::sha256;
|
| 29 | +use lightning::chain; |
| 30 | +use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; |
| 31 | +use lightning::chain::keysinterface::{Sign, KeysInterface}; |
| 32 | +use lightning::ln::channelmanager::{ChannelManager, PaymentSecret}; |
27 | 33 | use lightning::ln::features::InvoiceFeatures;
|
28 |
| -use lightning::ln::channelmanager::PaymentSecret; |
29 |
| -#[cfg(any(doc, test))] |
30 | 34 | use lightning::routing::network_graph::RoutingFees;
|
31 | 35 | use lightning::routing::router::RouteHintHop;
|
| 36 | +use lightning::util::logger::Logger; |
32 | 37 |
|
33 | 38 | use secp256k1::key::PublicKey;
|
34 | 39 | use secp256k1::{Message, Secp256k1};
|
@@ -901,6 +906,79 @@ impl Deref for PositiveTimestamp {
|
901 | 906 | }
|
902 | 907 |
|
903 | 908 | impl Invoice {
|
| 909 | + /// Utility to construct an invoice. |
| 910 | + pub fn from_channelmanager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>( |
| 911 | + channelmanager: &ChannelManager<Signer, M, T, K, F, L>, amt_msat: Option<u64>, description: String, |
| 912 | + network: Network, |
| 913 | + ) -> Result<Invoice, CreationError> |
| 914 | + where |
| 915 | + M::Target: chain::Watch<Signer>, |
| 916 | + T::Target: BroadcasterInterface, |
| 917 | + K::Target: KeysInterface<Signer = Signer>, |
| 918 | + F::Target: FeeEstimator, |
| 919 | + L::Target: Logger, |
| 920 | + { |
| 921 | + // Marshall route hints. |
| 922 | + let our_channels = channelmanager.list_usable_channels(); |
| 923 | + let mut route_hints = vec![]; |
| 924 | + let mut min_final_cltv_expiry = 18; |
| 925 | + for channel in our_channels { |
| 926 | + let short_channel_id = match channel.short_channel_id { |
| 927 | + Some(id) => id, |
| 928 | + None => continue, |
| 929 | + }; |
| 930 | + let forwarding_info = match channel.counterparty_forwarding_info { |
| 931 | + Some(info) => info, |
| 932 | + None => continue, |
| 933 | + }; |
| 934 | + if forwarding_info.cltv_expiry_delta > min_final_cltv_expiry { |
| 935 | + min_final_cltv_expiry = forwarding_info.cltv_expiry_delta; |
| 936 | + } |
| 937 | + route_hints.push(vec![RouteHintHop { |
| 938 | + src_node_id: channel.remote_network_id, |
| 939 | + short_channel_id, |
| 940 | + fees: RoutingFees { |
| 941 | + base_msat: forwarding_info.fee_base_msat, |
| 942 | + proportional_millionths: forwarding_info.fee_proportional_millionths, |
| 943 | + }, |
| 944 | + cltv_expiry_delta: forwarding_info.cltv_expiry_delta, |
| 945 | + htlc_minimum_msat: None, |
| 946 | + htlc_maximum_msat: None, |
| 947 | + }]); |
| 948 | + } |
| 949 | + |
| 950 | + let (payment_hash, payment_secret) = channelmanager.create_inbound_payment( |
| 951 | + amt_msat, |
| 952 | + min_final_cltv_expiry as u32 * 10 * 60, // num blocks * 10 minutes per block |
| 953 | + 0, |
| 954 | + ); |
| 955 | + let secp_ctx = Secp256k1::new(); |
| 956 | + let our_node_pubkey = channelmanager.get_our_node_id(); |
| 957 | + let mut invoice = InvoiceBuilder::new(match network { |
| 958 | + Network::Bitcoin => Currency::Bitcoin, |
| 959 | + Network::Testnet => Currency::BitcoinTestnet, |
| 960 | + Network::Regtest => Currency::Regtest, |
| 961 | + Network::Signet => panic!("Signet invoices not supported"), |
| 962 | + }) |
| 963 | + .description(description) |
| 964 | + .current_timestamp() |
| 965 | + .payee_pub_key(our_node_pubkey) |
| 966 | + .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) |
| 967 | + .payment_secret(payment_secret) |
| 968 | + .features(InvoiceFeatures::known()) |
| 969 | + .min_final_cltv_expiry(min_final_cltv_expiry.into()); |
| 970 | + if let Some(amt) = amt_msat { |
| 971 | + invoice = invoice.amount_pico_btc(amt * 10); |
| 972 | + } |
| 973 | + for hint in route_hints.drain(..) { |
| 974 | + invoice = invoice.route(hint); |
| 975 | + } |
| 976 | + |
| 977 | + invoice.build_signed(|msg_hash| { |
| 978 | + secp_ctx.sign_recoverable(msg_hash, &channelmanager.get_our_node_secret()) |
| 979 | + }) |
| 980 | + } |
| 981 | + |
904 | 982 | /// Transform the `Invoice` into it's unchecked version
|
905 | 983 | pub fn into_signed_raw(self) -> SignedRawInvoice {
|
906 | 984 | self.signed_invoice
|
@@ -1294,8 +1372,17 @@ impl<S> Display for SignOrCreationError<S> {
|
1294 | 1372 |
|
1295 | 1373 | #[cfg(test)]
|
1296 | 1374 | mod test {
|
| 1375 | + use bitcoin::network::constants::Network; |
1297 | 1376 | use bitcoin_hashes::hex::FromHex;
|
1298 | 1377 | use bitcoin_hashes::sha256;
|
| 1378 | + use {Description, Invoice, InvoiceDescription}; |
| 1379 | + use lightning::ln::channelmanager::PaymentHash; |
| 1380 | + use lightning::ln::functional_test_utils::*; |
| 1381 | + use lightning::ln::features::InitFeatures; |
| 1382 | + use lightning::ln::msgs::ChannelMessageHandler; |
| 1383 | + use lightning::routing::router; |
| 1384 | + use lightning::util::events::MessageSendEventsProvider; |
| 1385 | + use lightning::util::test_utils; |
1299 | 1386 |
|
1300 | 1387 | #[test]
|
1301 | 1388 | fn test_system_time_bounds_assumptions() {
|
@@ -1600,4 +1687,60 @@ mod test {
|
1600 | 1687 | let raw_invoice = builder.build_raw().unwrap();
|
1601 | 1688 | assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice())
|
1602 | 1689 | }
|
| 1690 | + |
| 1691 | + #[test] |
| 1692 | + fn test_from_channelmanager() { |
| 1693 | + let chanmon_cfgs = create_chanmon_cfgs(2); |
| 1694 | + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); |
| 1695 | + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); |
| 1696 | + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); |
| 1697 | + let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()); |
| 1698 | + let invoice = Invoice::from_channelmanager(&nodes[1].node, Some(10_000), "test".to_string(), Network::Testnet).unwrap(); |
| 1699 | + assert_eq!(invoice.amount_pico_btc(), Some(100_000)); |
| 1700 | + assert_eq!(invoice.min_final_cltv_expiry(), Some(&18)); |
| 1701 | + assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string()))); |
| 1702 | + |
| 1703 | + let mut route_hints = invoice.routes().clone(); |
| 1704 | + let mut last_hops = Vec::new(); |
| 1705 | + for hint in route_hints.drain(..) { |
| 1706 | + last_hops.push(hint[hint.len() - 1].clone()); |
| 1707 | + } |
| 1708 | + let amt_msat = invoice.amount_pico_btc().unwrap() / 10; |
| 1709 | + |
| 1710 | + let first_hops = nodes[0].node.list_usable_channels(); |
| 1711 | + let network_graph = nodes[0].net_graph_msg_handler.network_graph.read().unwrap(); |
| 1712 | + let logger = test_utils::TestLogger::new(); |
| 1713 | + let route = router::get_route( |
| 1714 | + &nodes[0].node.get_our_node_id(), |
| 1715 | + &network_graph, |
| 1716 | + &invoice.recover_payee_pub_key(), |
| 1717 | + Some(invoice.features().unwrap().clone()), |
| 1718 | + Some(&first_hops.iter().collect::<Vec<_>>()), |
| 1719 | + &last_hops.iter().collect::<Vec<_>>(), |
| 1720 | + amt_msat, |
| 1721 | + *invoice.min_final_cltv_expiry().unwrap() as u32, |
| 1722 | + &logger, |
| 1723 | + ).unwrap(); |
| 1724 | + |
| 1725 | + let payment_event = { |
| 1726 | + let mut payment_hash = PaymentHash([0; 32]); |
| 1727 | + payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); |
| 1728 | + nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().unwrap().clone())).unwrap(); |
| 1729 | + let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); |
| 1730 | + assert_eq!(added_monitors.len(), 1); |
| 1731 | + added_monitors.clear(); |
| 1732 | + |
| 1733 | + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); |
| 1734 | + assert_eq!(events.len(), 1); |
| 1735 | + SendEvent::from_event(events.remove(0)) |
| 1736 | + |
| 1737 | + }; |
| 1738 | + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); |
| 1739 | + nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &payment_event.commitment_msg); |
| 1740 | + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); |
| 1741 | + assert_eq!(added_monitors.len(), 1); |
| 1742 | + added_monitors.clear(); |
| 1743 | + let events = nodes[1].node.get_and_clear_pending_msg_events(); |
| 1744 | + assert_eq!(events.len(), 2); |
| 1745 | + } |
1603 | 1746 | }
|
0 commit comments