feature_sync_coins_tip_after_chain_sync.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2024- The Limenka developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """
6 Test SyncCoinsTipAfterChainSync logic
7 """
8
9
10 from test_framework.blocktools import create_block, create_coinbase
11 from test_framework.messages import (
12 MSG_BLOCK,
13 MSG_TYPE_MASK,
14 )
15 from test_framework.p2p import (
16 CBlockHeader,
17 msg_block,
18 msg_headers,
19 P2PDataStore,
20 )
21 from test_framework.test_framework import LimenkaTestFramework
22 from test_framework.util import (
23 assert_equal,
24 )
25
26
27 class P2PBlockDelay(P2PDataStore):
28 def __init__(self, delay_block):
29 self.delay_block = delay_block
30 super().__init__()
31
32 def on_getdata(self, message):
33 for inv in message.inv:
34 self.getdata_requests.append(inv.hash)
35 if (inv.type & MSG_TYPE_MASK) == MSG_BLOCK:
36 if inv.hash != self.delay_block:
37 self.send_message(msg_block(self.block_store[inv.hash]))
38
39 def on_getheaders(self, message):
40 pass
41
42 def send_delayed(self):
43 self.send_message(msg_block(self.block_store[self.delay_block]))
44
45
46 SYNC_CHECK_INTERVAL = 30
47
48
49 class SyncCoinsTipAfterChainSyncTest(LimenkaTestFramework):
50 def set_test_params(self):
51 self.setup_clean_chain = True
52 self.num_nodes = 1
53 # Set maxtipage to 1 to get us out of IBD after 1 block past our mocktime
54 self.extra_args = [["-maxtipage=1"]]
55
56 def run_test(self):
57 NUM_BLOCKS = 3
58 node = self.nodes[0]
59 tip = int(node.getbestblockhash(), 16)
60 blocks = []
61 height = 1
62 block_time = node.getblock(node.getbestblockhash())["time"] + 1
63 # Set mock time to 2 past block time, so second block will exit IBD
64 node.setmocktime(block_time + 2)
65
66 # Prepare blocks without sending them to the node
67 block_dict = {}
68 for _ in range(NUM_BLOCKS):
69 blocks.append(create_block(tip, create_coinbase(height), block_time))
70 blocks[-1].solve()
71 tip = blocks[-1].sha256
72 block_time += 1
73 height += 1
74 block_dict[blocks[-1].sha256] = blocks[-1]
75 delay_block = blocks[-1].sha256
76
77 # Create peer which will not automatically send last block
78 peer = node.add_outbound_p2p_connection(
79 P2PBlockDelay(delay_block),
80 p2p_idx=1,
81 connection_type="outbound-full-relay",
82 )
83 peer.block_store = block_dict
84
85 self.log.info(
86 "Send headers message for first block, verify it won't sync because node is still in IBD"
87 )
88 headers_message = msg_headers()
89 headers_message.headers = [CBlockHeader(blocks[0])]
90 peer.send_message(headers_message)
91 peer.sync_with_ping()
92 assert_equal(node.getblockchaininfo()["initialblockdownload"], True)
93 with node.assert_debug_log(
94 ["Node is still in IBD, rescheduling post-IBD chainstate disk sync..."]
95 ):
96 node.mockscheduler(SYNC_CHECK_INTERVAL)
97
98 self.log.info(
99 "Send headers message for second block, verify it won't sync because node height has changed"
100 )
101 headers_message.headers = [CBlockHeader(blocks[1])]
102 peer.send_message(headers_message)
103 peer.sync_with_ping()
104 assert_equal(node.getblockchaininfo()["initialblockdownload"], False)
105 with node.assert_debug_log(
106 [
107 "Chain height updated since last check, rescheduling post-IBD chainstate disk sync..."
108 ]
109 ):
110 node.mockscheduler(SYNC_CHECK_INTERVAL)
111
112 self.log.info(
113 "Send headers message for last block, verify it won't sync because node is still downloading the block"
114 )
115 headers_message.headers = [CBlockHeader(blocks[2])]
116 peer.send_message(headers_message)
117 peer.sync_with_ping()
118 with node.assert_debug_log(
119 [
120 "Still downloading blocks from peers, rescheduling post-IBD chainstate disk sync..."
121 ]
122 ):
123 node.mockscheduler(SYNC_CHECK_INTERVAL)
124
125 self.log.info(
126 "Send last block, verify it won't sync because node height has changed"
127 )
128 peer.send_delayed()
129 peer.sync_with_ping()
130 with node.assert_debug_log(
131 [
132 "Chain height updated since last check, rescheduling post-IBD chainstate disk sync..."
133 ]
134 ):
135 node.mockscheduler(SYNC_CHECK_INTERVAL)
136
137 self.log.info("Verify node syncs chainstate to disk on next scheduler update")
138 with node.assert_debug_log(
139 ["Finished syncing to tip, syncing chainstate to disk"]
140 ):
141 node.mockscheduler(SYNC_CHECK_INTERVAL)
142
143
144 if __name__ == "__main__":
145 SyncCoinsTipAfterChainSyncTest(__file__).main()
146