{
 "number": 35444,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35444",
 "title": "wallet: make descriptor SPKM mutex non-recursive",
 "author": "w0xlt",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-06-02T16:33:24Z",
 "updated_at": "2026-09-17T00:11:49Z",
 "age_days": 106,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "b6d586638a9401271da6dece5f57233e6ccfbdd4",
 "head_ref": "wallet-descriptor-spkm-mutex-19303-simple-index",
 "head_repo": "w0xlt/bitcoin",
 "head_history": [
  {
   "t": "2026-06-17T23:27:40Z",
   "sha": "f762354edfe3a6e4a66bc280a4b84b47eb9d4fdd"
  },
  {
   "t": "2026-06-20T08:27:30Z",
   "sha": "2bd9f4dc5d7fbe210d53f4c7b80d1b87f5257007"
  },
  {
   "t": "2026-09-15T22:11:52Z",
   "sha": "4bb39d56b74a949c3e5b58f38ac5433c7e3cf644"
  },
  {
   "t": "2026-09-15T22:25:50Z",
   "sha": "56ca23f54b3cc82c213a7bb920b39ba2af2d7392"
  },
  {
   "t": "2026-09-15T22:44:21Z",
   "sha": "b6d586638a9401271da6dece5f57233e6ccfbdd4"
  }
 ],
 "additions": 506,
 "deletions": 213,
 "changed_files": 8,
 "commit_count": 4,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "pablomartin4btc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35444#pullrequestreview-4464278390"
     },
     {
      "login": "hebasto",
      "url": "https://github.com/bitcoin/bitcoin/pull/35444#pullrequestreview-4465420475"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35998,
     "title": "wallet: Handle or explicitly ignore `WalletBatch` write failures",
     "author": "achow101"
    },
    {
     "number": 35989,
     "title": "wallet: fix crash on importdescriptors with a range ending at 2^31-1",
     "author": "shuv-amp"
    },
    {
     "number": 35752,
     "title": "wallet: make encryption state updates atomic",
     "author": "l0rinc"
    },
    {
     "number": 34969,
     "title": "fuzz: several improvements to scriptpubkeyman harness",
     "author": "brunoerg"
    },
    {
     "number": 34520,
     "title": "refactor: Add [[nodiscard]] to functions returning bool+mutable ref",
     "author": "maflcko"
    }
   ]
  }
 },
 "acks_parsed": {
  "pablomartin4btc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-06-10T04:16:20Z",
   "stale": false
  },
  "hebasto": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-06-10T07:13:41Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "hebasto",
   "pablomartin4btc",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-15T22:55:30Z",
  "last_reviewer_activity": "2026-08-27T11:37:43Z",
  "last_reviewer": "sedited",
  "author_silent_days": 1,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": [
   35998,
   35989,
   35752,
   34969,
   34520
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/rpc/backup.cpp",
  "src/wallet/rpc/wallet.cpp",
  "src/wallet/scriptpubkeyman.cpp"
 ],
 "body": "Part of https://github.com/bitcoin/bitcoin/issues/19303\n\nThis PR makes `DescriptorScriptPubKeyMan` use a non-recursive private mutex by moving locking responsibility into its public methods and using lock-held internal helpers for shared logic.\n\nThe change removes external locking of `DescriptorScriptPubKeyMan::cs_desc_man`, splits recursive internal call paths such as keypool top-up and descriptor updates, and then renames the mutex to `m_desc_mutex`.\n\nNo wallet database format, RPC behavior, keypool semantics, or external signer behavior is intended to change.",
 "commits": [
  {
   "sha": "e12579abc7b53058f2f29930d60b6fee1b549676",
   "date": "2026-09-15T20:18:42Z",
   "message": "wallet: split descriptor SPKM locked internals\n\nPrepare DescriptorScriptPubKeyMan for making cs_desc_man non-recursive by\nseparating public lock-taking entry points from helpers that require\ncs_desc_man to already be held.\n\nThis removes recursive calls from the core descriptor paths without changing\ncaller behavior yet. The public mutex remains recursive and externally\naccessible in this commit, so existing call sites continue to build while\nlocked helpers centralize the shared logic used by keypool top-up, descriptor\nupdates, address generation, and descriptor matching."
  },
  {
   "sha": "1c32c431c028f0940c557384caad0b76069cedd8",
   "date": "2026-09-15T20:19:35Z",
   "message": "wallet: stop locking descriptor SPKM externally\n\nMove the remaining descriptor SPKM callers to self-locking public methods\ninstead of taking cs_desc_man directly. This removes direct locks from wallet,\nRPC, and fuzz code and lets DescriptorScriptPubKeyMan own synchronization for\nits descriptor state.\n\nExternal signer wallet creation also stops mutating m_wallet_descriptor under\na direct external lock. It now calls SetupDescriptor, which performs the same\ndescriptor write and top-up sequence behind the SPKM API.\n\nThe mutex remains recursive and public in this commit, so this is a call-site\ncleanup that sets up the final non-recursive conversion without changing\nwallet behavior."
  },
  {
   "sha": "eebdf62e418af1f8d4ae3f1f32dde5c1c9e1d7fd",
   "date": "2026-09-15T22:42:57Z",
   "message": "wallet: make descriptor SPKM mutex non-recursive\n\nMake cs_desc_man private and replace RecursiveMutex with Mutex. Annotate\nself-locking entry points with negative lock requirements and retain\nLOCKS_EXCLUDED on factory-only setup methods.\n\nMove address-availability notifications to the outer operations. Compare\navailability before and after each operation under the mutex, then notify\nafter unlocking so synchronous callbacks can query the wallet safely.\nNotify only for a net change, including partial updates that throw, and\nrethrow captured exceptions after notification.\n\nAdd a watch-only hardened-descriptor regression test covering synchronous\ncallback re-entry, address consumption and return, range extension,\nunchanged availability, and partial failures."
  },
  {
   "sha": "b6d586638a9401271da6dece5f57233e6ccfbdd4",
   "date": "2026-09-15T22:42:57Z",
   "message": "scripted-diff: wallet: rename cs_desc_man to m_desc_mutex\n\nNow that DescriptorScriptPubKeyMan::cs_desc_man is a non-recursive, private\nMutex, rename it to m_desc_mutex to match the member-mutex naming convention\nadopted by the other RecursiveMutex->Mutex conversions. No behavior change.\n\n-BEGIN VERIFY SCRIPT-\nsed -i 's/cs_desc_man/m_desc_mutex/g' $(git grep -l cs_desc_man -- '*.cpp' '*.h')\n-END VERIFY SCRIPT-"
  }
 ],
 "timeline": [
  {
   "t": "2026-06-10T03:42:12Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/scriptpubkeyman.cpp",
   "commit": "2bd9f4dc5d7fbe210d53f4c7b80d1b87f5257007",
   "in_reply_to": null,
   "text": "Nice cleanup: this avoids updating index after a failed destination request."
  },
  {
   "t": "2026-06-10T03:52:59Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/rpc/backup.cpp",
   "commit": "2bd9f4dc5d7fbe210d53f4c7b80d1b87f5257007",
   "in_reply_to": null,
   "text": "I think removing the external lock here is fine now that `GetWalletDescriptor()` and `GetDescriptorString()` are self-locking. One subtle difference is that the descriptor metadata and descriptor string are now read under two separate locks instead of one caller-held lock. I don\u2019t see an issue because descriptor state should not normally change concurrently here, but I wanted to double-check that no atomicity across these two reads was intended."
  },
  {
   "t": "2026-06-10T04:09:50Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/rpc/wallet.cpp",
   "commit": "6f3f81c0635519f3c6147cd5c32b7a3aa9cc589b",
   "in_reply_to": null,
   "text": "Very minor readability nit: would it be clearer to first store the result of GetWalletDescriptor() in a local variable and then call `GetPubKeys()` on it? That would make it more obvious that the operation is performed on a copied descriptor returned by the accessor, rather than directly on internal SPKM state.\n```suggestion\n            auto wallet_descriptor = desc_spkm.GetWalletDescriptor(); // locks internally, returns copy\n            wallet_descriptor.descriptor->GetPubKeys(pubkeys, extpubs); // operates on the copy\n```"
  },
  {
   "t": "2026-06-10T04:16:20Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "6f3f81c0635519f3c6147cd5c32b7a3aa9cc589b",
   "text": "Concept ACK.\n\nI reviewed the commits individually. The split between public self-locking methods and lock-held helpers makes sense to me, and the external lock removals seem correct now that descriptor accessors return/ use state under their own lock. I left one question around `listdescriptors`, where two descriptor reads are now done through separate self-locking calls instead of under one caller-held lock, and a minor readability suggestion regarding the `cs_desc_man` lock removal in `addhdkey()`."
  },
  {
   "t": "2026-06-10T07:13:41Z",
   "kind": "review",
   "who": "hebasto",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "6f3f81c0635519f3c6147cd5c32b7a3aa9cc589b",
   "text": "Concept ACK."
  },
  {
   "t": "2026-06-17T22:39:31Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/rpc/backup.cpp",
   "commit": "2bd9f4dc5d7fbe210d53f4c7b80d1b87f5257007",
   "in_reply_to": 3385370769,
   "text": "Yes, I also think it is safe because `listdescriptors` holds `wallet->cs_wallet` across both calls. Any update that could change either the descriptor metadata or descriptor string also needs `cs_wallet`, so it cannot run between those two reads."
  },
  {
   "t": "2026-06-17T23:27:40Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "f762354edfe3a6e4a66bc280a4b84b47eb9d4fdd"
  },
  {
   "t": "2026-06-17T23:28:53Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/rpc/wallet.cpp",
   "commit": "6f3f81c0635519f3c6147cd5c32b7a3aa9cc589b",
   "in_reply_to": 3385435350,
   "text": "Done. Thanks."
  },
  {
   "t": "2026-06-18T04:26:45Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased."
  },
  {
   "t": "2026-06-20T08:27:30Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "2bd9f4dc5d7fbe210d53f4c7b80d1b87f5257007"
  },
  {
   "t": "2026-08-27T11:37:43Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Ping for rebase @w0xlt"
  },
  {
   "t": "2026-09-15T22:11:52Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "4bb39d56b74a949c3e5b58f38ac5433c7e3cf644"
  },
  {
   "t": "2026-09-15T22:25:50Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "56ca23f54b3cc82c213a7bb920b39ba2af2d7392"
  },
  {
   "t": "2026-09-15T22:44:21Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "b6d586638a9401271da6dece5f57233e6ccfbdd4"
  },
  {
   "t": "2026-09-15T22:55:30Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased."
  }
 ],
 "labels_log": [
  {
   "t": "2026-06-02T16:33:28Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-14T16:51:36Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-18T01:25:38Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-03T11:20:46Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T22:13:28Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T22:26:26Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T23:45:11Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 4942,
 "text_tokens_estimate": 1235,
 "changed_paths": [
  "src/wallet/export.cpp",
  "src/wallet/external_signer_scriptpubkeyman.cpp",
  "src/wallet/rpc/wallet.cpp",
  "src/wallet/scriptpubkeyman.cpp",
  "src/wallet/scriptpubkeyman.h",
  "src/wallet/test/fuzz/scriptpubkeyman.cpp",
  "src/wallet/test/scriptpubkeyman_tests.cpp",
  "src/wallet/wallet.cpp"
 ],
 "files": [
  {
   "path": "src/wallet/export.cpp",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/wallet/external_signer_scriptpubkeyman.cpp",
   "add": 2,
   "del": 9
  },
  {
   "path": "src/wallet/rpc/wallet.cpp",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/wallet/scriptpubkeyman.cpp",
   "add": 317,
   "del": 143
  },
  {
   "path": "src/wallet/scriptpubkeyman.h",
   "add": 67,
   "del": 52
  },
  {
   "path": "src/wallet/test/fuzz/scriptpubkeyman.cpp",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/wallet/test/scriptpubkeyman_tests.cpp",
   "add": 118,
   "del": 0
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 2,
   "del": 6
  }
 ],
 "test_lines": 119,
 "git": {
  "head": "b6d586638a9401271da6dece5f57233e6ccfbdd4",
  "head_matches_backup": true,
  "base": "57721f8074c2e8ccfe4c6d10114d4683dca58118",
  "commits": [
   {
    "sha": "e12579abc7",
    "subject": "wallet: split descriptor SPKM locked internals",
    "files": 2,
    "add": 126,
    "del": 58
   },
   {
    "sha": "1c32c431c0",
    "subject": "wallet: stop locking descriptor SPKM externally",
    "files": 5,
    "add": 4,
    "del": 18
   },
   {
    "sha": "eebdf62e41",
    "subject": "wallet: make descriptor SPKM mutex non-recursive",
    "files": 3,
    "add": 376,
    "del": 137
   },
   {
    "sha": "b6d586638a",
    "subject": "scripted-diff: wallet: rename cs_desc_man to m_desc_mutex",
    "files": 2,
    "add": 110,
    "del": 110
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "fac33def9b88cb1f",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}