@@ -183,6 +183,14 @@ impl RefundBuilder {
183
183
}
184
184
}
185
185
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
+
186
194
/// A `Refund` is a request to send an `Invoice` without a preceding [`Offer`].
187
195
///
188
196
/// 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 {
480
488
481
489
#[ cfg( test) ]
482
490
mod tests {
483
- use super :: { Refund , RefundBuilder } ;
491
+ use super :: { Refund , RefundBuilder , RefundTlvStreamRef } ;
484
492
485
493
use bitcoin:: blockdata:: constants:: ChainHash ;
486
494
use bitcoin:: network:: constants:: Network ;
487
495
use bitcoin:: secp256k1:: { KeyPair , PublicKey , Secp256k1 , SecretKey } ;
488
496
use core:: convert:: TryFrom ;
489
497
#[ cfg( feature = "std" ) ]
490
498
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 } ;
493
501
use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
494
502
use crate :: offers:: offer:: OfferTlvStreamRef ;
495
- use crate :: offers:: parse:: SemanticError ;
503
+ use crate :: offers:: parse:: { ParseError , SemanticError } ;
496
504
use crate :: offers:: payer:: PayerTlvStreamRef ;
497
505
use crate :: onion_message:: { BlindedHop , BlindedPath } ;
498
- use crate :: util:: ser:: Writeable ;
506
+ use crate :: util:: ser:: { BigSize , Writeable } ;
499
507
use crate :: util:: string:: PrintableString ;
500
508
501
509
fn payer_pubkey ( ) -> PublicKey {
@@ -512,6 +520,18 @@ mod tests {
512
520
SecretKey :: from_slice ( & [ byte; 32 ] ) . unwrap ( )
513
521
}
514
522
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
+
515
535
#[ test]
516
536
fn builds_refund_with_defaults ( ) {
517
537
let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
@@ -701,4 +721,229 @@ mod tests {
701
721
assert_eq ! ( refund. payer_note( ) , Some ( PrintableString ( "baz" ) ) ) ;
702
722
assert_eq ! ( tlv_stream. payer_note, Some ( & String :: from( "baz" ) ) ) ;
703
723
}
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
+ }
704
949
}
0 commit comments