Instead of prividing the descriptor string as stored in the db, use the normalized descriptor as is done for getaddressinfo’s parent_desc field.
Split from #32489
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32594.
See the guideline for information on the review process.
If your review is incorrectly listed, please react with π to this comment and the bot will ignore it on the next update.
No conflicts as of last run.
Possible typos and grammar issues:
drahtbot_id_4_m
Concept ACK
In general normalized descriptors are more useful, because you can’t derive addresses from e.g. xpub/1h/*
. So it makes sense to use them for the parent.
ACK 107517ea7d29d959e7a0b94d8217dcb894680547
I checked that the tests fail if I revert the first commit.
nit: “prividing” -> “providing”
ACK 107517ea7d29d959e7a0b94d8217dcb894680547
Before change listtransactions (and others) shows un-normalized descriptors, after the descriptors are normalized
0 {
1 "address": "bcrt1qgymj02gx6dzrkh49d9sj0cj8rxfcrut7dyxz7r",
2 "parent_descs": [
3 "wpkh(tpubD6NzVbkrYhZ4YdzwuYwQLEypddJ64KVEG9tyZibLxLKMxyMCaru1c7RFX2S8NEQ7dZuNccdzk5qikw51rxzXYWdkeuREgxgdqbJy3ty68qy/84h/1h/0h/0/*)#tz7s9af2"
4 ],
5 "category": "immature",
6 "amount": 50.00000000,
7 "label": "",
8 "vout": 0,
9 "abandoned": false,
10 "confirmations": 1,
11 "generated": true,
12 [...]
this commit:
0 "address": "bcrt1qgwme02jk087aessn9tht9yjkawdxys3xm36cy8",
1 "parent_descs": [
2 "wpkh([83869ebd/84h/1h/0h]tpubDCS1CwM385BY77ffiamcKJ4CnZc2G15YxGXeisvNhkYzocxNcSjLrPZL8KaMtYLXSCo9ng7XRrX7q7Yn7oh2652QU79ybjh4YoY7m5zZxgN/0/*)#ettrwdmf"
3 ],
4 "category": "immature",
5 "amount": 50.00000000,
6 "label": "",
7 "vout": 0,
8 "abandoned": false,
9 "confirmations": 1,
10 "generated": true,
11 [...]
nit: “prividing” -> “providing”
Fixed
re ACK a759a22d59e805834d077a28c95695e4834983a9
Changes since last ACK:
46+ # Next extract the xpub
47+ desc_verify = re.sub(r"tpub\w+?(?=/)", "", desc_verify)
48+
49+ # Walk the remaining descriptor backwards looking for the hardened indicator.
50+ # It should not be found until after a ']' is found, and before a '['
51+ # The hardened indicator 'h' should not be found until after a ']' is found
108@@ -109,7 +109,10 @@ void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey,
109 {
110 UniValue parent_descs(UniValue::VARR);
111 for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
112- parent_descs.push_back(desc.descriptor->ToString());
113+ std::string desc_str;
114+ FlatSigningProvider provider;
115+ if (!CHECK_NONFATAL(desc.descriptor->ToNormalizedString(provider, desc_str, &desc.cache))) continue;
IIUC, this is just a dummy signing provider being passed here and essentially the last hardened xpub would be retrieved from the desc cache because I don’t see this provider being populated in the call stack.
Ideally, passing a null for the provider arg could have been better to emphasise on this but I see the function signature accepts a reference instead.
A less ideal alternative would be to explicitly name the arg here dummy_provider
to make it clear to the reader.
0FlatSigningProvider dummy_provider;
ACK a759a22d59e805834d077a28c95695e4834983a9
Agree that normalized descriptors are more useful as no further hardened derivation is required.
Not suggesting any change, only a question: Specific for the listunspent
RPC though where both parent_descs
and desc
fields are present, is parent_descs
field useful only when the unspent is not solvable? Because parent_descs
seem like the superset of desc
from few values I checked and also by its name.
is parent_descs field useful only when the unspent is not solvable?
No. The purpose is to allow identifying the actual descriptor in the wallet that produced the address. These are supposed to be the same descriptors as output by listdescriptors false
.
Instead of providing the descriptor string as stored in the db, use the
normalized descriptor as is done for getaddressinfo's parent_desc field.
re ACK fdabfc7b01ec2cff5f2356e1b13b5537d30dbaf8
Changes since last ACK:
55+ for c in desc_verify[::-1]:
56+ if c == "]":
57+ found_origin_end = True
58+ elif c == "[":
59+ break
60+ elif c == "h" or c == "'":
0 or c == "'"
I don’t think checking for apostrophe is really required specifically in this case because as I checked in the code that only the hardened derive types are used when the SPKMs are setup & that’s what this test is using. https://github.com/bitcoin/bitcoin/blob/88b22acc3d6faeb2c0860695783a2a082d4c8098/src/wallet/walletutil.cpp#L58-L75
And the ToNormalizedString
doesn’t use the compact string version as well.
https://github.com/bitcoin/bitcoin/blob/88b22acc3d6faeb2c0860695783a2a082d4c8098/src/script/descriptor.cpp#L496-L502
From what I looked in the importdescriptors
RPC flow, the apostrophe would not be used in the normalized forms of such descriptors as well but I guess fine to keep this check - not a big thing.
108@@ -109,7 +109,10 @@ void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey,
109 {
110 UniValue parent_descs(UniValue::VARR);
111 for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
112- parent_descs.push_back(desc.descriptor->ToString());
113+ std::string desc_str;
114+ FlatSigningProvider dummy_provider;
115+ if (!CHECK_NONFATAL(desc.descriptor->ToNormalizedString(dummy_provider, desc_str, &desc.cache))) continue;
I wanted to see how the case would be handled by CHECK_NONFATAL
when for some buggy reason the call here fails. I simulated a scenario and found that the error is propagated to the RPC result - good.
0β bitcoin git:(getaddrinfo-normalized-parent) β bitcoinclireg listunspent
1error code: -1
2error message:
3Internal bug detected: false
4wallet/rpc/util.cpp:115 (PushParentDescriptors)
5Bitcoin Core v29.99.0-a759a22d59e8-dirty
6Please report this issue here: https://github.com/bitcoin/bitcoin/issues
ACK fdabfc7b01ec2cff5f2356e1b13b5537d30dbaf8
Thanks for incorporating the previously suggested small changes.
61+ if found_origin_end:
62+ found_hardened_in_origin = True
63+ else:
64+ found_hardened_after_origin = True
65+ assert_equal(found_hardened_in_origin, True)
66+ assert_equal(found_hardened_after_origin, False)
0 # 1) Find the first β[ β¦ ]β-wrapped bit and capture whatβs inside it
1 origin_match = re.search(r'\[([^\]]*)\]', desc_verify)
2 # 2) If we got a match, pull out the inner text; otherwise use empty string
3 origin_part = origin_match.group(1) if origin_match else ""
4 # 3) Remove everything up to and including the first β]β, leaving only what follows
5 after_origin = re.sub(r'^.*?\]', '', desc_verify) if origin_match else desc_verify
6 # 4) Look for any hardened markers (either βhβ or apostrophe) inside each piece
7 found_hardened_in_origin = bool(re.search(r"[h']", origin_part))
8 found_hardened_after_origin = bool(re.search(r"[h']", after_origin))
9
10 assert_equal(found_hardened_in_origin, True)
11 assert_equal(found_hardened_after_origin, False)
getaddressinfo, listunspent, listtransactions, listsinceblock, and
gettransaction all include parent_desc(s). Make sure that these are
consistent with each other, as well as being in normalized form.
re-utACK 0def84d407
“vcpkg install failed” on Windows CI seems unrelated.
43+
44+ # Verify that the parent descriptor is normalized
45+ # First remove the checksum
46+ desc_verify = parent_desc.split("#")[0]
47+ # Next extract the xpub
48+ desc_verify = re.sub(r"tpub\w+?(?=/)", "", desc_verify)
desc_verify
contains everything in the descriptor but the xpub.
r"tpub\w+?(?=/)"
The \w
will accept an xpub with underscore as well that is not allowed, but ok.
45+ # First remove the checksum
46+ desc_verify = parent_desc.split("#")[0]
47+ # Next extract the xpub
48+ desc_verify = re.sub(r"tpub\w+?(?=/)", "", desc_verify)
49+ # Extract origin info
50+ origin_match = re.search(r'\[([\da-fh/]+)\]', desc_verify)
(\da-f)
and the origin_part
is found even if the desc_verify
doesn’t contain hardened marker such as in wpkh([58d258be/84/1/0]/0/*
. That’s why I believe the subsequent "h" in origin_part
check is required.
50+ origin_match = re.search(r'\[([\da-fh/]+)\]', desc_verify)
51+ origin_part = origin_match.group(1) if origin_match else ""
52+ # Split on "]" for everything after the origin info
53+ after_origin = desc_verify.split("]", maxsplit=1)[-1]
54+ # Look for the hardened markers βhβ inside each piece
55+ # We don't need to check for aspostrophe as normalization will not output aspostrophe
If retouched.
0- aspostrophe
1+ apostrophe
ACK 0def84d407facd319b52826d013cad0d5fc8dbf5
Apparently I didn’t have the fdabfc7b01ec2cff5f2356e1b13b5537d30dbaf8 commit in my local, maybe I forgot to check it out earlier.
0git range-diff a759a22...0def84d
Good job replacing the loop in the test with the regex.