{
 "number": 36236,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36236",
 "title": "wallet, rpc: add verify_balance option to importdescriptors",
 "author": "musaHaruna",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-09-13T07:23:54Z",
 "updated_at": "2026-09-17T02:27:56Z",
 "age_days": 4,
 "draft": false,
 "labels": [
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "3eccd3463c565995bee869a041ea40d197d27fba",
 "head_ref": "wallet/importdescriptors-utxo-check",
 "head_repo": "musaHaruna/bitcoin",
 "head_history": [
  {
   "t": "2026-09-13T08:35:10Z",
   "sha": "904ed1686c944aa4a46c8782b6ee11e009393ae3"
  },
  {
   "t": "2026-09-15T12:40:47Z",
   "sha": "3eccd3463c565995bee869a041ea40d197d27fba"
  }
 ],
 "additions": 584,
 "deletions": 47,
 "changed_files": 9,
 "commit_count": 4,
 "size_bucket": "L",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": [
    {
     "number": 35989,
     "title": "wallet: fix crash on importdescriptors with a range ending at 2^31-1",
     "author": "shuv-amp"
    },
    {
     "number": 35377,
     "title": "wallet: Allow importing of descriptors without private keys when the wallet has the private keys",
     "author": "achow101"
    },
    {
     "number": 34861,
     "title": "wallet: Add importdescriptors interface",
     "author": "polespinasa"
    },
    {
     "number": 31668,
     "title": "Added rescan option for import descriptors",
     "author": "saikiran57"
    }
   ]
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": []
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-09-15T12:40:47Z",
  "last_reviewer_activity": null,
  "last_reviewer": null,
  "author_silent_days": 2,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   28898
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 28898,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "wallet RPC to double-check the calculated balance"
   }
  ],
  "conflicts": [
   35989,
   35377,
   34861,
   31668
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "Another Attempt to fix [#28898](https://github.com/bitcoin/bitcoin/issues/28898)\n\nWhen importing descriptors, users may provide a timestamp that is too recent. This causes the rescan to miss older payments, leaving the wallet with an incomplete balance.\n\nThis PR adds an optional `verify_balance` argument to importdescriptors, which is false by default.\n\nWhen enabled, it scans the chainstate UTXO set once for outputs belonging to the wallet\u2019s known scripts. If it finds outputs missing from the wallet, it uses the earliest missing output\u2019s block height to extend the rescan backwards where needed, then scans forward. An earlier requested timestamp is still respected so older transaction history is scanned as well.\n\nAfter rescanning, it compares the wallet\u2019s confirmed mature UTXO outpoints with those collected from the initial chainstate scan. The response includes an info object showing the verification result, UTXO counts and scan details.\n\nFor example:\n```bash\nbitcoin-cli -rpcwallet=restore importdescriptors \\\n  '[{\"desc\":\"<descriptor>\",\"timestamp\":\"now\"}]' true\n```\n\nExample response when verification recovers payments:\n\n```json\n[\n  {\n    \"success\": true,\n    \"info\": {\n      \"status\": \"matched after recovery\",\n      \"utxo_check\": true,\n      \"scanned_blocks\": 22,\n      \"wallet_utxos\": 2,\n      \"chain_utxos\": 2,\n      \"scan_start_height\": 101,\n      \"snapshot_block\": \"0000000000000001234..01f\",\n      \"snapshot_height\": 122,\n      \"recovery_start_height\": 101\n    }\n  }\n]\n```\n\nIf the sets still differ, verification reports `\"utxo_check\": false` with an error explaining the mismatch. For example, the info object may contain:\n\nFailure Response\n\n```json\n{\n  \"status\": \"unmatched\",\n  \"utxo_check\": false,\n  \"scanned_blocks\": 22,\n  \"wallet_utxos\": 2,\n  \"chain_utxos\": 1,\n  \"scan_start_height\": 101,\n  \"snapshot_block\": \"00000000000000001234...01f\",\n  \"snapshot_height\": 122,\n  \"recovery_start_height\": 101\n}\n```\n\nVerification failures leave the descriptors imported and keep any transactions already discovered. Required blocks being unavailable is reported explicitly. No automatic retry or fallback rescan is performed.\n\nA match verifies the compared UTXO sets; it does not guarantee complete transaction history. Older spent transactions still require an appropriate timestamp or a full rescan. Changes to relevant UTXOs during verification can also cause a mismatch because the final wallet check is compared with the initial chainstate scan.\n\nFunctional tests cover recovery with recent timestamps, preserving older requested history, mismatches, mempool transactions, invalid requests and unavailable blocks.",
 "commits": [
  {
   "sha": "96c1ea40cae0469cf1b8f21170c75dd3f78e9e2c",
   "date": "2026-09-15T12:25:26Z",
   "message": "node: add chainstate UTXO scan by output script\n\nAdd an interface function to scan the chainstate for coins matching\nthe requested output scripts, fill the output map with matching\ncoins, and set the best block of the scanned snapshot."
  },
  {
   "sha": "e46d1ba1d560e76a8cf81542c1ca519df9837f30",
   "date": "2026-09-15T12:25:26Z",
   "message": "wallet: add UTXO scan for known wallet scripts\n\nCollect unique scripts from the wallet's script managers and scan\nchainstate for matching unspent outputs. Filter out coinbase outputs\nthat are immature at the scanned block height.\n\nReturn the matching coins, searched scripts, and the scan's block hash\nand height so callers can identify missing outputs and report which\nchainstate was scanned."
  },
  {
   "sha": "cbfd1c15d8b678d930fe34a3d5310e3fb39ce68a",
   "date": "2026-09-15T12:40:18Z",
   "message": "wallet, rpc: add verify_balance option to importdescriptors\n\nAdd an optional verify_balance argument to check the wallet's confirmed\nmature UTXOs against a chainstate scan for known wallet scripts.\n\nIf the wallet is missing outputs, extend the rescan to the earliest\nmissing output when it is before the requested scan start. Keep the\nrequested timestamp range so older transaction history is still scanned.\n\nReturn the comparison result, UTXO counts and scan details. Verification\nfailures leave descriptors and any discovered transactions in the wallet.\nThe option defaults to false."
  },
  {
   "sha": "3eccd3463c565995bee869a041ea40d197d27fba",
   "date": "2026-09-15T12:40:25Z",
   "message": "test: cover importdescriptors UTXO verification\n\nCover missing UTXO recovery, timestamp handling, mismatch reporting and mempool transactions.\n\nCheck that pruned blocks fail verification without removing the\nimported descriptor."
  }
 ],
 "timeline": [
  {
   "t": "2026-09-13T08:35:10Z",
   "kind": "force_push",
   "who": "musaHaruna",
   "commit": "904ed1686c944aa4a46c8782b6ee11e009393ae3"
  },
  {
   "t": "2026-09-15T12:40:47Z",
   "kind": "force_push",
   "who": "musaHaruna",
   "commit": "3eccd3463c565995bee869a041ea40d197d27fba"
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-13T08:26:48Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-13T09:36:50Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-14T13:10:36Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T13:35:32Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-17T02:27:56Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 4139,
 "text_tokens_estimate": 1034,
 "changed_paths": [
  "src/interfaces/chain.h",
  "src/node/coin.cpp",
  "src/node/coin.h",
  "src/node/interfaces.cpp",
  "src/rpc/client.cpp",
  "src/wallet/receive.cpp",
  "src/wallet/receive.h",
  "src/wallet/rpc/backup.cpp",
  "test/functional/wallet_importdescriptors.py"
 ],
 "files": [
  {
   "path": "src/interfaces/chain.h",
   "add": 7,
   "del": 0
  },
  {
   "path": "src/node/coin.cpp",
   "add": 31,
   "del": 0
  },
  {
   "path": "src/node/coin.h",
   "add": 14,
   "del": 0
  },
  {
   "path": "src/node/interfaces.cpp",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/rpc/client.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/wallet/receive.cpp",
   "add": 35,
   "del": 0
  },
  {
   "path": "src/wallet/receive.h",
   "add": 11,
   "del": 0
  },
  {
   "path": "src/wallet/rpc/backup.cpp",
   "add": 231,
   "del": 47
  },
  {
   "path": "test/functional/wallet_importdescriptors.py",
   "add": 250,
   "del": 0
  }
 ],
 "test_lines": 250,
 "git": {
  "head": "3eccd3463c565995bee869a041ea40d197d27fba",
  "head_matches_backup": true,
  "base": "dadca55f60ad1e756bd9ad88351729f0e6af2181",
  "commits": [
   {
    "sha": "96c1ea40ca",
    "subject": "node: add chainstate UTXO scan by output script",
    "files": 4,
    "add": 56,
    "del": 0
   },
   {
    "sha": "e46d1ba1d5",
    "subject": "wallet: add UTXO scan for known wallet scripts",
    "files": 2,
    "add": 46,
    "del": 0
   },
   {
    "sha": "cbfd1c15d8",
    "subject": "wallet, rpc: add verify_balance option to importdescriptors",
    "files": 2,
    "add": 232,
    "del": 47
   },
   {
    "sha": "3eccd3463c",
    "subject": "test: cover importdescriptors UTXO verification",
    "files": 1,
    "add": 250,
    "del": 0
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "bf3000e416a60d78",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}