@@ -58,6 +58,10 @@ def wrapper(self):
58
58
self .generate (self .nodes [0 ], 1 )
59
59
self .nodes [0 ].disconnect_p2ps ()
60
60
self .nodes [0 ].bumpmocktime (LONG_TIME_SKIP )
61
+ # Check that mempool and orphanage have been cleared
62
+ assert_equal (0 , len (self .nodes [0 ].getorphantxs ()))
63
+ assert_equal (0 , len (self .nodes [0 ].getrawmempool ()))
64
+ self .wallet .rescan_utxos (include_mempool = True )
61
65
return wrapper
62
66
63
67
class PeerTxRelayer (P2PTxInvStore ):
@@ -533,7 +537,7 @@ def test_same_txid_orphan_of_orphan(self):
533
537
assert tx_middle ["txid" ] in node_mempool
534
538
assert tx_grandchild ["txid" ] in node_mempool
535
539
assert_equal (node .getmempoolentry (tx_middle ["txid" ])["wtxid" ], tx_middle ["wtxid" ])
536
- assert_equal ( len (node .getorphantxs ()), 0 )
540
+ self . wait_until ( lambda : len (node .getorphantxs ()) == 0 )
537
541
538
542
@cleanup
539
543
def test_orphan_txid_inv (self ):
@@ -585,7 +589,7 @@ def test_orphan_txid_inv(self):
585
589
assert tx_parent ["txid" ] in node_mempool
586
590
assert tx_child ["txid" ] in node_mempool
587
591
assert_equal (node .getmempoolentry (tx_child ["txid" ])["wtxid" ], tx_child ["wtxid" ])
588
- assert_equal ( len (node .getorphantxs ()), 0 )
592
+ self . wait_until ( lambda : len (node .getorphantxs ()) == 0 )
589
593
590
594
@cleanup
591
595
def test_max_orphan_amount (self ):
@@ -610,7 +614,7 @@ def test_max_orphan_amount(self):
610
614
611
615
peer_1 .sync_with_ping ()
612
616
orphanage = node .getorphantxs ()
613
- assert_equal ( len (orphanage ), DEFAULT_MAX_ORPHAN_TRANSACTIONS )
617
+ self . wait_until ( lambda : len (node . getorphantxs ()) == DEFAULT_MAX_ORPHAN_TRANSACTIONS )
614
618
615
619
for orphan in orphans :
616
620
assert tx_in_orphanage (node , orphan )
@@ -626,15 +630,181 @@ def test_max_orphan_amount(self):
626
630
self .log .info ("Clearing the orphanage" )
627
631
for index , parent_orphan in enumerate (parent_orphans ):
628
632
peer_1 .send_and_ping (msg_tx (parent_orphan ))
629
- assert_equal (len (node .getorphantxs ()),0 )
633
+ self .wait_until (lambda : len (node .getorphantxs ()) == 0 )
634
+
635
+ @cleanup
636
+ def test_orphan_handling_prefer_outbound (self ):
637
+ self .log .info ("Test that the node prefers requesting from outbound peers" )
638
+ node = self .nodes [0 ]
639
+ orphan_wtxid , orphan_tx , parent_tx = self .create_parent_and_child ()
640
+ orphan_inv = CInv (t = MSG_WTX , h = int (orphan_wtxid , 16 ))
641
+
642
+ peer_inbound = node .add_p2p_connection (PeerTxRelayer ())
643
+ peer_outbound = node .add_outbound_p2p_connection (PeerTxRelayer (), p2p_idx = 1 )
644
+
645
+ # Inbound peer relays the transaction.
646
+ peer_inbound .send_and_ping (msg_inv ([orphan_inv ]))
647
+ self .nodes [0 ].bumpmocktime (TXREQUEST_TIME_SKIP )
648
+ peer_inbound .wait_for_getdata ([int (orphan_wtxid , 16 )])
649
+
650
+ # Both peers send invs for the orphan, so the node can expect both to know its ancestors.
651
+ peer_outbound .send_and_ping (msg_inv ([orphan_inv ]))
652
+
653
+ peer_inbound .send_and_ping (msg_tx (orphan_tx ))
654
+
655
+ # There should be 1 orphan with 2 announcers (we don't know what their peer IDs are)
656
+ orphanage = node .getorphantxs (verbosity = 2 )
657
+ assert_equal (orphanage [0 ]["wtxid" ], orphan_wtxid )
658
+ assert_equal (len (orphanage [0 ]["from" ]), 2 )
659
+
660
+ # The outbound peer should be preferred for getting orphan parents
661
+ self .nodes [0 ].bumpmocktime (TXID_RELAY_DELAY )
662
+ peer_outbound .wait_for_parent_requests ([int (parent_tx .rehash (), 16 )])
663
+
664
+ # There should be no request to the inbound peer
665
+ peer_inbound .assert_never_requested (int (parent_tx .rehash (), 16 ))
666
+
667
+ self .log .info ("Test that, if the preferred peer doesn't respond, the node sends another request" )
668
+ self .nodes [0 ].bumpmocktime (GETDATA_TX_INTERVAL )
669
+ peer_inbound .sync_with_ping ()
670
+ peer_inbound .wait_for_parent_requests ([int (parent_tx .rehash (), 16 )])
671
+
672
+ @cleanup
673
+ def test_announcers_before_and_after (self ):
674
+ self .log .info ("Test that the node uses all peers who announced the tx prior to realizing it's an orphan" )
675
+ node = self .nodes [0 ]
676
+ orphan_wtxid , orphan_tx , parent_tx = self .create_parent_and_child ()
677
+ orphan_inv = CInv (t = MSG_WTX , h = int (orphan_wtxid , 16 ))
678
+
679
+ # Announces before tx is sent, disconnects while node is requesting parents
680
+ peer_early_disconnected = node .add_outbound_p2p_connection (PeerTxRelayer (), p2p_idx = 3 )
681
+ # Announces before tx is sent, doesn't respond to parent request
682
+ peer_early_unresponsive = node .add_p2p_connection (PeerTxRelayer ())
683
+
684
+ # Announces after tx is sent
685
+ peer_late_announcer = node .add_p2p_connection (PeerTxRelayer ())
686
+
687
+ # Both peers send invs for the orphan, so the node can expect both to know its ancestors.
688
+ peer_early_disconnected .send_and_ping (msg_inv ([orphan_inv ]))
689
+ self .nodes [0 ].bumpmocktime (TXREQUEST_TIME_SKIP )
690
+ peer_early_disconnected .wait_for_getdata ([int (orphan_wtxid , 16 )])
691
+ peer_early_unresponsive .send_and_ping (msg_inv ([orphan_inv ]))
692
+ peer_early_disconnected .send_and_ping (msg_tx (orphan_tx ))
693
+
694
+ # There should be 1 orphan with 2 announcers (we don't know what their peer IDs are)
695
+ orphanage = node .getorphantxs (verbosity = 2 )
696
+ assert_equal (len (orphanage ), 1 )
697
+ assert_equal (orphanage [0 ]["wtxid" ], orphan_wtxid )
698
+ assert_equal (len (orphanage [0 ]["from" ]), 2 )
699
+
700
+ # Peer disconnects before responding to request
701
+ self .nodes [0 ].bumpmocktime (TXID_RELAY_DELAY )
702
+ peer_early_disconnected .wait_for_parent_requests ([int (parent_tx .rehash (), 16 )])
703
+ peer_early_disconnected .peer_disconnect ()
630
704
705
+ # The orphan should have 1 announcer left after the node finishes disconnecting peer_early_disconnected.
706
+ self .wait_until (lambda : len (node .getorphantxs (verbosity = 2 )[0 ]["from" ]) == 1 )
707
+
708
+ # The node should retry with the other peer that announced the orphan earlier.
709
+ # This node's request was additionally delayed because it's an inbound peer.
710
+ self .nodes [0 ].bumpmocktime (NONPREF_PEER_TX_DELAY )
711
+ peer_early_unresponsive .wait_for_parent_requests ([int (parent_tx .rehash (), 16 )])
712
+
713
+ self .log .info ("Test that the node uses peers who announce the tx after realizing it's an orphan" )
714
+ peer_late_announcer .send_and_ping (msg_inv ([orphan_inv ]))
715
+
716
+ # The orphan should have 2 announcers now
717
+ orphanage = node .getorphantxs (verbosity = 2 )
718
+ assert_equal (orphanage [0 ]["wtxid" ], orphan_wtxid )
719
+ assert_equal (len (orphanage [0 ]["from" ]), 2 )
720
+
721
+ self .nodes [0 ].bumpmocktime (GETDATA_TX_INTERVAL )
722
+ peer_late_announcer .wait_for_parent_requests ([int (parent_tx .rehash (), 16 )])
723
+
724
+ @cleanup
725
+ def test_parents_change (self ):
726
+ self .log .info ("Test that, if a parent goes missing during orphan reso, it is requested" )
727
+ node = self .nodes [0 ]
728
+ # Orphan will have 2 parents, 1 missing and 1 already in mempool when received.
729
+ # Create missing parent.
730
+ parent_missing = self .wallet .create_self_transfer ()
731
+
732
+ # Create parent that will already be in mempool, but become missing during orphan resolution.
733
+ # Get 3 UTXOs for replacement-cycled parent, UTXOS A, B, C
734
+ coin_A = self .wallet .get_utxo (confirmed_only = True )
735
+ coin_B = self .wallet .get_utxo (confirmed_only = True )
736
+ coin_C = self .wallet .get_utxo (confirmed_only = True )
737
+ # parent_peekaboo_AB spends A and B. It is replaced by tx_replacer_BC (conflicting UTXO B),
738
+ # and then replaced by tx_replacer_C (conflicting UTXO C). This replacement cycle is used to
739
+ # ensure that parent_peekaboo_AB can be reintroduced without requiring package RBF.
740
+ FEE_INCREMENT = 2400
741
+ parent_peekaboo_AB = self .wallet .create_self_transfer_multi (
742
+ utxos_to_spend = [coin_A , coin_B ],
743
+ num_outputs = 1 ,
744
+ fee_per_output = FEE_INCREMENT
745
+ )
746
+ tx_replacer_BC = self .wallet .create_self_transfer_multi (
747
+ utxos_to_spend = [coin_B , coin_C ],
748
+ num_outputs = 1 ,
749
+ fee_per_output = 2 * FEE_INCREMENT
750
+ )
751
+ tx_replacer_C = self .wallet .create_self_transfer (
752
+ utxo_to_spend = coin_C ,
753
+ fee_per_output = 3 * FEE_INCREMENT
754
+ )
755
+
756
+ # parent_peekaboo_AB starts out in the mempool
757
+ node .sendrawtransaction (parent_peekaboo_AB ["hex" ])
758
+
759
+ orphan = self .wallet .create_self_transfer_multi (utxos_to_spend = [parent_peekaboo_AB ["new_utxos" ][0 ], parent_missing ["new_utxo" ]])
760
+ orphan_wtxid = orphan ["wtxid" ]
761
+ orphan_inv = CInv (t = MSG_WTX , h = int (orphan_wtxid , 16 ))
762
+
763
+ # peer1 sends the orphan and gets a request for the missing parent
764
+ peer1 = node .add_p2p_connection (PeerTxRelayer ())
765
+ peer1 .send_and_ping (msg_inv ([orphan_inv ]))
766
+ node .bumpmocktime (TXREQUEST_TIME_SKIP )
767
+ peer1 .wait_for_getdata ([int (orphan_wtxid , 16 )])
768
+ peer1 .send_and_ping (msg_tx (orphan ["tx" ]))
769
+ self .wait_until (lambda : node .getorphantxs (verbosity = 0 ) == [orphan ["txid" ]])
770
+ node .bumpmocktime (NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY )
771
+ peer1 .wait_for_getdata ([int (parent_missing ["txid" ], 16 )])
772
+
773
+ # Replace parent_peekaboo_AB so that is is a newly missing parent.
774
+ # Then, replace the replacement so that it can be resubmitted.
775
+ node .sendrawtransaction (tx_replacer_BC ["hex" ])
776
+ assert tx_replacer_BC ["txid" ] in node .getrawmempool ()
777
+ node .sendrawtransaction (tx_replacer_C ["hex" ])
778
+ assert tx_replacer_BC ["txid" ] not in node .getrawmempool ()
779
+ assert tx_replacer_C ["txid" ] in node .getrawmempool ()
780
+
781
+ # Second peer is an additional announcer for this orphan
782
+ peer2 = node .add_p2p_connection (PeerTxRelayer ())
783
+ peer2 .send_and_ping (msg_inv ([orphan_inv ]))
784
+ assert_equal (len (node .getorphantxs (verbosity = 2 )[0 ]["from" ]), 2 )
785
+
786
+ # Disconnect peer1. peer2 should become the new candidate for orphan resolution.
787
+ peer1 .peer_disconnect ()
788
+ node .bumpmocktime (NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY )
789
+ self .wait_until (lambda : len (node .getorphantxs (verbosity = 2 )[0 ]["from" ]) == 1 )
790
+ # Both parents should be requested, now that they are both missing.
791
+ peer2 .wait_for_parent_requests ([int (parent_peekaboo_AB ["txid" ], 16 ), int (parent_missing ["txid" ], 16 )])
792
+ peer2 .send_and_ping (msg_tx (parent_missing ["tx" ]))
793
+ peer2 .send_and_ping (msg_tx (parent_peekaboo_AB ["tx" ]))
794
+
795
+ final_mempool = node .getrawmempool ()
796
+ assert parent_missing ["txid" ] in final_mempool
797
+ assert parent_peekaboo_AB ["txid" ] in final_mempool
798
+ assert orphan ["txid" ] in final_mempool
799
+ assert tx_replacer_C ["txid" ] in final_mempool
631
800
632
801
def run_test (self ):
633
802
self .nodes [0 ].setmocktime (int (time .time ()))
634
803
self .wallet_nonsegwit = MiniWallet (self .nodes [0 ], mode = MiniWalletMode .RAW_P2PK )
635
804
self .generate (self .wallet_nonsegwit , 10 )
636
805
self .wallet = MiniWallet (self .nodes [0 ])
637
806
self .generate (self .wallet , 160 )
807
+
638
808
self .test_arrival_timing_orphan ()
639
809
self .test_orphan_rejected_parents_exceptions ()
640
810
self .test_orphan_multiple_parents ()
@@ -645,6 +815,9 @@ def run_test(self):
645
815
self .test_same_txid_orphan_of_orphan ()
646
816
self .test_orphan_txid_inv ()
647
817
self .test_max_orphan_amount ()
818
+ self .test_orphan_handling_prefer_outbound ()
819
+ self .test_announcers_before_and_after ()
820
+ self .test_parents_change ()
648
821
649
822
650
823
if __name__ == '__main__' :
0 commit comments