8
8
that spend (directly or indirectly) coinbase transactions.
9
9
"""
10
10
11
+ import time
12
+
13
+ from test_framework .messages import (
14
+ CInv ,
15
+ MSG_WTX ,
16
+ msg_getdata ,
17
+ )
18
+ from test_framework .p2p import (
19
+ P2PTxInvStore ,
20
+ p2p_lock ,
21
+ )
11
22
from test_framework .test_framework import BitcoinTestFramework
12
23
from test_framework .util import assert_equal , assert_raises_rpc_error
13
24
from test_framework .wallet import MiniWallet
@@ -22,8 +33,84 @@ def set_test_params(self):
22
33
[]
23
34
]
24
35
36
+ def test_reorg_relay (self ):
37
+ self .log .info ("Test that transactions from disconnected blocks are available for relay immediately" )
38
+ # Prevent time from moving forward
39
+ self .nodes [1 ].setmocktime (int (time .time ()))
40
+ self .connect_nodes (0 , 1 )
41
+ self .generate (self .wallet , 3 )
42
+
43
+ # Disconnect node0 and node1 to create different chains.
44
+ self .disconnect_nodes (0 , 1 )
45
+ # Connect a peer to node1, which doesn't have immediate tx relay
46
+ peer1 = self .nodes [1 ].add_p2p_connection (P2PTxInvStore ())
47
+
48
+ # Create a transaction that is included in a block.
49
+ tx_disconnected = self .wallet .send_self_transfer (from_node = self .nodes [1 ])
50
+ self .generate (self .nodes [1 ], 1 , sync_fun = self .no_op )
51
+
52
+ # Create a transaction and submit it to node1's mempool.
53
+ tx_before_reorg = self .wallet .send_self_transfer (from_node = self .nodes [1 ])
54
+
55
+ # Create a child of that transaction and submit it to node1's mempool.
56
+ tx_child = self .wallet .send_self_transfer (utxo_to_spend = tx_disconnected ["new_utxo" ], from_node = self .nodes [1 ])
57
+ assert_equal (self .nodes [1 ].getmempoolentry (tx_child ["txid" ])["ancestorcount" ], 1 )
58
+ assert_equal (len (peer1 .get_invs ()), 0 )
59
+
60
+ # node0 has a longer chain in which tx_disconnected was not confirmed.
61
+ self .generate (self .nodes [0 ], 3 , sync_fun = self .no_op )
62
+
63
+ # Reconnect the nodes and sync chains. node0's chain should win.
64
+ self .connect_nodes (0 , 1 )
65
+ self .sync_blocks ()
66
+
67
+ # Child now has an ancestor from the disconnected block
68
+ assert_equal (self .nodes [1 ].getmempoolentry (tx_child ["txid" ])["ancestorcount" ], 2 )
69
+ assert_equal (self .nodes [1 ].getmempoolentry (tx_before_reorg ["txid" ])["ancestorcount" ], 1 )
70
+
71
+ # peer1 should not have received an inv for any of the transactions during this time, as not
72
+ # enough time has elapsed for those transactions to be announced. Likewise, it cannot
73
+ # request very recent, unanounced transactions.
74
+ assert_equal (len (peer1 .get_invs ()), 0 )
75
+ # It's too early to request these two transactions
76
+ requests_too_recent = msg_getdata ([CInv (t = MSG_WTX , h = int (tx ["tx" ].getwtxid (), 16 )) for tx in [tx_before_reorg , tx_child ]])
77
+ peer1 .send_and_ping (requests_too_recent )
78
+ for _ in range (len (requests_too_recent .inv )):
79
+ peer1 .sync_with_ping ()
80
+ with p2p_lock :
81
+ assert "tx" not in peer1 .last_message
82
+ assert "notfound" in peer1 .last_message
83
+
84
+ # Request the tx from the disconnected block
85
+ request_disconnected_tx = msg_getdata ([CInv (t = MSG_WTX , h = int (tx_disconnected ["tx" ].getwtxid (), 16 ))])
86
+ peer1 .send_and_ping (request_disconnected_tx )
87
+
88
+ # The tx from the disconnected block was never announced, and it entered the mempool later
89
+ # than the transactions that are too recent.
90
+ assert_equal (len (peer1 .get_invs ()), 0 )
91
+ with p2p_lock :
92
+ # However, the node will answer requests for the tx from the recently-disconnected block.
93
+ assert_equal (peer1 .last_message ["tx" ].tx .getwtxid (),tx_disconnected ["tx" ].getwtxid ())
94
+
95
+ self .nodes [1 ].setmocktime (int (time .time ()) + 30 )
96
+ peer1 .sync_with_ping ()
97
+ # the transactions are now announced
98
+ assert_equal (len (peer1 .get_invs ()), 3 )
99
+ for _ in range (3 ):
100
+ # make sure all tx requests have been responded to
101
+ peer1 .sync_with_ping ()
102
+ last_tx_received = peer1 .last_message ["tx" ]
103
+
104
+ tx_after_reorg = self .wallet .send_self_transfer (from_node = self .nodes [1 ])
105
+ request_after_reorg = msg_getdata ([CInv (t = MSG_WTX , h = int (tx_after_reorg ["tx" ].getwtxid (), 16 ))])
106
+ assert tx_after_reorg ["txid" ] in self .nodes [1 ].getrawmempool ()
107
+ peer1 .send_and_ping (request_after_reorg )
108
+ with p2p_lock :
109
+ assert_equal (peer1 .last_message ["tx" ], last_tx_received )
110
+
25
111
def run_test (self ):
26
- wallet = MiniWallet (self .nodes [0 ])
112
+ self .wallet = MiniWallet (self .nodes [0 ])
113
+ wallet = self .wallet
27
114
28
115
# Start with a 200 block chain
29
116
assert_equal (self .nodes [0 ].getblockcount (), 200 )
@@ -103,6 +190,8 @@ def run_test(self):
103
190
assert_equal (set (self .nodes [0 ].getrawmempool ()), set ())
104
191
self .sync_all ()
105
192
193
+ self .test_reorg_relay ()
194
+
106
195
107
196
if __name__ == '__main__' :
108
197
MempoolCoinbaseTest ().main ()
0 commit comments