Skip to content

Commit a9efe23

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

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed

lightning/src/offers/refund.rs

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

0 commit comments

Comments
 (0)