mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-21 12:22:50 -05:00
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:
parent
fa1b7cd6e2
commit
e8c3efc7d8
1 changed files with 40 additions and 38 deletions
|
@ -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
|
||||
// So we have to check if any of our scripts are a multisig and if so, add the P2SH
|
||||
for (const auto& script_pair : mapScripts) {
|
||||
const CScript script = script_pair.second;
|
||||
// Make sure that we have accounted for all scriptPubKeys
|
||||
if (!Assume(spks.empty())) {
|
||||
LogPrintf("%s\n", STR_INTERNAL_BUG("Error: Some output scripts were not migrated.\n"));
|
||||
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
|
||||
uint64_t creation_time = 0;
|
||||
|
@ -2004,45 +2023,28 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
|
|||
creation_time = it->second.nCreateTime;
|
||||
}
|
||||
|
||||
std::vector<std::vector<unsigned char>> sols;
|
||||
TxoutType type = Solver(script, sols);
|
||||
if (type == TxoutType::MULTISIG) {
|
||||
CScript sh_spk = GetScriptForDestination(ScriptHash(script));
|
||||
CTxDestination witdest = WitnessV0ScriptHash(script);
|
||||
CScript witprog = GetScriptForDestination(witdest);
|
||||
CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog));
|
||||
// InferDescriptor as that will get us all the solving info if it is there
|
||||
std::unique_ptr<Descriptor> desc = InferDescriptor(script, *GetSolvingProvider(script));
|
||||
if (!desc->IsSolvable()) {
|
||||
// The wallet was able to provide some information, but not enough to make a descriptor that actually
|
||||
// contains anything useful. This is probably because the script itself is actually unsignable (e.g. P2WSH-P2WSH).
|
||||
continue;
|
||||
}
|
||||
|
||||
// We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable
|
||||
// For P2SH, a multisig is not ISMINE_NO when:
|
||||
// * All keys are in the wallet
|
||||
// * The multisig itself is watch only
|
||||
// * The P2SH is watch only
|
||||
// For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH.
|
||||
// For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions:
|
||||
// * The P2WSH script is in the wallet and it is being watched
|
||||
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);
|
||||
// Past bugs in InferDescriptor have caused it to create descriptors which cannot be re-parsed
|
||||
// Re-parse the descriptors to detect that, and skip any that do not parse.
|
||||
{
|
||||
std::string desc_str = desc->ToString();
|
||||
FlatSigningProvider parsed_keys;
|
||||
std::string parse_error;
|
||||
std::vector<std::unique_ptr<Descriptor>> parsed_descs = Parse(desc_str, parsed_keys, parse_error, false);
|
||||
if (parsed_descs.empty()) {
|
||||
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
|
||||
assert(spks.size() == 0);
|
||||
out.solvable_descs.emplace_back(desc->ToString(), creation_time);
|
||||
}
|
||||
|
||||
// Finalize transaction
|
||||
if (!batch.TxnCommit()) {
|
||||
|
|
Loading…
Add table
Reference in a new issue