{
 "number": 35170,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35170",
 "title": "test: Better test coverage for legacy ParseHDKeypath()",
 "author": "optout21",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-04-28T09:21:34Z",
 "updated_at": "2026-09-04T13:33:13Z",
 "age_days": 142,
 "draft": false,
 "labels": [
  "Tests"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "56b1a59b47bf7eb80bad5cbaf115e524a4192d57",
 "head_ref": "2604-parse-keypath-legacy",
 "head_repo": "optout21/bitcoin",
 "head_history": [
  {
   "t": "2026-04-28T09:24:32Z",
   "sha": "c1bbc27002db756acd1bbe08d33bf221fcc84bbb"
  },
  {
   "t": "2026-04-28T09:31:39Z",
   "sha": "c2def1524577ee1cb0187875b23a43ddfc1b3f40"
  },
  {
   "t": "2026-04-28T09:51:21Z",
   "sha": "2bd25fc3a4ae663b0da77a9a4d9fecac50260183"
  },
  {
   "t": "2026-04-29T04:21:26Z",
   "sha": "a548fac787a89e2e69ec89a6f9450935b3a9e08f"
  },
  {
   "t": "2026-06-17T19:48:34Z",
   "sha": "cf76d345c85283e64a37e850b92b748c5ee29d2b"
  },
  {
   "t": "2026-08-18T18:06:18Z",
   "sha": "b3e4cd2eef85a55b2a734f5f9817993ff943e852"
  },
  {
   "t": "2026-08-21T11:32:31Z",
   "sha": "56b1a59b47bf7eb80bad5cbaf115e524a4192d57"
  }
 ],
 "additions": 127,
 "deletions": 103,
 "changed_files": 6,
 "commit_count": 5,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35170#pullrequestreview-4187740368"
     },
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/35170#issuecomment-5334478635"
     }
    ],
    "stale_ack": [
     {
      "login": "haishmg",
      "url": "https://github.com/bitcoin/bitcoin/pull/35170#issuecomment-4337163769"
     },
     {
      "login": "brunoerg",
      "url": "https://github.com/bitcoin/bitcoin/pull/35170#pullrequestreview-4776853898"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-04-28T10:19:46Z",
   "stale": false
  },
  "haishmg": {
   "kind": "ack",
   "hash": "2bd25fc3a4ae663b0da77a9a4d9fecac50260183",
   "t": "2026-04-28T16:24:38Z",
   "stale": true
  },
  "brunoerg": {
   "kind": "ack",
   "hash": "cf76d345c85283e64a37e850b92b748c5ee29d2b",
   "t": "2026-07-24T21:24:42Z",
   "stale": true
  },
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-18T21:49:30Z",
   "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": [
   "brunoerg",
   "haishmg",
   "l0rinc",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-08-21T11:32:34Z",
  "last_reviewer_activity": "2026-08-18T21:49:30Z",
  "last_reviewer": "w0xlt",
  "author_silent_days": 27,
  "waiting_on_author_days": 0,
  "days_since_update": 13
 },
 "refs": {
  "mentioned": [
   32768,
   32784,
   35069
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 32768,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-06-25",
    "title": "wallet: Remove `CWalletTx::fTimeReceivedIsTxTime`"
   },
   {
    "number": 32784,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-15",
    "title": "wallet: derivehdkey RPC to get xpub at arbitrary path"
   },
   {
    "number": 35069,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-19",
    "title": "Refactor keypath parser"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/test/fuzz/parse_hd_keypath.cpp",
  "src/wallet/test/psbt_wallet_tests.cpp"
 ],
 "body": "Background: The `ParseHDKeypath` method (`src/util/bip32.cpp`) is used only in a limited way: it is used when loading legacy wallets. Note that similar functionality is implemented independently in `src/script/descriptor.cpp` (`ParseKeyPath`, `ParseKeyPathNum`).\n\nDeficiencies: The method has a unit test, but it checks only the bool result, and not the parsed keypath. This can be demonstrated by breaking the implementation by adding a `keypath.clear();` statement just before the final `return true;`. With this obviously broken implementation the tests pass.\n\nSolution proposed: Extend the tests with checks (without modifying the behavior), and also rename the method.\n\nNotes:\n- Behavior is not changed. This is a legacy method expected to be removed in the future. Production code is touched only by the rename.\n- Here there is no attempt to merge `ParseHDKeypath` with `ParseKeyPath`.\n\nPotential future steps: The duplicated implementation -- `ParseHDKeypath` and `ParseKeyPath` -- could be merged, to reduce code duplication. Combining the two implementations could result in slightly better code; the former implementation looks better, while the latter has better unit test coverage.\n\nChanges:\n\n- Extend the unit tests with checks for the returned parsed keypath. Add the expected parsed numerical keypath as test data. Note: breaking the production code as described above now results in test failure.\n\n- In the unit test also perform round-trip conversion: convert to string, then parse again, and convert to string again. In the last two steps the results should match the input before the round-trip (note: this is not true for the first roundtrip). Additionally, add negative tests for the unsupported 'h' hardened delimiter.\n\n- Rename the method `ParseHDKeypath` to `ParseHDKeypathLegacy`, to make it clearer that it is used only in the legacy wallet use case, and reduce the chance of confusion with similar parsing functionality in `descriptor.cpp`.",
 "commits": [
  {
   "sha": "41bba5e1b0977d5b4225f77f8ed103b7b145cd11",
   "date": "2026-08-21T11:15:34Z",
   "message": "test: Refactor in bip32_tests: extract test data in an array\n\nFor easier expansion of the test calls, extract test data in an array,\nand have a single instance of calling the method under test."
  },
  {
   "sha": "4c7969f6e2a0a219b0527385f89b04e823e24746",
   "date": "2026-08-21T11:15:47Z",
   "message": "test: bip32_tests: Add expected result checks\n\nExtend the unit tests with checks for the returned parsed keypath.\nAdd the expected parsed numerical keypath as test data."
  },
  {
   "sha": "bb057076dcc4b5f26e88640b87e186dd04804f5d",
   "date": "2026-08-21T11:21:26Z",
   "message": "test: bip32_tests: Add round-trip tests\n\nIn the unit test perform round-trip conversion: convert to string, then parse again,\nand convert to string again. In the last two steps the results should match\nthe input before the round-trip (note: this is not true for the first roundtrip)."
  },
  {
   "sha": "dd665be15b553ef4aa5913c03f4b5e3a53751ce3",
   "date": "2026-08-21T11:21:44Z",
   "message": "test: bip32_tests: Add some more tests cases"
  },
  {
   "sha": "56b1a59b47bf7eb80bad5cbaf115e524a4192d57",
   "date": "2026-08-21T11:21:44Z",
   "message": "scripted-diff: Rename ParseHDKeypath to ParseHDKeypathLegacy\n\nRename the method `ParseHDKeypath` to `ParseHDKeypathLegacy`,\nto make it clearer that it is used only in the legacy wallet use case,\nand reduce the chance of confusion with similar parsing functionality\nin `descriptor.cpp`.\n\n-BEGIN VERIFY SCRIPT-\ngit grep -l ParseHDKeypath -- '*.h' '*.cpp' | xargs sed -i 's/ParseHDKeypath/ParseHDKeypathLegacy/g'\n-END VERIFY SCRIPT-"
  }
 ],
 "timeline": [
  {
   "t": "2026-04-28T09:24:32Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "c1bbc27002db756acd1bbe08d33bf221fcc84bbb"
  },
  {
   "t": "2026-04-28T09:31:39Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "c2def1524577ee1cb0187875b23a43ddfc1b3f40"
  },
  {
   "t": "2026-04-28T09:51:21Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "2bd25fc3a4ae663b0da77a9a4d9fecac50260183"
  },
  {
   "t": "2026-04-28T10:15:53Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/psbt_wallet_tests.cpp",
   "commit": "67ae7a2df4c847263e1da4c01a4c03e4fb85863f",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\ncould we swap the order to store the value first and the validity next to follow the order in the usage, i.e. `BOOST_CHECK_EQUAL(ParseHDKeypath(keypath_str, keypath), is_valid);`"
  },
  {
   "t": "2026-04-28T10:18:20Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "commit": "1f80ddb330e844602adc502e83554eb065791e35",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nwe don't want to correlate the two calls for the fuzzer:\n```suggestion\n    (void)FormatHDKeypath(random_keypath, /*apostrophe=*/fuzzed_data_provider.ConsumeBool());\n    (void)WriteHDKeypath(random_keypath, /*apostrophe=*/fuzzed_data_provider.ConsumeBool());\n```"
  },
  {
   "t": "2026-04-28T10:19:01Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "commit": "56b1a59b47bf7eb80bad5cbaf115e524a4192d57",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nCan you make this a scripted diff for simplicity?"
  },
  {
   "t": "2026-04-28T10:19:46Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "2bd25fc3a4ae663b0da77a9a4d9fecac50260183",
   "text": "Concept ACK, only reviewed lightly to understand the extent of the change"
  },
  {
   "t": "2026-04-28T16:24:38Z",
   "kind": "comment",
   "who": "haishmg",
   "assoc": "NONE",
   "text": "ACK 2bd25fc3a4ae663b0da77a9a4d9fecac50260183\n\nReviewed the test refactor and added coverage, the `ParseHDKeypathLegacy` rename, and the fuzz target change. The new assertions cover returned path elements and round-trip behavior without changing the legacy wallet load behavior.\n\nRan locally on macOS arm64:\n\n- `build/bin/test_bitcoin --run_test=psbt_wallet_tests/parse_hd_keypath`\n- `ctest --test-dir build -R 'psbt_wallet|ipc_tests|walletdb_tests' --output-on-failure -j 9`\\n- `ctest --test-dir build -j 9 --output-on-failure` (0 failed out of 348; `script_assets_tests` skipped)\\n- `git diff --check upstream/master...HEAD`"
  },
  {
   "t": "2026-04-28T17:03:19Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/psbt_wallet_tests.cpp",
   "commit": "bab6209ed47a2792e4a92c61d9187ae37f468a2b",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis type conversion is unnecessary (and wrong)."
  },
  {
   "t": "2026-04-29T04:18:54Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/psbt_wallet_tests.cpp",
   "commit": "67ae7a2df4c847263e1da4c01a4c03e4fb85863f",
   "in_reply_to": 3153240231,
   "text": "That's how it was done first, but to keep the formatting so that items align to the same columns, it produced a lot of extra white space. The length of the string input varies a lot, while the bool only a little (4 or 5 chars), so it was put first for that reason."
  },
  {
   "t": "2026-04-29T04:20:09Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "commit": "1f80ddb330e844602adc502e83554eb065791e35",
   "in_reply_to": 3153254889,
   "text": "Done."
  },
  {
   "t": "2026-04-29T04:20:27Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "commit": "56b1a59b47bf7eb80bad5cbaf115e524a4192d57",
   "in_reply_to": 3153258869,
   "text": "Thanks for insisting, done."
  },
  {
   "t": "2026-04-29T04:20:40Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/psbt_wallet_tests.cpp",
   "commit": "bab6209ed47a2792e4a92c61d9187ae37f468a2b",
   "in_reply_to": 3155904564,
   "text": "Done"
  },
  {
   "t": "2026-04-29T04:21:25Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "2bd25fc3a4ae663b0da77a9a4d9fecac50260183",
   "text": "Thanks for the reviews! Pushed changes for the suggestions from the first review. Also extended the PR description with 'Potential future steps'.\n\n[quoted text omitted]\n`git diff 2bd25fc3a4ae663b0da77a9a4d9fecac50260183..a548fac787a89e2e69ec89a6f9450935b3a9e08f`"
  },
  {
   "t": "2026-04-29T04:21:26Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "a548fac787a89e2e69ec89a6f9450935b3a9e08f"
  },
  {
   "t": "2026-06-17T19:48:34Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "cf76d345c85283e64a37e850b92b748c5ee29d2b"
  },
  {
   "t": "2026-06-17T19:49:44Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased to current master (to refresh)"
  },
  {
   "t": "2026-07-24T20:48:10Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2026-07-24T20:49:38Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/wallet/test/psbt_wallet_tests.cpp",
   "commit": "2d6efe8414aeb75675769ca3ca71b0113ebf7169",
   "in_reply_to": null,
   "text": "2d6efe8414aeb75675769ca3ca71b0113ebf7169: nit: These comments \"num -> str2\" and \"str2 -> num2\" seem unnecessary, the code is already clear."
  },
  {
   "t": "2026-07-24T20:54:23Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "commit": "94536aba8f0f374953e765b9b76772765a18d2aa",
   "in_reply_to": null,
   "text": "94536aba8f0f374953e765b9b76772765a18d2aa: Nice catch! The apostrophe param was added in `WriteHDKeypath` in bd13dc2f46ea10302a928fcf0f53b7aed77ad260 (3 years ago). I'm just wondering how effective is fuzzing `FormatHDKeypath` if `WriteHDKeypath` can exactly call `FormatHDKeypath` again with same values."
  },
  {
   "t": "2026-07-24T21:24:42Z",
   "kind": "review",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "cf76d345c85283e64a37e850b92b748c5ee29d2b",
   "text": "code review ACK cf76d345c85283e64a37e850b92b748c5ee29d2b\n\nNothing blocker from my comments but happy to re-ack if any of them (or any other thing) is addressed."
  },
  {
   "t": "2026-08-18T18:06:18Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "b3e4cd2eef85a55b2a734f5f9817993ff943e852"
  },
  {
   "t": "2026-08-18T18:06:32Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "PR #32784 has been merged recently, and it overlaps with the changes of this PR (resulted in a conflict). Rebasing had to be done manually, including changes:\n- The test cases had to be updated, as they were changed & extended in #32784\n- The change to the fuzz test was dropped, as the same change has been independently done in #32768 as well.\n\n`git range-diff cf76d345c85283e64a37e850b92b748c5ee29d2b...b3e4cd2eef85a55b2a734f5f9817993ff943e852`"
  },
  {
   "t": "2026-08-18T18:08:39Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/psbt_wallet_tests.cpp",
   "commit": "2d6efe8414aeb75675769ca3ca71b0113ebf7169",
   "in_reply_to": 3648065343,
   "text": "Applied (as I had to touch due to merge conflict). Note that this code has been moved to `src/test/bip32_tests.cpp` now."
  },
  {
   "t": "2026-08-18T18:09:43Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "commit": "94536aba8f0f374953e765b9b76772765a18d2aa",
   "in_reply_to": 3648090678,
   "text": "Note: this is irrelevant now, as #32784 had solved this (in a different way)."
  },
  {
   "t": "2026-08-18T18:10:56Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "b3e4cd2eef85a55b2a734f5f9817993ff943e852",
   "text": "@brunoerg, thanks for the review. I've applied the nit, as the PR had to be changed (due to merge conflict)."
  },
  {
   "t": "2026-08-18T21:49:30Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-08-21T11:32:31Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "56b1a59b47bf7eb80bad5cbaf115e524a4192d57"
  },
  {
   "t": "2026-08-21T11:32:34Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Reworked, due to new conflict. #35069 has been merged, whereby the implementation of `ParseHDKeypath` and `ParsePubkey` has been mostly deduplicated, and test data changed.\nUpdated this PR accordingly, it still keeps the test improvements (test data as data, check expected results, add round-trip tests), and the renaming.\nReady for review."
  }
 ],
 "labels_log": [
  {
   "t": "2026-04-28T09:21:37Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-28T09:25:43Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-28T10:44:34Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-15T02:48:45Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-18T19:06:49Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-19T19:25:31Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-21T13:12:30Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-04-28T17:03:34Z",
   "kind": "ready_for_review",
   "who": "optout21"
  }
 ],
 "text_chars": 6817,
 "text_tokens_estimate": 1704,
 "changed_paths": [
  "src/rpc/util.cpp",
  "src/test/bip32_tests.cpp",
  "src/test/fuzz/parse_hd_keypath.cpp",
  "src/util/bip32.cpp",
  "src/util/bip32.h",
  "src/wallet/walletdb.cpp"
 ],
 "files": [
  {
   "path": "src/rpc/util.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/bip32_tests.cpp",
   "add": 120,
   "del": 96
  },
  {
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/util/bip32.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/util/bip32.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 1,
   "del": 1
  }
 ],
 "test_lines": 222,
 "git": {
  "head": "56b1a59b47bf7eb80bad5cbaf115e524a4192d57",
  "head_matches_backup": true,
  "base": "f5e91c6fbae7dc0377dd382814ace063f50e2741",
  "commits": [
   {
    "sha": "41bba5e1b0",
    "subject": "test: Refactor in bip32_tests: extract test data in an array",
    "files": 1,
    "add": 81,
    "del": 96
   },
   {
    "sha": "4c7969f6e2",
    "subject": "test: bip32_tests: Add expected result checks",
    "files": 1,
    "add": 83,
    "del": 65
   },
   {
    "sha": "bb057076dc",
    "subject": "test: bip32_tests: Add round-trip tests",
    "files": 1,
    "add": 16,
    "del": 0
   },
   {
    "sha": "dd665be15b",
    "subject": "test: bip32_tests: Add some more tests cases",
    "files": 1,
    "add": 5,
    "del": 0
   },
   {
    "sha": "56b1a59b47",
    "subject": "scripted-diff: Rename ParseHDKeypath to ParseHDKeypathLegacy",
    "files": 6,
    "add": 9,
    "del": 9
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "e14b47fb382da5be",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}