Skip to content

Commit c5655c9

Browse files
committed
Refund building tests
Tests for checking invoice_request message semantics when building a refund as defined by BOLT 12.
1 parent 21ede4b commit c5655c9

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed

lightning/src/offers/refund.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,11 @@ impl Refund {
286286
pub fn payer_note(&self) -> Option<PrintableString> {
287287
self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
288288
}
289+
290+
#[cfg(test)]
291+
fn as_tlv_stream(&self) -> RefundTlvStreamRef {
292+
self.contents.as_tlv_stream()
293+
}
289294
}
290295

291296
impl AsRef<[u8]> for Refund {
@@ -472,3 +477,227 @@ impl core::fmt::Display for Refund {
472477
self.fmt_bech32_str(f)
473478
}
474479
}
480+
481+
#[cfg(test)]
482+
mod tests {
483+
use super::{Refund, RefundBuilder};
484+
485+
use bitcoin::blockdata::constants::ChainHash;
486+
use bitcoin::network::constants::Network;
487+
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
488+
use core::convert::TryFrom;
489+
use core::time::Duration;
490+
use crate::ln::features::InvoiceRequestFeatures;
491+
use crate::ln::msgs::MAX_VALUE_MSAT;
492+
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
493+
use crate::offers::offer::OfferTlvStreamRef;
494+
use crate::offers::parse::SemanticError;
495+
use crate::offers::payer::PayerTlvStreamRef;
496+
use crate::onion_message::{BlindedHop, BlindedPath};
497+
use crate::util::ser::Writeable;
498+
use crate::util::string::PrintableString;
499+
500+
fn payer_pubkey() -> PublicKey {
501+
let secp_ctx = Secp256k1::new();
502+
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()).public_key()
503+
}
504+
505+
fn pubkey(byte: u8) -> PublicKey {
506+
let secp_ctx = Secp256k1::new();
507+
PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
508+
}
509+
510+
fn privkey(byte: u8) -> SecretKey {
511+
SecretKey::from_slice(&[byte; 32]).unwrap()
512+
}
513+
514+
#[test]
515+
fn builds_refund_with_defaults() {
516+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
517+
.build().unwrap();
518+
519+
let mut buffer = Vec::new();
520+
refund.write(&mut buffer).unwrap();
521+
522+
assert_eq!(refund.bytes, buffer.as_slice());
523+
assert_eq!(refund.metadata(), &[1; 32]);
524+
assert_eq!(refund.description(), PrintableString("foo"));
525+
assert_eq!(refund.absolute_expiry(), None);
526+
#[cfg(feature = "std")]
527+
assert!(!refund.is_expired());
528+
assert_eq!(refund.paths(), &[]);
529+
assert_eq!(refund.issuer(), None);
530+
assert_eq!(refund.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
531+
assert_eq!(refund.amount_msats(), 1000);
532+
assert_eq!(refund.features(), &InvoiceRequestFeatures::empty());
533+
assert_eq!(refund.payer_id(), payer_pubkey());
534+
assert_eq!(refund.payer_note(), None);
535+
536+
assert_eq!(
537+
refund.as_tlv_stream(),
538+
(
539+
PayerTlvStreamRef { metadata: Some(&vec![1; 32]) },
540+
OfferTlvStreamRef {
541+
chains: None,
542+
metadata: None,
543+
currency: None,
544+
amount: None,
545+
description: Some(&String::from("foo")),
546+
features: None,
547+
absolute_expiry: None,
548+
paths: None,
549+
issuer: None,
550+
quantity_max: None,
551+
node_id: None,
552+
},
553+
InvoiceRequestTlvStreamRef {
554+
chain: None,
555+
amount: Some(1000),
556+
features: None,
557+
quantity: None,
558+
payer_id: Some(&payer_pubkey()),
559+
payer_note: None,
560+
},
561+
),
562+
);
563+
564+
if let Err(e) = Refund::try_from(buffer) {
565+
panic!("error parsing refund: {:?}", e);
566+
}
567+
}
568+
569+
#[test]
570+
fn fails_buidling_refund_with_invalid_amount() {
571+
match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), MAX_VALUE_MSAT + 1) {
572+
Ok(_) => panic!("expected error"),
573+
Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
574+
}
575+
}
576+
577+
#[test]
578+
fn builds_refund_with_absolute_expiry() {
579+
let future_expiry = Duration::from_secs(u64::max_value());
580+
let past_expiry = Duration::from_secs(0);
581+
582+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
583+
.absolute_expiry(future_expiry)
584+
.build()
585+
.unwrap();
586+
let (_, tlv_stream, _) = refund.as_tlv_stream();
587+
#[cfg(feature = "std")]
588+
assert!(!refund.is_expired());
589+
assert_eq!(refund.absolute_expiry(), Some(future_expiry));
590+
assert_eq!(tlv_stream.absolute_expiry, Some(future_expiry.as_secs()));
591+
592+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
593+
.absolute_expiry(future_expiry)
594+
.absolute_expiry(past_expiry)
595+
.build()
596+
.unwrap();
597+
let (_, tlv_stream, _) = refund.as_tlv_stream();
598+
#[cfg(feature = "std")]
599+
assert!(refund.is_expired());
600+
assert_eq!(refund.absolute_expiry(), Some(past_expiry));
601+
assert_eq!(tlv_stream.absolute_expiry, Some(past_expiry.as_secs()));
602+
}
603+
604+
#[test]
605+
fn builds_refund_with_paths() {
606+
let paths = vec![
607+
BlindedPath {
608+
introduction_node_id: pubkey(40),
609+
blinding_point: pubkey(41),
610+
blinded_hops: vec![
611+
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
612+
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
613+
],
614+
},
615+
BlindedPath {
616+
introduction_node_id: pubkey(40),
617+
blinding_point: pubkey(41),
618+
blinded_hops: vec![
619+
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
620+
BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
621+
],
622+
},
623+
];
624+
625+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
626+
.path(paths[0].clone())
627+
.path(paths[1].clone())
628+
.build()
629+
.unwrap();
630+
let (_, offer_tlv_stream, invoice_request_tlv_stream) = refund.as_tlv_stream();
631+
assert_eq!(refund.paths(), paths.as_slice());
632+
assert_eq!(refund.payer_id(), pubkey(42));
633+
assert_ne!(pubkey(42), pubkey(44));
634+
assert_eq!(offer_tlv_stream.paths, Some(&paths));
635+
assert_eq!(invoice_request_tlv_stream.payer_id, Some(&pubkey(42)));
636+
}
637+
638+
#[test]
639+
fn builds_refund_with_issuer() {
640+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
641+
.issuer("bar".into())
642+
.build()
643+
.unwrap();
644+
let (_, tlv_stream, _) = refund.as_tlv_stream();
645+
assert_eq!(refund.issuer(), Some(PrintableString("bar")));
646+
assert_eq!(tlv_stream.issuer, Some(&String::from("bar")));
647+
648+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
649+
.issuer("bar".into())
650+
.issuer("baz".into())
651+
.build()
652+
.unwrap();
653+
let (_, tlv_stream, _) = refund.as_tlv_stream();
654+
assert_eq!(refund.issuer(), Some(PrintableString("baz")));
655+
assert_eq!(tlv_stream.issuer, Some(&String::from("baz")));
656+
}
657+
658+
#[test]
659+
fn builds_refund_with_chain() {
660+
let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);
661+
let testnet = ChainHash::using_genesis_block(Network::Testnet);
662+
663+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
664+
.chain(Network::Bitcoin)
665+
.build().unwrap();
666+
let (_, _, tlv_stream) = refund.as_tlv_stream();
667+
assert_eq!(refund.chain(), mainnet);
668+
assert_eq!(tlv_stream.chain, None);
669+
670+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
671+
.chain(Network::Testnet)
672+
.build().unwrap();
673+
let (_, _, tlv_stream) = refund.as_tlv_stream();
674+
assert_eq!(refund.chain(), testnet);
675+
assert_eq!(tlv_stream.chain, Some(&testnet));
676+
677+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
678+
.chain(Network::Regtest)
679+
.chain(Network::Testnet)
680+
.build().unwrap();
681+
let (_, _, tlv_stream) = refund.as_tlv_stream();
682+
assert_eq!(refund.chain(), testnet);
683+
assert_eq!(tlv_stream.chain, Some(&testnet));
684+
}
685+
686+
#[test]
687+
fn builds_refund_with_payer_note() {
688+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
689+
.payer_note("bar".into())
690+
.build().unwrap();
691+
let (_, _, tlv_stream) = refund.as_tlv_stream();
692+
assert_eq!(refund.payer_note(), Some(PrintableString("bar")));
693+
assert_eq!(tlv_stream.payer_note, Some(&String::from("bar")));
694+
695+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
696+
.payer_note("bar".into())
697+
.payer_note("baz".into())
698+
.build().unwrap();
699+
let (_, _, tlv_stream) = refund.as_tlv_stream();
700+
assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
701+
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
702+
}
703+
}

0 commit comments

Comments
 (0)