{
 "number": 27865,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/27865",
 "title": "wallet: Track no-longer-spendable TXOs separately",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2023-06-12T18:40:10Z",
 "updated_at": "2026-09-15T04:00:36Z",
 "age_days": 1192,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
 "head_ref": "wallet-unspent-txos",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2023-06-28T17:53:06Z",
   "sha": "7a50755e3329316f85f612ad2ffec3c0fc903dc6"
  },
  {
   "t": "2023-06-28T18:30:38Z",
   "sha": "8c4b04a75508de0d30aae96e7363a97f6b2ac35f"
  },
  {
   "t": "2023-06-28T22:26:40Z",
   "sha": "9730ec037c5994bf019cfac0a65cae4199a86933"
  },
  {
   "t": "2023-09-07T18:57:18Z",
   "sha": "d9c4dcfe68c547d23f7687e5f0e9db363aa3b460"
  },
  {
   "t": "2023-09-14T22:05:15Z",
   "sha": "75b17036576ae19541326aad36a5648a563ae048"
  },
  {
   "t": "2023-09-15T18:50:55Z",
   "sha": "805f12f656ad9ec549bc0e9f6e46e258412c6450"
  },
  {
   "t": "2023-09-15T21:48:04Z",
   "sha": "7c4edf2b59a52aab723067cfa79be619b01f5de5"
  },
  {
   "t": "2023-09-27T16:51:19Z",
   "sha": "022cea9c0c873bca2dd3a28c8783fe7dbc9eea28"
  },
  {
   "t": "2023-10-16T20:55:19Z",
   "sha": "a4d189f6e33285ea0e0a536f01c4b1a8e576696f"
  },
  {
   "t": "2023-10-24T02:17:52Z",
   "sha": "e0a9c23fae4246a26f48a077c56119d2cdcde781"
  },
  {
   "t": "2023-10-24T15:47:22Z",
   "sha": "6cbe82a8f63ec34f027a8747ab43f0b7440c5f71"
  },
  {
   "t": "2023-11-13T22:57:48Z",
   "sha": "84bcdb53adf57de1b41eaf65c4595040634bccd5"
  },
  {
   "t": "2023-11-15T18:41:08Z",
   "sha": "322c97951c068ebdfe4fed61ecc5f71e1aecea64"
  },
  {
   "t": "2023-11-28T16:20:35Z",
   "sha": "dbc5912cc1a730b2b820b09b88ebe256763b15ac"
  },
  {
   "t": "2023-12-08T22:09:05Z",
   "sha": "b9d3d2ef1b5cccc1ee926ea5ffad6b7840674a6f"
  },
  {
   "t": "2023-12-11T22:19:28Z",
   "sha": "a68fee0a4e661df4755faea1d6cefe8108047c64"
  },
  {
   "t": "2023-12-19T17:23:05Z",
   "sha": "9015c770c44b26a05f643b2fc3218c6378ceee5e"
  },
  {
   "t": "2024-02-03T06:54:11Z",
   "sha": "4aae8915196882f2d75da95b35fbdc8cfdb4bcee"
  },
  {
   "t": "2024-02-20T23:46:02Z",
   "sha": "90346c82f87a33f6ec9ea64dbab9a9a97bc119c8"
  },
  {
   "t": "2024-04-01T20:37:29Z",
   "sha": "91205a883db7697f97c7fc094b77d1f961a76ad9"
  },
  {
   "t": "2024-04-25T15:37:06Z",
   "sha": "b677b00f25d834cfd90a1d256de1a68dcf1b898a"
  },
  {
   "t": "2024-06-06T23:49:36Z",
   "sha": "9ca69266c1f51d0d0c71dd42dd279eacf6037c63"
  },
  {
   "t": "2024-08-29T16:34:37Z",
   "sha": "2c9c37a87499a1ea1276d71d8b655837f85e98ac"
  },
  {
   "t": "2024-09-10T20:01:28Z",
   "sha": "a89fc9a90034363f64113c61060177ade3223260"
  },
  {
   "t": "2024-10-24T23:21:48Z",
   "sha": "ef7212857a486048cbdc84fc4d33842be6c6227b"
  },
  {
   "t": "2024-10-28T19:59:58Z",
   "sha": "7ed8abd4f5ffdea3ef28e6404ed984f28c652f90"
  },
  {
   "t": "2024-10-28T20:39:24Z",
   "sha": "9527db6cc06467eae666f17fd8cdd7342ff14b2a"
  },
  {
   "t": "2024-10-28T21:55:41Z",
   "sha": "09fb29b82c7bf704758329fc3b664ed51d02e1d3"
  },
  {
   "t": "2025-04-10T01:19:36Z",
   "sha": "892803e56137170ecaf38cb05c24742139d01dc3"
  },
  {
   "t": "2025-04-25T16:15:05Z",
   "sha": "6fac290996cc43d67615f292210ab279e4203f57"
  },
  {
   "t": "2025-05-07T18:45:11Z",
   "sha": "128268f79a87a0434857d82bc2a60a763629aefc"
  },
  {
   "t": "2025-05-07T19:01:35Z",
   "sha": "87480d4f1bb59a07efde4511336c2d878897d55e"
  },
  {
   "t": "2025-05-15T21:40:45Z",
   "sha": "caf62ee890395f9f20e2da3917c3b49f96209ae5"
  },
  {
   "t": "2025-05-16T01:47:08Z",
   "sha": "d227fc65cdb778cf2907cdc8af5c6719d8130d09"
  },
  {
   "t": "2025-05-16T19:31:38Z",
   "sha": "ab59837c7c910f14c33ded05ff102fefbb398963"
  },
  {
   "t": "2025-05-16T21:37:39Z",
   "sha": "1ec5374bccbb8451222ce83e4b16e613c4fd7b83"
  },
  {
   "t": "2025-05-19T17:34:31Z",
   "sha": "2a31dfae1d0ccc0eb6738c590a96ae75d12db8df"
  },
  {
   "t": "2025-05-20T00:53:17Z",
   "sha": "07df5b573fc35312758caf9ccb7860e60bd1fa8d"
  },
  {
   "t": "2025-05-21T18:24:33Z",
   "sha": "a7f0519948803e9629d7a1ed9a5df2221dbfb931"
  },
  {
   "t": "2025-05-30T21:52:13Z",
   "sha": "9554a4d79acc8bd92b6b22fe413f5cddb372647f"
  },
  {
   "t": "2025-06-02T19:38:01Z",
   "sha": "072e2835c4992b48ecf08db901ce8fd8a6b9217c"
  },
  {
   "t": "2025-06-27T02:17:25Z",
   "sha": "3f20d2e5133f3248a7f4b16b25c47b7d49b38dd0"
  },
  {
   "t": "2025-06-27T19:04:44Z",
   "sha": "6041def924934900caa6738e36785ebafa6382cf"
  },
  {
   "t": "2025-06-27T23:18:20Z",
   "sha": "856f58cfdadb9124d4e50e4231aae2be088f9125"
  },
  {
   "t": "2025-07-01T17:37:57Z",
   "sha": "232c9eae2f02d8147c80fd461f4367166ffaa5ae"
  },
  {
   "t": "2025-07-07T23:14:43Z",
   "sha": "3c56e99057b7c8b480a4fadfc1577181783425c4"
  },
  {
   "t": "2025-08-16T03:10:32Z",
   "sha": "edaf52d848d8f816a1c4fe4f224d1f3971005ab3"
  },
  {
   "t": "2025-08-19T17:30:51Z",
   "sha": "1784af71c589040a07dfc269c7c4d8ba2e68289a"
  },
  {
   "t": "2025-08-19T22:50:37Z",
   "sha": "c05ed69864df10866e5ac47f05f3255b1d985f39"
  },
  {
   "t": "2025-08-20T01:17:01Z",
   "sha": "c41741e72b0245a2d0bb3ad236885796c258a590"
  },
  {
   "t": "2025-08-21T22:09:59Z",
   "sha": "64c2c8aec1029f8d4214b78d8e2594721737c5b3"
  },
  {
   "t": "2025-09-01T22:51:00Z",
   "sha": "78e22fe5b827220ef4ca88d42fca6ab8634e7907"
  },
  {
   "t": "2025-09-02T02:46:38Z",
   "sha": "f94516927d1876496d6f98ccbb35a40b479a979c"
  },
  {
   "t": "2025-09-23T23:33:53Z",
   "sha": "9b0bfea08cac080589e313b0ee0e70d7ec2a63e1"
  },
  {
   "t": "2025-09-23T23:51:03Z",
   "sha": "7b2718f877f776a7c7bea098feee6b19f3e9c4a8"
  },
  {
   "t": "2025-11-10T16:05:59Z",
   "sha": "b5e229a785fa838296647de9e990d2d8940af57a"
  },
  {
   "t": "2026-01-02T21:45:14Z",
   "sha": "3d454eb45e8ff889dec013ae64662012d9766973"
  },
  {
   "t": "2026-01-15T20:56:25Z",
   "sha": "52b677d4550c9d5e32e7e3cb5e9fe76eebcea7b3"
  },
  {
   "t": "2026-01-29T21:36:04Z",
   "sha": "3fb18453cd95e35b0aee299279853e514e8a9695"
  },
  {
   "t": "2026-03-23T21:51:46Z",
   "sha": "412008bcb454b8d02f9e00f7e7a0fbb2aae5f690"
  },
  {
   "t": "2026-04-23T18:08:06Z",
   "sha": "861f01a98ef2a35d48958e380234280f9ba2a1e8"
  },
  {
   "t": "2026-05-15T00:31:02Z",
   "sha": "29a953a948d280628936c8e59ec6244cb697ddad"
  },
  {
   "t": "2026-05-26T22:55:17Z",
   "sha": "61ba701b9e680a84fa9a11c55ca1ff1381c52861"
  },
  {
   "t": "2026-06-06T05:02:46Z",
   "sha": "00a3a5e5bbc59cf8da474d645d3a6004acbbbb57"
  },
  {
   "t": "2026-06-10T20:40:29Z",
   "sha": "e8fc840837aee8724fa62e7d85c60704cbbbf88e"
  },
  {
   "t": "2026-07-01T22:51:22Z",
   "sha": "c2b4d6efa542f8844e00e2615be29ea001f27a2a"
  },
  {
   "t": "2026-07-01T23:11:45Z",
   "sha": "d588e22f3a1f1dffa355cf3d81798eba6d05e050"
  },
  {
   "t": "2026-07-02T23:33:46Z",
   "sha": "33338485a2113469bb9796332694168223071488"
  },
  {
   "t": "2026-07-14T00:33:47Z",
   "sha": "96f4b4d9c4e0e84e95a0258bbdbdbe588536d583"
  },
  {
   "t": "2026-08-05T23:40:10Z",
   "sha": "b52c4849626f8cfdbdaadee62b1c5dc7cab747fc"
  },
  {
   "t": "2026-08-08T03:59:21Z",
   "sha": "905a6ad84eaaea5127eeac0dcf1793e38eddb52a"
  },
  {
   "t": "2026-08-11T19:49:34Z",
   "sha": "e4923fe7b0027040848d7f26bd96fbb6554dcd2d"
  },
  {
   "t": "2026-09-14T23:56:27Z",
   "sha": "4cbc69f50e7ab066ba1292eeb9ca7e25102425ce"
  },
  {
   "t": "2026-09-15T02:41:09Z",
   "sha": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d"
  }
 ],
 "additions": 549,
 "deletions": 162,
 "changed_files": 18,
 "commit_count": 19,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "remyers",
      "url": "https://github.com/bitcoin/bitcoin/pull/27865#issuecomment-1716323392"
     },
     {
      "login": "murchandamus",
      "url": "https://github.com/bitcoin/bitcoin/pull/27865#pullrequestreview-1798375168"
     },
     {
      "login": "jonatack",
      "url": "https://github.com/bitcoin/bitcoin/pull/27865#issuecomment-2500682230"
     },
     {
      "login": "rkrux",
      "url": "https://github.com/bitcoin/bitcoin/pull/27865#issuecomment-3380212696"
     },
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/27865#pullrequestreview-5036289629"
     }
    ],
    "stale_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/27865#pullrequestreview-4008824179"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36167,
     "title": "[RFC] Enable `-Wunused`",
     "author": "fanquake"
    },
    {
     "number": 35998,
     "title": "wallet: Handle or explicitly ignore `WalletBatch` write failures",
     "author": "achow101"
    },
    {
     "number": 35935,
     "title": "wallet: Avoid unnecessary wtxvariant rewrites",
     "author": "achow101"
    },
    {
     "number": 35786,
     "title": "wallet: drop spent parents redundant cache invalidation and notification",
     "author": "furszy"
    },
    {
     "number": 35760,
     "title": "wallet: make corrupted transaction records fail wallet loading instead of forcing a rescan",
     "author": "achow101"
    },
    {
     "number": 35716,
     "title": "wallet: Replace mapWallet and wtxOrdered with a boost::multi_index",
     "author": "achow101"
    },
    {
     "number": 35569,
     "title": "Encapsulation for CTransaction",
     "author": "purpleKarrot"
    },
    {
     "number": 35511,
     "title": "RFC: consensus: Make `CAmount` a class",
     "author": "hodlinator"
    },
    {
     "number": 35294,
     "title": "wallet: Update tx chain state during loading during AttachChain instead of before",
     "author": "achow101"
    },
    {
     "number": 35151,
     "title": "wallet, follow-up: Refactor IsSpent to use HowSpent",
     "author": "musaHaruna"
    },
    {
     "number": 34909,
     "title": "wallet, refactor: modularise wallet by extracting out legacy wallet migration",
     "author": "rkrux"
    },
    {
     "number": 34681,
     "title": "wallet: move rescan logic into ChainScanner and wallet/scan",
     "author": "Eunovo"
    },
    {
     "number": 32895,
     "title": "wallet: Prepare for future upgrades by recording versions of last client to open and decrypt",
     "author": "achow101"
    },
    {
     "number": 29278,
     "title": "Wallet:  Add `maxfeerate` wallet startup option",
     "author": "ismaelsadeeq"
    }
   ]
  }
 },
 "acks_parsed": {
  "remyers": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2023-09-12T19:49:19Z",
   "stale": false
  },
  "murchandamus": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2023-12-28T19:47:06Z",
   "stale": false
  },
  "jonatack": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2024-11-26T12:35:02Z",
   "stale": false
  },
  "rkrux": {
   "kind": "concept_ack",
   "hash": "7b2718f",
   "t": "2025-10-08T08:00:28Z",
   "stale": false
  },
  "w0xlt": {
   "kind": "ack",
   "hash": "412008bcb454b8d02f9e00f7e7a0fbb2aae5f690",
   "t": "2026-03-25T18:16:13Z",
   "stale": true
  },
  "jeanpablojp": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-27T00:50:02Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 5,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Zeegaths",
   "brunoerg",
   "jeanpablojp",
   "jonatack",
   "maflcko",
   "murchandamus",
   "remyers",
   "rkrux",
   "t-bast",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-15T02:41:09Z",
  "last_reviewer_activity": "2026-08-27T00:50:02Z",
  "last_reviewer": "jeanpablojp",
  "author_silent_days": 2,
  "waiting_on_author_days": 0,
  "days_since_update": 2
 },
 "refs": {
  "mentioned": [
   27286,
   27920,
   34360,
   35790
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 27286,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-07-01",
    "title": "wallet: Keep track of the wallet's own transaction outputs in memory"
   },
   {
    "number": 27920,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2023-07-04",
    "title": "wallet: bugfix, always use apostrophe for spkm descriptor ID"
   },
   {
    "number": 34360,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "bench: add WalletBalanceManySpent for high-history wallet scenario"
   },
   {
    "number": 35790,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-04",
    "title": "fuzz: populate wallet TXO index in wallet_create_transaction"
   }
  ],
  "conflicts": [
   36167,
   35998,
   35935,
   35786,
   35760,
   35716,
   35569,
   35511,
   35294,
   35151,
   34909,
   34681,
   32895,
   29278
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/receive.cpp",
  "src/wallet/test/util.cpp",
  "src/wallet/transaction.cpp",
  "src/wallet/transaction.h",
  "src/wallet/wallet.cpp",
  "src/wallet/walletdb.cpp"
 ],
 "body": "In #27286, the wallet keeps track of all of its transaction outputs, even if they are already spent or are otherwise unspendable. This TXO set is iterated for balance checking and coin selection preparation, which can still be slow for wallets that have had a lot of activity. This PR aims to improve the performance of such wallets by moving UTXOs that are definitely no longer spendable to a different map in the wallet so that far fewer TXOs need to be iterated for the aforementioned functions.\n\nUnspendable TXOs (not to be confused with Unspent TXOs) are those which have a spending transaction that has been confirmed, or are no longer valid due to reorgs. TXOs that are spent in unconfirmed transactions remain in the primary TXO set, and are filtered out of balance and coin selection as before.",
 "commits": [
  {
   "sha": "6de5f523315fd7f63750602099fe1fbe00dd76ab",
   "date": "2026-09-14T20:51:11Z",
   "message": "test: Test removedprunedfunds makes input TXOs spendable"
  },
  {
   "sha": "d037e8f53b555e291ad042fab9a59e7fc39cd75a",
   "date": "2026-09-14T20:51:11Z",
   "message": "test: Test that send adds double spends as inactive\n\nThe send RPC can be used to create a double spend of a confirmed\ntransaction which is added unconditionally to the wallet as an inactive\ntransaction. The test ensures that the transaction is in the wallet and\nthat the balance is still being counted correctly."
  },
  {
   "sha": "2b64eb52c154b4722b6a20f8d8cf37e722fe1ba2",
   "date": "2026-09-14T20:51:11Z",
   "message": "wallet: Make SubmitTxMemoryPoolAndRelay non-const"
  },
  {
   "sha": "19562ad71bb2552bba6c4a081c5fe0488c0d689b",
   "date": "2026-09-14T22:00:21Z",
   "message": "wallet: Add m_from_me to cache \"from me\" status\n\nm_from_me is used to track whether a transaction is \"from me\", i.e. has\nany inputs which belong to the wallet."
  },
  {
   "sha": "747e714b969df4d8f46f20ddbca3a8f9905a5ff3",
   "date": "2026-09-14T22:07:36Z",
   "message": "wallet: Replace CachedTxIsFromMe with direct m_from_me lookup\n\nInstead of looking at the cached amounts or searching every input of a\ntransaction each time we want to determine whether it is \"from me\", use\nm_from_me  which stores this value for us."
  },
  {
   "sha": "460e948d57b653676386b23e23527bdd1ea7de8b",
   "date": "2026-09-14T22:13:26Z",
   "message": "wallet: Make CWalletTx::m_state private with {get,set}ters"
  },
  {
   "sha": "0432ff0225164941edcea6845340a2889836d83e",
   "date": "2026-09-14T22:47:16Z",
   "message": "wallet: Store the TxState in each WalletTXO as well\n\nThe states will be updated whenever CWaleltTx::SetState is called too.\nThis is achieved by having CWalletTx::SetState take a function that\napplies the new state to the specified TXO. This ensures that CWalletTx\nstates and WalletTXO states are kept in sync."
  },
  {
   "sha": "6c6578f90abe7f61bd8a1584755a691a8fbccbfa",
   "date": "2026-09-14T22:47:18Z",
   "message": "wallet: Get the depth in main chain for a TxState"
  },
  {
   "sha": "6d257d6b8e8559481f68c0cae565a08de29664d1",
   "date": "2026-09-14T22:52:15Z",
   "message": "wallet: Get the coinbase maturity state for a TXO and TxState"
  },
  {
   "sha": "55553166eaa2d0eb402abe3a57a714a9cb3b7d90",
   "date": "2026-09-14T22:57:32Z",
   "message": "wallet: Use WalletTXO stored state and coinbase rather than wtx"
  },
  {
   "sha": "a79eafb4166949771527c0b03796ccbd2c8c9423",
   "date": "2026-09-14T23:01:55Z",
   "message": "walletdb: Move ReorderTransactions to immediately after loading txs\n\nPerform the transaction reorder upgrade immediately after loading txs\ninstead of waiting for the end of loading."
  },
  {
   "sha": "09d6abe8dd58c9c4195c20384cb2229a8ce3020b",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet: Store a copy of m_from_me in WalletTXOs and use for \"from me\"\n\nSince we need to know whether the transaction that creates a WalletTXO\nis \"from me\", we should store this state in the WalletTXO too, copied\nfrom its parent CWalletTx."
  },
  {
   "sha": "0e566d29b152bf4ddd16c05291b45e5b6b4de0d5",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet: Have WalletTXOs also store parent tx time\n\nWalletTXOs need to know their parent tx's timestamp for AvailableCoins\nto work."
  },
  {
   "sha": "588888624025e9f5b49f7cb2887b2004ad4bae8e",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet: Include transaction version in WalletTXO"
  },
  {
   "sha": "5741a65b69f494865935223d068e418783793f96",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet: Remove unused WalletTXO::GetWalletTx()"
  },
  {
   "sha": "85b847d15f19fa6dc7368a868f9d790683d8e1a6",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet: Have IsSpent take a min_conf\n\nA min_conf parameter is added to IsSpent so that it can set a\nconfirmation threshold for whether something is considered spent."
  },
  {
   "sha": "223f1df655a6bcf531f74eb3b24e49a31182e3ed",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet: Iterate block txs in reverse on blockDisconnected\n\nWhen a block is disconnected, we need to process the transactions in\nreverse order so that the wallet's TXO set is updated in the correct\norder."
  },
  {
   "sha": "8571959493db5d16115fdcba62d60f360bdf4842",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet, tests: Have CreateSyncedWallet use CWallet::Create\n\nCWallet::Create will properly connect the wallet to the chain, so we\nshould be doing that rather than ad-hoc chain connection."
  },
  {
   "sha": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "date": "2026-09-15T02:41:02Z",
   "message": "wallet: Move definintely unusable TXOs to a separate container\n\nDefinitely unusable TXOs are those that are spent by a confirmed\ntransaction or were produced by a now conflicted transaction. However,\nwe still need them for GetDebit, so we store them in a separate\nm_unusable_txos container. MarkConflicted, AbandonTransaction, and\nloading (via PruneSpentTXOs) will ensure that these unusable TXOs are\nproperly moved."
  }
 ],
 "timeline": [
  {
   "t": "2023-06-28T17:53:06Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "7a50755e3329316f85f612ad2ffec3c0fc903dc6"
  },
  {
   "t": "2023-06-28T18:30:38Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "8c4b04a75508de0d30aae96e7363a97f6b2ac35f"
  },
  {
   "t": "2023-06-28T22:26:40Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9730ec037c5994bf019cfac0a65cae4199a86933"
  },
  {
   "t": "2023-09-07T18:57:18Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "d9c4dcfe68c547d23f7687e5f0e9db363aa3b460"
  },
  {
   "t": "2023-09-07T21:32:16Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "@t-bast You may be interested in this for very large wallets."
  },
  {
   "t": "2023-09-11T12:26:00Z",
   "kind": "comment",
   "who": "t-bast",
   "assoc": "CONTRIBUTOR",
   "text": "Neat, thanks @achow101, @remyers will give that a try and will review the related PRs!"
  },
  {
   "t": "2023-09-12T19:49:19Z",
   "kind": "comment",
   "who": "remyers",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK - on a large synthetic test wallet I'm seeing a 250x speed up.\n\nI created a large wallet in regtest with many fanout style transactions - 100,000 blocks each creating 1000 utxos. There are only 117 utxos currently, but the wallet.dat file is 370M.\n\nUsing the same wallet.dat file and (transaction history) `fundrawtransaction` saw a substantial speedup:\n ~5 seconds using v25.99.0-a36134fcc7b4\n ~20 milliseconds using v25.99.0-d9c4dcfe68c5 (latest [wallet-unspent-txos](https://github.com/bitcoin/bitcoin/commit/d9c4dcfe68c547d23f7687e5f0e9db363aa3b460))\n\nI will try to recreate my large test wallet.dat file tomorrow so I can confirm the steps to reproduce it. Is there anything in particular I can do to help? Would you like more testing? code review?\n\nNB: I had to export my original wallet descriptors and re-import them to work around the issue fixed in [#27920](https://github.com/bitcoin/bitcoin/pull/27920#pullrequestreview-1608551611). Before the export/import the wallet.dat file was 1.1G, but the speed of `fundrawtransaction` was 5 sec both in the original file and the tested one created via importing descriptors."
  },
  {
   "t": "2023-09-12T20:55:10Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThe prerequisite PR (#27286) will need review and testing before this can be merged. That should also provide some speed up."
  },
  {
   "t": "2023-09-13T09:53:26Z",
   "kind": "comment",
   "who": "remyers",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nWill do. I'm getting 400-500ms for the same test using just #27286, so indeed also ~100x speed-up."
  },
  {
   "t": "2023-09-14T22:05:15Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "75b17036576ae19541326aad36a5648a563ae048"
  },
  {
   "t": "2023-09-15T18:50:55Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "805f12f656ad9ec549bc0e9f6e46e258412c6450"
  },
  {
   "t": "2023-09-15T21:48:04Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "7c4edf2b59a52aab723067cfa79be619b01f5de5"
  },
  {
   "t": "2023-09-27T16:51:19Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "022cea9c0c873bca2dd3a28c8783fe7dbc9eea28"
  },
  {
   "t": "2023-10-16T20:55:19Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "a4d189f6e33285ea0e0a536f01c4b1a8e576696f"
  },
  {
   "t": "2023-10-24T02:17:52Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "e0a9c23fae4246a26f48a077c56119d2cdcde781"
  },
  {
   "t": "2023-10-24T15:47:22Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "6cbe82a8f63ec34f027a8747ab43f0b7440c5f71"
  },
  {
   "t": "2023-11-13T22:57:48Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "84bcdb53adf57de1b41eaf65c4595040634bccd5"
  },
  {
   "t": "2023-11-15T18:41:08Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "322c97951c068ebdfe4fed61ecc5f71e1aecea64"
  },
  {
   "t": "2023-11-28T16:20:35Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "dbc5912cc1a730b2b820b09b88ebe256763b15ac"
  },
  {
   "t": "2023-12-08T22:09:05Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "b9d3d2ef1b5cccc1ee926ea5ffad6b7840674a6f"
  },
  {
   "t": "2023-12-11T22:19:28Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "a68fee0a4e661df4755faea1d6cefe8108047c64"
  },
  {
   "t": "2023-12-19T17:23:05Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9015c770c44b26a05f643b2fc3218c6378ceee5e"
  },
  {
   "t": "2023-12-28T19:47:06Z",
   "kind": "review",
   "who": "murchandamus",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "9015c770c44b26a05f643b2fc3218c6378ceee5e",
   "text": "Concept ACK"
  },
  {
   "t": "2024-02-03T06:54:11Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "4aae8915196882f2d75da95b35fbdc8cfdb4bcee"
  },
  {
   "t": "2024-02-20T23:46:02Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "90346c82f87a33f6ec9ea64dbab9a9a97bc119c8"
  },
  {
   "t": "2024-04-01T20:37:29Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "91205a883db7697f97c7fc094b77d1f961a76ad9"
  },
  {
   "t": "2024-04-25T15:37:06Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "b677b00f25d834cfd90a1d256de1a68dcf1b898a"
  },
  {
   "t": "2024-06-06T23:49:36Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9ca69266c1f51d0d0c71dd42dd279eacf6037c63"
  },
  {
   "t": "2024-08-29T16:34:37Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "2c9c37a87499a1ea1276d71d8b655837f85e98ac"
  },
  {
   "t": "2024-09-10T20:01:28Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "a89fc9a90034363f64113c61060177ade3223260"
  },
  {
   "t": "2024-10-24T23:21:48Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "ef7212857a486048cbdc84fc4d33842be6c6227b"
  },
  {
   "t": "2024-10-28T19:59:58Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "7ed8abd4f5ffdea3ef28e6404ed984f28c652f90"
  },
  {
   "t": "2024-10-28T20:39:24Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9527db6cc06467eae666f17fd8cdd7342ff14b2a"
  },
  {
   "t": "2024-10-28T21:55:41Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "09fb29b82c7bf704758329fc3b664ed51d02e1d3"
  },
  {
   "t": "2024-11-26T12:35:02Z",
   "kind": "comment",
   "who": "jonatack",
   "assoc": "MEMBER",
   "text": "Concept ACK, pending https://github.com/bitcoin/bitcoin/pull/27286, if this improves performance for wallets with many transactions."
  },
  {
   "t": "2025-04-10T01:19:36Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "892803e56137170ecaf38cb05c24742139d01dc3"
  },
  {
   "t": "2025-04-25T16:15:05Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "6fac290996cc43d67615f292210ab279e4203f57"
  },
  {
   "t": "2025-05-07T18:45:11Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "128268f79a87a0434857d82bc2a60a763629aefc"
  },
  {
   "t": "2025-05-07T19:01:35Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "87480d4f1bb59a07efde4511336c2d878897d55e"
  },
  {
   "t": "2025-05-15T21:40:45Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "caf62ee890395f9f20e2da3917c3b49f96209ae5"
  },
  {
   "t": "2025-05-16T01:47:08Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "d227fc65cdb778cf2907cdc8af5c6719d8130d09"
  },
  {
   "t": "2025-05-16T19:31:38Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "ab59837c7c910f14c33ded05ff102fefbb398963"
  },
  {
   "t": "2025-05-16T21:37:39Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "1ec5374bccbb8451222ce83e4b16e613c4fd7b83"
  },
  {
   "t": "2025-05-19T17:34:31Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "2a31dfae1d0ccc0eb6738c590a96ae75d12db8df"
  },
  {
   "t": "2025-05-20T00:53:17Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "07df5b573fc35312758caf9ccb7860e60bd1fa8d"
  },
  {
   "t": "2025-05-21T18:24:33Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "a7f0519948803e9629d7a1ed9a5df2221dbfb931"
  },
  {
   "t": "2025-05-30T21:52:13Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9554a4d79acc8bd92b6b22fe413f5cddb372647f"
  },
  {
   "t": "2025-06-02T07:17:51Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/wallet/test/util.cpp",
   "commit": "f94516927d1876496d6f98ccbb35a40b479a979c",
   "in_reply_to": null,
   "text": "\u201cwith it's default\u201d -> \u201cwith its default\u201d [\u201cit's\u201d is a contraction of \u201cit is,\u201d but here the possessive \u201cits\u201d is required]"
  },
  {
   "t": "2025-06-02T19:37:54Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/test/util.cpp",
   "commit": "f94516927d1876496d6f98ccbb35a40b479a979c",
   "in_reply_to": 2120269086,
   "text": "Fixed"
  },
  {
   "t": "2025-06-02T19:38:01Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "072e2835c4992b48ecf08db901ce8fd8a6b9217c"
  },
  {
   "t": "2025-06-27T02:17:25Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3f20d2e5133f3248a7f4b16b25c47b7d49b38dd0"
  },
  {
   "t": "2025-06-27T19:04:44Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "6041def924934900caa6738e36785ebafa6382cf"
  },
  {
   "t": "2025-06-27T23:18:20Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "856f58cfdadb9124d4e50e4231aae2be088f9125"
  },
  {
   "t": "2025-07-01T17:37:57Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "232c9eae2f02d8147c80fd461f4367166ffaa5ae"
  },
  {
   "t": "2025-07-03T00:26:44Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/transaction.h",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "Why is it optional instead of false by default?"
  },
  {
   "t": "2025-07-07T23:14:43Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3c56e99057b7c8b480a4fadfc1577181783425c4"
  },
  {
   "t": "2025-07-09T20:50:46Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/transaction.h",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": 2181221787,
   "text": "Using a default value has the potential very problematic because its value is expected to be correct. By using an optional, we can detect when there is a programming error that did not set the value."
  },
  {
   "t": "2025-08-01T00:54:49Z",
   "kind": "comment",
   "who": "Zeegaths",
   "assoc": "NONE",
   "text": "## Performance Testing Results\n\nTested this PR against master using regtest with high-activity wallets.\n\n### Test Setup:\n- Ubuntu 22.04, CMake build\n- Fresh regtest environment for both branches\n- 1,110 transactions (200 initial coinbase + 500 small sends + 100 coinbase + 300 large sends + 10 final coinbase)\n- Identical test patterns on both branches\n\n### Results:\n\n| Operation | Master | PR Branch | Performance Change |\n|-----------|--------|-----------|-------------------|\n| `getwalletinfo` | 0.008s | 0.011s | +38% slower |\n| `getbalance` | 0.009s | 0.010s | +11% slower |\n| `listunspent` | 0.051s | 0.035s | **-31% faster** |\n| `sendtoaddress` | 0.036s | 0.037s | +3% slower |\n\n### Observations:\nThe TXO separation optimization shows its intended benefit in `listunspent` (31% improvement), which makes sense given that this operation iterates through unspent outputs - exactly what the PR optimizes. Other operations show minimal performance differences within expected measurement variance.\n\nResults suggest the optimization is working as designed for the primary use case."
  },
  {
   "t": "2025-08-16T03:10:32Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "edaf52d848d8f816a1c4fe4f224d1f3971005ab3"
  },
  {
   "t": "2025-08-19T17:30:51Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "1784af71c589040a07dfc269c7c4d8ba2e68289a"
  },
  {
   "t": "2025-08-19T22:50:37Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "c05ed69864df10866e5ac47f05f3255b1d985f39"
  },
  {
   "t": "2025-08-20T01:17:01Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "c41741e72b0245a2d0bb3ad236885796c258a590"
  },
  {
   "t": "2025-08-21T22:09:59Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "64c2c8aec1029f8d4214b78d8e2594721737c5b3"
  },
  {
   "t": "2025-09-01T22:51:00Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "78e22fe5b827220ef4ca88d42fca6ab8634e7907"
  },
  {
   "t": "2025-09-02T02:46:38Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "f94516927d1876496d6f98ccbb35a40b479a979c"
  },
  {
   "t": "2025-09-23T23:33:53Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9b0bfea08cac080589e313b0ee0e70d7ec2a63e1"
  },
  {
   "t": "2025-09-23T23:51:03Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "7b2718f877f776a7c7bea098feee6b19f3e9c4a8"
  },
  {
   "t": "2025-10-08T08:00:28Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "MEMBER",
   "text": "Concept ACK 7b2718f\n\nSeems like a natural step after #27286, will get to this PR soon."
  },
  {
   "t": "2025-11-10T16:05:59Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "b5e229a785fa838296647de9e990d2d8940af57a"
  },
  {
   "t": "2026-01-02T21:45:14Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3d454eb45e8ff889dec013ae64662012d9766973"
  },
  {
   "t": "2026-01-14T21:38:07Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/transaction.cpp",
   "commit": "3d454eb45e8ff889dec013ae64662012d9766973",
   "in_reply_to": null,
   "text": "nit:\n```suggestion\n    for (const auto& [_, txo] : m_txos) {\n```"
  },
  {
   "t": "2026-01-14T21:56:10Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "Can `m_from_me` be `nullopt` here ?\nIf so, can this be UB ?\n\n```suggestion\n        std::optional<bool> from_me_before = wtx->m_from_me;\n```"
  },
  {
   "t": "2026-01-15T01:22:20Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "3d454eb45e8ff889dec013ae64662012d9766973",
   "in_reply_to": null,
   "text": "nit: It can be removed since it's already declared in `src/wallet/wallet.h`.\n```diff\n-std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUnusable(const COutPoint& outpoint)\n+std::pair<CWallet::TXOMap::iterator, CWallet::TXOMap::iterator> CWallet::MarkTXOUnusable(const COutPoint& outpoint)\n {\n     AssertLockHeld(cs_wallet);\n     auto txos_it = m_txos.find(outpoint);\n@@ -4646,7 +4645,7 @@ std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUnusable(const COu\n     return {txos_it, unusable_txos_it};\n }\n\n-std::pair<TXOMap::iterator, TXOMap::iterator> CWallet::MarkTXOUsable(const COutPoint& outpoint)\n+std::pair<CWallet::TXOMap::iterator, CWallet::TXOMap::iterator> CWallet::MarkTXOUsable(const COutPoint& outpoint)\n {\n     AssertLockHeld(cs_wallet);\n     auto txos_it = m_txos.end();\n```"
  },
  {
   "t": "2026-01-15T19:36:27Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": 2692205431,
   "text": "No, it cannot be `nullopt`. It is set when a transaction is added to the wallet, or immediately after loading a transaction from disk."
  },
  {
   "t": "2026-01-15T20:56:25Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "52b677d4550c9d5e32e7e3cb5e9fe76eebcea7b3"
  },
  {
   "t": "2026-01-15T20:56:30Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/transaction.cpp",
   "commit": "3d454eb45e8ff889dec013ae64662012d9766973",
   "in_reply_to": 2692144473,
   "text": "Done"
  },
  {
   "t": "2026-01-15T20:56:35Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "3d454eb45e8ff889dec013ae64662012d9766973",
   "in_reply_to": 2692615197,
   "text": "Done"
  },
  {
   "t": "2026-01-20T04:30:39Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/transaction.h",
   "commit": "e8fc840837aee8724fa62e7d85c60704cbbbf88e",
   "in_reply_to": null,
   "text": "This naming collision (m_txos in both `CWallet::m_txos` and `CWalletTx::m_txos`) can be confusing.\n\nSince `CWalletTx::m_txos` stores pointers into the wallet-level maps (`CWallet::m_txos` and `m_unusable_txos`), keyed by output index (0, 1, 2, ...), a clearer naming might be:\n\n```suggestion\n    mutable std::unordered_map<uint32_t, WalletTXO*> m_txo_ptrs;\n```"
  },
  {
   "t": "2026-01-21T03:22:28Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Results with #34360 on my machine:\n\n| Branch | ns/op | ops/s | Speedup |\n| ------------- | ------------- | ------------- | ------------- |\n| Master (no optimization) | 3,529,535 | 283 | baseline |\n| PR #27865 (with optimization) |  6,951 |143,867 | ~508x faster |"
  },
  {
   "t": "2026-01-21T12:34:08Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "Unkilled mutant:\n\n```diff\ndiff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp\nindex c68390c82e..d0b9488e20 100644\n--- a/src/wallet/wallet.cpp\n+++ b/src/wallet/wallet.cpp\n@@ -4042,7 +4042,7 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,\n         bool mine = IsMine(*wtx->tx);\n         bool from_me = IsFromMe(*wtx->tx);\n         bool is_mine = mine || from_me;\n-        if (is_mine) {\n+        if (1==1) {\n             wtx->m_from_me = from_me;\n             // Rewrite all txs that are \"mine\" to ensure that any tx upgrades, including the from_me update, is written\n             local_wallet_batch.WriteTx(*wtx);\n```"
  },
  {
   "t": "2026-01-21T12:34:59Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "52b677d4550c9d5e32e7e3cb5e9fe76eebcea7b3",
   "in_reply_to": null,
   "text": "Unkilled mutant:\n\n```diff\ndiff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp\nindex c68390c82e..b43d35596c 100644\n--- a/src/wallet/wallet.cpp\n+++ b/src/wallet/wallet.cpp\n@@ -1403,7 +1403,7 @@ void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const Txid& tx_hash, co\n\n                 if (std::get_if<TxStateBlockConflicted>(&unusable_txo_it->second.GetState()) ||\n                     std::get_if<TxStateConfirmed>(&unusable_txo_it->second.GetState())) {\n-                    continue;\n+                    break;\n                 }\n\n                 MarkTXOUsable(txin.prevout);\n```"
  },
  {
   "t": "2026-01-21T12:36:52Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "Unkilled mutant:\n\n```diff\ndiff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp\nindex c68390c82e..3f660e2c87 100644\n--- a/src/wallet/wallet.cpp\n+++ b/src/wallet/wallet.cpp\n@@ -895,7 +895,7 @@ bool CWallet::ReorderTransactions(WalletBatch& batch)\n\n             // Since we're changing the order, write it back\n             if (!batch.WriteTx(*pwtx)) {\n-                return false;\n+                return true;\n             }\n         }\n     }\n```"
  },
  {
   "t": "2026-01-21T12:41:54Z",
   "kind": "review",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "52b677d4550c9d5e32e7e3cb5e9fe76eebcea7b3",
   "text": "I ran an incremental mutation testing for `src/wallet/wallet.cpp` - it generated 58 mutants and the analysis showed a mutation score of **94.8%** (excellent). Left some comments with the unkilled mutants, feel free to address them or ignore, in any case a feedback would be valuable."
  },
  {
   "t": "2026-01-21T18:25:46Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": 2712389402,
   "text": "`ReorderTransaction` is basically impossible to test for as it requires a wallet created in 0.6.0 or earlier."
  },
  {
   "t": "2026-01-21T18:56:32Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": 2712380475,
   "text": "This isn't really testable as it is performing an upgrade that would happen on startup anyways. The `if` is there as an optimization to avoid rewriting transaction records that are going to be deleted later."
  },
  {
   "t": "2026-01-21T23:24:44Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "52b677d4550c9d5e32e7e3cb5e9fe76eebcea7b3",
   "text": "ACK https://github.com/bitcoin/bitcoin/pull/27865/commits/52b677d4550c9d5e32e7e3cb5e9fe76eebcea7b3"
  },
  {
   "t": "2026-01-29T21:36:04Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3fb18453cd95e35b0aee299279853e514e8a9695"
  },
  {
   "t": "2026-01-29T21:36:26Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "52b677d4550c9d5e32e7e3cb5e9fe76eebcea7b3",
   "in_reply_to": 2712383395,
   "text": "Spent a while looking at this and I've come to the conclusion that this entire loop is actually unnecessary, so I've removed it."
  },
  {
   "t": "2026-03-12T21:42:55Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/transaction.h",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "```suggestion\n    int64_t m_tx_time;\n    const uint32_t m_tx_version;\n```\n\nTo keep the same pattern, `m_tx_version` could be private with the getter `uint32_t GetTxVersion() const { return m_tx_version; }`.\n\nAnt then:\n\n```diff\ndiff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp\nindex 043f533b55..d68f441e89 100644\n--- a/src/wallet/spend.cpp\n+++ b/src/wallet/spend.cpp\n@@ -284,10 +284,10 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const\n                 input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);\n             }\n             if (wallet.GetTxStateDepthInMainChain(txo->GetState()) == 0) {\n-                if (txo->m_tx_version == TRUC_VERSION && coin_control.m_version != TRUC_VERSION) {\n+                if (txo->GetTxVersion() == TRUC_VERSION && coin_control.m_version != TRUC_VERSION) {\n                     return util::Error{strprintf(_(\"Can't spend unconfirmed version 3 pre-selected input with a version %d tx\"), coin_control.m_version)};\n-                } else if (coin_control.m_version == TRUC_VERSION && txo->m_tx_version != TRUC_VERSION) {\n-                    return util::Error{strprintf(_(\"Can't spend unconfirmed version %d pre-selected input with a version 3 tx\"), txo->m_tx_version)};\n+                } else if (coin_control.m_version == TRUC_VERSION && txo->GetTxVersion() != TRUC_VERSION) {\n+                    return util::Error{strprintf(_(\"Can't spend unconfirmed version %d pre-selected input with a version 3 tx\"), txo->GetTxVersion())};\n                 }\n             }\n         } else {\n@@ -475,7 +475,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,\n\n         auto available_output_type = GetOutputType(type, is_from_p2sh);\n         auto available_output = COutput(outpoint, output, nDepth, input_bytes, solvable, tx_safe, txo.GetTxTime(), tx_from_me, feerate);\n-        if (txo.m_tx_version == TRUC_VERSION && nDepth == 0 && params.check_version_trucness) {\n+        if (txo.GetTxVersion() == TRUC_VERSION && nDepth == 0 && params.check_version_trucness) {\n             unconfirmed_truc_coins.emplace_back(available_output_type, available_output);\n             auto [it, _] = truc_txid_by_value.try_emplace(outpoint.hash, 0);\n             it->second += output.nValue;\n```"
  },
  {
   "t": "2026-03-12T21:44:40Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "3fb18453cd95e35b0aee299279853e514e8a9695",
   "in_reply_to": null,
   "text": "Dangling comment ?\n```suggestion\n```"
  },
  {
   "t": "2026-03-12T21:46:34Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "3fb18453cd95e35b0aee299279853e514e8a9695",
   "in_reply_to": null,
   "text": "This can be simplified.\n\n```suggestion\n            WalletTXO new_txo{txout, wtx.GetState(), wtx.IsCoinBase(), *wtx.m_from_me, wtx.GetTxTime(), wtx.tx->version};\n            TXOMap& target = (m_last_block_processed_height >= 0 && IsSpent(outpoint, /*min_depth=*/1)) ? m_unusable_txos : m_txos;\n            auto [txo_it, txos_inserted] = target.emplace(outpoint, std::move(new_txo));\n            assert(txos_inserted);\n\n```"
  },
  {
   "t": "2026-03-23T21:23:42Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/transaction.h",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": 2927561366,
   "text": "I don't think that matters as `m_tx_version` is const."
  },
  {
   "t": "2026-03-23T21:51:46Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "412008bcb454b8d02f9e00f7e7a0fbb2aae5f690"
  },
  {
   "t": "2026-03-23T21:51:57Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "3fb18453cd95e35b0aee299279853e514e8a9695",
   "in_reply_to": 2927567497,
   "text": "Removed"
  },
  {
   "t": "2026-03-23T21:52:06Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "3fb18453cd95e35b0aee299279853e514e8a9695",
   "in_reply_to": 2927574098,
   "text": "Taken the suggestion"
  },
  {
   "t": "2026-03-23T21:52:14Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "Rebased for silent merge conflicts."
  },
  {
   "t": "2026-03-25T18:16:13Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "412008bcb454b8d02f9e00f7e7a0fbb2aae5f690",
   "text": "reACK 412008bcb454b8d02f9e00f7e7a0fbb2aae5f690"
  },
  {
   "t": "2026-04-23T18:08:06Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "861f01a98ef2a35d48958e380234280f9ba2a1e8"
  },
  {
   "t": "2026-05-15T00:31:02Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "29a953a948d280628936c8e59ec6244cb697ddad"
  },
  {
   "t": "2026-05-22T23:56:47Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/receive.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "`m_from_me = false` passes because a populated optional converts to `true`:\n\n```suggestion\n    if (!wallet.m_spend_zero_conf_change || !wtx->m_from_me.value_or(false)) return false;\n```\n\nExample:\n```c++\n#include <iostream>\n#include <optional>\n\nint main()\n{\n    std::optional<bool> false_value{false};\n    std::cout << \"false_value in if: \" << (static_cast<bool>(false_value) ? \"true\" : \"false\") << \"\\n\";\n}\n```\n\nTest:\n```diff\ndiff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp\nindex aaf1b40955..0089da789b 100644\n--- a/src/wallet/receive.cpp\n+++ b/src/wallet/receive.cpp\n@@ -210,7 +210,7 @@ bool CachedTxIsTrusted(const CWallet& wallet, const TxState& state, const Txid&\n     const CWalletTx* wtx = wallet.GetWalletTx(txid);\n     assert(wtx);\n\n-    if (!wallet.m_spend_zero_conf_change || !wtx->m_from_me) return false;\n+    if (!wallet.m_spend_zero_conf_change || !wtx->m_from_me.value_or(false)) return false;\n\n     // Trusted if all inputs are from us and are in the mempool:\n     for (const CTxIn& txin : wtx->tx->vin)\ndiff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp\nindex c5183c1c98..47ed036dab 100644\n--- a/src/wallet/test/wallet_tests.cpp\n+++ b/src/wallet/test/wallet_tests.cpp\n@@ -90,6 +90,36 @@ BOOST_FIXTURE_TEST_CASE(update_non_range_descriptor, TestingSetup)\n     }\n }\n\n+BOOST_AUTO_TEST_CASE(cached_tx_is_trusted_checks_from_me_value)\n+{\n+    CWallet wallet(m_node.chain.get(), \"\", CreateMockableWalletDatabase());\n+    {\n+        LOCK(wallet.cs_wallet);\n+        wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);\n+        wallet.m_spend_zero_conf_change = true;\n+    }\n+\n+    const CKey key{GenerateRandomKey()};\n+    AddKey(wallet, key);\n+    const CScript script_pub_key{GetScriptForRawPubKey(key.GetPubKey())};\n+\n+    CMutableTransaction parent_tx;\n+    parent_tx.vout.emplace_back(COIN, script_pub_key);\n+    CWalletTx* parent_wtx{wallet.AddToWallet(MakeTransactionRef(parent_tx), TxStateConfirmed{GetRandHash(), /*height=*/1, /*index=*/0})};\n+    BOOST_REQUIRE(parent_wtx);\n+\n+    CMutableTransaction child_tx;\n+    child_tx.vin.emplace_back(CTxIn{parent_wtx->GetHash(), /*nIn=*/0});\n+    child_tx.vout.emplace_back(COIN - DEFAULT_TRANSACTION_MAXFEE, script_pub_key);\n+    CWalletTx* child_wtx{wallet.AddToWallet(MakeTransactionRef(child_tx), TxStateInMempool{})};\n+    BOOST_REQUIRE(child_wtx);\n+\n+    LOCK(wallet.cs_wallet);\n+    BOOST_CHECK(*child_wtx->m_from_me);\n+    child_wtx->m_from_me = false;\n+    BOOST_CHECK(!CachedTxIsTrusted(wallet, *child_wtx));\n+}\n+\n BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)\n {\n     // Cap last block file size, and mine new block in a new block file.\n```"
  },
  {
   "t": "2026-05-23T07:05:03Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "29a953a948d280628936c8e59ec6244cb697ddad",
   "in_reply_to": null,
   "text": "`RefreshTXOsFromTx(*wtx)` creates or updates `WalletTXO` entries using the current `wtx->m_from_me` value.\nDuring migration that value may still be the legacy-wallet value, and it is recomputed later with `wtx->m_from_me = from_me`, so the refresh needs to happen after that assignment.\n\n```diff\ndiff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp\nindex 73bd712910..ab2902efea 100644\n--- a/src/wallet/wallet.cpp\n+++ b/src/wallet/wallet.cpp\n@@ -4121,9 +4121,7 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,\n         }\n     }\n     for (const auto& [_pos, wtx] : wtxOrdered) {\n-        // First update the UTXOs\n         wtx->m_txos.clear();\n-        RefreshTXOsFromTx(*wtx);\n         // Check it is the watchonly wallet's\n         // solvable_wallet doesn't need to be checked because transactions for those scripts weren't being watched for\n         bool mine = IsMine(*wtx->tx);\n@@ -4131,6 +4129,7 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,\n         bool is_mine = mine || from_me;\n         if (is_mine) {\n             wtx->m_from_me = from_me;\n+            RefreshTXOsFromTx(*wtx);\n             // Rewrite all txs that are \"mine\" to ensure that any tx upgrades, including the from_me update, is written\n             local_wallet_batch.WriteTx(*wtx);\n         }\n```\n\nTest:\n\ndiff\n\n```diff\ndiff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp\nindex c5183c1c98..4a18393be4 100644\n--- a/src/wallet/test/wallet_tests.cpp\n+++ b/src/wallet/test/wallet_tests.cpp\n@@ -90,6 +90,47 @@ BOOST_FIXTURE_TEST_CASE(update_non_range_descriptor, TestingSetup)\n     }\n }\n\n+BOOST_AUTO_TEST_CASE(migration_refreshes_txos_after_from_me_update)\n+{\n+    CWallet wallet(m_node.chain.get(), \"\", CreateMockableWalletDatabase());\n+    const CKey key{GenerateRandomKey()};\n+    const CPubKey pubkey{key.GetPubKey()};\n+    const CScript script_pub_key{GetScriptForDestination(PKHash{pubkey})};\n+\n+    LOCK(wallet.cs_wallet);\n+    auto* legacy_spkm{wallet.GetOrCreateLegacyDataSPKM()};\n+    BOOST_REQUIRE(legacy_spkm);\n+    BOOST_REQUIRE(legacy_spkm->LoadKey(key, pubkey));\n+\n+    bilingual_str error;\n+    auto migration_data{wallet.GetDescriptorsForLegacy(error)};\n+    BOOST_REQUIRE_MESSAGE(migration_data, error.original);\n+\n+    CMutableTransaction tx;\n+    tx.vout.emplace_back(COIN, script_pub_key);\n+    CTransactionRef tx_ref{MakeTransactionRef(tx)};\n+    const Txid txid{tx_ref->GetHash()};\n+    auto [it, inserted]{wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(tx_ref, TxStateInMempool{}))};\n+    BOOST_REQUIRE(inserted);\n+    CWalletTx& wtx{it->second};\n+    wtx.m_from_me = true;\n+    wtx.nOrderPos = 0;\n+    wtx.m_it_wtxOrdered = wallet.wtxOrdered.emplace(wtx.nOrderPos, &wtx);\n+\n+    WalletBatch batch{wallet.GetDatabase()};\n+    BOOST_REQUIRE(batch.WriteBestBlock(CBlockLocator{}));\n+    auto res{wallet.ApplyMigrationData(batch, *migration_data)};\n+    BOOST_REQUIRE_MESSAGE(res, util::ErrorString(res).original);\n+\n+    const CWalletTx& migrated_wtx{wallet.mapWallet.at(txid)};\n+    BOOST_REQUIRE(migrated_wtx.m_from_me.has_value());\n+    BOOST_CHECK(!*migrated_wtx.m_from_me);\n+\n+    auto txo{wallet.GetTXO(COutPoint{txid, 0})};\n+    BOOST_REQUIRE(txo);\n+    BOOST_CHECK(!txo->GetTxFromMe());\n+}\n+\n BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)\n {\n     // Cap last block file size, and mine new block in a new block file.\n```"
  },
  {
   "t": "2026-05-23T07:57:55Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "`CWallet::RemoveTxs()` deletes wallet transactions, but it was not keeping the new TXO caches (`m_txos` and `m_unusable_txos`) in sync.\n\nSuggestion:\n```diff\ndiff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp\nindex 73bd712910..a8faa6be39 100644\n--- a/src/wallet/wallet.cpp\n+++ b/src/wallet/wallet.cpp\n@@ -2522,6 +2522,7 @@ util::Result<void> CWallet::RemoveTxs(WalletBatch& batch, std::vector<Txid>& txs\n\n     // Register callback to update the memory state only when the db txn is actually dumped to disk\n     batch.RegisterTxnListener({.on_commit=[&, erased_txs]() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {\n+        std::vector<COutPoint> spent_outpoints;\n         // Update the in-memory state and notify upper layers about the removals\n         for (const auto& it : erased_txs) {\n             const Txid hash{it->first};\n@@ -2534,14 +2535,24 @@ util::Result<void> CWallet::RemoveTxs(WalletBatch& batch, std::vector<Txid>& txs\n                         break;\n                     }\n                 }\n+                spent_outpoints.push_back(txin.prevout);\n             }\n             for (unsigned int i = 0; i < it->second.tx->vout.size(); ++i) {\n-                m_txos.erase(COutPoint(hash, i));\n+                const COutPoint outpoint{hash, i};\n+                m_txos.erase(outpoint);\n+                m_unusable_txos.erase(outpoint);\n             }\n             mapWallet.erase(it);\n             NotifyTransactionChanged(hash, CT_DELETED);\n         }\n\n+        for (const auto& outpoint : spent_outpoints) {\n+            auto txo_it = m_unusable_txos.find(outpoint);\n+            if (txo_it != m_unusable_txos.end() && !std::get_if<TxStateBlockConflicted>(&txo_it->second.GetState()) && !IsSpent(outpoint, /*min_depth=*/1)) {\n+                MarkTXOUsable(outpoint);\n+            }\n+        }\n+\n         MarkDirty();\n     }, .on_abort={}});\n```\n\nTest:\n\n  diff\n\n```diff\ndiff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp\nindex c5183c1c98..a8ae79f51b 100644\n--- a/src/wallet/test/wallet_tests.cpp\n+++ b/src/wallet/test/wallet_tests.cpp\n@@ -705,22 +705,52 @@ BOOST_FIXTURE_TEST_CASE(RemoveTxs, TestChain100Setup)\n     m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);\n     auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));\n     CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));\n+    const CTransaction block_tx_ref{block_tx};\n+    auto child_tx = TestSimpleSpend(block_tx_ref, 0, key, GetScriptForRawPubKey(key.GetPubKey()));\n+    CreateAndProcessBlock({child_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));\n+    const CTransaction child_tx_ref{child_tx};\n+    auto grandchild_tx = TestSimpleSpend(child_tx_ref, 0, key, GetScriptForRawPubKey(key.GetPubKey()));\n+    CreateAndProcessBlock({grandchild_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));\n\n     m_node.validation_signals->SyncWithValidationInterfaceQueue();\n\n     {\n-        auto block_hash = block_tx.GetHash();\n+        const Txid block_hash = block_tx.GetHash();\n+        const Txid child_hash = child_tx.GetHash();\n+        const Txid grandchild_hash = grandchild_tx.GetHash();\n         auto prev_tx = m_coinbase_txns[0];\n+        const COutPoint block_outpoint{block_hash, 0};\n+        const COutPoint child_outpoint{child_hash, 0};\n+        const COutPoint grandchild_outpoint{grandchild_hash, 0};\n\n         LOCK(wallet->cs_wallet);\n         BOOST_CHECK(wallet->HasWalletSpend(prev_tx));\n         BOOST_CHECK(wallet->mapWallet.contains(block_hash));\n-\n-        std::vector<Txid> vHashIn{ block_hash };\n+        BOOST_CHECK(wallet->HasWalletSpend(MakeTransactionRef(block_tx_ref)));\n+        BOOST_CHECK(wallet->mapWallet.contains(child_hash));\n+        BOOST_CHECK(wallet->mapWallet.contains(grandchild_hash));\n+\n+        BOOST_CHECK(wallet->GetTXO(block_outpoint));\n+        BOOST_CHECK(!wallet->GetTXOs().contains(block_outpoint));\n+        BOOST_CHECK(wallet->GetTXO(child_outpoint));\n+        BOOST_CHECK(!wallet->GetTXOs().contains(child_outpoint));\n+        BOOST_CHECK(wallet->GetTXO(grandchild_outpoint));\n+        BOOST_CHECK(wallet->GetTXOs().contains(grandchild_outpoint));\n+\n+        std::vector<Txid> vHashIn{child_hash};\n         BOOST_CHECK(wallet->RemoveTxs(vHashIn));\n\n-        BOOST_CHECK(!wallet->HasWalletSpend(prev_tx));\n-        BOOST_CHECK(!wallet->mapWallet.contains(block_hash));\n+        BOOST_CHECK(wallet->HasWalletSpend(prev_tx));\n+        BOOST_CHECK(wallet->mapWallet.contains(block_hash));\n+        BOOST_CHECK(!wallet->HasWalletSpend(MakeTransactionRef(block_tx_ref)));\n+        BOOST_CHECK(!wallet->mapWallet.contains(child_hash));\n+        BOOST_CHECK(wallet->mapWallet.contains(grandchild_hash));\n+\n+        BOOST_CHECK(wallet->GetTXO(block_outpoint));\n+        BOOST_CHECK(wallet->GetTXOs().contains(block_outpoint));\n+        BOOST_CHECK(!wallet->GetTXO(child_outpoint));\n+        BOOST_CHECK(wallet->GetTXO(grandchild_outpoint));\n+        BOOST_CHECK(wallet->GetTXOs().contains(grandchild_outpoint));\n     }\n\n     TestUnloadWallet(std::move(wallet));\n```"
  },
  {
   "t": "2026-05-26T22:53:30Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI'm not convinced that `MarkTXOUsable` is correct or relevant. Please write a functional test that fails."
  },
  {
   "t": "2026-05-26T22:55:17Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "61ba701b9e680a84fa9a11c55ca1ff1381c52861"
  },
  {
   "t": "2026-05-26T22:55:32Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/receive.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": 3291665910,
   "text": "Fixed. The unit test is not meaningfully helpful."
  },
  {
   "t": "2026-05-26T22:56:47Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "29a953a948d280628936c8e59ec6244cb697ddad",
   "in_reply_to": 3292328153,
   "text": "Fixed, but the issue is not reachable and the unit test is not meaningfully helpful. The in memory state after migration should never be used, I'm pretty sure there are a few other places where it is out of sync. We specifically reload the wallet in order to ensure consistency before the wallet goes back into use."
  },
  {
   "t": "2026-06-02T18:40:16Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nDone.\nThe test below passes on master and on 61ba701b9e with the suggested patch, but fails on 61ba701b9e without it.\n\n```diff\ndiff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py\nindex 95e2a5b3a4..bd08819fdc 100755\n--- a/test/functional/wallet_importprunedfunds.py\n+++ b/test/functional/wallet_importprunedfunds.py\n@@ -157,6 +157,29 @@ class ImportPrunedFundsTest(BitcoinTestFramework):\n         available_utxos = [u[\"txid\"] for u in node.listunspent(minconf=0)]\n         assert utxo[\"txid\"] not in available_utxos, \"UTXO should still be spent by conflicting tx\"\n\n+        self.log.info(\"Test removeprunedfunds restores inputs of removed confirmed spend\")\n+        parent_txid = node.sendtoaddress(node.getnewaddress(), 1)\n+        self.generate(node, 1)\n+        parent_utxo = next(\n+            utxo for utxo in node.listunspent() if utxo[\"txid\"] == parent_txid\n+        )\n+        parent_outpoint = (parent_txid, parent_utxo[\"vout\"])\n+\n+        child_txid = node.sendall(\n+            recipients=[w1.getnewaddress()], inputs=[parent_utxo]\n+        )[\"txid\"]\n+        self.generate(node, 1)\n+\n+        def parent_is_unspent():\n+            return any(\n+                (utxo[\"txid\"], utxo[\"vout\"]) == parent_outpoint\n+                for utxo in node.listunspent(minconf=0)\n+            )\n+\n+        assert_equal(parent_is_unspent(), False)\n+        node.removeprunedfunds(child_txid)\n+        assert_equal(parent_is_unspent(), True)\n+\n\n if __name__ == '__main__':\n     ImportPrunedFundsTest(__file__).main()\n```"
  },
  {
   "t": "2026-06-06T05:02:46Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "00a3a5e5bbc59cf8da474d645d3a6004acbbbb57"
  },
  {
   "t": "2026-06-07T08:26:19Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/util.cpp",
   "commit": "00a3a5e5bbc59cf8da474d645d3a6004acbbbb57",
   "in_reply_to": null,
   "text": "This should fix the CI error.\n\n```suggestion\n    auto wallet = CWallet::CreateNew(context, \"\", CreateMockableWalletDatabase(), WALLET_FLAG_DESCRIPTORS, /*born_encrypted=*/false, error, warnings);\n```"
  },
  {
   "t": "2026-06-10T20:40:29Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "e8fc840837aee8724fa62e7d85c60704cbbbf88e"
  },
  {
   "t": "2026-06-21T21:36:17Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "I think the earlier `RemoveTxs()` suggestion (https://github.com/bitcoin/bitcoin/pull/27865#issuecomment-4524698348) may still be needed here.\n\nThe unconditional `MarkTXOUsable(txin.prevout)` works when the removed tx is the only confirmed spend, but if another conflicting wallet tx is already confirmed, the same outpoint should stay unusable.\n\nCollecting the inputs in `spent_outpoints` lets us erase the removed txs from `mapWallet` first, then run the suggested `!IsSpent(outpoint, /*min_depth=*/1)` check against the remaining wallet transactions before moving anything back to `m_txos`.\n\nSuggested test:\n\n```diff\ndiff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py\nindex bd08819fdc..0b2c05c0f7 100755\n--- a/test/functional/wallet_importprunedfunds.py\n+++ b/test/functional/wallet_importprunedfunds.py\n@@ -157,6 +157,36 @@ class ImportPrunedFundsTest(BitcoinTestFramework):\n         available_utxos = [u[\"txid\"] for u in node.listunspent(minconf=0)]\n         assert utxo[\"txid\"] not in available_utxos, \"UTXO should still be spent by conflicting tx\"\n\n+        self.log.info(\"Test removeprunedfunds preserves inputs spent by another confirmed tx\")\n+        parent_txid = node.sendtoaddress(node.getnewaddress(), 1)\n+        self.generate(node, 1)\n+        parent_utxo = next(\n+            utxo for utxo in node.listunspent() if utxo[\"txid\"] == parent_txid\n+        )\n+        parent_outpoint = (parent_txid, parent_utxo[\"vout\"])\n+\n+        child_txid = node.send(outputs=[{node.getnewaddress(): Decimal(\"0.5\")}], inputs=[parent_utxo])[\"txid\"]\n+        child_fee = node.gettransaction(child_txid)[\"fee\"]\n+\n+        # Replace child_txid with a conflicting tx and confirm it. Removing\n+        # child_txid must not make parent_outpoint spendable again.\n+        output_value = parent_utxo[\"amount\"] + child_fee - Decimal(\"0.00001\")\n+        raw_conflict_tx = node.createrawtransaction(inputs=[parent_utxo], outputs=[{node.getnewaddress(): output_value}])\n+        signed_conflict_tx = node.signrawtransactionwithwallet(raw_conflict_tx)\n+        conflict_txid = node.sendrawtransaction(signed_conflict_tx[\"hex\"])\n+        assert_not_equal(conflict_txid, child_txid)\n+        self.generate(node, 1)\n+\n+        def confirmed_parent_is_unspent():\n+            return any(\n+                (utxo[\"txid\"], utxo[\"vout\"]) == parent_outpoint\n+                for utxo in node.listunspent(minconf=0)\n+            )\n+\n+        assert_equal(confirmed_parent_is_unspent(), False)\n+        node.removeprunedfunds(child_txid)\n+        assert_equal(confirmed_parent_is_unspent(), False)\n+\n         self.log.info(\"Test removeprunedfunds restores inputs of removed confirmed spend\")\n         parent_txid = node.sendtoaddress(node.getnewaddress(), 1)\n         self.generate(node, 1)\n```"
  },
  {
   "t": "2026-07-01T22:51:22Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "c2b4d6efa542f8844e00e2615be29ea001f27a2a"
  },
  {
   "t": "2026-07-01T22:53:21Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nChecking the states to revert them was starting to get a bit complicated, so I ended up doing the overkill thing of recomputing all of the TXOs after removing any transaction. That should resolve any issues here.\n\n[quoted text omitted]\nIncluded the test.\n\n***\n\nAlso ended up reworking how the tx states are set so that `CWalletTx` does not need to hold pointers to this that might disappear out from under it."
  },
  {
   "t": "2026-07-01T23:11:45Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "d588e22f3a1f1dffa355cf3d81798eba6d05e050"
  },
  {
   "t": "2026-07-02T04:01:39Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "d588e22f3a1f1dffa355cf3d81798eba6d05e050",
   "text": "`AddToWallet()`'s `isInactive()` branch marks the inputs of an inactive tx usable again without checking whether another **confirmed** tx still spends them, which lets a confirmed-spent TXO back into `m_txos` and trips the new `Assert(!wallet.IsSpent(outpoint, 1))` in `GetBalance()`, aborting the node on every subsequent balance query.\n\nSequence:\n1. UTXO `O` is spent by `T1`, which confirms \u2192 `O` moves to `m_unusable_txos`.\n2. `send` with `O` as a preset input builds `T2`: `FetchSelectedInputs` resolves it via `GetTXO()` (which now also returns unusable TXOs) and has no `IsSpent` check on the preset path.\n3. `CommitTransaction` calls `AddToWallet(T2, TxStateInactive{})` *before* broadcasting; the `isInactive()` branch runs `MarkTXOUsable(O)`. The broadcast then fails with `bad-txns-inputs-missingorspent`, but the wallet state was already mutated.\n4. Next `getbalances`:\n```\nwallet/receive.cpp:256 GetBalance: Assertion `!wallet.IsSpent(outpoint, 1)' failed.\n```\n\nSuggestion:\n\n```diff\n     } else if (wtx.isInactive()) {\n-        // When a transaction becomes inactive, we need to mark its inputs as usable again\n+        // When a transaction becomes inactive, we need to mark its inputs as usable again,\n+        // unless an input is still spent by a different confirmed transaction\n         for (const CTxIn& txin : wtx.tx->vin) {\n-            MarkTXOUsable(txin.prevout);\n+            if (!IsSpent(txin.prevout, /*min_depth=*/1)) {\n+                MarkTXOUsable(txin.prevout);\n+            }\n         }\n     }\n```\n\n(Alternatively the guard could live inside `MarkTXOUsable()` itself, which would protect the other/future callers too.)\n\nThe test below crashes the node at `getbalances` on the current head and passes with the patch:\n\ndiff\n\n```python\n#!/usr/bin/env python3\n\"\"\"Test that committing a tx that spends an already confirmed-spent input\ndoes not resurrect the spent TXO into the usable TXO set.\"\"\"\n\nfrom decimal import Decimal\n\nfrom test_framework.test_framework import BitcoinTestFramework\nfrom test_framework.util import assert_equal\n\nclass WalletSpentTXOResurrectTest(BitcoinTestFramework):\n    def set_test_params(self):\n        self.num_nodes = 1\n\n    def skip_test_if_missing_module(self):\n        self.skip_if_no_wallet()\n\n    def run_test(self):\n        node = self.nodes[0]\n        node.createwallet(\"resurrect\")\n        wallet = node.get_wallet_rpc(\"resurrect\")\n        default = node.get_wallet_rpc(self.default_wallet_name)\n\n        self.log.info(\"Fund the wallet with UTXO O\")\n        txid = default.sendtoaddress(wallet.getnewaddress(), 1)\n        self.generate(node, 1)\n        utxo = next(u for u in wallet.listunspent() if u[\"txid\"] == txid)\n        outpoint = {\"txid\": utxo[\"txid\"], \"vout\": utxo[\"vout\"]}\n\n        self.log.info(\"Spend O in T1 and confirm it -> O becomes definitely unspendable\")\n        t1 = wallet.send(outputs=[{default.getnewaddress(): Decimal(\"0.9\")}],\n                         inputs=[outpoint], add_inputs=False)\n        assert t1[\"complete\"]\n        self.generate(node, 1)\n\n        self.log.info(\"Try to double-spend O via a preset input\")\n        # The broadcast fails, but the tx is committed to the wallet first.\n        try:\n            wallet.send(outputs=[{default.getnewaddress(): Decimal(\"0.8\")}],\n                        inputs=[outpoint], add_inputs=False)\n        except Exception as e:\n            self.log.info(f\"send reported: {e}\")\n\n        self.log.info(\"Balance queries must still work and O must remain spent\")\n        wallet.getbalances()  # aborts the node without the fix\n        assert_equal(utxo[\"txid\"] in [u[\"txid\"] for u in wallet.listunspent()], False)\n\nif __name__ == \"__main__\":\n    WalletSpentTXOResurrectTest(__file__).main()\n```"
  },
  {
   "t": "2026-07-02T23:33:46Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "33338485a2113469bb9796332694168223071488"
  },
  {
   "t": "2026-07-02T23:35:30Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI've changed `MarkTXOUsable` to always do an `IsSpent` check. Added a test in an earlier commit as well."
  },
  {
   "t": "2026-07-14T00:33:47Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "96f4b4d9c4e0e84e95a0258bbdbdbe588536d583"
  },
  {
   "t": "2026-08-05T23:40:10Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "b52c4849626f8cfdbdaadee62b1c5dc7cab747fc"
  },
  {
   "t": "2026-08-08T03:59:21Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "905a6ad84eaaea5127eeac0dcf1793e38eddb52a"
  },
  {
   "t": "2026-08-11T19:49:34Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "e4923fe7b0027040848d7f26bd96fbb6554dcd2d"
  },
  {
   "t": "2026-08-27T00:50:02Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "e4923fe7b0027040848d7f26bd96fbb6554dcd2d",
   "text": "Concept ACK\n\nAfter a reorg and `abandontransaction`, an output that should be spendable again is missing from the balance and from `listunspent` until the wallet is reloaded. Trusted 150 BTC against 200 on `512dc9af1b`. And after `removeprunedfunds`, a block-conflicted output can take the node down on the next `getbalances`, tripping `Assert(tx_depth >= 0)`. Both are inline, and neither happens on the base.\n\ntest\n\nThe reorg has to be deep enough for the coinbase to be immature at the new tip, otherwise the spend goes back to the mempool. Restarting the node rebuilds the containers and hides it.\n\n```python\nself.generatetoaddress(node, 1, w.getnewaddress())\nc_txid = node.getblock(node.getblockhash(1))[\"tx\"][0]\nself.generatetoaddress(node, COINBASE_MATURITY, w.getnewaddress())\no = next(u for u in w.listunspent() if u[\"txid\"] == c_txid)\n\nt1 = w.send(outputs=[{w.getnewaddress(): o[\"amount\"] - Decimal(\"0.001\")}],\n            inputs=[{\"txid\": o[\"txid\"], \"vout\": o[\"vout\"]}], add_inputs=False)[\"txid\"]\nself.generatetoaddress(node, 1, w.getnewaddress())\n\nnode.invalidateblock(node.getblockhash(COINBASE_MATURITY))\nassert_equal(node.getrawmempool(), [])\nassert_equal(w.gettransaction(t1)[\"confirmations\"], 0)\nself.generatetoaddress(node, 5, w.getnewaddress())\n\nw.abandontransaction(t1)\nassert any(u[\"txid\"] == c_txid for u in w.listunspent(minconf=0))\n```\n\nBuilt this and the base and ran the wallet tests."
  },
  {
   "t": "2026-08-27T00:50:02Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "`min_depth` left at the default instead of the 1 that matches `m_unusable_txos`'s definition. An output spent by a transaction that confirms and is then unconfirmed by a reorg doesn't come back, and `abandontransaction` doesn't either, since `RecursiveUpdateTxState` only walks the transaction's outputs and never its inputs. With 1 the wallet functional tests still pass and the case goes away."
  },
  {
   "t": "2026-08-27T00:50:02Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "This is missing the `TxStateBlockConflicted` check that `PruneSpentTXOs` does. `RemoveTxs` exposes it, since it clears the containers before rebuilding. Mirroring `PruneSpentTXOs` here makes the balance match the base again."
  },
  {
   "t": "2026-08-27T00:50:02Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/walletdb.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "`ReorderTransactions` used to sit after `LoadWallet`'s `if (result != DBErrors::LOAD_OK) return result;`, and now both it and the new upgrade loop run before that return. With one corrupt `tx` record, `loadwallet` fails and the intact records still get rewritten, 310 to 311 bytes here."
  },
  {
   "t": "2026-08-27T00:50:02Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
   "in_reply_to": null,
   "text": "`m_from_me` is an `optional<bool>` and `CWalletTx(tx, state)` doesn't set it. That's the three red fuzz jobs, all on `wallet_create_transaction`, with UBSan reporting `invalid-enum-load` and MSan the libc++ hardening assertion on `operator*`. The RefreshTXOsFromTx call there came in with #35790. `LoadToWallet` guards with `has_value()`, but the precondition isn't documented anywhere and the method is public."
  },
  {
   "t": "2026-09-14T23:56:27Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "4cbc69f50e7ab066ba1292eeb9ca7e25102425ce"
  },
  {
   "t": "2026-09-15T02:41:09Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d"
  }
 ],
 "labels_log": [
  {
   "t": "2023-06-12T18:40:14Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2023-06-12T20:07:07Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2023-06-28T01:06:28Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-06-28T18:19:36Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-09-06T19:05:51Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-09-07T20:28:01Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-09-14T21:22:13Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-09-14T23:26:34Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-09-19T18:12:13Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-09-27T18:54:53Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-10-16T16:17:33Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-10-16T22:34:40Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-10-23T23:35:06Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-10-24T03:42:36Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-10-29T11:02:21Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-11-13T23:14:40Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-11-15T15:36:48Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-11-15T19:54:58Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-11-24T16:47:23Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-11-28T17:00:59Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-12-08T12:12:17Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-12-08T22:35:12Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-12-11T17:19:38Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-12-11T23:32:33Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-12-12T12:06:30Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2023-12-19T18:29:46Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-02-03T04:53:59Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-02-03T06:57:39Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-02-20T11:54:17Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-02-21T00:24:22Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-03-27T17:14:13Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-04-01T22:01:12Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-04-08T16:44:14Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-04-25T16:56:59Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-08-27T17:15:10Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-08-29T20:32:01Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-10-24T18:24:20Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-10-25T00:56:12Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-10-28T23:23:12Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-03-13T20:54:29Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-04-10T01:30:01Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-04-25T13:17:16Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-04-25T17:28:17Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-07T14:36:33Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-07T18:50:54Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-14T23:27:35Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-15T22:59:46Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-16T09:39:25Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-16T21:04:50Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-19T07:16:59Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-19T19:00:19Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-19T21:06:07Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-20T01:17:32Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-21T14:25:32Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-21T18:36:46Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-30T12:14:35Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-30T22:41:10Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-06-25T21:07:31Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-06-27T03:08:16Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-06-27T03:35:11Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-06-28T00:03:02Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-07T21:37:36Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-07T23:44:13Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-16T00:55:27Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-16T05:09:17Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-19T11:39:32Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-19T18:21:08Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-21T16:07:19Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-21T22:44:41Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-09-01T22:58:52Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-09-02T04:16:53Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-09-12T15:33:14Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-09-24T01:17:37Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-10-31T19:18:53Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-11-11T12:03:41Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-27T17:59:50Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-02T22:13:19Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-23T06:17:08Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-23T19:38:12Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-13T21:53:37Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-15T02:33:36Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-26T23:57:38Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-28T08:39:06Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-06T06:12:15Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-10T21:54:53Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-01T23:12:11Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-03T00:48:27Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-13T22:40:02Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T00:49:01Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-04T23:01:01Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-06T00:52:42Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-06T01:07:16Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-11T19:05:40Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-11T21:16:45Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-17T08:30:03Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-17T08:31:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-17T08:32:17Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-17T08:32:54Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-12T12:06:06Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T00:51:44Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T04:00:36Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2025-07-01T17:38:09Z",
   "kind": "ready_for_review",
   "who": "achow101"
  }
 ],
 "text_chars": 37894,
 "text_tokens_estimate": 9473,
 "changed_paths": [
  "src/wallet/receive.cpp",
  "src/wallet/receive.h",
  "src/wallet/rpc/transactions.cpp",
  "src/wallet/spend.cpp",
  "src/wallet/test/fuzz/spend.cpp",
  "src/wallet/test/spend_tests.cpp",
  "src/wallet/test/util.cpp",
  "src/wallet/test/util.h",
  "src/wallet/test/wallet_tests.cpp",
  "src/wallet/transaction.cpp",
  "src/wallet/transaction.h",
  "src/wallet/wallet.cpp",
  "src/wallet/wallet.h",
  "src/wallet/walletdb.cpp",
  "test/functional/wallet_backwards_compatibility.py",
  "test/functional/wallet_balance.py",
  "test/functional/wallet_importprunedfunds.py",
  "test/functional/wallet_send.py"
 ],
 "files": [
  {
   "path": "src/wallet/receive.cpp",
   "add": 27,
   "del": 29
  },
  {
   "path": "src/wallet/receive.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/wallet/rpc/transactions.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/wallet/spend.cpp",
   "add": 13,
   "del": 11
  },
  {
   "path": "src/wallet/test/fuzz/spend.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/wallet/test/spend_tests.cpp",
   "add": 9,
   "del": 2
  },
  {
   "path": "src/wallet/test/util.cpp",
   "add": 13,
   "del": 12
  },
  {
   "path": "src/wallet/test/util.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/wallet/test/wallet_tests.cpp",
   "add": 12,
   "del": 6
  },
  {
   "path": "src/wallet/transaction.cpp",
   "add": 19,
   "del": 7
  },
  {
   "path": "src/wallet/transaction.h",
   "add": 37,
   "del": 12
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 230,
   "del": 51
  },
  {
   "path": "src/wallet/wallet.h",
   "add": 21,
   "del": 6
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 39,
   "del": 22
  },
  {
   "path": "test/functional/wallet_backwards_compatibility.py",
   "add": 18,
   "del": 0
  },
  {
   "path": "test/functional/wallet_balance.py",
   "add": 32,
   "del": 0
  },
  {
   "path": "test/functional/wallet_importprunedfunds.py",
   "add": 53,
   "del": 0
  },
  {
   "path": "test/functional/wallet_send.py",
   "add": 21,
   "del": 0
  }
 ],
 "test_lines": 181,
 "git": {
  "head": "37cd9d68856d7c7ececacdb723d34d964bfb6f6d",
  "head_matches_backup": true,
  "base": "cff00c547e7460fa1ec4aa1e38217793593d4263",
  "commits": [
   {
    "sha": "6de5f52331",
    "subject": "test: Test removedprunedfunds makes input TXOs spendable",
    "files": 1,
    "add": 53,
    "del": 0
   },
   {
    "sha": "d037e8f53b",
    "subject": "test: Test that send adds double spends as inactive",
    "files": 1,
    "add": 21,
    "del": 0
   },
   {
    "sha": "2b64eb52c1",
    "subject": "wallet: Make SubmitTxMemoryPoolAndRelay non-const",
    "files": 2,
    "add": 2,
    "del": 2
   },
   {
    "sha": "19562ad71b",
    "subject": "wallet: Add m_from_me to cache \"from me\" status",
    "files": 4,
    "add": 46,
    "del": 8
   },
   {
    "sha": "747e714b96",
    "subject": "wallet: Replace CachedTxIsFromMe with direct m_from_me lookup",
    "files": 5,
    "add": 5,
    "del": 17
   },
   {
    "sha": "460e948d57",
    "subject": "wallet: Make CWalletTx::m_state private with {get,set}ters",
    "files": 4,
    "add": 19,
    "del": 15
   },
   {
    "sha": "0432ff0225",
    "subject": "wallet: Store the TxState in each WalletTXO as well",
    "files": 5,
    "add": 68,
    "del": 24
   },
   {
    "sha": "6c6578f90a",
    "subject": "wallet: Get the depth in main chain for a TxState",
    "files": 2,
    "add": 10,
    "del": 3
   },
   {
    "sha": "6d257d6b8e",
    "subject": "wallet: Get the coinbase maturity state for a TXO and TxState",
    "files": 2,
    "add": 30,
    "del": 3
   },
   {
    "sha": "55553166ea",
    "subject": "wallet: Use WalletTXO stored state and coinbase rather than wtx",
    "files": 3,
    "add": 27,
    "del": 22
   },
   {
    "sha": "a79eafb416",
    "subject": "walletdb: Move ReorderTransactions to immediately after loading txs",
    "files": 3,
    "add": 34,
    "del": 30
   },
   {
    "sha": "09d6abe8dd",
    "subject": "wallet: Store a copy of m_from_me in WalletTXOs and use for \"from me\"",
    "files": 7,
    "add": 45,
    "del": 9
   },
   {
    "sha": "0e566d29b1",
    "subject": "wallet: Have WalletTXOs also store parent tx time",
    "files": 3,
    "add": 8,
    "del": 4
   },
   {
    "sha": "5888886240",
    "subject": "wallet: Include transaction version in WalletTXO",
    "files": 3,
    "add": 12,
    "del": 10
   },
   {
    "sha": "5741a65b69",
    "subject": "wallet: Remove unused WalletTXO::GetWalletTx()",
    "files": 3,
    "add": 5,
    "del": 11
   },
   {
    "sha": "85b847d15f",
    "subject": "wallet: Have IsSpent take a min_conf",
    "files": 2,
    "add": 10,
    "del": 4
   },
   {
    "sha": "223f1df655",
    "subject": "wallet: Iterate block txs in reverse on blockDisconnected",
    "files": 1,
    "add": 5,
    "del": 3
   },
   {
    "sha": "8571959493",
    "subject": "wallet, tests: Have CreateSyncedWallet use CWallet::Create",
    "files": 4,
    "add": 32,
    "del": 19
   },
   {
    "sha": "37cd9d6885",
    "subject": "wallet: Move definintely unusable TXOs to a separate container",
    "files": 6,
    "add": 153,
    "del": 14
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "6470e72036d149e3",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}