Skip to content

Commit 28602ba

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 f167596 commit 28602ba

File tree

2 files changed

+156
-5
lines changed

2 files changed

+156
-5
lines changed

lightning-invoice/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ secp256k1 = { version = "0.20", features = ["recovery"] }
1515
num-traits = "0.2.8"
1616
bitcoin_hashes = "0.9.4"
1717

18+
[dev-dependencies]
19+
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }
20+
1821
[lib]
1922
crate-type = ["cdylib", "rlib"]
2023

lightning-invoice/src/lib.rs

Lines changed: 153 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,19 @@ extern crate secp256k1;
2424
use bech32::u5;
2525
use bitcoin_hashes::Hash;
2626
use bitcoin_hashes::sha256;
27-
use lightning::ln::features::InvoiceFeatures;
2827
use lightning::ln::channelmanager::PaymentSecret;
29-
#[cfg(any(doc, test))]
30-
use lightning::routing::network_graph::RoutingFees;
28+
use lightning::ln::features::InvoiceFeatures;
3129
use lightning::routing::router::RouteHintHop;
3230

3331
use secp256k1::key::PublicKey;
3432
use secp256k1::{Message, Secp256k1};
3533
use secp256k1::recovery::RecoverableSignature;
36-
use std::ops::Deref;
3734

35+
use std::fmt::{Display, Formatter, self};
3836
use std::iter::FilterMap;
37+
use std::ops::Deref;
3938
use std::slice::Iter;
4039
use std::time::{SystemTime, Duration, UNIX_EPOCH};
41-
use std::fmt::{Display, Formatter, self};
4240

4341
mod de;
4442
mod ser;
@@ -1292,10 +1290,104 @@ impl<S> Display for SignOrCreationError<S> {
12921290
}
12931291
}
12941292

1293+
/// Convenient utilities to create an invoice.
1294+
pub mod utils {
1295+
use {SemanticError, Currency, Invoice, InvoiceBuilder};
1296+
use bitcoin_hashes::Hash;
1297+
use lightning::chain;
1298+
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
1299+
use lightning::chain::keysinterface::{Sign, KeysInterface};
1300+
use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY};
1301+
use lightning::ln::features::InvoiceFeatures;
1302+
use lightning::routing::network_graph::RoutingFees;
1303+
use lightning::routing::router::RouteHintHop;
1304+
use lightning::util::logger::Logger;
1305+
use secp256k1::Secp256k1;
1306+
use std::ops::Deref;
1307+
1308+
/// Utility to construct an invoice. Generally, unless you want to do something like a custom
1309+
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
1310+
/// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user
1311+
/// doesn't have to store preimage/payment secret information and (b) `ChannelManager` can verify
1312+
/// that the payment secret is valid when the invoice is paid.
1313+
pub fn create_invoice_from_channelmanager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
1314+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, amt_msat: Option<u64>, description: String,
1315+
network: Currency,
1316+
) -> Result<Invoice, SemanticError>
1317+
where
1318+
M::Target: chain::Watch<Signer>,
1319+
T::Target: BroadcasterInterface,
1320+
K::Target: KeysInterface<Signer = Signer>,
1321+
F::Target: FeeEstimator,
1322+
L::Target: Logger,
1323+
{
1324+
// Marshall route hints.
1325+
let our_channels = channelmanager.list_usable_channels();
1326+
let mut route_hints = vec![];
1327+
for channel in our_channels {
1328+
let short_channel_id = match channel.short_channel_id {
1329+
Some(id) => id,
1330+
None => continue,
1331+
};
1332+
let forwarding_info = match channel.counterparty_forwarding_info {
1333+
Some(info) => info,
1334+
None => continue,
1335+
};
1336+
route_hints.push(vec![RouteHintHop {
1337+
src_node_id: channel.remote_network_id,
1338+
short_channel_id,
1339+
fees: RoutingFees {
1340+
base_msat: forwarding_info.fee_base_msat,
1341+
proportional_millionths: forwarding_info.fee_proportional_millionths,
1342+
},
1343+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
1344+
htlc_minimum_msat: None,
1345+
htlc_maximum_msat: None,
1346+
}]);
1347+
}
1348+
1349+
let (payment_hash, payment_secret) = channelmanager.create_inbound_payment(
1350+
amt_msat,
1351+
7200, // default invoice expiry is 2 hours
1352+
0,
1353+
);
1354+
let secp_ctx = Secp256k1::new();
1355+
let our_node_pubkey = channelmanager.get_our_node_id();
1356+
let mut invoice = InvoiceBuilder::new(network)
1357+
.description(description)
1358+
.current_timestamp()
1359+
.payee_pub_key(our_node_pubkey)
1360+
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
1361+
.payment_secret(payment_secret)
1362+
.features(InvoiceFeatures::known())
1363+
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
1364+
if let Some(amt) = amt_msat {
1365+
invoice = invoice.amount_pico_btc(amt * 10);
1366+
}
1367+
for hint in route_hints.drain(..) {
1368+
invoice = invoice.route(hint);
1369+
}
1370+
1371+
invoice.build_signed(|msg_hash| {
1372+
secp_ctx.sign_recoverable(msg_hash, &channelmanager.get_our_node_secret())
1373+
})
1374+
}
1375+
1376+
}
1377+
12951378
#[cfg(test)]
12961379
mod test {
12971380
use bitcoin_hashes::hex::FromHex;
12981381
use bitcoin_hashes::sha256;
1382+
use {Currency, Description, InvoiceDescription};
1383+
use lightning::ln::PaymentHash;
1384+
use lightning::ln::functional_test_utils::*;
1385+
use lightning::ln::features::InitFeatures;
1386+
use lightning::ln::msgs::ChannelMessageHandler;
1387+
use lightning::routing::network_graph::RoutingFees;
1388+
use lightning::routing::router;
1389+
use lightning::util::events::MessageSendEventsProvider;
1390+
use lightning::util::test_utils;
12991391

13001392
#[test]
13011393
fn test_system_time_bounds_assumptions() {
@@ -1600,4 +1692,60 @@ mod test {
16001692
let raw_invoice = builder.build_raw().unwrap();
16011693
assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice())
16021694
}
1695+
1696+
#[test]
1697+
fn test_from_channelmanager() {
1698+
let chanmon_cfgs = create_chanmon_cfgs(2);
1699+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1700+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1701+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1702+
let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
1703+
let invoice = ::utils::create_invoice_from_channelmanager(&nodes[1].node, Some(10_000), "test".to_string(), Currency::BitcoinTestnet).unwrap();
1704+
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
1705+
assert_eq!(invoice.min_final_cltv_expiry(), Some(&9));
1706+
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
1707+
1708+
let mut route_hints = invoice.routes().clone();
1709+
let mut last_hops = Vec::new();
1710+
for hint in route_hints.drain(..) {
1711+
last_hops.push(hint[hint.len() - 1].clone());
1712+
}
1713+
let amt_msat = invoice.amount_pico_btc().unwrap() / 10;
1714+
1715+
let first_hops = nodes[0].node.list_usable_channels();
1716+
let network_graph = nodes[0].net_graph_msg_handler.network_graph.read().unwrap();
1717+
let logger = test_utils::TestLogger::new();
1718+
let route = router::get_route(
1719+
&nodes[0].node.get_our_node_id(),
1720+
&network_graph,
1721+
&invoice.recover_payee_pub_key(),
1722+
Some(invoice.features().unwrap().clone()),
1723+
Some(&first_hops.iter().collect::<Vec<_>>()),
1724+
&last_hops.iter().collect::<Vec<_>>(),
1725+
amt_msat,
1726+
*invoice.min_final_cltv_expiry().unwrap() as u32,
1727+
&logger,
1728+
).unwrap();
1729+
1730+
let payment_event = {
1731+
let mut payment_hash = PaymentHash([0; 32]);
1732+
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
1733+
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().unwrap().clone())).unwrap();
1734+
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
1735+
assert_eq!(added_monitors.len(), 1);
1736+
added_monitors.clear();
1737+
1738+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
1739+
assert_eq!(events.len(), 1);
1740+
SendEvent::from_event(events.remove(0))
1741+
1742+
};
1743+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
1744+
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &payment_event.commitment_msg);
1745+
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
1746+
assert_eq!(added_monitors.len(), 1);
1747+
added_monitors.clear();
1748+
let events = nodes[1].node.get_and_clear_pending_msg_events();
1749+
assert_eq!(events.len(), 2);
1750+
}
16031751
}

0 commit comments

Comments
 (0)