Skip to content

Commit 9b92a09

Browse files
committed
Refund parsing tests
Tests for checking refund semantics when parsing invoice_request bytes as defined by BOLT 12.
1 parent 71b370b commit 9b92a09

File tree

1 file changed

+250
-5
lines changed

1 file changed

+250
-5
lines changed

lightning/src/offers/refund.rs

+250-5
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,21 +488,21 @@ 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
use core::time::Duration;
490-
use crate::ln::features::InvoiceRequestFeatures;
491-
use crate::ln::msgs::MAX_VALUE_MSAT;
498+
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
499+
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
492500
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
493501
use crate::offers::offer::OfferTlvStreamRef;
494-
use crate::offers::parse::SemanticError;
502+
use crate::offers::parse::{ParseError, SemanticError};
495503
use crate::offers::payer::PayerTlvStreamRef;
496504
use crate::onion_message::{BlindedHop, BlindedPath};
497-
use crate::util::ser::Writeable;
505+
use crate::util::ser::{BigSize, Writeable};
498506
use crate::util::string::PrintableString;
499507

500508
fn payer_pubkey() -> PublicKey {
@@ -511,6 +519,18 @@ mod tests {
511519
SecretKey::from_slice(&[byte; 32]).unwrap()
512520
}
513521

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

0 commit comments

Comments
 (0)