Skip to content

Commit 7edce77

Browse files
committed
Merge bitcoin#28067: descriptors: do not return top-level only funcs as sub descriptors
dd9633b test: wallet, add coverage for watch-only raw sh script migration (furszy) cc781a2 descriptor: InferScript, do not return top-level only func as sub descriptor (furszy) 286e0c7 wallet: loading, log descriptor parsing error details (furszy) Pull request description: Linked to bitcoin#28057. Currently, the `InferScript` function returns an invalid descriptor when it tries to infer a p2sh-p2pkh script whose pubkey is not known by the wallet. This behavior occurs because the inference process bypasses the `pkh` subscript when the pubkey is not contained by the wallet (no pubkey provider), interpreting it as a `sh(addr(ADDR))` descriptor. Then, the failure arises because the `addr()` function is restricted to being used only at the top level. For reviewers, would recommend to start by examining the functional test to understand the context and the circumstances on which this can result in a fatal error (e.g. during the migration process). ACKs for top commit: achow101: ACK dd9633b darosior: utACK dd9633b Tree-SHA512: 61e763206c604c372019d2c36e31684f3dddf81f8b154eb9aba5cd66d8d61bda457ed4e591613eb6ce6c76cf7c3f11764abc6cd727a7c2b6414f1065783be032
2 parents 7995490 + dd9633b commit 7edce77

File tree

5 files changed

+59
-3
lines changed

5 files changed

+59
-3
lines changed

src/script/descriptor.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,10 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
17231723
}
17241724
}
17251725

1726+
// The following descriptors are all top-level only descriptors.
1727+
// So if we are not at the top level, return early.
1728+
if (ctx != ParseScriptContext::TOP) return nullptr;
1729+
17261730
CTxDestination dest;
17271731
if (ExtractDestination(script, dest)) {
17281732
if (GetScriptForDestination(dest) == script) {

src/wallet/walletdb.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,11 +794,13 @@ static DBErrors LoadDescriptorWalletRecords(CWallet* pwallet, DatabaseBatch& bat
794794
WalletDescriptor desc;
795795
try {
796796
value >> desc;
797-
} catch (const std::ios_base::failure&) {
797+
} catch (const std::ios_base::failure& e) {
798798
strErr = strprintf("Error: Unrecognized descriptor found in wallet %s. ", pwallet->GetName());
799799
strErr += (last_client > CLIENT_VERSION) ? "The wallet might had been created on a newer version. " :
800800
"The database might be corrupted or the software version is not compatible with one of your wallet descriptors. ";
801801
strErr += "Please try running the latest software version";
802+
// Also include error details
803+
strErr = strprintf("%s\nDetails: %s", strErr, e.what());
802804
return DBErrors::UNKNOWN_DESCRIPTOR;
803805
}
804806
pwallet->LoadDescriptorScriptPubKeyMan(id, desc);

test/functional/data/rpc_decodescript.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"p2sh": "2N34iiGoUUkVSPiaaTFpJjB1FR9TXQu3PGM",
7070
"segwit": {
7171
"asm": "0 96c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42",
72-
"desc": "wsh(raw(02eeee))#gtay4y0z",
72+
"desc": "addr(bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5)#5akkdska",
7373
"hex": "002096c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42",
7474
"address": "bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5",
7575
"type": "witness_v0_scripthash",

test/functional/rpc_decodescript.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ def decodescript_miniscript(self):
271271
assert res["segwit"]["desc"] == "wsh(and_v(and_v(v:hash160(ffffffffffffffffffffffffffffffffffffffff),v:pk(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0)),older(1)))#gm8xz4fl"
272272
# Miniscript-incompatible offered HTLC
273273
res = self.nodes[0].decodescript("82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2")
274-
assert res["segwit"]["desc"] == "wsh(raw(82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2))#ra6w2xa7"
274+
assert res["segwit"]["desc"] == "addr(bcrt1q73qyfypp47hvgnkjqnav0j3k2lq3v76wg22dk8tmwuz5sfgv66xsvxg6uu)#9p3q328s"
275275
# Miniscript-compatible multisig bigger than 520 byte P2SH limit.
276276
res = self.nodes[0].decodescript("5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68")
277277
assert_equal(res["segwit"]["desc"], "wsh(or_d(multi(11,020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261,02675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af99,02896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d48,029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c,02a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc4010,031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb,03079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b,03111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2,0318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa0840174,03230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de1,035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a6,03bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c,03cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d17546,03d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d424828,03ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a),and_v(v:older(4032),multi(2,03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79,0291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807,0386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb))))#7jwwklk4")

test/functional/wallet_migration.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import shutil
99
from test_framework.descriptors import descsum_create
1010
from test_framework.test_framework import BitcoinTestFramework
11+
from test_framework.messages import COIN, CTransaction, CTxOut
12+
from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_script, script_to_p2wsh_script
1113
from test_framework.util import (
1214
assert_equal,
1315
assert_raises_rpc_error,
@@ -639,6 +641,53 @@ def check(info, node):
639641
for addr_info in [addr_external, addr_external_with_label]:
640642
check(addr_info, wallet_solvables)
641643

644+
def test_migrate_raw_p2sh(self):
645+
self.log.info("Test migration of watch-only raw p2sh script")
646+
df_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
647+
wallet = self.create_legacy_wallet("raw_p2sh")
648+
649+
def send_to_script(script, amount):
650+
tx = CTransaction()
651+
tx.vout.append(CTxOut(nValue=amount*COIN, scriptPubKey=script))
652+
653+
hex_tx = df_wallet.fundrawtransaction(tx.serialize().hex())['hex']
654+
signed_tx = df_wallet.signrawtransactionwithwallet(hex_tx)
655+
df_wallet.sendrawtransaction(signed_tx['hex'])
656+
self.generate(self.nodes[0], 1)
657+
658+
# Craft sh(pkh(key)) script and send coins to it
659+
pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"]
660+
script_pkh = key_to_p2pkh_script(pubkey)
661+
script_sh_pkh = script_to_p2sh_script(script_pkh)
662+
send_to_script(script=script_sh_pkh, amount=2)
663+
664+
# Import script and check balance
665+
wallet.rpc.importaddress(address=script_pkh.hex(), label="raw_spk", rescan=True, p2sh=True)
666+
assert_equal(wallet.getbalances()['watchonly']['trusted'], 2)
667+
668+
# Craft wsh(pkh(key)) and send coins to it
669+
pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"]
670+
script_wsh_pkh = script_to_p2wsh_script(key_to_p2pkh_script(pubkey))
671+
send_to_script(script=script_wsh_pkh, amount=3)
672+
673+
# Import script and check balance
674+
wallet.rpc.importaddress(address=script_wsh_pkh.hex(), label="raw_spk2", rescan=True, p2sh=False)
675+
assert_equal(wallet.getbalances()['watchonly']['trusted'], 5)
676+
677+
# Migrate wallet and re-check balance
678+
info_migration = wallet.migratewallet()
679+
wallet_wo = self.nodes[0].get_wallet_rpc(info_migration["watchonly_name"])
680+
681+
# Watch-only balance is under "mine".
682+
assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5)
683+
# The watch-only scripts are no longer part of the main wallet
684+
assert_equal(wallet.getbalances()['mine']['trusted'], 0)
685+
686+
# Just in case, also verify wallet restart
687+
self.nodes[0].unloadwallet(info_migration["watchonly_name"])
688+
self.nodes[0].loadwallet(info_migration["watchonly_name"])
689+
assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5)
690+
642691
def run_test(self):
643692
self.generate(self.nodes[0], 101)
644693

@@ -654,6 +703,7 @@ def run_test(self):
654703
self.test_default_wallet()
655704
self.test_direct_file()
656705
self.test_addressbook()
706+
self.test_migrate_raw_p2sh()
657707

658708
if __name__ == '__main__':
659709
WalletMigrationTest().main()

0 commit comments

Comments
 (0)