{
 "number": 35786,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35786",
 "title": "wallet: drop spent parents redundant cache invalidation and notification",
 "author": "furszy",
 "author_association": "MEMBER",
 "created_at": "2026-07-23T19:28:28Z",
 "updated_at": "2026-09-16T19:47:08Z",
 "age_days": 55,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
 "head_ref": "2026_wallet_drop_redundant_dirty_cache",
 "head_repo": "furszy/bitcoin-core",
 "head_history": [
  {
   "t": "2026-07-27T15:24:48Z",
   "sha": "0ec1a08131d175f6e2a8cca2bac02beb0e106c34"
  },
  {
   "t": "2026-08-13T14:40:54Z",
   "sha": "9a075fca1253249a5972f04e8167fb7b2f20a25b"
  },
  {
   "t": "2026-09-14T18:37:46Z",
   "sha": "4591142a8fc876d280e0a0a6a61ee3c497010a65"
  }
 ],
 "additions": 108,
 "deletions": 32,
 "changed_files": 4,
 "commit_count": 3,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/35786#issuecomment-5169321351"
     },
     {
      "login": "rkrux",
      "url": "https://github.com/bitcoin/bitcoin/pull/35786#issuecomment-5424644841"
     }
    ],
    "stale_ack": [
     {
      "login": "Bicaru20",
      "url": "https://github.com/bitcoin/bitcoin/pull/35786#issuecomment-5122543745"
     },
     {
      "login": "pablomartin4btc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35786#pullrequestreview-5055081708"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35998,
     "title": "wallet: Handle or explicitly ignore `WalletBatch` write failures",
     "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": 33034,
     "title": "wallet: Store transactions in a separate sqlite table",
     "author": "achow101"
    },
    {
     "number": 27865,
     "title": "wallet: Track no-longer-spendable TXOs separately",
     "author": "achow101"
    }
   ]
  }
 },
 "acks_parsed": {
  "pablomartin4btc": {
   "kind": "ack",
   "hash": "9a075fca1253249a5972f04e8167fb7b2f20a25b",
   "t": "2026-08-28T21:06:20Z",
   "stale": true
  },
  "Bicaru20": {
   "kind": "ack",
   "hash": "0ec1a08131",
   "t": "2026-07-29T19:27:35Z",
   "stale": true
  },
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-03T16:56:38Z",
   "stale": false
  },
  "rkrux": {
   "kind": "concept_ack",
   "hash": "9a075fca1253249a5972f04e8167fb7b2f20a25b",
   "t": "2026-08-26T11:30:56Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 2,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Bicaru20",
   "pablomartin4btc",
   "rkrux",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-14T18:37:46Z",
  "last_reviewer_activity": "2026-09-15T00:04:45Z",
  "last_reviewer": "w0xlt",
  "author_silent_days": 2,
  "waiting_on_author_days": 2,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   27286
  ],
  "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"
   }
  ],
  "conflicts": [
   35998,
   35716,
   35569,
   33034,
   27865
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/test/wallet_tests.cpp",
  "src/wallet/transaction.h",
  "src/wallet/wallet.cpp"
 ],
 "body": "When a transaction spends outputs owned by the wallet, the wallet invalidates\nthe cached amounts of the transactions those outputs came from, and\n`CommitTransaction` also notifies them. Neither is needed.\n\nBoth date back to pre-#27286, when a `CWalletTx` also cached the available\namount. Now the balance is read live from `m_txos` and `mapTxSpends`, and\n`CWalletTx` cache only its own, tx specific, incoming and outgoing amounts,\nand whether the tx is from the wallet. None of this changes after one of its\noutputs is spent. So the cache invalidation recomputes the same values, and\nthe notification makes the GUI refresh a status that has not changed.",
 "commits": [
  {
   "sha": "ed3553b161b37802a040a81424d4c133cc0e8635",
   "date": "2026-09-14T18:34:36Z",
   "message": "test: check spend doesn't change parent tx cache\n\nCWalletTx caches its own incoming and outgoing amounts, not the\nwallet available balance. Which means spending one of its outputs\ndo not change the cache in any way, in the same way, abandoning\nthe spend does not change it either.\n\nThe cache can become stalled only when a descriptor is imported."
  },
  {
   "sha": "503c10e63965359a384a30a70a1425fd48480f9f",
   "date": "2026-09-14T18:35:20Z",
   "message": "wallet: don't mark spent parents dirty on spend\n\nSince #27286 the CWalletTx amounts cache does not contain the available\namount anymore, only the per-tx incoming and outgoing amounts, these are\nfrozen except when new descriptor is imported.\n\nThis means we no longer need to mark the parent txs of a spend as dirty\nand recompute their values.\n\nWe can drop the MarkDirty loop in CommitTransaction along with the\nMarkInputsDirty calls in AddToWallet, SyncTransaction and\nRecursiveUpdateTxState, which do the same on the block, mempool,\nabandon and conflict paths.\n\nThe single mentioned import case exception is already covered by\nAddWalletDescriptor, which calls MarkDirty itself. So we can\nsafely delete MarkInputsDirty."
  },
  {
   "sha": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "date": "2026-09-14T18:35:21Z",
   "message": "wallet: drop redundant notification in CommitTransaction\n\nWhen the wallet creates a spend, CommitTransaction notifies the new tx\nand every wallet tx it spends from. But no other spending path does\nthis: SyncTransaction, for a spend arriving from the mempool or a block,\nnotifies only the tx it was given, and RecursiveUpdateTxState, for\nabandoning one, notifies only the txs whose own state changed. The ones\nwhose outputs become spendable again did not change state, so they are\nnot notified either.\n\nOn top of that, the GUI is the only consumer, and neither of its two\nsubscribers needs it. The transaction table takes the CT_UPDATED and\nmarks the notified tx row for a status refresh, but that status comes\nfrom the tx's own depth, maturity and abandoned flag, and from whether\nit is trusted. A spend changes none of that, and the amounts are not\npart of the refresh (which are actually not changed during a spend)."
  }
 ],
 "timeline": [
  {
   "t": "2026-07-24T17:27:45Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/wallet_tests.cpp",
   "commit": "6b6236ca4671edcfbe2890b1b77c9c2cd01d8bf9",
   "in_reply_to": null,
   "text": "typo nit (the body in 59da62d2e28d5986d9445e583663e63c9eed4911 is correct):\n```suggestion\n    // Now check that abandoning the spending tx does not change the parent cached values either.\n```"
  },
  {
   "t": "2026-07-24T17:39:46Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/transaction.h",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "in_reply_to": null,
   "text": "nit: in fb9c53c515cabbb89ff2429a2c7cd3f9f7da2823, the comment \"Only importing a descriptor could make values change\" applies to `m_cached_from_me` too? If so, perhaps could extend it to cover both.\n\n **Option A** \u2014 extend the block comment to cover both fields, drop the separate one:\n  ```\n// Memory only\n  //\n  // Represents the incoming/outgoing amounts of this specific tx and whether it\n  // spends any wallet inputs. Not the final wallet balance.\n  // Only importing a descriptor could make these values change.\n  enum AmountType { DEBIT, CREDIT, AMOUNTTYPE_ENUM_ELEMENTS };\n  mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS];\n  mutable std::optional<bool> m_cached_from_me{std::nullopt};\n```\n\n  **Option B** \u2014 keep the separate comment but cross-reference:\n```\n  // Cached value for whether the transaction spends any inputs known to the wallet.\n  // Same caching semantics as m_amounts: frozen except on descriptor import.\n  mutable std::optional<bool> m_cached_from_me{std::nullopt};\n```\n  Option A seems cleaner."
  },
  {
   "t": "2026-07-24T18:00:57Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "0ec1a08131d175f6e2a8cca2bac02beb0e106c34",
   "in_reply_to": null,
   "text": "any parent tx that is wallet-relevant would be its own `desc_tx` and get `MarkDirty()` in its own iteration, is that the reasoning? (apart from `AddWalletDescriptor` [calls](https://github.com/bitcoin/bitcoin/blob/3a2c52f9d70db6076ce64b8f7ef0eb301d7935a5/src/wallet/wallet.cpp#L3855) `CWallet::MarkDirty()` at the end regardless)?"
  },
  {
   "t": "2026-07-24T18:11:46Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/wallet_tests.cpp",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "in_reply_to": null,
   "text": "minor nit: perhaps `CheckValuesUnchanged` or `AssertValuesUnchanged` make the intent immediately clear at the call site..."
  },
  {
   "t": "2026-07-24T18:13:05Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/wallet_tests.cpp",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "in_reply_to": null,
   "text": "minor nit: `bool is_from_me;` (I understand that perhaps it could be mirroring `CachedTxIsFromMe` in [`spend.cpp`](https://github.com/bitcoin/bitcoin/blob/3a2c52f9d70db6076ce64b8f7ef0eb301d7935a5/src/wallet/spend.cpp#L444) - `bool tx_from_me`)"
  },
  {
   "t": "2026-07-24T18:21:52Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "in_reply_to": null,
   "text": "`MarkInputsDirty` was actually a slightly misleading name (it marks the parent transactions dirty, not the inputs themselves), so removing it entirely is the best outcome."
  },
  {
   "t": "2026-07-24T19:36:09Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "6b6236ca4671edcfbe2890b1b77c9c2cd01d8bf9",
   "text": "Concept ACK\n\nChecked https://github.com/bitcoin/bitcoin/pull/27286.\n\nAfter the above PR, the per-transaction caches (`m_amounts`, `m_cached_from_me`) reflect the transaction's own incoming/outgoing amounts \u2014 none of these change when a child tx spends one of the parent's outputs. So `MarkInputsDirty` is forcing a recompute that produces the same result, and the `CommitTransaction` notification loop is asking the GUI to repaint a row that looks identical. Both are dead work; removing them is correct.\n\nLeft some comments and nits (none blocking)."
  },
  {
   "t": "2026-07-27T14:53:26Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/wallet/transaction.h",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "in_reply_to": 3647124927,
   "text": "The change values also have the same behaviour. We could probably move the \"Only importing a descriptor could make values change\" at the top, just below \"Memory only\" or.. encapsulate all values in a struct. But still, seems a tiny nit we could leave it as is too."
  },
  {
   "t": "2026-07-27T14:57:48Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "0ec1a08131d175f6e2a8cca2bac02beb0e106c34",
   "in_reply_to": 3647241045,
   "text": "Not sure I understand the comment. Resetting a transaction's cache when no new descriptor has been imported results on the exact same values being re-computed again."
  },
  {
   "t": "2026-07-27T15:22:06Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/wallet/test/wallet_tests.cpp",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "in_reply_to": 3647305648,
   "text": "I don't think this add much value, the intent should be clear enough as is now. But could change it if you are strong on it."
  },
  {
   "t": "2026-07-27T15:23:49Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/wallet/test/wallet_tests.cpp",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
   "in_reply_to": 3647312106,
   "text": "Not sure. Everything is tx based, it is inside the TxValues struct."
  },
  {
   "t": "2026-07-27T15:24:48Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "0ec1a08131d175f6e2a8cca2bac02beb0e106c34"
  },
  {
   "t": "2026-07-27T15:24:50Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/wallet/test/wallet_tests.cpp",
   "commit": "6b6236ca4671edcfbe2890b1b77c9c2cd01d8bf9",
   "in_reply_to": 3647061720,
   "text": "done, pushed"
  },
  {
   "t": "2026-07-29T19:27:35Z",
   "kind": "comment",
   "who": "Bicaru20",
   "assoc": "CONTRIBUTOR",
   "text": "ACK 0ec1a08131.\n\nI agree `MarkInputsDirty` is no longer needed. As explained in the description, since https://github.com/bitcoin/bitcoin/pull/27286 `CWalletTx` no longer saves on cache the amount aviable to spend since this was moved to `m_txos` and `mapTxSpends`. Now, the `CWalletTx` it only save the `m_amounts` which saves the fixed incoming and outgoing amounts, and the `m_cached_from_me` which saves whether the tx originated from the wallet.\n\nIn bdf7c84eda, the tests checks, through various paths, that the cached values are not changed. The first test checks the `CommitTransaction`  and `AddToWallet` path by commiting a transaction that spends from a coin owned by the wallet, and the `RecursiveUpdateTxState` path by using `AbandonTransaction` on a spending transaction. The other test, chechs the `SyncTransaction` path by using `transactionAddedToMempool` with a spending transaction."
  },
  {
   "t": "2026-08-03T16:56:38Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-08-13T14:40:54Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "9a075fca1253249a5972f04e8167fb7b2f20a25b"
  },
  {
   "t": "2026-08-26T11:30:56Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "MEMBER",
   "text": "Concept ACK 9a075fca1253249a5972f04e8167fb7b2f20a25b\n\nI never preferred reading those mark dirty calls, used to break my flow.\nGood that they can be removed now."
  },
  {
   "t": "2026-08-28T21:06:20Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "9a075fca1253249a5972f04e8167fb7b2f20a25b",
   "text": "ACK 9a075fca1253249a5972f04e8167fb7b2f20a25b\n\nConfirmed only the typo [fix](https://github.com/bitcoin/bitcoin/pull/35786#discussion_r3658548501) and a [rebase](https://github.com/bitcoin/bitcoin/compare/0ec1a08131d175f6e2a8cca2bac02beb0e106c34..9a075fca1253249a5972f04e8167fb7b2f20a25b) since my last review \u2014 nothing else changed."
  },
  {
   "t": "2026-09-14T18:37:46Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "4591142a8fc876d280e0a0a6a61ee3c497010a65"
  },
  {
   "t": "2026-09-15T00:04:45Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "This invalidation is still needed in a narrow case. A transaction pays 1 BTC to each of two wallet addresses, but the wallet hasn\u2019t derived one address yet, so it recognizes and caches only 1 BTC.\n\nA later spend can expand the keypool and make the wallet recognize the second payment. Without this invalidation, `gettransaction` still reports 1 BTC, even though details shows both payments.\n\nThe test below passes on master (51ddab532c) and fails on 4591142a8f: the parent\u2019s amount remains 1 BTC when 2 BTC is expected.\n\nIt\u2019s a very narrow edge case, but it may still be worth addressing.\n\ndiff\n\n```diff\ndiff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py\nindex aae6985f5b..a8277bb231 100755\n--- a/test/functional/wallet_keypool_topup.py\n+++ b/test/functional/wallet_keypool_topup.py\n@@ -11,8 +11,11 @@ Two nodes. Node1 is under test. Node0 is providing transactions and generating b\n - Stop node1, clear the datadir, move wallet file back into the datadir and restart node1.\n - connect node1 to node0. Verify that they sync and node1 receives its funds.\"\"\"\n import shutil\n+from decimal import Decimal\n\n from test_framework.blocktools import COINBASE_MATURITY\n+from test_framework.descriptors import descsum_create\n+from test_framework.extendedkey import ExtendedPrivateKey\n from test_framework.test_framework import BitcoinTestFramework\n from test_framework.util import (\n     assert_equal,\n@@ -88,6 +91,64 @@ class KeypoolRestoreTest(BitcoinTestFramework):\n             elif output_type == 'bech32m':\n                 assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], \"m/86h/1h/0h/0/110\")\n\n+        self.test_cached_parent_credit()\n+\n+    def test_cached_parent_credit(self):\n+        self.log.info(\"Test parent credit is refreshed when a spend expands the keypool\")\n+        self.restart_node(1, extra_args=['-keypool=1'])\n+        self.connect_nodes(0, 1)\n+        self.sync_all()\n+\n+        for delivery in (\"mempool\", \"block\"):\n+            self.log.info(f\"Test keypool expansion via a {delivery} notification\")\n+            wallet_name = f\"cached_credit_{delivery}\"\n+            self.nodes[1].createwallet(wallet_name, blank=True)\n+            wallet = self.nodes[1].get_wallet_rpc(wallet_name)\n+            desc = descsum_create(f\"wpkh({ExtendedPrivateKey.generate().to_string()}/*)\")\n+            assert_equal(wallet.importdescriptors([{\n+                \"desc\": desc,\n+                \"timestamp\": \"now\",\n+                \"range\": [0, 0],\n+                \"active\": True,\n+            }]), [{\"success\": True}])\n+            addresses = self.nodes[0].deriveaddresses(desc, [0, 2])\n+\n+            # Index 2 is outside the keypool even after receiving at index 0\n+            # extends it to index 1. Keep these outputs in this order.\n+            raw_parent = self.nodes[0].createrawtransaction([], [{addresses[2]: 1}, {addresses[0]: 1}])\n+            funded_parent = self.nodes[0].fundrawtransaction(raw_parent, {\"changePosition\": 2})\n+            signed_parent = self.nodes[0].signrawtransactionwithwallet(funded_parent[\"hex\"])\n+            parent_txid = self.nodes[0].sendrawtransaction(signed_parent[\"hex\"])\n+            self.generate(self.nodes[0], 1)\n+\n+            # Populate the parent's credit cache before index 2 becomes owned.\n+            parent = wallet.gettransaction(parent_txid)\n+            assert_equal(parent[\"amount\"], 1)\n+            assert_equal(len(parent[\"details\"]), 1)\n+            assert_equal(wallet.getaddressinfo(addresses[2])[\"ismine\"], False)\n+\n+            raw_spend = self.nodes[0].createrawtransaction(\n+                [{\"txid\": parent_txid, \"vout\": 1}], [{addresses[1]: Decimal(\"0.9999\")}])\n+            signed_spend = wallet.signrawtransactionwithwallet(raw_spend)\n+            assert_equal(signed_spend[\"complete\"], True)\n+            if delivery == \"block\":\n+                self.disconnect_nodes(0, 1)\n+                assert_equal(self.nodes[1].getconnectioncount(), 0)\n+            self.nodes[0].sendrawtransaction(signed_spend[\"hex\"])\n+            if delivery == \"block\":\n+                self.generate(self.nodes[0], 1, sync_fun=self.no_op)\n+                self.connect_nodes(0, 1)\n+                self.sync_all()\n+            else:\n+                self.sync_mempools()\n+\n+            # Processing the spend at index 1 extends the keypool to index 2,\n+            # making the parent's other output ours without a descriptor import.\n+            assert_equal(wallet.getaddressinfo(addresses[2])[\"ismine\"], True)\n+            parent = wallet.gettransaction(parent_txid)\n+            assert_equal(len(parent[\"details\"]), 2)\n+            assert_equal(parent[\"amount\"], 2)\n+\n\n if __name__ == '__main__':\n     KeypoolRestoreTest(__file__).main()\n```"
  }
 ],
 "labels_log": [
  {
   "t": "2026-07-23T19:28:32Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-04T22:54:34Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-13T16:11:26Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-13T16:13:17Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-17T09:00:27Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-12T12:02:08Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-14T20:18:23Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 12214,
 "text_tokens_estimate": 3053,
 "changed_paths": [
  "src/wallet/test/wallet_tests.cpp",
  "src/wallet/transaction.h",
  "src/wallet/wallet.cpp",
  "src/wallet/wallet.h"
 ],
 "files": [
  {
   "path": "src/wallet/test/wallet_tests.cpp",
   "add": 101,
   "del": 0
  },
  {
   "path": "src/wallet/transaction.h",
   "add": 7,
   "del": 3
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 0,
   "del": 26
  },
  {
   "path": "src/wallet/wallet.h",
   "add": 0,
   "del": 3
  }
 ],
 "test_lines": 101,
 "git": {
  "head": "4591142a8fc876d280e0a0a6a61ee3c497010a65",
  "head_matches_backup": true,
  "base": "cff00c547e7460fa1ec4aa1e38217793593d4263",
  "commits": [
   {
    "sha": "ed3553b161",
    "subject": "test: check spend doesn't change parent tx cache",
    "files": 1,
    "add": 101,
    "del": 0
   },
   {
    "sha": "503c10e639",
    "subject": "wallet: don't mark spent parents dirty on spend",
    "files": 3,
    "add": 7,
    "del": 26
   },
   {
    "sha": "4591142a8f",
    "subject": "wallet: drop redundant notification in CommitTransaction",
    "files": 1,
    "add": 0,
    "del": 6
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "2d192164dc4423fc",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}