{
 "number": 34907,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34907",
 "title": "wallet, test: make wallet_fast_rescan robust",
 "author": "rkrux",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-03-24T10:40:27Z",
 "updated_at": "2026-08-15T20:35:52Z",
 "age_days": 177,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "f217da5c59f43b43abca96de66b95c24ae99eed7",
 "head_ref": "rescan-fast",
 "head_repo": "rkrux/bitcoin",
 "head_history": [
  {
   "t": "2026-03-24T10:44:10Z",
   "sha": "735e9f610b12e0126cb049fa3699ab6937b0bacc"
  },
  {
   "t": "2026-04-24T14:39:37Z",
   "sha": "c03de01138650114fbd39800f5c9e0bfd193141c"
  },
  {
   "t": "2026-04-24T14:48:41Z",
   "sha": "292a949863b4c13e1f88ac6f1b2622eaac7c4e70"
  },
  {
   "t": "2026-05-22T10:04:48Z",
   "sha": "bf0f5d9941fb251692af58c411362c5b0e4e8396"
  },
  {
   "t": "2026-06-04T14:44:58Z",
   "sha": "f217da5c59f43b43abca96de66b95c24ae99eed7"
  }
 ],
 "additions": 56,
 "deletions": 33,
 "changed_files": 1,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "blocked",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "Bicaru20",
      "url": "https://github.com/bitcoin/bitcoin/pull/34907#issuecomment-4653672220"
     }
    ],
    "stale_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/34907#pullrequestreview-4374545915"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "w0xlt": {
   "kind": "ack",
   "hash": "bf0f5d9941fb251692af58c411362c5b0e4e8396",
   "t": "2026-05-27T17:22:54Z",
   "stale": true
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Bicaru20",
   "sedited",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "blocked",
  "last_author_activity": "2026-06-05T11:04:58Z",
  "last_reviewer_activity": "2026-08-10T20:20:17Z",
  "last_reviewer": "sedited",
  "author_silent_days": 104,
  "waiting_on_author_days": 37,
  "days_since_update": 32
 },
 "refs": {
  "mentioned": [
   34667
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 34667,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-03-24",
    "title": "test: ensure FastWalletRescanFilter is correctly updated during scanning"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "test/functional/wallet_fast_rescan.py"
 ],
 "body": "This patch addresses my own review comments from the review of #34667 and adds some more changes that I find helpful. It was observed in the review of the earlier PR that there is a tendency for the test code to cause the topups not being done that defeats the purpose of the test. Combine that with the earlier issue where the block filter was not being updated ever since this test was written, I think it's helpful that some robustness is added in the test.\n\nExact details are in the commit message.",
 "commits": [
  {
   "sha": "f217da5c59f43b43abca96de66b95c24ae99eed7",
   "date": "2026-06-04T14:43:58Z",
   "message": "wallet, test: wallet_fast_rescan follow-ups\n\nThis patch addresses my own review comments from the review of PR 34667 and\nadds some more changes that I find helpful:\n\n- It was observed in the review of the earlier PR that there is a tendency for\nthe test code to cause the topups not being done that defeats the purpose of\nthe test. Combine that with the earlier issue where the block filter was not\nbeing updated ever since this test was written, I think it's helpful that some\nrobustness is added in the test via asserting the descriptor end ranges that\nthe topup is being indeed done.\n- I think it's also helpful to highlight when the top is being done exactly,\nwhich is when the wallet detects the concerned transaction for the first time,\nthat is when the transaction is first broadcast.\n- The `w` wallet is also unloaded post transactions generation and before\nthe various restore and import cases that follow.\n- The transaction sending to the non-ranged descriptor derives its address kind\nof out of band because it doesn't use the wallet that already has imported it.\nThis patch addresses it.\n- One extra block is also generated containing only non-wallet transaction.\n- There are some other minor changes done that made sense after all the above\nchanges but I can remove them if others find them not helpful."
  }
 ],
 "timeline": [
  {
   "t": "2026-03-24T10:44:10Z",
   "kind": "force_push",
   "who": "rkrux",
   "commit": "735e9f610b12e0126cb049fa3699ab6937b0bacc"
  },
  {
   "t": "2026-03-25T18:34:22Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "735e9f610b12e0126cb049fa3699ab6937b0bacc",
   "text": "The test already records the txids it creates, but at the end it only checks counts and that fast/slow agree with each other.\n\nIt would be stronger to flatten the generated wallet-related txids and assert each rescan result equals that exact expected set.\n\n  diff\n\n```diff\ndiff --git a/test/functional/wallet_fast_rescan.py b/test/functional/wallet_fast_rescan.py\nindex b777ed9ced..683443d8a7 100755\n--- a/test/functional/wallet_fast_rescan.py\n+++ b/test/functional/wallet_fast_rescan.py\n@@ -85,7 +85,7 @@ class WalletFastRescanTest(BitcoinTestFramework):\n         txs_per_blocks.append([tx_result['txid'] for tx_result in send_result])\n\n         self.log.info(\"Start generating blocks one by one with associated txs\")\n-        total_wallet_txs = 0\n+        expected_wallet_txids = []\n         fast_rescan_messages = []\n         for i, block_txs in enumerate(txs_per_blocks):\n             self.log.info(f\"Block {i+1}/{NUM_BLOCKS}\")\n@@ -94,7 +94,7 @@ class WalletFastRescanTest(BitcoinTestFramework):\n             # transactions - this block may or may not be fetched due to block filters false positives.\n             if i < NUM_BLOCKS-1:\n                 fast_rescan_messages.append(f\"Fast rescan: inspect block {self.nodes[0].getblockcount()} [{generated_block['hash']}] (filter matched)\")\n-                total_wallet_txs += len(block_txs)\n+                expected_wallet_txids.extend(block_txs)\n\n         self.log.info(\"Import wallet backup with block filter index\")\n         with node.assert_debug_log(['fast variant using block filters', *fast_rescan_messages]):\n@@ -122,12 +122,11 @@ class WalletFastRescanTest(BitcoinTestFramework):\n         txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive')\n\n         self.log.info(\"Verify that all rescans found the same txs in slow and fast variants\")\n-        assert_equal(len(txids_slow), total_wallet_txs)\n-        assert_equal(len(txids_fast), total_wallet_txs)\n-        assert_equal(len(txids_slow_nonactive), total_wallet_txs)\n-        assert_equal(len(txids_fast_nonactive), total_wallet_txs)\n-        assert_equal(sorted(txids_slow), sorted(txids_fast))\n-        assert_equal(sorted(txids_slow_nonactive), sorted(txids_fast_nonactive))\n+        expected_wallet_txids.sort()\n+        assert_equal(sorted(txids_slow), expected_wallet_txids)\n+        assert_equal(sorted(txids_fast), expected_wallet_txids)\n+        assert_equal(sorted(txids_slow_nonactive), expected_wallet_txids)\n+        assert_equal(sorted(txids_fast_nonactive), expected_wallet_txids)\n\n if __name__ == '__main__':\n```"
  },
  {
   "t": "2026-03-25T19:59:22Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "735e9f610b12e0126cb049fa3699ab6937b0bacc",
   "text": "The test doesn't need to depend on a specific debug-log string.\nYou can assert the same behavior more directly via `listdescriptors`: get the descriptor's current `end_range`, send to that address, then verify the range increases.\n\n  diff\n\n```diff\ndiff --git a/test/functional/wallet_fast_rescan.py b/test/functional/wallet_fast_rescan.py\nindex b777ed9ced..f3d88c408a 100755\n--- a/test/functional/wallet_fast_rescan.py\n+++ b/test/functional/wallet_fast_rescan.py\n@@ -48,6 +48,12 @@ class WalletFastRescanTest(BitcoinTestFramework):\n         # backup the wallet here so that the restorations later are unaware of the below transactions\n         w.backupwallet(WALLET_BACKUP_FILENAME)\n\n+        def get_descriptor_end_range(desc_str):\n+            for descriptor in w.listdescriptors()['descriptors']:\n+                if descriptor['desc'] == desc_str:\n+                    return descriptor['range'][1]\n+            raise AssertionError(f\"Descriptor not found: {desc_str}\")\n+\n         txs_per_blocks = []\n\n         self.log.info(\"Create and broadcast tx sending to non-ranged descriptor\")\n@@ -70,8 +76,8 @@ class WalletFastRescanTest(BitcoinTestFramework):\n                 spk = address_to_scriptpubkey(addr)\n\n                 self.log.debug(f\"-> range [{start_range},{end_range}], last address {addr}\")\n-                with node.assert_debug_log([f'Detected a used keypool item at index {end_range}, mark all keypool items up to this item as used']):\n-                    send_result = funding_wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000)\n+                send_result = funding_wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000)\n+                self.wait_until(lambda desc_str=desc_str, end_range=end_range: get_descriptor_end_range(desc_str) > end_range)\n                 block_txs.append(send_result[\"txid\"])\n\n             txs_per_blocks.append(block_txs)\n```"
  },
  {
   "t": "2026-03-25T20:07:35Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_fast_rescan.py",
   "commit": "735e9f610b12e0126cb049fa3699ab6937b0bacc",
   "in_reply_to": null,
   "text": "This part seems more complex than the behavior being asserted. If the goal is just to include a block with wallet-unrelated transactions, would a single unrelated tx be enough ? That would make the intent clearer.\n\n```suggestion\n        self.log.info(\"Create and broadcast tx unrelated to the wallet\")\n        send_result = funding_wallet.send_self_transfer(from_node=node)\n        txs_per_blocks.append([send_result['txid']])\n```"
  },
  {
   "t": "2026-03-25T20:08:20Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_fast_rescan.py",
   "commit": "735e9f610b12e0126cb049fa3699ab6937b0bacc",
   "in_reply_to": null,
   "text": "nit:\n```suggestion\n        txids_per_block = []\n```"
  },
  {
   "t": "2026-03-27T15:22:13Z",
   "kind": "review_comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_fast_rescan.py",
   "commit": "735e9f610b12e0126cb049fa3699ab6937b0bacc",
   "in_reply_to": 2990758911,
   "text": "This was intentional on my part but I see it can come across as confusing and is also not necessary. I will remove the chain."
  },
  {
   "t": "2026-03-27T15:22:55Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "text": "Good suggestions, I will add them."
  },
  {
   "t": "2026-04-24T14:39:37Z",
   "kind": "force_push",
   "who": "rkrux",
   "commit": "c03de01138650114fbd39800f5c9e0bfd193141c"
  },
  {
   "t": "2026-04-24T14:40:30Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "text": "Added all the suggestions from w0xlt's review in latest push."
  },
  {
   "t": "2026-04-24T14:48:41Z",
   "kind": "force_push",
   "who": "rkrux",
   "commit": "292a949863b4c13e1f88ac6f1b2622eaac7c4e70"
  },
  {
   "t": "2026-04-24T14:49:57Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "text": "I realised that wait_until is not needed for asserting that the top is being done because adding a simple greater than assertion post send_to call is sufficient, so made that change."
  },
  {
   "t": "2026-05-21T20:04:26Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_fast_rescan.py",
   "commit": "bf0f5d9941fb251692af58c411362c5b0e4e8396",
   "in_reply_to": null,
   "text": "nit:\n```suggestion\n        assert_equal(len(txids_per_block), NUM_BLOCKS)\n        self.log.info(\"Start generating blocks one by one with associated txs\")\n```"
  },
  {
   "t": "2026-05-21T20:04:35Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_fast_rescan.py",
   "commit": "292a949863b4c13e1f88ac6f1b2622eaac7c4e70",
   "in_reply_to": null,
   "text": "nit:\n```suggestion\n                fast_rescan_messages.append(f\"Fast rescan: inspect block {node.getblockcount()} [{generated_block['hash']}] (filter matched)\")\n```"
  },
  {
   "t": "2026-05-21T20:04:40Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_fast_rescan.py",
   "commit": "292a949863b4c13e1f88ac6f1b2622eaac7c4e70",
   "in_reply_to": null,
   "text": "nit:\n```suggestion\n        for _ in range(1, NUM_BLOCKS-1):\n```"
  },
  {
   "t": "2026-05-21T20:04:49Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/wallet_fast_rescan.py",
   "commit": "292a949863b4c13e1f88ac6f1b2622eaac7c4e70",
   "in_reply_to": null,
   "text": "nit:\n```diff\n-   top-ups correctly and finds the same transactions than the slow variant.\"\"\"\n+   top-ups correctly and finds the same transactions than as the slow variant.\"\"\"\n```"
  },
  {
   "t": "2026-05-21T20:05:54Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "292a949863b4c13e1f88ac6f1b2622eaac7c4e70",
   "text": "ACK 292a949863b4c13e1f88ac6f1b2622eaac7c4e70 with non-blocking suggestions."
  },
  {
   "t": "2026-05-22T10:04:48Z",
   "kind": "force_push",
   "who": "rkrux",
   "commit": "bf0f5d9941fb251692af58c411362c5b0e4e8396"
  },
  {
   "t": "2026-05-22T10:05:51Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "text": "Force pushed to use suggestions from @w0xlt's review: https://github.com/bitcoin/bitcoin/pull/34907#pullrequestreview-4340246369."
  },
  {
   "t": "2026-05-27T17:22:54Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "bf0f5d9941fb251692af58c411362c5b0e4e8396",
   "text": "reACK bf0f5d9941fb251692af58c411362c5b0e4e8396"
  },
  {
   "t": "2026-06-04T13:50:07Z",
   "kind": "comment",
   "who": "Bicaru20",
   "assoc": "CONTRIBUTOR",
   "text": "I'm fine as leaving as it is now. But wouldn\u2019t it be clearer to generate the blocks when we broadcast the transaction? I understand your point about wanting to separate the transaction broadcast and block generation flows to highlight when the top is being done, but I think it makes the test a little bit more confusing. I believe that if the blocks are generated in the same loop where the topups are being done, it can still be clear to follow both flows and will make the test clearer and more compact.\nI'll leave the diff of how I would do it.\n\ndiff\n\n```diff\ndiff --git a/test/functional/wallet_fast_rescan.py b/test/functional/wallet_fast_rescan.py\nindex ce65855779..096a1b80ef 100755\n--- a/test/functional/wallet_fast_rescan.py\n+++ b/test/functional/wallet_fast_rescan.py\n@@ -54,14 +54,18 @@ class WalletFastRescanTest(BitcoinTestFramework):\n                     return descriptor['range'][1]\n             raise AssertionError(f\"Descriptor not found: {desc_str}\")\n\n-        txids_per_block = []\n+        expected_wallet_txids = []\n+        fast_rescan_messages = []\n\n         self.log.info(\"Create and broadcast tx sending to non-ranged descriptor\")\n         addr = w.deriveaddresses(non_ranged_descs[0]['desc'])[0]\n         spk = address_to_scriptpubkey(addr)\n         self.log.debug(f\"-> fixed non-range descriptor address {addr}\")\n         send_result = funding_wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000)\n-        txids_per_block.append([send_result[\"txid\"]])\n+\n+        generated_block = self.generateblock(node, output=\"raw(42)\", transactions=[send_result[\"txid\"]])\n+        fast_rescan_messages.append(f\"Fast rescan: inspect block {node.getblockcount()} [{generated_block['hash']}] (filter matched)\")\n+        expected_wallet_txids.extend([send_result[\"txid\"]])\n\n         self.log.info(\"Create and broadcast txs sending to end range address of each descriptor, triggering top-ups\")\n         for _ in range(1, NUM_BLOCKS-1):\n@@ -80,7 +84,9 @@ class WalletFastRescanTest(BitcoinTestFramework):\n                 assert_greater_than(get_descriptor_end_range(desc_str), end_range)\n                 block_tx_ids.append(send_result[\"txid\"])\n\n-            txids_per_block.append(block_tx_ids)\n+            generated_block = self.generateblock(node, output=\"raw(42)\", transactions=block_tx_ids)\n+            fast_rescan_messages.append(f\"Fast rescan: inspect block {node.getblockcount()} [{generated_block['hash']}] (filter matched)\")\n+            expected_wallet_txids.extend(block_tx_ids)\n\n         # wallet w (topup_test) is not required to be loaded from here on, unload so that it\n         # doesn't needlessly process block generation notifications in the background\n@@ -88,20 +94,6 @@ class WalletFastRescanTest(BitcoinTestFramework):\n\n         self.log.info(\"Create and broadcast tx unrelated to the wallet\")\n         send_result = funding_wallet.send_self_transfer(from_node=node)\n-        txids_per_block.append([send_result['txid']])\n-        assert_equal(len(txids_per_block), NUM_BLOCKS)\n-\n-        self.log.info(\"Start generating blocks one by one with associated txs\")\n-        expected_wallet_txids = []\n-        fast_rescan_messages = []\n-        for i, block_tx_ids in enumerate(txids_per_block):\n-            self.log.info(f\"Block {i+1}/{NUM_BLOCKS}\")\n-            generated_block = self.generateblock(node, output=\"raw(42)\", transactions=block_tx_ids)\n-            # Not asserting the fast rescan message in the last block because it contains wallet-unrelated\n-            # transactions - this block may or may not be fetched due to block filters false positives.\n-            if i < NUM_BLOCKS-1:\n-                fast_rescan_messages.append(f\"Fast rescan: inspect block {node.getblockcount()} [{generated_block['hash']}] (filter matched)\")\n-                expected_wallet_txids.extend(block_tx_ids)\n\n         self.log.info(\"Import wallet backup with block filter index\")\n         with node.assert_debug_log(['fast variant using block filters', *fast_rescan_messages]):\n```\n\nAs said at the begining I'm fine with merging as is. Seeing other people have already ack maybe is just me who find this flow confusing."
  },
  {
   "t": "2026-06-04T14:44:58Z",
   "kind": "force_push",
   "who": "rkrux",
   "commit": "f217da5c59f43b43abca96de66b95c24ae99eed7"
  },
  {
   "t": "2026-06-04T14:46:26Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nYeah, that makes sense as it can make the test feel less contrived. I had considered it when I had raised the PR but ended up didn't doing it then.\nDone now."
  },
  {
   "t": "2026-06-05T11:04:58Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "text": "The [Windows native, VS](https://github.com/bitcoin/bitcoin/actions/runs/26959178759/job/79544538694?pr=34907#logs) failure is a Github issue."
  },
  {
   "t": "2026-06-08T21:22:57Z",
   "kind": "comment",
   "who": "Bicaru20",
   "assoc": "CONTRIBUTOR",
   "text": "lgtm ACK f217da5c59"
  },
  {
   "t": "2026-08-10T20:20:17Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "text": "@w0xlt do you want to give this another look?"
  }
 ],
 "labels_log": [
  {
   "t": "2026-03-24T10:44:43Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-24T11:29:44Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-24T14:49:39Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-24T15:56:10Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-04T15:36:43Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-05T13:13:41Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-06-04T14:47:05Z",
   "kind": "renamed",
   "who": "rkrux",
   "from": "wallet, test: wallet_fast_rescan follow-ups",
   "to": "wallet, test: make wallet_fast_rescan robust"
  }
 ],
 "text_chars": 12590,
 "text_tokens_estimate": 3147,
 "changed_paths": [
  "test/functional/wallet_fast_rescan.py"
 ],
 "files": [
  {
   "path": "test/functional/wallet_fast_rescan.py",
   "add": 56,
   "del": 33
  }
 ],
 "test_lines": 89,
 "git": {
  "head": "f217da5c59f43b43abca96de66b95c24ae99eed7",
  "head_matches_backup": true,
  "base": "400aa68b4aa8de52c1a8bf543dd195cd1b2b1f32",
  "commits": [
   {
    "sha": "f217da5c59",
    "subject": "wallet, test: wallet_fast_rescan follow-ups",
    "files": 1,
    "add": 56,
    "del": 33
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "282b73c89d998e3e",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}