{
 "number": 36230,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36230",
 "title": "wallet: Improve `HasWalletDescriptor` performance and other canonical descriptor string followups",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2026-09-12T00:17:13Z",
 "updated_at": "2026-09-17T11:09:21Z",
 "age_days": 5,
 "draft": false,
 "labels": [
  "Wallet",
  "Needs Backport (32.x)"
 ],
 "milestone": "32.0",
 "base": "master",
 "head_sha": "f9359d06af84b070e93689c624fa149938b7e5a4",
 "head_ref": "miniscript-desc-id-perf",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2026-09-12T00:20:44Z",
   "sha": "41aab45e17920400b011366d7c257898d999e430"
  },
  {
   "t": "2026-09-16T20:35:39Z",
   "sha": "ad0cd36fdfc06e1a26f7ba9d2a9fe2b7d7b4eb6b"
  },
  {
   "t": "2026-09-17T06:52:12Z",
   "sha": "f9359d06af84b070e93689c624fa149938b7e5a4"
  }
 ],
 "additions": 86,
 "deletions": 30,
 "changed_files": 9,
 "commit_count": 7,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "polespinasa",
      "url": "https://github.com/bitcoin/bitcoin/pull/36230#pullrequestreview-5208172468"
     }
    ],
    "stale_ack": [
     {
      "login": "rkrux",
      "url": "https://github.com/bitcoin/bitcoin/pull/36230#pullrequestreview-5186373879"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "polespinasa": {
   "kind": "concept_ack",
   "hash": "41aab45e17920400b011366d7c257898d999e430",
   "t": "2026-09-15T10:01:35Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Faiqonli",
   "Sjors",
   "davidgumberg",
   "fanquake",
   "hebasto",
   "maflcko",
   "polespinasa",
   "rkrux"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-17T06:52:12Z",
  "last_reviewer_activity": "2026-09-17T11:09:20Z",
  "last_reviewer": "polespinasa",
  "author_silent_days": 0,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/script/descriptor.cpp",
  "src/script/descriptor.h",
  "src/wallet/scriptpubkeyman.cpp",
  "src/wallet/walletutil.cpp",
  "src/wallet/walletutil.h",
  "test/functional/wallet_backwards_compatibility.py",
  "test/get_previous_releases.py"
 ],
 "body": "Instead of re-computing the canonical descriptor string for every call to `HasWalletDescriptor`. `WalletDescriptor` will now compute it once upon construction and cache the hash of that string. The comparison uses a new `WalletDescriptor::Equals` function which compares the canonical string hashes. The hash is used to avoid holding possibly a large amount of memory for a string that is rarely used. This should fix the performance regression described in https://github.com/bitcoin/bitcoin/pull/35445#issuecomment-5633483553\n\nAlso addresses several review comments related to documentation and code readability:\n- https://github.com/bitcoin/bitcoin/pull/35445#discussion_r3924705885\n- https://github.com/bitcoin/bitcoin/pull/35445#discussion_r3924785232\n- https://github.com/bitcoin/bitcoin/pull/35445#discussion_r3925352894\n- https://github.com/bitcoin/bitcoin/pull/35445#discussion_r3925462409\n- https://github.com/bitcoin/bitcoin/pull/35445#discussion_r3937800942\n- https://github.com/bitcoin/bitcoin/pull/35445#discussion_r3961028442",
 "commits": [
  {
   "sha": "69f779caaec77adb4c07582fb4f766582a4f6209",
   "date": "2026-09-16T20:35:13Z",
   "message": "wallet: Remove WalletDescriptor's default constructor"
  },
  {
   "sha": "489e87587476cc497b31e33f86106b20f89b66f2",
   "date": "2026-09-16T20:35:15Z",
   "message": "wallet: Compare descriptors by hash of canonical string\n\nThe canonical string comparison was slow because it would compute the\ncanonical string for each comparison. This can be sped up by holding the\ncanonical string in memory, computed upon construction of\nWalletDescriptor. To reduce memory usage, this string is further hashed\nso that the comparison operates over the hash of the canonical string."
  },
  {
   "sha": "838c5ff4b5686422f5d10ed32938e7380c6483ce",
   "date": "2026-09-16T20:35:15Z",
   "message": "descriptor: Explicitly handle use_apostrophe cases"
  },
  {
   "sha": "6eecb8aeb1958399da85bea1a80e4654b2912612",
   "date": "2026-09-16T20:35:15Z",
   "message": "descriptor, doc: Clarify miniscript CompatDescriptorHash\n\nThe hardened indicator for Minscript expressions in\nCompatDescriptorHash uses whichever hardened indicator was originally\ngiven by the user."
  },
  {
   "sha": "3298323b7f043ff26495097418e7731ff846c6ea",
   "date": "2026-09-16T20:35:26Z",
   "message": "wallet: Document WalletDescriptor::UpdateFrom"
  },
  {
   "sha": "1f2aad275806d4292df54c4eb35bc8dce6a8fd74",
   "date": "2026-09-16T20:35:27Z",
   "message": "test: Simplify miniscript descriptor check in wallet back compat"
  },
  {
   "sha": "f9359d06af84b070e93689c624fa149938b7e5a4",
   "date": "2026-09-17T06:51:52Z",
   "message": "test: Add 31.1 to wallet back compat"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-12T00:20:44Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "41aab45e17920400b011366d7c257898d999e430"
  },
  {
   "t": "2026-09-12T11:41:38Z",
   "kind": "review",
   "who": "hebasto",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "41aab45e17920400b011366d7c257898d999e430",
   "text": "Tested 41aab45e17920400b011366d7c257898d999e430 on Alpine:\n```console\n$ ./build-pr36230/test/functional/test_runner.py wallet_miniscript\nTemporary test directory at /tmp/test_runner_\u20bf_\ud83c\udfc3_20260912_113648\nRemaining jobs: [wallet_miniscript.py]\n....................................................................................................................................................................................................................................................................................                                                                                                                                                                                                                                                                                    1/1 - wallet_miniscript.py passed, Duration: 144 s\n\nTEST                 | STATUS    | DURATION\n\nwallet_miniscript.py | \u2713 Passed  | 144 s\n\nALL                  | \u2713 Passed  | 144 s (accumulated)\nRuntime: 144 s\n\n```\n\nThe test performance [issue](https://github.com/bitcoin/bitcoin/pull/35445#issuecomment-5633483553) is resolved. This is also [confirmed](https://github.com/bitcoin/bitcoin/actions/runs/34661317268/job/103464303732?pr=36230#step:11:4702) by the CI:\n```\nwallet_miniscript.py                                                             | \u2713 Passed  | 262 s\n```"
  },
  {
   "t": "2026-09-12T12:11:18Z",
   "kind": "review_comment",
   "who": "rkrux",
   "assoc": "MEMBER",
   "path": "src/wallet/scriptpubkeyman.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": null,
   "text": "In 27034d39cb59c67a80304bf1d1f18a72ccf3aec9 \"wallet: Compare descriptors by hash of canonical string\"\n\nThis comment can be moved out to become the documentation for `Equals`."
  },
  {
   "t": "2026-09-12T12:14:44Z",
   "kind": "comment",
   "who": "hebasto",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nDo we need a benchmark for that?"
  },
  {
   "t": "2026-09-12T12:16:10Z",
   "kind": "review",
   "who": "rkrux",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "41aab45e17920400b011366d7c257898d999e430",
   "text": "lgtm ACK 41aab45e17920400b011366d7c257898d999e430\n\nI notice a 50% improvement in execution time of the `wallet_miniscript.py` test on my machine."
  },
  {
   "t": "2026-09-14T09:18:04Z",
   "kind": "comment",
   "who": "fanquake",
   "assoc": "MEMBER",
   "text": "cc @Sjors @davidgumberg @furszy"
  },
  {
   "t": "2026-09-15T08:04:28Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": null,
   "text": "In 27034d39cb59c67a80304bf1d1f18a72ccf3aec9 _wallet: Compare descriptors by hash of canonical string_: this should document that it only compares the canonical string."
  },
  {
   "t": "2026-09-15T08:13:58Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": null,
   "text": "In 27034d39cb59c67a80304bf1d1f18a72ccf3aec9 _wallet: Compare descriptors by hash of canonical string_: nit, it's never to late to use `m_` (and it would make `CalculateCanonicalHash` easier to read)."
  },
  {
   "t": "2026-09-15T09:11:55Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.cpp",
   "commit": "db58563a34949fbcf8bb4e477622400a8b4be5f5",
   "in_reply_to": null,
   "text": "In db58563a34949fbcf8bb4e477622400a8b4be5f5 _descriptor: Explicitly handle use_apostrophe cases_: nit, don't need no ternary."
  },
  {
   "t": "2026-09-15T09:15:25Z",
   "kind": "review",
   "who": "Sjors",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "41aab45e17920400b011366d7c257898d999e430",
   "text": "Code review 41aab45e17920400b011366d7c257898d999e430.\n\nTwo non-blocking questions / suggestions:\n\n- is there a reason why this cache lives on `WalletDescriptor` and not `Descriptor`?\n- an alternative design could be to have a just-in-time (`private`) `CanonicalHash()` getter."
  },
  {
   "t": "2026-09-15T09:33:25Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": null,
   "text": "in 27034d39cb59c67a80304bf1d1f18a72ccf3aec9 wallet: Compare descriptors by hash of canonical string\n\nWhy not instead of `Assert` just compute the hash if it was not already computed and then cache it?"
  },
  {
   "t": "2026-09-15T09:34:02Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": null,
   "text": "in wallet: Compare descriptors by hash of canonical string 27034d39cb59c67a80304bf1d1f18a72ccf3aec9\n\nDefault constructor does not initialize the hash.\nhttps://github.com/bitcoin/bitcoin/pull/36230#discussion_r4014126574 would fix it tho."
  },
  {
   "t": "2026-09-15T09:34:29Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/wallet/scriptpubkeyman.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 3996160000,
   "text": "+1 also we now compare by using the canonical string hash, not the string."
  },
  {
   "t": "2026-09-15T09:48:52Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.cpp",
   "commit": "db58563a34949fbcf8bb4e477622400a8b4be5f5",
   "in_reply_to": null,
   "text": "in db58563a34949fbcf8bb4e477622400a8b4be5f5 descriptor: Explicitly handle use_apostrophe cases\n\nThe code is duplicated from `OriginString`. Maybe a helper function?\n\ndiff\n\n ```diff\n$ git diff\ndiff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp\nindex 00028a6eb7..b3f09202cc 100644\n--- a/src/script/descriptor.cpp\n+++ b/src/script/descriptor.cpp\n@@ -249,6 +249,26 @@ public:\n\n     /** Whether this PubkeyProvider can always provide a public key without cache or private key arguments */\n     virtual bool CanSelfExpand() const = 0;\n+\n+protected:\n+    virtual bool UseApostrophe(StringType type, bool normalized, bool apostrophe) const\n+    {\n+        bool use_apostrophe{false};\n+            switch (type) {\n+            case StringType::COMPAT:\n+                // COMPAT always uses apostrophe to stay compatible with previous versions\n+                use_apostrophe = true;\n+                break;\n+            case StringType::CANONICAL:\n+                // CANONICAL always uses h\n+                use_apostrophe = false;\n+                break;\n+            case StringType::PUBLIC:\n+                use_apostrophe = (!normalized && apostrophe) ? true : false;\n+                break;\n+            } // no default case, so the compiler can warn about missing cases\n+        return use_apostrophe;\n+    }\n };\n\n class OriginPubkeyProvider final : public PubkeyProvider\n@@ -259,20 +279,7 @@ class OriginPubkeyProvider final : public PubkeyProvider\n\n     std::string OriginString(StringType type, bool normalized=false) const\n     {\n-        bool use_apostrophe{false};\n-        switch (type) {\n-        case StringType::COMPAT:\n-            // COMPAT always uses apostrophe to stay compatible with previous versions\n-            use_apostrophe = true;\n-            break;\n-        case StringType::CANONICAL:\n-            // CANONICAL always uses h\n-            use_apostrophe = false;\n-            break;\n-        case StringType::PUBLIC:\n-            use_apostrophe = (!normalized && m_apostrophe) ? true : false;\n-            break;\n-        } // no default case, so the compiler can warn about missing cases\n+        bool use_apostrophe{UseApostrophe(type, normalized, m_apostrophe)};\n         return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, use_apostrophe);\n     }\n\n@@ -521,20 +528,7 @@ public:\n     }\n     std::string ToString(StringType type, bool normalized) const\n     {\n-        bool use_apostrophe{false};\n-        switch (type) {\n-        case StringType::COMPAT:\n-            // COMPAT always uses apostrophe to stay compatible with previous versions\n-            use_apostrophe = true;\n-            break;\n-        case StringType::CANONICAL:\n-            // CANONICAL always uses h\n-            use_apostrophe = false;\n-            break;\n-        case StringType::PUBLIC:\n-            use_apostrophe = (!normalized && m_apostrophe) ? true : false;\n-            break;\n-        } // no default case, so the compiler can warn about missing cases\n+        bool use_apostrophe{UseApostrophe(type, normalized, m_apostrophe)};\n         std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path, /*apostrophe=*/use_apostrophe);\n         if (IsRange()) {\n             ret += \"/*\";\n\n```"
  },
  {
   "t": "2026-09-15T09:53:55Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.h",
   "commit": "814d48616002669864c75a335608be7b6249ea86",
   "in_reply_to": null,
   "text": "in 814d48616002669864c75a335608be7b6249ea86 descriptor, doc: Clarify miniscript CompatDescriptorHash\n\nserializtion -> serialization\n\nIn commit message:\n\nuse whichever -> uses whichever."
  },
  {
   "t": "2026-09-15T10:01:12Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "test/functional/wallet_backwards_compatibility.py",
   "commit": "f9359d06af84b070e93689c624fa149938b7e5a4",
   "in_reply_to": null,
   "text": "in 41aab45e17920400b011366d7c257898d999e430 test: Use 31.1 for wallet back compat instead of 31.0\n\nHere you add a version for 31.1 and keep 31.0 but in `add_nodes` you change from 31.0 to 31.1.\nMaybe keep both in `add_nodes` or remove 31.0 in here?"
  },
  {
   "t": "2026-09-15T10:01:35Z",
   "kind": "review",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "41aab45e17920400b011366d7c257898d999e430",
   "text": "concept ACK 41aab45e17920400b011366d7c257898d999e430\n\nLeft some comments."
  },
  {
   "t": "2026-09-16T17:58:59Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": null,
   "text": "nit: whitespace\n\nI wonder why this wasn't caught by the linter"
  },
  {
   "t": "2026-09-16T18:01:42Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4013389879,
   "text": "yeah, and I think it would be wise to change the name from `Equals()` to something scarier"
  },
  {
   "t": "2026-09-16T18:02:13Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4014126574,
   "text": "+1"
  },
  {
   "t": "2026-09-16T18:05:14Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4014126574,
   "text": "I think the best way to do this would be to change `CalculateCanonicalHash()` so that it returns the cached value if one exists, if not compute the hash and store it,\n\nThen you also don't have to do it in the constructor and can just lazily do it at comparison time"
  },
  {
   "t": "2026-09-16T18:17:09Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4014126574,
   "text": "example diff\n\n```diff\ndiff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp\nindex 3bc013c174..ab32a5283b 100644\n--- a/src/wallet/walletutil.cpp\n+++ b/src/wallet/walletutil.cpp\n@@ -97,16 +97,21 @@ void WalletDescriptor::UpdateFrom(const WalletDescriptor& other)\n     cache = other.cache;\n }\n\n-void WalletDescriptor::CalculateCanonicalHash()\n+uint256 WalletDescriptor::CalculateCanonicalHash() const\n {\n-    std::string canonical = descriptor->ToCanonicalString();\n-    CSHA256().Write((unsigned char*)canonical.data(), canonical.size()).Finalize(canonical_hash.begin());\n+    if (!m_canonical_hash) {\n+        std::string canonical = descriptor->ToCanonicalString();\n+        uint256 hash;\n+        CSHA256().Write((unsigned char*)canonical.data(), canonical.size()).Finalize(hash.begin());\n+        m_canonical_hash = hash;\n+    }\n+\n+    return m_canonical_hash.value();\n }\n\n bool WalletDescriptor::Equals(const WalletDescriptor& other) const\n {\n-    Assert(!canonical_hash.IsNull() && !other.canonical_hash.IsNull());\n-    return canonical_hash == other.canonical_hash;\n+    return CalculateCanonicalHash() == other.CalculateCanonicalHash();\n }\n\n } // namespace wallet\ndiff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h\nindex 6a3a111e6f..9b990cd246 100644\n--- a/src/wallet/walletutil.h\n+++ b/src/wallet/walletutil.h\n@@ -67,9 +67,8 @@ private:\n     int32_t next_index = 0; // Position of the next item to generate\n     int32_t range_end = 0; // Item after the last; end of range, exclusive, i.e. [range_start, range_end). This will increment with each TopUp()\n\n-    uint256 canonical_hash; // Hash of the canonical string, used as a shortcut for comparing canonical strings\n-\n-    void CalculateCanonicalHash();\n+    mutable std::optional<uint256> m_canonical_hash; // Hash of the canonical string, used as a shortcut for comparing canonical strings\n+    uint256 CalculateCanonicalHash() const;\n\n public:\n     std::shared_ptr<Descriptor> descriptor;\n@@ -113,7 +112,6 @@ public:\n             throw std::ios_base::failure(\"Can't load a multipath descriptor from databases\");\n         }\n         descriptor = std::move(descs.at(0));\n-        CalculateCanonicalHash();\n     }\n\n     SERIALIZE_METHODS(WalletDescriptor, obj)\n@@ -131,9 +129,7 @@ public:\n       range_end(descriptor->IsRange() ? range_end : 1),\n       descriptor(descriptor),\n       creation_time(creation_time)\n-    {\n-        CalculateCanonicalHash();\n-    }\n+    {}\n\n     void UpdateFrom(const WalletDescriptor& other);\n ```"
  },
  {
   "t": "2026-09-16T18:19:32Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4014126574,
   "text": "This would also fix this: https://github.com/bitcoin/bitcoin/pull/36230#discussion_r4014131679"
  },
  {
   "t": "2026-09-16T18:29:22Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4014126574,
   "text": "The fact that this is being cached also means that there ideally should be some guard against the `descriptor` mutating during the lifetime of the `WalletDescriptor`:\n\n```diff\n     int32_t next_index = 0; // Position of the next item to generate\n     int32_t range_end = 0; // Item after the last; end of range, exclusive, i.e. [range_start, range_end). This will increment with each TopUp()\n public:\n-    std::shared_ptr<Descriptor> descriptor;\n+    std::shared_ptr<const Descriptor> descriptor;\n     uint64_t creation_time = 0;\n     DescriptorCache cache;\n```\n\n```diff\n     void DeserializeDescriptor(const std::string& str)\n     {\n+        // This should not be used to mutate a descriptor.\n+        Assume(descriptor == nullptr);\n+\n         std::string error;\n         FlatSigningProvider keys;\n         auto descs = Parse(str, keys, error, true);\n```"
  },
  {
   "t": "2026-09-16T19:09:53Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.cpp",
   "commit": "db58563a34949fbcf8bb4e477622400a8b4be5f5",
   "in_reply_to": 4013951717,
   "text": "no it aint need'nt it"
  },
  {
   "t": "2026-09-16T20:14:32Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI don't think so"
  },
  {
   "t": "2026-09-16T20:35:39Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "ad0cd36fdfc06e1a26f7ba9d2a9fe2b7d7b4eb6b"
  },
  {
   "t": "2026-09-16T20:36:00Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4013389879,
   "text": "Added a comment and renamed to `IsCanonicallyEquivalent`"
  },
  {
   "t": "2026-09-16T20:36:07Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4013463936,
   "text": "Done"
  },
  {
   "t": "2026-09-16T20:36:12Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.cpp",
   "commit": "db58563a34949fbcf8bb4e477622400a8b4be5f5",
   "in_reply_to": 4013951717,
   "text": "Done"
  },
  {
   "t": "2026-09-16T20:36:21Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4014126574,
   "text": "Done"
  },
  {
   "t": "2026-09-16T20:36:46Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4014131679,
   "text": "I added a commit that removes the default constructor outright. We should do things RAII."
  },
  {
   "t": "2026-09-16T20:36:54Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.cpp",
   "commit": "db58563a34949fbcf8bb4e477622400a8b4be5f5",
   "in_reply_to": 4014250819,
   "text": "Done"
  },
  {
   "t": "2026-09-16T20:37:00Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.h",
   "commit": "814d48616002669864c75a335608be7b6249ea86",
   "in_reply_to": 4014294905,
   "text": "Fixed"
  },
  {
   "t": "2026-09-16T20:37:06Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "test/functional/wallet_backwards_compatibility.py",
   "commit": "f9359d06af84b070e93689c624fa149938b7e5a4",
   "in_reply_to": 4014348171,
   "text": "Added 31.0"
  },
  {
   "t": "2026-09-16T20:37:12Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4029146937,
   "text": "Fixed"
  },
  {
   "t": "2026-09-17T06:03:02Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "test/functional/wallet_backwards_compatibility.py",
   "commit": "f9359d06af84b070e93689c624fa149938b7e5a4",
   "in_reply_to": 4014348171,
   "text": "This is not resolved (CI still fails)"
  },
  {
   "t": "2026-09-17T06:12:17Z",
   "kind": "review_comment",
   "who": "Faiqonli",
   "assoc": "NONE",
   "path": "src/wallet/walletutil.h",
   "commit": "27034d39cb59c67a80304bf1d1f18a72ccf3aec9",
   "in_reply_to": 4013389879,
   "text": "1GGC7C8mcPeoXxpjAn1duquC2GEHkatpmF"
  },
  {
   "t": "2026-09-17T06:44:06Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "test/get_previous_releases.py",
   "commit": "f9359d06af84b070e93689c624fa149938b7e5a4",
   "in_reply_to": null,
   "text": "The ci fails because some file isn't found. I never checked this previous release code, but I guess this is deleting the 31.0 file so backwards compatibility test cannot find the file.\nInstead of delete 31.0 and change for 31.1 shouldn't this commit just add 31.1?"
  },
  {
   "t": "2026-09-17T06:52:12Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "f9359d06af84b070e93689c624fa149938b7e5a4"
  },
  {
   "t": "2026-09-17T10:27:59Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.cpp",
   "commit": "489e87587476cc497b31e33f86106b20f89b66f2",
   "in_reply_to": null,
   "text": "in 489e87587476cc497b31e33f86106b20f89b66f2 wallet: Compare descriptors by hash of canonical string\n\nThis is dead code as is now. Because every constructor pre-computes the canonical hash. Now that the default constructor is deleted I would just drop the `GetCanonicalHash` from the other constructors and only compute it the first time we need to compare it then store it for future usages.\n\nOtherwise can just go back to the assume and always pre-compute.\n\nI prefer first tho."
  },
  {
   "t": "2026-09-17T10:55:20Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "test/functional/wallet_backwards_compatibility.py",
   "commit": "f9359d06af84b070e93689c624fa149938b7e5a4",
   "in_reply_to": null,
   "text": "in f9359d06af84b070e93689c624fa149938b7e5a4 test: Add 31.1 to wallet back compat\n\n```suggestion\n                    if wallet_name == \"miniscript\" and n.version in (310000, 310100):\n```"
  },
  {
   "t": "2026-09-17T11:00:20Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/script/descriptor.cpp",
   "commit": "838c5ff4b5686422f5d10ed32938e7380c6483ce",
   "in_reply_to": null,
   "text": "in 838c5ff4b5686422f5d10ed32938e7380c6483ce descriptor: Explicitly handle use_apostrophe cases\n\nStatic instead of const?"
  },
  {
   "t": "2026-09-17T11:09:20Z",
   "kind": "review",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "f9359d06af84b070e93689c624fa149938b7e5a4",
   "text": "I think the descriptor should be protected from mutating, as David pointed in https://github.com/bitcoin/bitcoin/pull/36230#discussion_r4029422463\n\nIf not we could store a hash that does not match the current descriptor."
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-12T00:17:17Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-12T00:21:16Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-12T01:20:04Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-14T08:18:31Z",
   "action": "labeled",
   "label": "Needs Backport (32.x)",
   "who": "sedited"
  },
  {
   "t": "2026-09-16T21:39:21Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-17T07:53:35Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-12T00:19:50Z",
   "kind": "milestoned",
   "who": "achow101"
  },
  {
   "t": "2026-09-12T00:22:10Z",
   "kind": "renamed",
   "who": "achow101",
   "from": "wallet: Imrpove `HasWalletDescriptor` performance and other canonical descriptor string followups",
   "to": "wallet: Improve `HasWalletDescriptor` performance and other canonical descriptor string followups"
  }
 ],
 "text_chars": 14293,
 "text_tokens_estimate": 3573,
 "changed_paths": [
  "src/script/descriptor.cpp",
  "src/script/descriptor.h",
  "src/wallet/scriptpubkeyman.cpp",
  "src/wallet/test/wallet_tests.cpp",
  "src/wallet/walletdb.cpp",
  "src/wallet/walletutil.cpp",
  "src/wallet/walletutil.h",
  "test/functional/wallet_backwards_compatibility.py",
  "test/get_previous_releases.py"
 ],
 "files": [
  {
   "path": "src/script/descriptor.cpp",
   "add": 22,
   "del": 4
  },
  {
   "path": "src/script/descriptor.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/wallet/scriptpubkeyman.cpp",
   "add": 1,
   "del": 2
  },
  {
   "path": "src/wallet/test/wallet_tests.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 4,
   "del": 4
  },
  {
   "path": "src/wallet/walletutil.cpp",
   "add": 16,
   "del": 1
  },
  {
   "path": "src/wallet/walletutil.h",
   "add": 21,
   "del": 2
  },
  {
   "path": "test/functional/wallet_backwards_compatibility.py",
   "add": 10,
   "del": 14
  },
  {
   "path": "test/get_previous_releases.py",
   "add": 9,
   "del": 0
  }
 ],
 "test_lines": 37,
 "git": {
  "head": "f9359d06af84b070e93689c624fa149938b7e5a4",
  "head_matches_backup": true,
  "base": "f3fec67c3eeb27d5be1bc9d57ca737b74fcd762d",
  "commits": [
   {
    "sha": "69f779caae",
    "subject": "wallet: Remove WalletDescriptor's default constructor",
    "files": 3,
    "add": 10,
    "del": 7
   },
   {
    "sha": "489e875874",
    "subject": "wallet: Compare descriptors by hash of canonical string",
    "files": 3,
    "add": 30,
    "del": 4
   },
   {
    "sha": "838c5ff4b5",
    "subject": "descriptor: Explicitly handle use_apostrophe cases",
    "files": 1,
    "add": 22,
    "del": 4
   },
   {
    "sha": "6eecb8aeb1",
    "subject": "descriptor, doc: Clarify miniscript CompatDescriptorHash",
    "files": 1,
    "add": 1,
    "del": 1
   },
   {
    "sha": "3298323b7f",
    "subject": "wallet: Document WalletDescriptor::UpdateFrom",
    "files": 1,
    "add": 4,
    "del": 0
   },
   {
    "sha": "1f2aad2758",
    "subject": "test: Simplify miniscript descriptor check in wallet back compat",
    "files": 1,
    "add": 4,
    "del": 10
   },
   {
    "sha": "f9359d06af",
    "subject": "test: Add 31.1 to wallet back compat",
    "files": 2,
    "add": 15,
    "del": 4
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "cae9d8147b04ab54",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}