@@ -380,3 +380,258 @@ impl core::fmt::Display for MempoolPolicyError {
380380
381381#[ cfg( feature = "std" ) ]
382382impl std:: error:: Error for MempoolPolicyError { }
383+
384+ #[ cfg( test) ]
385+ mod tests {
386+ use super :: * ;
387+ use crate :: { test_utils:: * , Output } ;
388+ use alloc:: vec:: Vec ;
389+ use bitcoin:: { transaction:: Version , Amount , ScriptBuf , Transaction , TxOut } ;
390+
391+ fn default_tip ( ) -> ChainTip {
392+ ChainTip {
393+ height : absolute:: Height :: from_consensus ( 3_000 ) . unwrap ( ) ,
394+ mtp : absolute:: Time :: from_consensus ( 500_001_000 ) . unwrap ( ) ,
395+ }
396+ }
397+
398+ #[ test]
399+ fn default_policy_matches_bitcoin_core_v30 ( ) {
400+ let default_policy = MempoolPolicy :: default ( ) ;
401+ let v30 = MempoolPolicy :: bitcoin_core_v30 ( ) ;
402+ assert_eq ! ( default_policy. dust_relay_feerate, v30. dust_relay_feerate) ;
403+ assert_eq ! ( default_policy. min_relay_feerate, v30. min_relay_feerate) ;
404+ assert_eq ! (
405+ default_policy. max_op_return_aggregate_bytes,
406+ v30. max_op_return_aggregate_bytes
407+ ) ;
408+ assert_eq ! (
409+ default_policy. max_standard_tx_weight,
410+ v30. max_standard_tx_weight
411+ ) ;
412+ assert_eq ! ( default_policy. max_truc_tx_weight, v30. max_truc_tx_weight) ;
413+ assert_eq ! (
414+ default_policy. min_standard_tx_nonwitness_size,
415+ v30. min_standard_tx_nonwitness_size
416+ ) ;
417+ assert_eq ! (
418+ default_policy. max_witness_stack_items,
419+ v30. max_witness_stack_items
420+ ) ;
421+ assert_eq ! ( default_policy. allowed_versions, v30. allowed_versions) ;
422+ }
423+
424+ #[ test]
425+ fn test_tx_version ( ) {
426+ let policy = MempoolPolicy :: default ( ) ;
427+ let tip = default_tip ( ) ;
428+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
429+ let output = create_output ( p2tr_script ( ) , 9_000 ) ;
430+ let ( selection, mut tx) = build_selection_with_tx ( & [ input] , & [ output] ) ;
431+
432+ // Default version is 2, which is standard.
433+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
434+
435+ // Test version 1, which is also standard.
436+ tx. version = Version :: ONE ;
437+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
438+
439+ // Version 3 (TRUC) is standard under v30+.
440+ tx. version = Version ( 3 ) ;
441+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
442+
443+ // Test version 4, which is non-standard.
444+ tx. version = Version ( 4 ) ;
445+ assert ! ( matches!(
446+ policy. check_post_selection( & selection, & tx, tip) ,
447+ Err ( MempoolPolicyError :: UnsupportedVersion ( _) )
448+ ) ) ;
449+ }
450+
451+ #[ test]
452+ fn test_tx_locktime ( ) {
453+ let policy = MempoolPolicy :: default ( ) ;
454+ let tip = default_tip ( ) ;
455+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
456+ let output = create_output ( p2tr_script ( ) , 9_000 ) ;
457+ let ( selection, mut tx) = build_selection_with_tx ( & [ input] , & [ output] ) ;
458+
459+ // Locktime exactly equal to the tip height.
460+ tx. lock_time = absolute:: LockTime :: from_consensus ( 3_000 ) ;
461+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
462+
463+ // Locktime below the tip height.
464+ tx. lock_time = absolute:: LockTime :: from_consensus ( 2_500 ) ;
465+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
466+
467+ // Locktime above the tip height.
468+ tx. lock_time = absolute:: LockTime :: from_consensus ( 3_001 ) ;
469+ assert ! ( matches!(
470+ policy. check_post_selection( & selection, & tx, tip) ,
471+ Err ( MempoolPolicyError :: LockTimeNotMet ( _) )
472+ ) ) ;
473+
474+ // Locktime one second below the tip MTP.
475+ tx. lock_time = absolute:: LockTime :: from_consensus ( 500_000_999 ) ;
476+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
477+
478+ // Locktime exactly equal to the tip MTP.
479+ tx. lock_time = absolute:: LockTime :: from_consensus ( 500_001_000 ) ;
480+ assert ! ( matches!(
481+ policy. check_post_selection( & selection, & tx, tip) ,
482+ Err ( MempoolPolicyError :: LockTimeNotMet ( _) )
483+ ) ) ;
484+
485+ // Locktime above the tip MTP.
486+ tx. lock_time = absolute:: LockTime :: from_consensus ( 500_002_000 ) ;
487+ assert ! ( matches!(
488+ policy. check_post_selection( & selection, & tx, tip) ,
489+ Err ( MempoolPolicyError :: LockTimeNotMet ( _) )
490+ ) ) ;
491+ }
492+
493+ #[ test]
494+ fn test_max_tx_weight ( ) {
495+ let policy = MempoolPolicy :: default ( ) ;
496+ let tip = default_tip ( ) ;
497+
498+ // A normal transaction with 1 input and 1 output.
499+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
500+ let output = create_output ( p2tr_script ( ) , 9_000 ) ;
501+ let ( selection, tx) = build_selection_with_tx ( core:: slice:: from_ref ( & input) , & [ output] ) ;
502+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
503+
504+ // Heavy transaction with excess weight.
505+ let outputs_with_excess_weight: Vec < Output > = ( 0 ..2_350 )
506+ . map ( |_| create_output ( p2tr_script ( ) , 1_000 ) )
507+ . collect ( ) ;
508+
509+ let ( _, heavy_tx) =
510+ build_selection_with_tx ( & [ input] , outputs_with_excess_weight. as_slice ( ) ) ;
511+
512+ assert ! ( heavy_tx. weight( ) > policy. max_standard_tx_weight) ;
513+ assert ! ( matches!(
514+ policy. check_max_tx_weight( heavy_tx. weight( ) , heavy_tx. version) ,
515+ Err ( MempoolPolicyError :: MaxWeightExceeded { .. } )
516+ ) ) ;
517+ }
518+
519+ #[ test]
520+ fn test_tx_min_non_witness_size ( ) {
521+ let policy = MempoolPolicy :: default ( ) ;
522+ let tip = default_tip ( ) ;
523+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
524+ let output = create_output ( p2tr_script ( ) , 9_000 ) ;
525+
526+ // Transaction with 1 input and 1 output.
527+ let ( selection, tx) = build_selection_with_tx ( & [ input] , & [ output] ) ;
528+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
529+
530+ // Transaction with no inputs and 1 output.
531+ let tx_below_min_non_witness_size = Transaction {
532+ version : Version :: TWO ,
533+ lock_time : absolute:: LockTime :: ZERO ,
534+ input : vec ! [ ] ,
535+ output : vec ! [ TxOut {
536+ script_pubkey: ScriptBuf :: new( ) ,
537+ value: Amount :: ZERO ,
538+ } ] ,
539+ } ;
540+ let empty_selection = Selection {
541+ inputs : vec ! [ ] ,
542+ outputs : vec ! [ Output :: with_script( ScriptBuf :: new( ) , Amount :: ZERO ) ] ,
543+ } ;
544+ assert ! ( tx_below_min_non_witness_size. base_size( ) < policy. min_standard_tx_nonwitness_size) ;
545+ assert ! ( matches!(
546+ policy. check_post_selection( & empty_selection, & tx_below_min_non_witness_size, tip) ,
547+ Err ( MempoolPolicyError :: TxTooSmall { .. } )
548+ ) ) ;
549+ }
550+
551+ #[ test]
552+ fn test_min_fee_relay ( ) {
553+ let policy = MempoolPolicy :: default ( ) ;
554+ let tip = default_tip ( ) ;
555+
556+ // Sufficient fee passes.
557+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
558+ let output = create_output ( p2tr_script ( ) , 9_000 ) ;
559+
560+ let ( selection, tx) = build_selection_with_tx ( & [ input] , & [ output] ) ;
561+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
562+
563+ // Fee below the 1 sat/vB minimum is rejected.
564+ let input_with_insufficient_fee = setup_test_input ( 2_000 ) . unwrap ( ) ;
565+ let output_with_insufficient_fee = create_output ( p2tr_script ( ) , 9_999 ) ;
566+
567+ let ( selection_with_insufficient_fee, tx_with_insufficient_fee) = build_selection_with_tx (
568+ & [ input_with_insufficient_fee] ,
569+ & [ output_with_insufficient_fee] ,
570+ ) ;
571+ assert ! ( matches!(
572+ policy. check_post_selection(
573+ & selection_with_insufficient_fee,
574+ & tx_with_insufficient_fee,
575+ tip
576+ ) ,
577+ Err ( MempoolPolicyError :: MinRelayFeeNotMet { .. } )
578+ ) ) ;
579+ }
580+
581+ #[ test]
582+ fn test_max_witness_stack ( ) {
583+ let policy = MempoolPolicy :: default ( ) ;
584+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
585+
586+ assert ! ( policy. check_max_witness_stack( & [ input] ) . is_ok( ) ) ;
587+ }
588+
589+ #[ test]
590+ fn test_input_spendability ( ) {
591+ let policy = MempoolPolicy :: default ( ) ;
592+ let tip = default_tip ( ) ;
593+
594+ // Confirmed input.
595+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
596+ assert ! ( policy. check_input_spendability( & [ input] , tip) . is_ok( ) ) ;
597+
598+ // Immature coinbase (within COINBASE_MATURITY of the tip).
599+ let input_with_immature_coinbase = setup_test_input ( 2_950 ) . unwrap ( ) ;
600+ assert ! ( policy
601+ . check_input_spendability( & [ input_with_immature_coinbase] , tip)
602+ . is_err( ) ) ;
603+ }
604+
605+ #[ test]
606+ fn test_input_script_type ( ) {
607+ let policy = MempoolPolicy :: default ( ) ;
608+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
609+ assert ! ( policy. check_input_script_type( & [ input] ) . is_ok( ) ) ;
610+ }
611+
612+ #[ test]
613+ fn test_custom_policy_overrides_default ( ) {
614+ // A custom policy that allows only v3 (TRUC) transactions.
615+ static V3_ONLY : & [ Version ] = & [ Version ( 3 ) ] ;
616+ let policy = MempoolPolicy {
617+ allowed_versions : V3_ONLY ,
618+ ..MempoolPolicy :: default ( )
619+ } ;
620+
621+ let tip = default_tip ( ) ;
622+ let input = setup_test_input ( 2_000 ) . unwrap ( ) ;
623+ let output = create_output ( p2tr_script ( ) , 9_000 ) ;
624+ let ( selection, mut tx) = build_selection_with_tx ( & [ input] , & [ output] ) ;
625+
626+ // v2 (the default tx version) is now rejected.
627+ tx. version = Version :: TWO ;
628+ assert ! ( matches!(
629+ policy. check_post_selection( & selection, & tx, tip) ,
630+ Err ( MempoolPolicyError :: UnsupportedVersion ( _) )
631+ ) ) ;
632+
633+ // v3 passes.
634+ tx. version = Version ( 3 ) ;
635+ assert ! ( policy. check_post_selection( & selection, & tx, tip) . is_ok( ) ) ;
636+ }
637+ }
0 commit comments