wallet_reindex.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2023-present The Limenka developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or https://www.opensource.org/licenses/mit-license.php.
   5  
   6  """Test wallet-reindex interaction"""
   7  
   8  import time
   9  
  10  from test_framework.blocktools import COINBASE_MATURITY
  11  from test_framework.descriptors import descsum_create
  12  from test_framework.test_framework import LimenkaTestFramework
  13  from test_framework.util import (
  14      assert_equal,
  15  )
  16  BLOCK_TIME = 60 * 10
  17  
  18  class WalletReindexTest(LimenkaTestFramework):
  19      def add_options(self, parser):
  20          self.add_wallet_options(parser)
  21  
  22      def set_test_params(self):
  23          self.num_nodes = 1
  24          self.setup_clean_chain = True
  25  
  26      def skip_test_if_missing_module(self):
  27          self.skip_if_no_wallet()
  28  
  29      def advance_time(self, node, secs):
  30          self.node_time += secs
  31          node.setmocktime(self.node_time)
  32  
  33      # Verify the wallet updates the birth time accordingly when it detects a transaction
  34      # with a time older than the oldest descriptor timestamp.
  35      # This could happen when the user blindly imports a descriptor with 'timestamp=now'.
  36      def birthtime_test(self, node, miner_wallet):
  37          self.log.info("Test birth time update during tx scanning")
  38          # Fund address to test
  39          wallet_addr = miner_wallet.getnewaddress()
  40          tx_id = miner_wallet.sendtoaddress(wallet_addr, 2)
  41  
  42          # Generate 50 blocks, one every 10 min to surpass the 2 hours rescan window the wallet has
  43          for _ in range(50):
  44              self.generate(node, 1)
  45              self.advance_time(node, BLOCK_TIME)
  46  
  47          # Now create a new wallet, and import the descriptor
  48          node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True)
  49          wallet_watch_only = node.get_wallet_rpc('watch_only')
  50          # Blank wallets don't have a birth time
  51          assert 'birthtime' not in wallet_watch_only.getwalletinfo()
  52  
  53          # For a descriptors wallet: Import address with timestamp=now.
  54          # For legacy wallet: There is no way of importing a script/address with a custom time. The wallet always imports it with birthtime=1.
  55          # In both cases, disable rescan to not detect the transaction.
  56          if self.options.descriptors:
  57              import_res = wallet_watch_only.importdescriptors([{
  58                  'desc': descsum_create('addr(' + wallet_addr + ')'),
  59                  'timestamp': 'now',
  60              }])
  61              assert len(import_res) == 1
  62              assert import_res[0]['success']
  63          else:
  64              wallet_watch_only.importaddress(wallet_addr, rescan=False)
  65          assert_equal(len(wallet_watch_only.listtransactions()), 0)
  66  
  67          # Depending on the wallet type, the birth time changes.
  68          wallet_birthtime = wallet_watch_only.getwalletinfo()['birthtime']
  69          if self.options.descriptors:
  70              # As blocks were generated every 10 min, the chain MTP timestamp is node_time - 60 min.
  71              assert_equal(self.node_time - BLOCK_TIME * 6, wallet_birthtime)
  72          else:
  73              # No way of importing scripts/addresses with a custom time on a legacy wallet.
  74              # It's always set to the beginning of time.
  75              assert_equal(wallet_birthtime, 1)
  76  
  77          # Rescan the wallet to detect the missing transaction
  78          wallet_watch_only.rescanblockchain()
  79          assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50)
  80          assert_equal(wallet_watch_only.getbalances()['mine' if self.options.descriptors else 'watchonly']['trusted'], 2)
  81  
  82          # Reindex and wait for it to finish
  83          with node.assert_debug_log(expected_msgs=["initload thread exit"]):
  84              self.restart_node(0, extra_args=['-reindex=1', f'-mocktime={self.node_time}'])
  85          node.syncwithvalidationinterfacequeue()
  86  
  87          # Verify the transaction is still 'confirmed' after reindex
  88          wallet_watch_only = node.get_wallet_rpc('watch_only')
  89          tx_info = wallet_watch_only.gettransaction(tx_id)
  90          assert_equal(tx_info['confirmations'], 50)
  91  
  92          # Depending on the wallet type, the birth time changes.
  93          if self.options.descriptors:
  94              # For descriptors, verify the wallet updated the birth time to the transaction time
  95              assert_equal(tx_info['time'], wallet_watch_only.getwalletinfo()['birthtime'])
  96          else:
  97              # For legacy, as the birth time was set to the beginning of time, verify it did not change
  98              assert_equal(wallet_birthtime, 1)
  99  
 100          wallet_watch_only.unloadwallet()
 101  
 102      def run_test(self):
 103          node = self.nodes[0]
 104          self.node_time = int(time.time())
 105          node.setmocktime(self.node_time)
 106  
 107          # Fund miner
 108          node.createwallet(wallet_name='miner', load_on_startup=True)
 109          miner_wallet = node.get_wallet_rpc('miner')
 110          self.generatetoaddress(node, COINBASE_MATURITY + 10, miner_wallet.getnewaddress())
 111  
 112          # Tests
 113          self.birthtime_test(node, miner_wallet)
 114  
 115  
 116  if __name__ == '__main__':
 117      WalletReindexTest(__file__).main()
 118