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.
This commit is contained in:
Ava Chow 2024-12-13 14:46:31 -05:00
parent fa1b7cd6e2
commit e8c3efc7d8

View file

@ -1992,10 +1992,29 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
} }
} }
// Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH // Make sure that we have accounted for all scriptPubKeys
// So we have to check if any of our scripts are a multisig and if so, add the P2SH if (!Assume(spks.empty())) {
for (const auto& script_pair : mapScripts) { LogPrintf("%s\n", STR_INTERNAL_BUG("Error: Some output scripts were not migrated.\n"));
const CScript script = script_pair.second; return std::nullopt;
}
// Legacy wallets can also contain scripts whose P2SH, P2WSH, or P2SH-P2WSH it is not watching for
// but can provide script data to a PSBT spending them. These "solvable" output scripts will need to
// be put into the separate "solvables" wallet.
// These can be detected by going through the entire candidate output scripts, finding the ISMINE_NO scripts,
// and checking CanProvide() which will dummy sign.
for (const CScript& script : GetCandidateScriptPubKeys()) {
// Since we only care about P2SH, P2WSH, and P2SH-P2WSH, filter out any scripts that are not those
if (!script.IsPayToScriptHash() && !script.IsPayToWitnessScriptHash()) {
continue;
}
if (IsMine(script) != ISMINE_NO) {
continue;
}
SignatureData dummy_sigdata;
if (!CanProvide(script, dummy_sigdata)) {
continue;
}
// Get birthdate from script meta // Get birthdate from script meta
uint64_t creation_time = 0; uint64_t creation_time = 0;
@ -2004,45 +2023,28 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
creation_time = it->second.nCreateTime; creation_time = it->second.nCreateTime;
} }
std::vector<std::vector<unsigned char>> sols; // InferDescriptor as that will get us all the solving info if it is there
TxoutType type = Solver(script, sols); std::unique_ptr<Descriptor> desc = InferDescriptor(script, *GetSolvingProvider(script));
if (type == TxoutType::MULTISIG) { if (!desc->IsSolvable()) {
CScript sh_spk = GetScriptForDestination(ScriptHash(script)); // The wallet was able to provide some information, but not enough to make a descriptor that actually
CTxDestination witdest = WitnessV0ScriptHash(script); // contains anything useful. This is probably because the script itself is actually unsignable (e.g. P2WSH-P2WSH).
CScript witprog = GetScriptForDestination(witdest); continue;
CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog)); }
// We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable // Past bugs in InferDescriptor have caused it to create descriptors which cannot be re-parsed
// For P2SH, a multisig is not ISMINE_NO when: // Re-parse the descriptors to detect that, and skip any that do not parse.
// * All keys are in the wallet {
// * The multisig itself is watch only std::string desc_str = desc->ToString();
// * The P2SH is watch only FlatSigningProvider parsed_keys;
// For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH. std::string parse_error;
// For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions: std::vector<std::unique_ptr<Descriptor>> parsed_descs = Parse(desc_str, parsed_keys, parse_error, false);
// * The P2WSH script is in the wallet and it is being watched if (parsed_descs.empty()) {
std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1);
if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) {
// The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine
assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO);
continue; continue;
} }
assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO);
std::unique_ptr<Descriptor> sh_desc = InferDescriptor(sh_spk, *GetSolvingProvider(sh_spk));
out.solvable_descs.emplace_back(sh_desc->ToString(), creation_time);
const auto desc = InferDescriptor(witprog, *this);
if (desc->IsSolvable()) {
std::unique_ptr<Descriptor> wsh_desc = InferDescriptor(witprog, *GetSolvingProvider(witprog));
out.solvable_descs.emplace_back(wsh_desc->ToString(), creation_time);
std::unique_ptr<Descriptor> sh_wsh_desc = InferDescriptor(sh_wsh_spk, *GetSolvingProvider(sh_wsh_spk));
out.solvable_descs.emplace_back(sh_wsh_desc->ToString(), creation_time);
}
} }
}
// Make sure that we have accounted for all scriptPubKeys out.solvable_descs.emplace_back(desc->ToString(), creation_time);
assert(spks.size() == 0); }
// Finalize transaction // Finalize transaction
if (!batch.TxnCommit()) { if (!batch.TxnCommit()) {