Skip to content

Commit 8caa169

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 255be7e commit 8caa169

File tree

2 files changed

+152
-5
lines changed

2 files changed

+152
-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: 149 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,100 @@ impl<S> Display for SignOrCreationError<S> {
12921290
}
12931291
}
12941292

1293+
mod utils {
1294+
use {CreationError, Currency, Invoice, InvoiceBuilder};
1295+
use bitcoin_hashes::Hash;
1296+
use lightning::chain;
1297+
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
1298+
use lightning::chain::keysinterface::{Sign, KeysInterface};
1299+
use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY};
1300+
use lightning::ln::features::InvoiceFeatures;
1301+
use lightning::routing::network_graph::RoutingFees;
1302+
use lightning::routing::router::RouteHintHop;
1303+
use lightning::util::logger::Logger;
1304+
use secp256k1::Secp256k1;
1305+
use std::ops::Deref;
1306+
1307+
/// Utility to construct an invoice.
1308+
#[allow(dead_code)]
1309+
pub fn from_channelmanager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
1310+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, amt_msat: Option<u64>, description: String,
1311+
network: Currency,
1312+
) -> Result<Invoice, CreationError>
1313+
where
1314+
M::Target: chain::Watch<Signer>,
1315+
T::Target: BroadcasterInterface,
1316+
K::Target: KeysInterface<Signer = Signer>,
1317+
F::Target: FeeEstimator,
1318+
L::Target: Logger,
1319+
{
1320+
// Marshall route hints.
1321+
let our_channels = channelmanager.list_usable_channels();
1322+
let mut route_hints = vec![];
1323+
for channel in our_channels {
1324+
let short_channel_id = match channel.short_channel_id {
1325+
Some(id) => id,
1326+
None => continue,
1327+
};
1328+
let forwarding_info = match channel.counterparty_forwarding_info {
1329+
Some(info) => info,
1330+
None => continue,
1331+
};
1332+
route_hints.push(vec![RouteHintHop {
1333+
src_node_id: channel.remote_network_id,
1334+
short_channel_id,
1335+
fees: RoutingFees {
1336+
base_msat: forwarding_info.fee_base_msat,
1337+
proportional_millionths: forwarding_info.fee_proportional_millionths,
1338+
},
1339+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
1340+
htlc_minimum_msat: None,
1341+
htlc_maximum_msat: None,
1342+
}]);
1343+
}
1344+
1345+
let (payment_hash, payment_secret) = channelmanager.create_inbound_payment(
1346+
amt_msat,
1347+
7200, // default invoice expiry is 2 hours
1348+
0,
1349+
);
1350+
let secp_ctx = Secp256k1::new();
1351+
let our_node_pubkey = channelmanager.get_our_node_id();
1352+
let mut invoice = InvoiceBuilder::new(network)
1353+
.description(description)
1354+
.current_timestamp()
1355+
.payee_pub_key(our_node_pubkey)
1356+
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
1357+
.payment_secret(payment_secret)
1358+
.features(InvoiceFeatures::known())
1359+
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
1360+
if let Some(amt) = amt_msat {
1361+
invoice = invoice.amount_pico_btc(amt * 10);
1362+
}
1363+
for hint in route_hints.drain(..) {
1364+
invoice = invoice.route(hint);
1365+
}
1366+
1367+
invoice.build_signed(|msg_hash| {
1368+
secp_ctx.sign_recoverable(msg_hash, &channelmanager.get_our_node_secret())
1369+
})
1370+
}
1371+
1372+
}
1373+
12951374
#[cfg(test)]
12961375
mod test {
12971376
use bitcoin_hashes::hex::FromHex;
12981377
use bitcoin_hashes::sha256;
1378+
use {Currency, Description, InvoiceDescription};
1379+
use lightning::ln::PaymentHash;
1380+
use lightning::ln::functional_test_utils::*;
1381+
use lightning::ln::features::InitFeatures;
1382+
use lightning::ln::msgs::ChannelMessageHandler;
1383+
use lightning::routing::network_graph::RoutingFees;
1384+
use lightning::routing::router;
1385+
use lightning::util::events::MessageSendEventsProvider;
1386+
use lightning::util::test_utils;
12991387

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

0 commit comments

Comments
 (0)