Skip to content

Commit e8c3efc

Browse files
committed
wallet migration: Determine Solvables with CanProvide
LegacySPKM would determine whether it could provide any script data to a transaction through the use of the CanProvide function. Instead of partially reversing signing logic to figure out the output scripts of solvable things, we use the same candidate set approach in GetScriptPubKeys() and instead filter the candidate set first for things that are ISMINE_NO, and second with CanProvide(). This should give a more accurate solvables wallet.
1 parent fa1b7cd commit e8c3efc

File tree

1 file changed

+41
-39
lines changed

1 file changed

+41
-39
lines changed

src/wallet/scriptpubkeyman.cpp

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,10 +1992,29 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
19921992
}
19931993
}
19941994

1995-
// Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH
1996-
// So we have to check if any of our scripts are a multisig and if so, add the P2SH
1997-
for (const auto& script_pair : mapScripts) {
1998-
const CScript script = script_pair.second;
1995+
// Make sure that we have accounted for all scriptPubKeys
1996+
if (!Assume(spks.empty())) {
1997+
LogPrintf("%s\n", STR_INTERNAL_BUG("Error: Some output scripts were not migrated.\n"));
1998+
return std::nullopt;
1999+
}
2000+
2001+
// Legacy wallets can also contain scripts whose P2SH, P2WSH, or P2SH-P2WSH it is not watching for
2002+
// but can provide script data to a PSBT spending them. These "solvable" output scripts will need to
2003+
// be put into the separate "solvables" wallet.
2004+
// These can be detected by going through the entire candidate output scripts, finding the ISMINE_NO scripts,
2005+
// and checking CanProvide() which will dummy sign.
2006+
for (const CScript& script : GetCandidateScriptPubKeys()) {
2007+
// Since we only care about P2SH, P2WSH, and P2SH-P2WSH, filter out any scripts that are not those
2008+
if (!script.IsPayToScriptHash() && !script.IsPayToWitnessScriptHash()) {
2009+
continue;
2010+
}
2011+
if (IsMine(script) != ISMINE_NO) {
2012+
continue;
2013+
}
2014+
SignatureData dummy_sigdata;
2015+
if (!CanProvide(script, dummy_sigdata)) {
2016+
continue;
2017+
}
19992018

20002019
// Get birthdate from script meta
20012020
uint64_t creation_time = 0;
@@ -2004,45 +2023,28 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
20042023
creation_time = it->second.nCreateTime;
20052024
}
20062025

2007-
std::vector<std::vector<unsigned char>> sols;
2008-
TxoutType type = Solver(script, sols);
2009-
if (type == TxoutType::MULTISIG) {
2010-
CScript sh_spk = GetScriptForDestination(ScriptHash(script));
2011-
CTxDestination witdest = WitnessV0ScriptHash(script);
2012-
CScript witprog = GetScriptForDestination(witdest);
2013-
CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog));
2014-
2015-
// We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable
2016-
// For P2SH, a multisig is not ISMINE_NO when:
2017-
// * All keys are in the wallet
2018-
// * The multisig itself is watch only
2019-
// * The P2SH is watch only
2020-
// For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH.
2021-
// For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions:
2022-
// * The P2WSH script is in the wallet and it is being watched
2023-
std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1);
2024-
if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) {
2025-
// The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine
2026-
assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO);
2027-
continue;
2028-
}
2029-
assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO);
2030-
2031-
std::unique_ptr<Descriptor> sh_desc = InferDescriptor(sh_spk, *GetSolvingProvider(sh_spk));
2032-
out.solvable_descs.emplace_back(sh_desc->ToString(), creation_time);
2026+
// InferDescriptor as that will get us all the solving info if it is there
2027+
std::unique_ptr<Descriptor> desc = InferDescriptor(script, *GetSolvingProvider(script));
2028+
if (!desc->IsSolvable()) {
2029+
// The wallet was able to provide some information, but not enough to make a descriptor that actually
2030+
// contains anything useful. This is probably because the script itself is actually unsignable (e.g. P2WSH-P2WSH).
2031+
continue;
2032+
}
20332033

2034-
const auto desc = InferDescriptor(witprog, *this);
2035-
if (desc->IsSolvable()) {
2036-
std::unique_ptr<Descriptor> wsh_desc = InferDescriptor(witprog, *GetSolvingProvider(witprog));
2037-
out.solvable_descs.emplace_back(wsh_desc->ToString(), creation_time);
2038-
std::unique_ptr<Descriptor> sh_wsh_desc = InferDescriptor(sh_wsh_spk, *GetSolvingProvider(sh_wsh_spk));
2039-
out.solvable_descs.emplace_back(sh_wsh_desc->ToString(), creation_time);
2034+
// Past bugs in InferDescriptor have caused it to create descriptors which cannot be re-parsed
2035+
// Re-parse the descriptors to detect that, and skip any that do not parse.
2036+
{
2037+
std::string desc_str = desc->ToString();
2038+
FlatSigningProvider parsed_keys;
2039+
std::string parse_error;
2040+
std::vector<std::unique_ptr<Descriptor>> parsed_descs = Parse(desc_str, parsed_keys, parse_error, false);
2041+
if (parsed_descs.empty()) {
2042+
continue;
20402043
}
20412044
}
2042-
}
20432045

2044-
// Make sure that we have accounted for all scriptPubKeys
2045-
assert(spks.size() == 0);
2046+
out.solvable_descs.emplace_back(desc->ToString(), creation_time);
2047+
}
20462048

20472049
// Finalize transaction
20482050
if (!batch.TxnCommit()) {

0 commit comments

Comments
 (0)