Skip to content

Commit 78b8298

Browse files
Add utility to create an invoice using the ChannelManager
This also allows the ChannelManager to track information for inbound payments to check the PaymentSecret on receive.
1 parent 5d270e3 commit 78b8298

File tree

2 files changed

+149
-2
lines changed

2 files changed

+149
-2
lines changed

lightning-invoice/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ readme = "README.md"
1010

1111
[dependencies]
1212
bech32 = "0.7"
13+
bitcoin = "0.26"
1314
lightning = { version = "0.0.13", path = "../lightning" }
1415
secp256k1 = { version = "0.20", features = ["recovery"] }
1516
num-traits = "0.2.8"
1617
bitcoin_hashes = "0.9.4"
1718

19+
[dev-dependencies]
20+
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }
21+
1822
[lib]
1923
crate-type = ["cdylib", "rlib"]
2024

lightning-invoice/src/lib.rs

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,24 @@
1616
//! * For serializing invoices use the `Display`/`ToString` traits
1717
1818
extern crate bech32;
19+
extern crate bitcoin;
1920
extern crate bitcoin_hashes;
2021
extern crate lightning;
2122
extern crate num_traits;
2223
extern crate secp256k1;
2324

2425
use bech32::u5;
26+
use bitcoin::network::constants::Network;
2527
use bitcoin_hashes::Hash;
2628
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};
2733
use lightning::ln::features::InvoiceFeatures;
28-
use lightning::ln::channelmanager::PaymentSecret;
29-
#[cfg(any(doc, test))]
3034
use lightning::routing::network_graph::RoutingFees;
3135
use lightning::routing::router::RouteHintHop;
36+
use lightning::util::logger::Logger;
3237

3338
use secp256k1::key::PublicKey;
3439
use secp256k1::{Message, Secp256k1};
@@ -901,6 +906,79 @@ impl Deref for PositiveTimestamp {
901906
}
902907

903908
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+
904982
/// Transform the `Invoice` into it's unchecked version
905983
pub fn into_signed_raw(self) -> SignedRawInvoice {
906984
self.signed_invoice
@@ -1294,8 +1372,17 @@ impl<S> Display for SignOrCreationError<S> {
12941372

12951373
#[cfg(test)]
12961374
mod test {
1375+
use bitcoin::network::constants::Network;
12971376
use bitcoin_hashes::hex::FromHex;
12981377
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;
12991386

13001387
#[test]
13011388
fn test_system_time_bounds_assumptions() {
@@ -1600,4 +1687,60 @@ mod test {
16001687
let raw_invoice = builder.build_raw().unwrap();
16011688
assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice())
16021689
}
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+
}
16031746
}

0 commit comments

Comments
 (0)