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