diff --git a/src/pivxd.cpp b/src/pivxd.cpp index 888fb52ac35b..a47fc15c5d4b 100644 --- a/src/pivxd.cpp +++ b/src/pivxd.cpp @@ -105,12 +105,12 @@ bool AppInit(int argc, char* argv[]) // Error out when loose non-argument tokens are encountered on command line for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { - fprintf(stderr, "Error: Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i]); + fprintf(stderr, "Error: Command line contains unexpected token '%s', see pivxd -h for a list of options.\n", argv[i]); exit(EXIT_FAILURE); } } - // -server defaults to true for bitcoind but not for the GUI so do this here + // -server defaults to true for pivxd but not for the GUI so do this here gArgs.SoftSetBoolArg("-server", true); // Set this early so that parameter interactions go to console InitLogging(); diff --git a/src/qt/pivx/settings/settingsbittoolwidget.cpp b/src/qt/pivx/settings/settingsbittoolwidget.cpp index 3fcad570f4bf..671962bf7ec6 100644 --- a/src/qt/pivx/settings/settingsbittoolwidget.cpp +++ b/src/qt/pivx/settings/settingsbittoolwidget.cpp @@ -271,6 +271,13 @@ void SettingsBitToolWidget::onDecryptClicked() void SettingsBitToolWidget::importAddressFromDecKey() { + // whenever a key is imported, we need to scan the whole chain + WalletRescanReserver reserver(pwalletMain); + if (!reserver.reserve()) { + ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_DEC->setText(tr("Wallet is currently rescanning. Abort existing rescan or wait.")); + return; + } WalletModel::UnlockContext ctx(walletModel->requestUnlock()); if (!ctx.isValid()) { ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); @@ -309,10 +316,7 @@ void SettingsBitToolWidget::importAddressFromDecKey() ui->statusLabel_DEC->setText(tr("Error adding key to the wallet")); return; } - - // whenever a key is imported, we need to scan the whole chain - pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' - pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), nullptr, true); + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); } ui->statusLabel_DEC->setStyleSheet("QLabel { color: green; }"); diff --git a/src/util.cpp b/src/util.cpp index 3ac197e0eeb2..c25216fc147f 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -197,7 +197,7 @@ void ArgsManager::InterpretNegatedOption(std::string& key, std::string& val) m_negated_args.insert(key); val = bool_val ? "0" : "1"; } else { - // In an invocation like "bitcoind -nofoo -foo" we want to unmark -foo + // In an invocation like "pivxd -nofoo -foo" we want to unmark -foo // as negated when we see the second option. m_negated_args.erase(key); } diff --git a/src/validation.cpp b/src/validation.cpp index 8f9644441d50..be53443ebdb4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -820,8 +820,10 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos) bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex) { - if (!ReadBlockFromDisk(block, pindex->GetBlockPos())) + CDiskBlockPos blockPos = WITH_LOCK(cs_main, return pindex->GetBlockPos(); ); + if (!ReadBlockFromDisk(block, blockPos)) { return false; + } if (block.GetHash() != pindex->GetBlockHash()) { LogPrintf("%s : block=%s index=%s\n", __func__, block.GetHash().GetHex(), pindex->GetBlockHash().GetHex()); return error("ReadBlockFromDisk(CBlock&, CBlockIndex*) : GetHash() doesn't match index"); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index d739f91e6615..f1f05a2f562d 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -92,9 +92,8 @@ UniValue importprivkey(const JSONRPCRequest& request) "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "4. fStakingAddress (boolean, optional, default=false) Whether this key refers to a (cold) staking address\n" - - "\nNote: This call can take minutes to complete if rescan is true.\n" - + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "\nExamples:\n" "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + @@ -108,6 +107,12 @@ UniValue importprivkey(const JSONRPCRequest& request) const std::string strSecret = request.params[0].get_str(); const std::string strLabel = (request.params.size() > 1 ? request.params[1].get_str() : ""); const bool fRescan = (request.params.size() > 2 ? request.params[2].get_bool() : true); + + WalletRescanReserver reserver(pwalletMain); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + const bool fStakingAddress = (request.params.size() > 3 ? request.params[3].get_bool() : false); CKey key = DecodeSecret(strSecret); @@ -130,22 +135,15 @@ UniValue importprivkey(const JSONRPCRequest& request) if (pwalletMain->HaveKey(vchAddress)) return NullUniValue; + // whenever a key is imported, we need to scan the whole chain + pwalletMain->UpdateTimeFirstKey(1); pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1; if (!pwalletMain->AddKeyPubKey(key, pubkey)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - - // whenever a key is imported, we need to scan the whole chain - pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' - - if (fRescan) { - CBlockIndex *pindex = chainActive.Genesis(); - if (fStakingAddress && !Params().IsRegTestNet()) { - // cold staking was activated after nBlockTimeProtocolV2 (PIVX v4.0). No need to scan the whole chain - pindex = chainActive[Params().GetConsensus().vUpgrades[Consensus::UPGRADE_V4_0].nActivationHeight]; - } - pwalletMain->ScanForWalletTransactions(pindex, nullptr, true); - } + } + if (fRescan) { + pwalletMain->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); } return NullUniValue; @@ -213,10 +211,8 @@ UniValue importaddress(const JSONRPCRequest& request) "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n" - - "\nNote: This call can take minutes to complete if rescan is true.\n" - "If you have the full public key, you should call importpublickey instead of this.\n" - + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "\nExamples:\n" "\nImport a script with rescan\n" + HelpExampleCli("importaddress", "\"myscript\"") + @@ -228,31 +224,37 @@ UniValue importaddress(const JSONRPCRequest& request) const std::string strLabel = (request.params.size() > 1 ? request.params[1].get_str() : ""); // Whether to perform rescan after import const bool fRescan = (request.params.size() > 2 ? request.params[2].get_bool() : true); + + WalletRescanReserver reserver(pwalletMain); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + // Whether to import a p2sh version, too const bool fP2SH = (request.params.size() > 3 ? request.params[3].get_bool() : false); - LOCK2(cs_main, pwalletMain->cs_wallet); - - bool isStakingAddress = false; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStakingAddress); + { + LOCK2(cs_main, pwalletMain->cs_wallet); - if (IsValidDestination(dest)) { - if (fP2SH) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); - ImportAddress(dest, strLabel, isStakingAddress ? - AddressBook::AddressBookPurpose::COLD_STAKING : - AddressBook::AddressBookPurpose::RECEIVE); + bool isStakingAddress = false; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStakingAddress); - } else if (IsHex(request.params[0].get_str())) { - std::vector data(ParseHex(request.params[0].get_str())); - ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); + if (IsValidDestination(dest)) { + if (fP2SH) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); + ImportAddress(dest, strLabel, isStakingAddress ? + AddressBook::AddressBookPurpose::COLD_STAKING : + AddressBook::AddressBookPurpose::RECEIVE); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address or script"); + } else if (IsHex(request.params[0].get_str())) { + std::vector data(ParseHex(request.params[0].get_str())); + ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address or script"); + } } - if (fRescan) { - pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), nullptr, true); + pwalletMain->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); pwalletMain->ReacceptWalletTransactions(); } @@ -269,7 +271,8 @@ UniValue importpubkey(const JSONRPCRequest& request) "1. \"pubkey\" (string, required) The hex-encoded public key\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" - "\nNote: This call can take minutes to complete if rescan is true.\n" + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "\nExamples:\n" "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + @@ -283,6 +286,11 @@ UniValue importpubkey(const JSONRPCRequest& request) // Whether to perform rescan after import const bool fRescan = (request.params.size() > 2 ? request.params[2].get_bool() : true); + WalletRescanReserver reserver(pwalletMain); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + if (!IsHex(request.params[0].get_str())) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); std::vector data(ParseHex(request.params[0].get_str())); @@ -290,13 +298,14 @@ UniValue importpubkey(const JSONRPCRequest& request) if (!pubKey.IsFullyValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); - LOCK2(cs_main, pwalletMain->cs_wallet); - - ImportAddress(pubKey.GetID(), strLabel, "receive"); - ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false); + { + LOCK2(cs_main, pwalletMain->cs_wallet); + ImportAddress(pubKey.GetID(), strLabel, "receive"); + ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false); + } if (fRescan) { - pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), nullptr, true); + pwalletMain->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); pwalletMain->ReacceptWalletTransactions(); } @@ -323,104 +332,101 @@ UniValue importwallet(const JSONRPCRequest& request) "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"")); - LOCK2(cs_main, pwalletMain->cs_wallet); - - EnsureWalletIsUnlocked(); - std::ifstream file; file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate); if (!file.is_open()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); - int64_t nTimeBegin = chainActive.Tip()->GetBlockTime(); + WalletRescanReserver reserver(pwalletMain); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + int64_t nTimeBegin = 0; bool fGood = true; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); - int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); - file.seekg(0, file.beg); - - pwalletMain->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI - while (file.good()) { - pwalletMain->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100)))); - std::string line; - std::getline(file, line); - if (line.empty() || line[0] == '#') - continue; - - std::vector vstr; - boost::split(vstr, line, boost::is_any_of(" ")); - if (vstr.size() < 2) - continue; - - // Sapling keys - // Let's see if the address is a valid PIVX spending key - if (pwalletMain->HasSaplingSPKM()) { - libzcash::SpendingKey spendingkey = KeyIO::DecodeSpendingKey(vstr[0]); - int64_t nTime = DecodeDumpTime(vstr[1]); - if (IsValidSpendingKey(spendingkey)) { - libzcash::SaplingExtendedSpendingKey saplingSpendingKey = *boost::get(&spendingkey); - auto addResult = pwalletMain->GetSaplingScriptPubKeyMan()->AddSpendingKeyToWallet( - Params().GetConsensus(), saplingSpendingKey, nTime); - if (addResult == KeyAlreadyExists) { - LogPrint(BCLog::SAPLING, "Skipping import of shielded addr (key already present)\n"); - } else if (addResult == KeyNotAdded) { - // Something went wrong - fGood = false; - } + nTimeBegin = chainActive.Tip()->GetBlockTime(); + int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); + file.seekg(0, file.beg); + + pwalletMain->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI + while (file.good()) { + pwalletMain->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100)))); + std::string line; + std::getline(file, line); + if (line.empty() || line[0] == '#') + continue; + + std::vector vstr; + boost::split(vstr, line, boost::is_any_of(" ")); + if (vstr.size() < 2) continue; + + // Sapling keys + // Let's see if the address is a valid PIVX spending key + if (pwalletMain->HasSaplingSPKM()) { + libzcash::SpendingKey spendingkey = KeyIO::DecodeSpendingKey(vstr[0]); + int64_t nTime = DecodeDumpTime(vstr[1]); + if (IsValidSpendingKey(spendingkey)) { + libzcash::SaplingExtendedSpendingKey saplingSpendingKey = *boost::get(&spendingkey); + auto addResult = pwalletMain->GetSaplingScriptPubKeyMan()->AddSpendingKeyToWallet( + Params().GetConsensus(), saplingSpendingKey, nTime); + if (addResult == KeyAlreadyExists) { + LogPrint(BCLog::SAPLING, "Skipping import of shielded addr (key already present)\n"); + } else if (addResult == KeyNotAdded) { + // Something went wrong + fGood = false; + } + continue; + } } - } - CKey key = DecodeSecret(vstr[0]); - if (!key.IsValid()) - continue; - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); - CKeyID keyid = pubkey.GetID(); - if (pwalletMain->HaveKey(keyid)) { - LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); - continue; - } - int64_t nTime = DecodeDumpTime(vstr[1]); - std::string strLabel; - bool fLabel = true; - for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { - const std::string& type = vstr[nStr]; - if (boost::algorithm::starts_with(type, "#")) - break; - if (type == "change=1") - fLabel = false; - else if (type == "reserve=1") - fLabel = false; - else if (type == "hdseed") - fLabel = false; - if (boost::algorithm::starts_with(type, "label=")) { - strLabel = DecodeDumpString(vstr[nStr].substr(6)); - fLabel = true; + CKey key = DecodeSecret(vstr[0]); + if (!key.IsValid()) + continue; + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + CKeyID keyid = pubkey.GetID(); + if (pwalletMain->HaveKey(keyid)) { + LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); + continue; } + int64_t nTime = DecodeDumpTime(vstr[1]); + std::string strLabel; + bool fLabel = true; + for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { + const std::string& type = vstr[nStr]; + if (boost::algorithm::starts_with(type, "#")) + break; + if (type == "change=1") + fLabel = false; + else if (type == "reserve=1") + fLabel = false; + else if (type == "hdseed") + fLabel = false; + if (boost::algorithm::starts_with(type, "label=")) { + strLabel = DecodeDumpString(vstr[nStr].substr(6)); + fLabel = true; + } + } + LogPrintf("Importing %s...\n", EncodeDestination(keyid)); + if (!pwalletMain->AddKeyPubKey(key, pubkey)) { + fGood = false; + continue; + } + pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; + if (fLabel) // TODO: This is not entirely true.. needs to be reviewed properly. + pwalletMain->SetAddressBook(keyid, strLabel, AddressBook::AddressBookPurpose::RECEIVE); + nTimeBegin = std::min(nTimeBegin, nTime); } - LogPrintf("Importing %s...\n", EncodeDestination(keyid)); - if (!pwalletMain->AddKeyPubKey(key, pubkey)) { - fGood = false; - continue; - } - pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; - if (fLabel) // TODO: This is not entirely true.. needs to be reviewed properly. - pwalletMain->SetAddressBook(keyid, strLabel, AddressBook::AddressBookPurpose::RECEIVE); - nTimeBegin = std::min(nTimeBegin, nTime); + file.close(); + pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI + pwalletMain->UpdateTimeFirstKey(nTimeBegin); } - file.close(); - pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI - - CBlockIndex* pindex = chainActive.Tip(); - while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - TIMESTAMP_WINDOW) - pindex = pindex->pprev; - - if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey) - pwalletMain->nTimeFirstKey = nTimeBegin; - - LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1); - pwalletMain->ScanForWalletTransactions(pindex); + pwalletMain->RescanFromTime(nTimeBegin, reserver, false /* update */); pwalletMain->MarkDirty(); if (!fGood) @@ -964,7 +970,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) " }\n" " ,...\n" "]\n" - + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n" "\nExamples:\n" + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"\" }, \"timestamp\":1455191478 }, " "{ \"scriptPubKey\": { \"address\": \"\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + @@ -986,58 +993,60 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) } } - LOCK2(cs_main, pwalletMain->cs_wallet); - EnsureWalletIsUnlocked(); - - // Verify all timestamps are present before importing any keys. - int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0; - for (const UniValue& data : requests.getValues()) { - GetImportTimestamp(data, now); + WalletRescanReserver reserver(pwalletMain); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } + int64_t now = 0; bool fRunScan = false; - const int64_t minimumTimestamp = 1; int64_t nLowestTimestamp = 0; - if (fRescan && chainActive.Tip()) { - nLowestTimestamp = chainActive.Tip()->GetBlockTime(); - } else { - fRescan = false; - } - UniValue response(UniValue::VARR); + { + LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); - for (const UniValue& data: requests.getValues()) { - const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); - const UniValue result = processImport(data, timestamp); - response.push_back(result); - - if (!fRescan) { - continue; + // Verify all timestamps are present before importing any keys. + int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0; + for (const UniValue& data : requests.getValues()) { + GetImportTimestamp(data, now); } - // If at least one request was successful then allow rescan. - if (result["success"].get_bool()) { - fRunScan = true; - } + const int64_t minimumTimestamp = 1; - // Get the lowest timestamp. - if (timestamp < nLowestTimestamp) { - nLowestTimestamp = timestamp; + if (fRescan && chainActive.Tip()) { + nLowestTimestamp = chainActive.Tip()->GetBlockTime(); + } else { + fRescan = false; } - } - if (fRescan && fRunScan && requests.size() && nLowestTimestamp <= chainActive.Tip()->GetBlockTimeMax()) { - CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindEarliestAtLeast(std::max(nLowestTimestamp - TIMESTAMP_WINDOW, 0)) - : chainActive.Genesis(); - CBlockIndex* scanFailed = nullptr; - if (pindex) { - scanFailed = pwalletMain->ScanForWalletTransactions(pindex, nullptr, true); - pwalletMain->ReacceptWalletTransactions(); + for (const UniValue& data: requests.getValues()) { + const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); + const UniValue result = processImport(data, timestamp); + response.push_back(result); + + if (!fRescan) { + continue; + } + + // If at least one request was successful then allow rescan. + if (result["success"].get_bool()) { + fRunScan = true; + } + + // Get the lowest timestamp. + if (timestamp < nLowestTimestamp) { + nLowestTimestamp = timestamp; + } } + } + if (fRescan && fRunScan && requests.size()) { + int64_t scannedTime = pwalletMain->RescanFromTime(nLowestTimestamp, reserver, true /* update */); + pwalletMain->ReacceptWalletTransactions(); - if (scanFailed) { - const std::vector& results = response.getValues(); + if (scannedTime > nLowestTimestamp) { + std::vector results = response.getValues(); response.clear(); response.setArray(); size_t i = 0; @@ -1046,7 +1055,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) // range, or if the import result already has an error set, let // the result stand unmodified. Otherwise replace the result // with an error message. - if (GetImportTimestamp(request, now) - TIMESTAMP_WINDOW >= scanFailed->GetBlockTimeMax() || results.at(i).exists("error")) { + if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { response.push_back(results.at(i)); } else { UniValue result = UniValue(UniValue::VOBJ); @@ -1059,7 +1068,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) "caused by pruning or data corruption (see pivxd log for details) and could " "be dealt with by downloading and rescanning the relevant blocks (see -reindex " "and -rescan options).", - GetImportTimestamp(request, now), scanFailed->GetBlockTimeMax(), TIMESTAMP_WINDOW))); + GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); response.push_back(std::move(result)); } ++i; @@ -1135,9 +1144,6 @@ UniValue bip38decrypt(const JSONRPCRequest& request) HelpExampleCli("bip38decrypt", "\"encryptedkey\" \"mypassphrase\"") + HelpExampleRpc("bip38decrypt", "\"encryptedkey\" \"mypassphrase\"")); - LOCK2(cs_main, pwalletMain->cs_wallet); - - EnsureWalletIsUnlocked(); /** Collect private key and passphrase **/ std::string strKey = request.params[0].get_str(); @@ -1157,11 +1163,18 @@ UniValue bip38decrypt(const JSONRPCRequest& request) if (!key.IsValid()) throw JSONRPCError(RPC_WALLET_ERROR, "Private Key Not Valid"); + WalletRescanReserver reserver(pwalletMain); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); result.pushKV("Address", EncodeDestination(pubkey.GetID())); CKeyID vchAddress = pubkey.GetID(); { + LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); pwalletMain->MarkDirty(); pwalletMain->SetAddressBook(vchAddress, "", AddressBook::AddressBookPurpose::RECEIVE); @@ -1173,12 +1186,11 @@ UniValue bip38decrypt(const JSONRPCRequest& request) if (!pwalletMain->AddKeyPubKey(key, pubkey)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - - // whenever a key is imported, we need to scan the whole chain - pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' - pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), nullptr, true); } + // whenever a key is imported, we need to scan the whole chain + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); + return result; } @@ -1217,8 +1229,6 @@ UniValue importsaplingkey(const JSONRPCRequest& request) ); EnsureWallet(); - LOCK2(cs_main, pwalletMain->cs_wallet); - EnsureWalletIsUnlocked(); // Whether to perform rescan after import bool fRescan = true; @@ -1237,40 +1247,49 @@ UniValue importsaplingkey(const JSONRPCRequest& request) } } - // Height to rescan from - int nRescanHeight = 0; - if (request.params.size() > 2) - nRescanHeight = request.params[2].get_int(); - if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); - } - - std::string strSecret = request.params[0].get_str(); - auto spendingkey = KeyIO::DecodeSpendingKey(strSecret); - if (!IsValidSpendingKey(spendingkey)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); + WalletRescanReserver reserver(pwalletMain); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - libzcash::SaplingExtendedSpendingKey saplingSpendingKey = *boost::get(&spendingkey); UniValue result(UniValue::VOBJ); - result.pushKV("address", KeyIO::EncodePaymentAddress( saplingSpendingKey.DefaultAddress())); + CBlockIndex* pindexRescan{nullptr}; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); - // Sapling support - auto addResult = pwalletMain->GetSaplingScriptPubKeyMan()->AddSpendingKeyToWallet(Params().GetConsensus(), saplingSpendingKey, -1); - if (addResult == KeyAlreadyExists && fIgnoreExistingKey) { - return result; - } - pwalletMain->MarkDirty(); - if (addResult == KeyNotAdded) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); - } + // Height to rescan from + int nRescanHeight = 0; + if (request.params.size() > 2) + nRescanHeight = request.params[2].get_int(); + if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); + } - // whenever a key is imported, we need to scan the whole chain - pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' + std::string strSecret = request.params[0].get_str(); + auto spendingkey = KeyIO::DecodeSpendingKey(strSecret); + if (!IsValidSpendingKey(spendingkey)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); + } + + libzcash::SaplingExtendedSpendingKey saplingSpendingKey = *boost::get(&spendingkey); + result.pushKV("address", KeyIO::EncodePaymentAddress( saplingSpendingKey.DefaultAddress())); + + // Sapling support + auto addResult = pwalletMain->GetSaplingScriptPubKeyMan()->AddSpendingKeyToWallet(Params().GetConsensus(), saplingSpendingKey, -1); + if (addResult == KeyAlreadyExists && fIgnoreExistingKey) { + return result; + } + pwalletMain->MarkDirty(); + if (addResult == KeyNotAdded) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet"); + } + pindexRescan = chainActive[nRescanHeight]; + } // We want to scan for transactions and notes if (fRescan) { - pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], nullptr, true); + pwalletMain->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); } return result; @@ -1309,9 +1328,6 @@ UniValue importsaplingviewingkey(const JSONRPCRequest& request) ); EnsureWallet(); - LOCK2(cs_main, pwalletMain->cs_wallet); - - EnsureWalletIsUnlocked(); // Whether to perform rescan after import bool fRescan = true; @@ -1330,40 +1346,53 @@ UniValue importsaplingviewingkey(const JSONRPCRequest& request) } } - // Height to rescan from - int nRescanHeight = 0; - if (request.params.size() > 2) { - nRescanHeight = request.params[2].get_int(); - } - if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); + WalletRescanReserver reserver(pwalletMain); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - std::string strVKey = request.params[0].get_str(); - libzcash::ViewingKey viewingkey = KeyIO::DecodeViewingKey(strVKey); - if (!IsValidViewingKey(viewingkey)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key"); - } - libzcash::SaplingExtendedFullViewingKey efvk = *boost::get(&viewingkey); UniValue result(UniValue::VOBJ); - result.pushKV("address", KeyIO::EncodePaymentAddress(efvk.DefaultAddress())); - - auto addResult = pwalletMain->GetSaplingScriptPubKeyMan()->AddViewingKeyToWallet(efvk); - if (addResult == SpendingKeyExists) { - throw JSONRPCError( - RPC_WALLET_ERROR, - "The wallet already contains the private key for this viewing key"); - } else if (addResult == KeyAlreadyExists && fIgnoreExistingKey) { - return result; - } - pwalletMain->MarkDirty(); - if (addResult == KeyNotAdded) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); + CBlockIndex* pindexRescan{nullptr}; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); + + // Height to rescan from + int nRescanHeight = 0; + if (request.params.size() > 2) { + nRescanHeight = request.params[2].get_int(); + } + if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); + } + + std::string strVKey = request.params[0].get_str(); + libzcash::ViewingKey viewingkey = KeyIO::DecodeViewingKey(strVKey); + if (!IsValidViewingKey(viewingkey)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key"); + } + libzcash::SaplingExtendedFullViewingKey efvk = *boost::get(&viewingkey); + result.pushKV("address", KeyIO::EncodePaymentAddress(efvk.DefaultAddress())); + + auto addResult = pwalletMain->GetSaplingScriptPubKeyMan()->AddViewingKeyToWallet(efvk); + if (addResult == SpendingKeyExists) { + throw JSONRPCError( + RPC_WALLET_ERROR, + "The wallet already contains the private key for this viewing key"); + } else if (addResult == KeyAlreadyExists && fIgnoreExistingKey) { + return result; + } + pwalletMain->MarkDirty(); + if (addResult == KeyNotAdded) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); + } + + pindexRescan = chainActive[nRescanHeight]; } // We want to scan for transactions and notes if (fRescan) { - pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], nullptr, true); + pwalletMain->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); } return result; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 322e2257841a..6b27ba5655a7 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4207,34 +4207,44 @@ UniValue rescanblockchain(const JSONRPCRequest& request) } EnsureWallet(); - LOCK2(cs_main, pwalletMain->cs_wallet); + WalletRescanReserver reserver(pwalletMain); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } - CBlockIndex *pindexStart = chainActive.Genesis(); + CBlockIndex *pindexStart = nullptr; CBlockIndex *pindexStop = nullptr; - if (!request.params[0].isNull()) { - pindexStart = chainActive[request.params[0].get_int()]; - if (!pindexStart) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); + CBlockIndex *pChainTip = nullptr; + { + LOCK(cs_main); + pindexStart = chainActive.Genesis(); + pChainTip = chainActive.Tip(); + + if (!request.params[0].isNull()) { + pindexStart = chainActive[request.params[0].get_int()]; + if (!pindexStart) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); + } } - } - if (!request.params[1].isNull()) { - pindexStop = chainActive[request.params[1].get_int()]; - if (!pindexStop) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); - } - else if (pindexStop->nHeight < pindexStart->nHeight) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height"); + if (!request.params[1].isNull()) { + pindexStop = chainActive[request.params[1].get_int()]; + if (!pindexStop) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); + } + else if (pindexStop->nHeight < pindexStart->nHeight) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height"); + } } } - CBlockIndex *stopBlock = pwalletMain->ScanForWalletTransactions(pindexStart, pindexStop, true); + CBlockIndex *stopBlock = pwalletMain->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true); if (!stopBlock) { if (pwalletMain->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); } // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex - stopBlock = pindexStop ? pindexStop : chainActive.Tip(); + stopBlock = pindexStop ? pindexStop : pChainTip; } else { throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index b2709e095ffd..659407c10b65 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -336,7 +336,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) CWallet wallet; WITH_LOCK(wallet.cs_wallet, wallet.SetLastBlockProcessed(newTip); ); AddKey(wallet, coinbaseKey); - BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr)); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 500 * COIN); } @@ -350,8 +352,10 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { CWallet wallet; AddKey(wallet, coinbaseKey); - BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr)); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));; + BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 250 * COIN); } */ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index dc2b4ee3a818..beee18dc5e26 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -252,6 +252,22 @@ bool CWallet::LoadCryptedKey(const CPubKey& vchPubKey, const std::vectornHeight + 1 : 0); + } + + if (startBlock) { + const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update); + if (failedBlock) { + return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; + } + } + return startTime; +} + /** * Scan the block chain (starting in pindexStart) for transactions * from or to us. If fUpdate is true, found transactions that already * exist in the wallet will be updated. * - * If pindexStop is not a nullptr, the scan will stop at the block-index - * defined by pindexStop - * * Returns null if scan was successful. Otherwise, if a complete rescan was not * possible (due to pruning or corruption), returns pointer to the most recent * block that could not be scanned. + * + * If pindexStop is not a nullptr, the scan will stop at the block-index + * defined by pindexStop + * + * Caller needs to make sure pindexStop (and the optional pindexStart) are on + * the main chain after to the addition of any new keys you want to detect + * transactions for. */ -CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate, bool fromStartup) +CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate, bool fromStartup) { - CBlockIndex* ret = nullptr; int64_t nNow = GetTime(); + assert(reserver.isReserved()); if (pindexStop) { assert(pindexStop->nHeight >= pindexStart->nHeight); } - fAbortRescan = false; - fScanningWallet = true; - CBlockIndex* pindex = pindexStart; - { - LOCK(cs_main); - // no need to read and scan block, if block was created before - // our wallet birthday (as adjusted for block time variability) - while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - TIMESTAMP_WINDOW))) - pindex = chainActive.Next(pindex); - } - + CBlockIndex* ret = nullptr; { ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup - const double dProgressStart = Checkpoints::GuessVerificationProgress(pindex, false); - const CBlockIndex* tip = nullptr; - double dProgressTip = 0.0; - std::vector myTxHashes; + CBlockIndex* tip = nullptr; + double dProgressStart; + double dProgressTip; + { + LOCK(cs_main); + tip = chainActive.Tip(); + dProgressStart = Checkpoints::GuessVerificationProgress(pindex, false); + dProgressTip = Checkpoints::GuessVerificationProgress(tip, false); + } - double gvp = dProgressStart; + std::vector myTxHashes; while (pindex && !fAbortRescan) { - gvp = Checkpoints::GuessVerificationProgress(pindex, false); + double gvp = 0; if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) { - ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int) ((gvp - dProgressStart) / - (dProgressTip - dProgressStart) * - 100)))); + gvp = WITH_LOCK(cs_main, return Checkpoints::GuessVerificationProgress(pindex, false); ); + ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); @@ -1862,26 +1905,14 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock } CBlock block; - if (!ReadBlockFromDisk(block, pindex)) { - LogPrintf("Unable to read block %d (%s) from disk.", pindex->nHeight, pindex->GetBlockHash().ToString()); - ret = pindex; - break; // failed, try to save txs and return the failed index - } - - { + if (ReadBlockFromDisk(block, pindex)) { LOCK2(cs_main, cs_wallet); - if (tip != chainActive.Tip()) { - tip = chainActive.Tip(); - // in case the tip has changed, update progress max - dProgressTip = Checkpoints::GuessVerificationProgress(tip, false); - } - - if (!chainActive.Contains(pindex)) { - // Abort scan if current block is no longer active, to prevent - // marking transactions as coming from the wrong block. - ret = pindex; - break; - } + if (pindex && !chainActive.Contains(pindex)) { + // Abort scan if current block is no longer active, to prevent + // marking transactions as coming from the wrong block. + ret = pindex; + break; + } for (int posInBlock = 0; posInBlock < (int) block.vtx.size(); posInBlock++) { const auto& tx = block.vtx[posInBlock]; CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, pindex->nHeight, pindex->GetBlockHash(), posInBlock); @@ -1901,10 +1932,20 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock ChainTipAdded(pindex, &block, saplingTree); } } - if (pindex == pindexStop) { - break; - } + } else { + ret = pindex; + } + if (pindex == pindexStop) { + break; + } + { + LOCK(cs_main); pindex = chainActive.Next(pindex); + if (tip != chainActive.Tip()) { + tip = chainActive.Tip(); + // in case the tip has changed, update progress max + dProgressTip = Checkpoints::GuessVerificationProgress(tip, false); + } } } @@ -1925,7 +1966,6 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, Checkpoints::GuessVerificationProgress(pindex, false)); } ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI - fScanningWallet = false; } return ret; } @@ -4324,10 +4364,24 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) if (chainActive.Tip() && chainActive.Tip() != pindexRescan) { uiInterface.InitMessage(_("Rescanning...")); LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); + + // no need to read and scan block, if block was created before + // our wallet birthday (as adjusted for block time variability) + while (pindexRescan && walletInstance->nTimeFirstKey && + pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW)) { + pindexRescan = chainActive.Next(pindexRescan); + } const int64_t nWalletRescanTime = GetTimeMillis(); - if (walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true, true) != nullptr) { - UIError(_("Shutdown requested over the txs scan. Exiting.")); - return nullptr; + { + WalletRescanReserver reserver(walletInstance); + if (!reserver.reserve()) { + UIError(_("Failed to rescan the wallet during initialization")); + return nullptr; + } + if (walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true, true) != nullptr) { + UIError(_("Shutdown requested over the txs scan. Exiting.")); + return nullptr; + } } LogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nWalletRescanTime); walletInstance->SetBestChain(chainActive.GetLocator()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a0079e58f511..5791665acfef 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -44,7 +44,8 @@ #include #include -extern CWallet* pwalletMain; +typedef CWallet* CWalletRef; +extern CWalletRef pwalletMain; /** * Settings @@ -85,6 +86,7 @@ static const unsigned int DEFAULT_CREATEWALLETBACKUPS = 10; static const bool DEFAULT_DISABLE_WALLET = false; extern const char * DEFAULT_WALLET_DAT; +static const int64_t TIMESTAMP_MIN = 0; class CAddressBookIterator; class CCoinControl; @@ -553,6 +555,9 @@ class CWalletTx bool AcceptToMemoryPool(CValidationState& state, bool fLimitFree = true, bool fRejectInsaneFee = true, bool ignoreFees = false); }; + +class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime + /** * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. @@ -562,7 +567,10 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface private: static std::atomic fFlushScheduled; std::atomic fAbortRescan; - std::atomic fScanningWallet; + std::atomic fScanningWallet; //controlled by WalletRescanReserver + std::mutex mutexScanning; + friend class WalletRescanReserver; + //! keeps track of whether Unlock has run a thorough check before bool fDecryptionThoroughlyChecked{false}; @@ -906,6 +914,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool LoadKeyMetadata(const CPubKey& pubkey, const CKeyMetadata& metadata); bool LoadMinVersion(int nVersion); + void UpdateTimeFirstKey(int64_t nCreateTime); //! Adds an encrypted key to the store, and saves it to disk. bool AddCryptedKey(const CPubKey& vchPubKey, const std::vector& vchCryptedSecret) override; @@ -958,7 +967,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool Upgrade(std::string& error, const int& prevVersion); bool ActivateSaplingWallet(bool memOnly = false); - CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop = nullptr, bool fUpdate = false, bool fromStartup = false); + int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); + CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false, bool fromStartup = false); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions(bool fFirstLoad = false); void ResendWalletTransactions(CConnman* connman) override; @@ -1226,4 +1236,39 @@ class CStakeableOutput : public COutput }; +/** RAII object to check and reserve a wallet rescan */ +class WalletRescanReserver +{ +private: + CWalletRef m_wallet; + bool m_could_reserve; +public: + explicit WalletRescanReserver(CWalletRef w) : m_wallet(w), m_could_reserve(false) {} + + bool reserve() + { + assert(!m_could_reserve); + std::lock_guard lock(m_wallet->mutexScanning); + if (m_wallet->fScanningWallet) { + return false; + } + m_wallet->fScanningWallet = true; + m_could_reserve = true; + return true; + } + + bool isReserved() const + { + return (m_could_reserve && m_wallet->fScanningWallet); + } + + ~WalletRescanReserver() + { + std::lock_guard lock(m_wallet->mutexScanning); + if (m_could_reserve) { + m_wallet->fScanningWallet = false; + } + } +}; + #endif // BITCOIN_WALLET_H