Skip to content

Commit 200f3d1

Browse files
committed
Accept multi-hop route hints in get_route
Lightning invoices allow for zero or more multi-hop route hints. Update get_route's interface to accept such hints, although only the last hop from each is used for the time being. Moves RouteHint from lightning-invoice crate to lightning crate. Adds a PrivateRoute wrapper around RouteHint for use in lightning-invoice.
1 parent c9d1ed5 commit 200f3d1

File tree

6 files changed

+94
-86
lines changed

6 files changed

+94
-86
lines changed

fuzz/src/router.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use lightning::chain::transaction::OutPoint;
1616
use lightning::ln::channelmanager::ChannelDetails;
1717
use lightning::ln::features::InitFeatures;
1818
use lightning::ln::msgs;
19-
use lightning::routing::router::{get_route, RouteHintHop};
19+
use lightning::routing::router::{get_route, RouteHint, RouteHintHop};
2020
use lightning::util::logger::Logger;
2121
use lightning::util::ser::Readable;
2222
use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
@@ -225,13 +225,13 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
225225
Some(&first_hops_vec[..])
226226
},
227227
};
228-
let mut last_hops_vec = Vec::new();
228+
let mut last_hops = Vec::new();
229229
{
230230
let count = get_slice!(1)[0];
231231
for _ in 0..count {
232232
scid += 1;
233233
let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap();
234-
last_hops_vec.push(RouteHintHop {
234+
last_hops.push(RouteHint(vec![RouteHintHop {
235235
src_node_id: *rnid,
236236
short_channel_id: scid,
237237
fees: RoutingFees {
@@ -241,10 +241,9 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
241241
cltv_expiry_delta: slice_to_be16(get_slice!(2)),
242242
htlc_minimum_msat: Some(slice_to_be64(get_slice!(8))),
243243
htlc_maximum_msat: None,
244-
});
244+
}]));
245245
}
246246
}
247-
let last_hops = &last_hops_vec[..];
248247
for target in node_pks.iter() {
249248
let _ = get_route(&our_pubkey, &net_graph, target, None,
250249
first_hops.map(|c| c.iter().collect::<Vec<_>>()).as_ref().map(|a| a.as_slice()),

lightning-invoice/src/de.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use bitcoin_hashes::Hash;
1212
use bitcoin_hashes::sha256;
1313
use lightning::ln::PaymentSecret;
1414
use lightning::routing::network_graph::RoutingFees;
15-
use lightning::routing::router::RouteHintHop;
15+
use lightning::routing::router::{RouteHint, RouteHintHop};
1616

1717
use num_traits::{CheckedAdd, CheckedMul};
1818

@@ -21,7 +21,7 @@ use secp256k1::recovery::{RecoveryId, RecoverableSignature};
2121
use secp256k1::key::PublicKey;
2222

2323
use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiry, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
24-
SemanticError, RouteHint, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawInvoice, constants, SignedRawInvoice,
24+
SemanticError, PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawInvoice, constants, SignedRawInvoice,
2525
RawDataPart, CreationError, InvoiceFeatures};
2626

2727
use self::hrp_sm::parse_hrp;
@@ -433,8 +433,8 @@ impl FromBase32 for TaggedField {
433433
Ok(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry::from_base32(field_data)?)),
434434
constants::TAG_FALLBACK =>
435435
Ok(TaggedField::Fallback(Fallback::from_base32(field_data)?)),
436-
constants::TAG_ROUTE =>
437-
Ok(TaggedField::Route(RouteHint::from_base32(field_data)?)),
436+
constants::TAG_PRIVATE_ROUTE =>
437+
Ok(TaggedField::PrivateRoute(PrivateRoute::from_base32(field_data)?)),
438438
constants::TAG_PAYMENT_SECRET =>
439439
Ok(TaggedField::PaymentSecret(PaymentSecret::from_base32(field_data)?)),
440440
constants::TAG_FEATURES =>
@@ -558,10 +558,10 @@ impl FromBase32 for Fallback {
558558
}
559559
}
560560

561-
impl FromBase32 for RouteHint {
561+
impl FromBase32 for PrivateRoute {
562562
type Err = ParseError;
563563

564-
fn from_base32(field_data: &[u5]) -> Result<RouteHint, ParseError> {
564+
fn from_base32(field_data: &[u5]) -> Result<PrivateRoute, ParseError> {
565565
let bytes = Vec::<u8>::from_base32(field_data)?;
566566

567567
if bytes.len() % 51 != 0 {
@@ -593,7 +593,7 @@ impl FromBase32 for RouteHint {
593593
route_hops.push(hop);
594594
}
595595

596-
Ok(RouteHint(route_hops))
596+
Ok(PrivateRoute(RouteHint(route_hops)))
597597
}
598598
}
599599

@@ -930,8 +930,8 @@ mod test {
930930
#[test]
931931
fn test_parse_route() {
932932
use lightning::routing::network_graph::RoutingFees;
933-
use lightning::routing::router::RouteHintHop;
934-
use ::RouteHint;
933+
use lightning::routing::router::{RouteHint, RouteHintHop};
934+
use ::PrivateRoute;
935935
use bech32::FromBase32;
936936
use de::parse_int_be;
937937

@@ -976,10 +976,10 @@ mod test {
976976
htlc_maximum_msat: None
977977
});
978978

979-
assert_eq!(RouteHint::from_base32(&input), Ok(RouteHint(expected)));
979+
assert_eq!(PrivateRoute::from_base32(&input), Ok(PrivateRoute(RouteHint(expected))));
980980

981981
assert_eq!(
982-
RouteHint::from_base32(&[u5::try_from_u8(0).unwrap(); 40][..]),
982+
PrivateRoute::from_base32(&[u5::try_from_u8(0).unwrap(); 40][..]),
983983
Err(ParseError::UnexpectedEndOfTaggedFields)
984984
);
985985
}

lightning-invoice/src/lib.rs

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use lightning::ln::PaymentSecret;
3030
use lightning::ln::features::InvoiceFeatures;
3131
#[cfg(any(doc, test))]
3232
use lightning::routing::network_graph::RoutingFees;
33-
use lightning::routing::router::RouteHintHop;
33+
use lightning::routing::router::RouteHint;
3434

3535
use secp256k1::key::PublicKey;
3636
use secp256k1::{Message, Secp256k1};
@@ -362,7 +362,7 @@ pub enum TaggedField {
362362
ExpiryTime(ExpiryTime),
363363
MinFinalCltvExpiry(MinFinalCltvExpiry),
364364
Fallback(Fallback),
365-
Route(RouteHint),
365+
PrivateRoute(PrivateRoute),
366366
PaymentSecret(PaymentSecret),
367367
Features(InvoiceFeatures),
368368
}
@@ -419,7 +419,7 @@ pub struct InvoiceSignature(pub RecoverableSignature);
419419
/// The encoded route has to be <1024 5bit characters long (<=639 bytes or <=12 hops)
420420
///
421421
#[derive(Eq, PartialEq, Debug, Clone)]
422-
pub struct RouteHint(Vec<RouteHintHop>);
422+
pub struct PrivateRoute(RouteHint);
423423

424424
/// Tag constants as specified in BOLT11
425425
#[allow(missing_docs)]
@@ -431,7 +431,7 @@ pub mod constants {
431431
pub const TAG_EXPIRY_TIME: u8 = 6;
432432
pub const TAG_MIN_FINAL_CLTV_EXPIRY: u8 = 24;
433433
pub const TAG_FALLBACK: u8 = 9;
434-
pub const TAG_ROUTE: u8 = 3;
434+
pub const TAG_PRIVATE_ROUTE: u8 = 3;
435435
pub const TAG_PAYMENT_SECRET: u8 = 16;
436436
pub const TAG_FEATURES: u8 = 5;
437437
}
@@ -509,9 +509,9 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
509509
}
510510

511511
/// Adds a private route.
512-
pub fn route(mut self, route: Vec<RouteHintHop>) -> Self {
513-
match RouteHint::new(route) {
514-
Ok(r) => self.tagged_fields.push(TaggedField::Route(r)),
512+
pub fn private_route(mut self, hint: RouteHint) -> Self {
513+
match PrivateRoute::new(hint) {
514+
Ok(r) => self.tagged_fields.push(TaggedField::PrivateRoute(r)),
515515
Err(e) => self.error = Some(e),
516516
}
517517
self
@@ -913,8 +913,8 @@ impl RawInvoice {
913913
find_all_extract!(self.known_tagged_fields(), TaggedField::Fallback(ref x), x).collect()
914914
}
915915

916-
pub fn routes(&self) -> Vec<&RouteHint> {
917-
find_all_extract!(self.known_tagged_fields(), TaggedField::Route(ref x), x).collect()
916+
pub fn private_routes(&self) -> Vec<&PrivateRoute> {
917+
find_all_extract!(self.known_tagged_fields(), TaggedField::PrivateRoute(ref x), x).collect()
918918
}
919919

920920
pub fn amount_pico_btc(&self) -> Option<u64> {
@@ -1163,8 +1163,15 @@ impl Invoice {
11631163
}
11641164

11651165
/// Returns a list of all routes included in the invoice
1166-
pub fn routes(&self) -> Vec<&RouteHint> {
1167-
self.signed_invoice.routes()
1166+
pub fn private_routes(&self) -> Vec<&PrivateRoute> {
1167+
self.signed_invoice.private_routes()
1168+
}
1169+
1170+
/// Returns a list of all routes included in the invoice as the underlying hints
1171+
pub fn route_hints(&self) -> Vec<&RouteHint> {
1172+
find_all_extract!(
1173+
self.signed_invoice.known_tagged_fields(), TaggedField::PrivateRoute(ref x), x
1174+
).map(|route| &**route).collect()
11681175
}
11691176

11701177
/// Returns the currency for which the invoice was issued
@@ -1195,7 +1202,7 @@ impl TaggedField {
11951202
TaggedField::ExpiryTime(_) => constants::TAG_EXPIRY_TIME,
11961203
TaggedField::MinFinalCltvExpiry(_) => constants::TAG_MIN_FINAL_CLTV_EXPIRY,
11971204
TaggedField::Fallback(_) => constants::TAG_FALLBACK,
1198-
TaggedField::Route(_) => constants::TAG_ROUTE,
1205+
TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE,
11991206
TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
12001207
TaggedField::Features(_) => constants::TAG_FEATURES,
12011208
};
@@ -1286,32 +1293,32 @@ impl ExpiryTime {
12861293
}
12871294
}
12881295

1289-
impl RouteHint {
1290-
/// Create a new (partial) route from a list of hops
1291-
pub fn new(hops: Vec<RouteHintHop>) -> Result<RouteHint, CreationError> {
1292-
if hops.len() <= 12 {
1293-
Ok(RouteHint(hops))
1296+
impl PrivateRoute {
1297+
/// Creates a new (partial) route from a list of hops
1298+
pub fn new(hops: RouteHint) -> Result<PrivateRoute, CreationError> {
1299+
if hops.0.len() <= 12 {
1300+
Ok(PrivateRoute(hops))
12941301
} else {
12951302
Err(CreationError::RouteTooLong)
12961303
}
12971304
}
12981305

1299-
/// Returrn the underlying vector of hops
1300-
pub fn into_inner(self) -> Vec<RouteHintHop> {
1306+
/// Returns the underlying list of hops
1307+
pub fn into_inner(self) -> RouteHint {
13011308
self.0
13021309
}
13031310
}
13041311

1305-
impl Into<Vec<RouteHintHop>> for RouteHint {
1306-
fn into(self) -> Vec<RouteHintHop> {
1312+
impl Into<RouteHint> for PrivateRoute {
1313+
fn into(self) -> RouteHint {
13071314
self.into_inner()
13081315
}
13091316
}
13101317

1311-
impl Deref for RouteHint {
1312-
type Target = Vec<RouteHintHop>;
1318+
impl Deref for PrivateRoute {
1319+
type Target = RouteHint;
13131320

1314-
fn deref(&self) -> &Vec<RouteHintHop> {
1321+
fn deref(&self) -> &RouteHint {
13151322
&self.0
13161323
}
13171324
}
@@ -1670,6 +1677,7 @@ mod test {
16701677
#[test]
16711678
fn test_builder_fail() {
16721679
use ::*;
1680+
use lightning::routing::router::RouteHintHop;
16731681
use std::iter::FromIterator;
16741682
use secp256k1::key::PublicKey;
16751683

@@ -1704,10 +1712,10 @@ mod test {
17041712
htlc_minimum_msat: None,
17051713
htlc_maximum_msat: None,
17061714
};
1707-
let too_long_route = vec![route_hop; 13];
1715+
let too_long_route = RouteHint(vec![route_hop; 13]);
17081716
let long_route_res = builder.clone()
17091717
.description("Test".into())
1710-
.route(too_long_route)
1718+
.private_route(too_long_route)
17111719
.build_raw();
17121720
assert_eq!(long_route_res, Err(CreationError::RouteTooLong));
17131721

@@ -1722,6 +1730,7 @@ mod test {
17221730
#[test]
17231731
fn test_builder_ok() {
17241732
use ::*;
1733+
use lightning::routing::router::RouteHintHop;
17251734
use secp256k1::Secp256k1;
17261735
use secp256k1::key::{SecretKey, PublicKey};
17271736
use std::time::{UNIX_EPOCH, Duration};
@@ -1737,7 +1746,7 @@ mod test {
17371746
).unwrap();
17381747
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);
17391748

1740-
let route_1 = vec![
1749+
let route_1 = RouteHint(vec![
17411750
RouteHintHop {
17421751
src_node_id: public_key.clone(),
17431752
short_channel_id: de::parse_int_be(&[123; 8], 256).expect("short chan ID slice too big?"),
@@ -1760,9 +1769,9 @@ mod test {
17601769
htlc_minimum_msat: None,
17611770
htlc_maximum_msat: None,
17621771
}
1763-
];
1772+
]);
17641773

1765-
let route_2 = vec![
1774+
let route_2 = RouteHint(vec![
17661775
RouteHintHop {
17671776
src_node_id: public_key.clone(),
17681777
short_channel_id: 0,
@@ -1785,7 +1794,7 @@ mod test {
17851794
htlc_minimum_msat: None,
17861795
htlc_maximum_msat: None,
17871796
}
1788-
];
1797+
]);
17891798

17901799
let builder = InvoiceBuilder::new(Currency::BitcoinTestnet)
17911800
.amount_pico_btc(123)
@@ -1794,8 +1803,8 @@ mod test {
17941803
.expiry_time(Duration::from_secs(54321))
17951804
.min_final_cltv_expiry(144)
17961805
.fallback(Fallback::PubKeyHash([0;20]))
1797-
.route(route_1.clone())
1798-
.route(route_2.clone())
1806+
.private_route(route_1.clone())
1807+
.private_route(route_2.clone())
17991808
.description_hash(sha256::Hash::from_slice(&[3;32][..]).unwrap())
18001809
.payment_hash(sha256::Hash::from_slice(&[21;32][..]).unwrap())
18011810
.payment_secret(PaymentSecret([42; 32]))
@@ -1818,7 +1827,7 @@ mod test {
18181827
assert_eq!(invoice.expiry_time(), Duration::from_secs(54321));
18191828
assert_eq!(invoice.min_final_cltv_expiry(), 144);
18201829
assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]);
1821-
assert_eq!(invoice.routes(), vec![&RouteHint(route_1), &RouteHint(route_2)]);
1830+
assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]);
18221831
assert_eq!(
18231832
invoice.description(),
18241833
InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))

lightning-invoice/src/ser.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter};
33
use bech32::{ToBase32, u5, WriteBase32, Base32Len};
44

55
use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiry, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
6-
RouteHint, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawInvoice, RawDataPart};
6+
PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawInvoice, RawDataPart};
77

88
/// Converts a stream of bytes written to it to base32. On finalization the according padding will
99
/// be applied. That means the results of writing two data blocks with one or two `BytesToBase32`
@@ -356,11 +356,11 @@ impl Base32Len for Fallback {
356356
}
357357
}
358358

359-
impl ToBase32 for RouteHint {
359+
impl ToBase32 for PrivateRoute {
360360
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
361361
let mut converter = BytesToBase32::new(writer);
362362

363-
for hop in self.iter() {
363+
for hop in (self.0).0.iter() {
364364
converter.append(&hop.src_node_id.serialize()[..])?;
365365
let short_channel_id = try_stretch(
366366
encode_int_be_base256(hop.short_channel_id),
@@ -392,9 +392,9 @@ impl ToBase32 for RouteHint {
392392
}
393393
}
394394

395-
impl Base32Len for RouteHint {
395+
impl Base32Len for PrivateRoute {
396396
fn base32_len(&self) -> usize {
397-
bytes_size_to_base32_size(self.0.len() * 51)
397+
bytes_size_to_base32_size((self.0).0.len() * 51)
398398
}
399399
}
400400

@@ -439,8 +439,8 @@ impl ToBase32 for TaggedField {
439439
TaggedField::Fallback(ref fallback_address) => {
440440
write_tagged_field(writer, constants::TAG_FALLBACK, fallback_address)
441441
},
442-
TaggedField::Route(ref route_hops) => {
443-
write_tagged_field(writer, constants::TAG_ROUTE, route_hops)
442+
TaggedField::PrivateRoute(ref route_hops) => {
443+
write_tagged_field(writer, constants::TAG_PRIVATE_ROUTE, route_hops)
444444
},
445445
TaggedField::PaymentSecret(ref payment_secret) => {
446446
write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret)

0 commit comments

Comments
 (0)