Skip to content

Commit 1990ae2

Browse files
committed
Refund parsing tests
Tests for checking refund semantics when parsing invoice_request bytes as defined by BOLT 12.
1 parent a9efe23 commit 1990ae2

File tree

1 file changed

+250
-5
lines changed

1 file changed

+250
-5
lines changed

lightning/src/offers/refund.rs

Lines changed: 250 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ impl RefundBuilder {
183183
}
184184
}
185185

186+
#[cfg(test)]
187+
impl RefundBuilder {
188+
fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
189+
self.refund.features = features;
190+
self
191+
}
192+
}
193+
186194
/// A `Refund` is a request to send an `Invoice` without a preceding [`Offer`].
187195
///
188196
/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
@@ -480,22 +488,22 @@ impl core::fmt::Display for Refund {
480488

481489
#[cfg(test)]
482490
mod tests {
483-
use super::{Refund, RefundBuilder};
491+
use super::{Refund, RefundBuilder, RefundTlvStreamRef};
484492

485493
use bitcoin::blockdata::constants::ChainHash;
486494
use bitcoin::network::constants::Network;
487495
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
488496
use core::convert::TryFrom;
489497
#[cfg(feature = "std")]
490498
use core::time::Duration;
491-
use crate::ln::features::InvoiceRequestFeatures;
492-
use crate::ln::msgs::MAX_VALUE_MSAT;
499+
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
500+
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
493501
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
494502
use crate::offers::offer::OfferTlvStreamRef;
495-
use crate::offers::parse::SemanticError;
503+
use crate::offers::parse::{ParseError, SemanticError};
496504
use crate::offers::payer::PayerTlvStreamRef;
497505
use crate::onion_message::{BlindedHop, BlindedPath};
498-
use crate::util::ser::Writeable;
506+
use crate::util::ser::{BigSize, Writeable};
499507
use crate::util::string::PrintableString;
500508

501509
fn payer_pubkey() -> PublicKey {
@@ -512,6 +520,18 @@ mod tests {
512520
SecretKey::from_slice(&[byte; 32]).unwrap()
513521
}
514522

523+
trait ToBytes {
524+
fn to_bytes(&self) -> Vec<u8>;
525+
}
526+
527+
impl<'a> ToBytes for RefundTlvStreamRef<'a> {
528+
fn to_bytes(&self) -> Vec<u8> {
529+
let mut buffer = Vec::new();
530+
self.write(&mut buffer).unwrap();
531+
buffer
532+
}
533+
}
534+
515535
#[test]
516536
fn builds_refund_with_defaults() {
517537
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
@@ -701,4 +721,229 @@ mod tests {
701721
assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
702722
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
703723
}
724+
725+
#[test]
726+
fn parses_refund_with_metadata() {
727+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
728+
.build().unwrap();
729+
if let Err(e) = refund.to_string().parse::<Refund>() {
730+
panic!("error parsing refund: {:?}", e);
731+
}
732+
733+
let mut tlv_stream = refund.as_tlv_stream();
734+
tlv_stream.0.metadata = None;
735+
736+
match Refund::try_from(tlv_stream.to_bytes()) {
737+
Ok(_) => panic!("expected error"),
738+
Err(e) => {
739+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata));
740+
},
741+
}
742+
}
743+
744+
#[test]
745+
fn parses_refund_with_description() {
746+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
747+
.build().unwrap();
748+
if let Err(e) = refund.to_string().parse::<Refund>() {
749+
panic!("error parsing refund: {:?}", e);
750+
}
751+
752+
let mut tlv_stream = refund.as_tlv_stream();
753+
tlv_stream.1.description = None;
754+
755+
match Refund::try_from(tlv_stream.to_bytes()) {
756+
Ok(_) => panic!("expected error"),
757+
Err(e) => {
758+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
759+
},
760+
}
761+
}
762+
763+
#[test]
764+
fn parses_refund_with_amount() {
765+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
766+
.build().unwrap();
767+
if let Err(e) = refund.to_string().parse::<Refund>() {
768+
panic!("error parsing refund: {:?}", e);
769+
}
770+
771+
let mut tlv_stream = refund.as_tlv_stream();
772+
tlv_stream.2.amount = None;
773+
774+
match Refund::try_from(tlv_stream.to_bytes()) {
775+
Ok(_) => panic!("expected error"),
776+
Err(e) => {
777+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount));
778+
},
779+
}
780+
781+
let mut tlv_stream = refund.as_tlv_stream();
782+
tlv_stream.2.amount = Some(MAX_VALUE_MSAT + 1);
783+
784+
match Refund::try_from(tlv_stream.to_bytes()) {
785+
Ok(_) => panic!("expected error"),
786+
Err(e) => {
787+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount));
788+
},
789+
}
790+
}
791+
792+
#[test]
793+
fn parses_refund_with_payer_id() {
794+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
795+
.build().unwrap();
796+
if let Err(e) = refund.to_string().parse::<Refund>() {
797+
panic!("error parsing refund: {:?}", e);
798+
}
799+
800+
let mut tlv_stream = refund.as_tlv_stream();
801+
tlv_stream.2.payer_id = None;
802+
803+
match Refund::try_from(tlv_stream.to_bytes()) {
804+
Ok(_) => panic!("expected error"),
805+
Err(e) => {
806+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId));
807+
},
808+
}
809+
}
810+
811+
#[test]
812+
fn parses_refund_with_optional_fields() {
813+
let past_expiry = Duration::from_secs(0);
814+
let paths = vec![
815+
BlindedPath {
816+
introduction_node_id: pubkey(40),
817+
blinding_point: pubkey(41),
818+
blinded_hops: vec![
819+
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
820+
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
821+
],
822+
},
823+
BlindedPath {
824+
introduction_node_id: pubkey(40),
825+
blinding_point: pubkey(41),
826+
blinded_hops: vec![
827+
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
828+
BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
829+
],
830+
},
831+
];
832+
833+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
834+
.absolute_expiry(past_expiry)
835+
.issuer("bar".into())
836+
.path(paths[0].clone())
837+
.path(paths[1].clone())
838+
.chain(Network::Testnet)
839+
.features_unchecked(InvoiceRequestFeatures::unknown())
840+
.payer_note("baz".into())
841+
.build()
842+
.unwrap();
843+
match refund.to_string().parse::<Refund>() {
844+
Ok(refund) => {
845+
assert_eq!(refund.absolute_expiry(), Some(past_expiry));
846+
#[cfg(feature = "std")]
847+
assert!(refund.is_expired());
848+
assert_eq!(refund.paths(), &paths[..]);
849+
assert_eq!(refund.issuer(), Some(PrintableString("bar")));
850+
assert_eq!(refund.chain(), ChainHash::using_genesis_block(Network::Testnet));
851+
assert_eq!(refund.features(), &InvoiceRequestFeatures::unknown());
852+
assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
853+
},
854+
Err(e) => panic!("error parsing refund: {:?}", e),
855+
}
856+
}
857+
858+
#[test]
859+
fn fails_parsing_refund_with_unexpected_fields() {
860+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
861+
.build().unwrap();
862+
if let Err(e) = refund.to_string().parse::<Refund>() {
863+
panic!("error parsing refund: {:?}", e);
864+
}
865+
866+
let chains = vec![ChainHash::using_genesis_block(Network::Testnet)];
867+
let mut tlv_stream = refund.as_tlv_stream();
868+
tlv_stream.1.chains = Some(&chains);
869+
870+
match Refund::try_from(tlv_stream.to_bytes()) {
871+
Ok(_) => panic!("expected error"),
872+
Err(e) => {
873+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedChain));
874+
},
875+
}
876+
877+
let mut tlv_stream = refund.as_tlv_stream();
878+
tlv_stream.1.currency = Some(&b"USD");
879+
tlv_stream.1.amount = Some(1000);
880+
881+
match Refund::try_from(tlv_stream.to_bytes()) {
882+
Ok(_) => panic!("expected error"),
883+
Err(e) => {
884+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedAmount));
885+
},
886+
}
887+
888+
let features = OfferFeatures::unknown();
889+
let mut tlv_stream = refund.as_tlv_stream();
890+
tlv_stream.1.features = Some(&features);
891+
892+
match Refund::try_from(tlv_stream.to_bytes()) {
893+
Ok(_) => panic!("expected error"),
894+
Err(e) => {
895+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedFeatures));
896+
},
897+
}
898+
899+
let mut tlv_stream = refund.as_tlv_stream();
900+
tlv_stream.1.quantity_max = Some(10);
901+
902+
match Refund::try_from(tlv_stream.to_bytes()) {
903+
Ok(_) => panic!("expected error"),
904+
Err(e) => {
905+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
906+
},
907+
}
908+
909+
let node_id = payer_pubkey();
910+
let mut tlv_stream = refund.as_tlv_stream();
911+
tlv_stream.1.node_id = Some(&node_id);
912+
913+
match Refund::try_from(tlv_stream.to_bytes()) {
914+
Ok(_) => panic!("expected error"),
915+
Err(e) => {
916+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedSigningPubkey));
917+
},
918+
}
919+
920+
let mut tlv_stream = refund.as_tlv_stream();
921+
tlv_stream.2.quantity = Some(10);
922+
923+
match Refund::try_from(tlv_stream.to_bytes()) {
924+
Ok(_) => panic!("expected error"),
925+
Err(e) => {
926+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
927+
},
928+
}
929+
}
930+
931+
#[test]
932+
fn fails_parsing_refund_with_extra_tlv_records() {
933+
let secp_ctx = Secp256k1::new();
934+
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
935+
let refund = RefundBuilder::new("foo".into(), vec![1; 32], keys.public_key(), 1000).unwrap()
936+
.build().unwrap();
937+
938+
let mut encoded_refund = Vec::new();
939+
refund.write(&mut encoded_refund).unwrap();
940+
BigSize(1002).write(&mut encoded_refund).unwrap();
941+
BigSize(32).write(&mut encoded_refund).unwrap();
942+
[42u8; 32].write(&mut encoded_refund).unwrap();
943+
944+
match Refund::try_from(encoded_refund) {
945+
Ok(_) => panic!("expected error"),
946+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
947+
}
948+
}
704949
}

0 commit comments

Comments
 (0)