Skip to content
This repository was archived by the owner on Apr 13, 2021. It is now read-only.

Commit 3a2639f

Browse files
committed
Add InvoiceBuilder tests and missing API functions
1 parent 30bc536 commit 3a2639f

File tree

2 files changed

+157
-7
lines changed

2 files changed

+157
-7
lines changed

src/lib.rs

Lines changed: 153 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,11 @@ impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H> {
369369
}
370370

371371
/// Adds a private route.
372-
pub fn route(mut self, route: Route) -> Self {
373-
self.tagged_fields.push(TaggedField::Route(route));
372+
pub fn route(mut self, route: Vec<RouteHop>) -> Self {
373+
match Route::new(route) {
374+
Ok(r) => self.tagged_fields.push(TaggedField::Route(r)),
375+
Err(e) => self.error = Some(e),
376+
}
374377
self
375378
}
376379

@@ -698,6 +701,10 @@ impl RawInvoice {
698701
}
699702

700703
impl Invoice {
704+
fn into_signed_raw(self) -> SignedRawInvoice {
705+
self.signed_invoice
706+
}
707+
701708
/// Check that all mandatory fields are present
702709
fn check_field_counts(&self) -> Result<(), SemanticError> {
703710
// "A writer MUST include exactly one p field […]."
@@ -764,6 +771,10 @@ impl Invoice {
764771
Ok(invoice)
765772
}
766773

774+
pub fn timestamp(&self) -> u64 {
775+
self.signed_invoice.raw_invoice.data.timestamp
776+
}
777+
767778
/// Returns an iterator over all tagged fields of this Invoice.
768779
pub fn tagged_fields(&self)
769780
-> FilterMap<Iter<RawTaggedField>, fn(&RawTaggedField) -> Option<&TaggedField>> {
@@ -1075,9 +1086,11 @@ mod test {
10751086
fn test_builder_amount() {
10761087
use ::*;
10771088

1078-
let invoice = InvoiceBuilder::new(Currency::Bitcoin)
1089+
let builder = InvoiceBuilder::new(Currency::Bitcoin)
10791090
.description("Test".into())
1080-
.payment_hash([0;32])
1091+
.payment_hash([0;32]);
1092+
1093+
let invoice = builder.clone()
10811094
.amount_pico_btc(15000)
10821095
.build_raw()
10831096
.unwrap();
@@ -1086,14 +1099,147 @@ mod test {
10861099
assert_eq!(invoice.hrp.raw_amount, Some(15));
10871100

10881101

1089-
let invoice = InvoiceBuilder::new(Currency::Bitcoin)
1090-
.description("Test".into())
1091-
.payment_hash([0;32])
1102+
let invoice = builder.clone()
10921103
.amount_pico_btc(1500)
10931104
.build_raw()
10941105
.unwrap();
10951106

10961107
assert_eq!(invoice.hrp.si_prefix, Some(SiPrefix::Pico));
10971108
assert_eq!(invoice.hrp.raw_amount, Some(1500));
10981109
}
1110+
1111+
#[test]
1112+
fn test_builder_fail() {
1113+
use ::*;
1114+
use std::iter::FromIterator;
1115+
use secp256k1::key::PublicKey;
1116+
use secp256k1::Secp256k1;
1117+
1118+
let builder = InvoiceBuilder::new(Currency::Bitcoin)
1119+
.payment_hash([0;32]);
1120+
1121+
let too_long_string = String::from_iter(
1122+
(0..1024).map(|_| '?')
1123+
);
1124+
1125+
let long_desc_res = builder.clone()
1126+
.description(too_long_string)
1127+
.build_raw();
1128+
assert_eq!(long_desc_res, Err(CreationError::DescriptionTooLong));
1129+
1130+
let route_hop = RouteHop {
1131+
pubkey: PublicKey::from_slice(
1132+
&Secp256k1::without_caps(),
1133+
&[
1134+
0x03, 0x9e, 0x03, 0xa9, 0x01, 0xb8, 0x55, 0x34, 0xff, 0x1e, 0x92, 0xc4,
1135+
0x3c, 0x74, 0x43, 0x1f, 0x7c, 0xe7, 0x20, 0x46, 0x06, 0x0f, 0xcf, 0x7a,
1136+
0x95, 0xc3, 0x7e, 0x14, 0x8f, 0x78, 0xc7, 0x72, 0x55
1137+
][..]
1138+
).unwrap(),
1139+
short_channel_id: [0; 8],
1140+
fee_base_msat: 0,
1141+
fee_proportional_millionths: 0,
1142+
cltv_expiry_delta: 0,
1143+
};
1144+
let too_long_route = vec![route_hop; 13];
1145+
let long_route_res = builder.clone()
1146+
.description("Test".into())
1147+
.route(too_long_route)
1148+
.build_raw();
1149+
assert_eq!(long_route_res, Err(CreationError::RouteTooLong));
1150+
1151+
let sign_error_res = builder.clone()
1152+
.description("Test".into())
1153+
.try_build_signed(|_| {
1154+
Err("ImaginaryError")
1155+
});
1156+
assert_eq!(sign_error_res, Err(SignOrCreationError::SignError("ImaginaryError")));
1157+
}
1158+
1159+
#[test]
1160+
fn test_builder_ok() {
1161+
use ::*;
1162+
use secp256k1::Secp256k1;
1163+
use secp256k1::key::{SecretKey, PublicKey};
1164+
1165+
let secp_ctx = Secp256k1::new();
1166+
1167+
let private_key = SecretKey::from_slice(
1168+
&secp_ctx,
1169+
&[
1170+
0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f, 0xe2,
1171+
0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04, 0xa8, 0xca,
1172+
0x3b, 0x2d, 0xb7, 0x34
1173+
][..]
1174+
).unwrap();
1175+
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);
1176+
1177+
let route_1 = vec![
1178+
RouteHop {
1179+
pubkey: public_key.clone(),
1180+
short_channel_id: [123; 8],
1181+
fee_base_msat: 2,
1182+
fee_proportional_millionths: 1,
1183+
cltv_expiry_delta: 145,
1184+
},
1185+
RouteHop {
1186+
pubkey: public_key.clone(),
1187+
short_channel_id: [42; 8],
1188+
fee_base_msat: 3,
1189+
fee_proportional_millionths: 2,
1190+
cltv_expiry_delta: 146,
1191+
}
1192+
];
1193+
1194+
let route_2 = vec![
1195+
RouteHop {
1196+
pubkey: public_key.clone(),
1197+
short_channel_id: [0; 8],
1198+
fee_base_msat: 4,
1199+
fee_proportional_millionths: 3,
1200+
cltv_expiry_delta: 147,
1201+
},
1202+
RouteHop {
1203+
pubkey: public_key.clone(),
1204+
short_channel_id: [1; 8],
1205+
fee_base_msat: 5,
1206+
fee_proportional_millionths: 4,
1207+
cltv_expiry_delta: 148,
1208+
}
1209+
];
1210+
1211+
let builder = InvoiceBuilder::new(Currency::BitcoinTestnet)
1212+
.amount_pico_btc(123)
1213+
.timestamp(1234567)
1214+
.payee_pub_key(public_key.clone())
1215+
.expiry_time_seconds(54321)
1216+
.min_final_cltv_expiry(144)
1217+
.min_final_cltv_expiry(143)
1218+
.fallback(Fallback::PubKeyHash([0;20]))
1219+
.route(route_1.clone())
1220+
.route(route_2.clone())
1221+
.description_hash([3;32])
1222+
.payment_hash([21;32]);
1223+
1224+
let invoice = builder.clone().build_signed(|hash| {
1225+
secp_ctx.sign_recoverable(hash, &private_key)
1226+
}).unwrap();
1227+
1228+
assert!(invoice.check_signature().is_ok());
1229+
assert_eq!(invoice.tagged_fields().count(), 9);
1230+
1231+
assert_eq!(invoice.amount_pico_btc(), Some(123));
1232+
assert_eq!(invoice.currency(), Currency::BitcoinTestnet);
1233+
assert_eq!(invoice.timestamp(), 1234567);
1234+
assert_eq!(invoice.payee_pub_key(), Some(&PayeePubKey(public_key)));
1235+
assert_eq!(invoice.expiry_time(), Some(&ExpiryTime{seconds: 54321}));
1236+
assert_eq!(invoice.min_final_cltv_expiry(), Some(&MinFinalCltvExpiry(144)));
1237+
assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]);
1238+
assert_eq!(invoice.routes(), vec![&Route(route_1), &Route(route_2)]);
1239+
assert_eq!(invoice.description(), InvoiceDescription::Hash(&Sha256([3;32])));
1240+
assert_eq!(invoice.payment_hash(), &Sha256([21;32]));
1241+
1242+
let raw_invoice = builder.build_raw().unwrap();
1243+
assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice())
1244+
}
10991245
}

src/tb.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
pub trait Bool {}
2+
3+
#[derive(Copy, Clone)]
24
pub struct True {}
5+
6+
#[derive(Copy, Clone)]
37
pub struct False {}
48

59
impl Bool for True {}

0 commit comments

Comments
 (0)