diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -133,7 +133,10 @@ } { LOCK(cs_main); - wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, + reserver, true); } wallet.SetBroadcastTransactions(true); diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -978,7 +978,13 @@ bool ReadBlockFromDisk(CBlock &block, const CBlockIndex *pindex, const Config &config) { - if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), config)) { + CDiskBlockPos blockPos; + { + LOCK(cs_main); + blockPos = pindex->GetBlockPos(); + } + + if (!ReadBlockFromDisk(block, blockPos, config)) { return false; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -86,8 +86,11 @@ "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 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\"") + @@ -100,66 +103,72 @@ "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")); - LOCK2(cs_main, pwallet->cs_wallet); + WalletRescanReserver reserver(pwallet); + bool fRescan = true; + { + LOCK2(cs_main, pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(pwallet); - std::string strSecret = request.params[0].get_str(); - std::string strLabel = ""; - if (!request.params[1].isNull()) { - strLabel = request.params[1].get_str(); - } + std::string strSecret = request.params[0].get_str(); + std::string strLabel = ""; + if (!request.params[1].isNull()) strLabel = request.params[1].get_str(); - // Whether to perform rescan after import - bool fRescan = true; - if (!request.params[2].isNull()) { - fRescan = request.params[2].get_bool(); - } - - if (fRescan && fPruneMode) { - throw JSONRPCError(RPC_WALLET_ERROR, - "Rescan is disabled in pruned mode"); - } + // Whether to perform rescan after import + if (!request.params[2].isNull()) { + fRescan = request.params[2].get_bool(); + } - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(strSecret); + if (fRescan && fPruneMode) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Rescan is disabled in pruned mode"); + } - if (!fGood) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Invalid private key encoding"); - } + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Wallet is currently rescanning. Abort existing " + "rescan or wait."); + } - CKey key = vchSecret.GetKey(); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Private key outside allowed range"); - } + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(strSecret); - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); - CKeyID vchAddress = pubkey.GetID(); - { - pwallet->MarkDirty(); - pwallet->SetAddressBook(vchAddress, strLabel, "receive"); + if (!fGood) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid private key encoding"); + } - // Don't throw error in case a key is already there - if (pwallet->HaveKey(vchAddress)) { - return NullUniValue; + CKey key = vchSecret.GetKey(); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Private key outside allowed range"); } - pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + CKeyID vchAddress = pubkey.GetID(); + { + pwallet->MarkDirty(); + pwallet->SetAddressBook(vchAddress, strLabel, "receive"); - if (!pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - } + // Don't throw error in case a key is already there + if (pwallet->HaveKey(vchAddress)) { + return NullUniValue; + } - // whenever a key is imported, we need to scan the whole chain - pwallet->UpdateTimeFirstKey(1); + // whenever a key is imported, we need to scan the whole chain + pwallet->UpdateTimeFirstKey(1); + pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; - if (fRescan) { - pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); + if (!pwallet->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error adding key to wallet"); + } } } + if (fRescan) { + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); + } return NullUniValue; } @@ -253,8 +262,12 @@ "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" + "\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" "If you have the full public key, you should call importpubkey " "instead of this.\n" "\nNote: If you import a non-standard raw script in hex form, " @@ -286,34 +299,42 @@ "Rescan is disabled in pruned mode"); } + WalletRescanReserver reserver(pwallet); + 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 bool fP2SH = false; if (!request.params[3].isNull()) { fP2SH = request.params[3].get_bool(); } - LOCK2(cs_main, pwallet->cs_wallet); + { + LOCK2(cs_main, pwallet->cs_wallet); - CTxDestination dest = - DecodeDestination(request.params[0].get_str(), config.GetChainParams()); - if (IsValidDestination(dest)) { - if (fP2SH) { + CTxDestination dest = DecodeDestination(request.params[0].get_str(), + config.GetChainParams()); + 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(pwallet, dest, strLabel); + } else if (IsHex(request.params[0].get_str())) { + std::vector data(ParseHex(request.params[0].get_str())); + ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, + fP2SH); + } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Cannot use the p2sh flag with an address - use " - "a script instead"); + "Invalid Bitcoin address or script"); } - ImportAddress(pwallet, dest, strLabel); - } else if (IsHex(request.params[0].get_str())) { - std::vector data(ParseHex(request.params[0].get_str())); - ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, - fP2SH); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Invalid Bitcoin address or script"); } - if (fRescan) { - pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); pwallet->ReacceptWalletTransactions(); } @@ -466,8 +487,12 @@ "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\"") + @@ -493,11 +518,17 @@ "Rescan is disabled in pruned mode"); } + WalletRescanReserver reserver(pwallet); + 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())); CPubKey pubKey(data.begin(), data.end()); if (!pubKey.IsFullyValid()) { @@ -505,13 +536,14 @@ "Pubkey is not a valid public key"); } - LOCK2(cs_main, pwallet->cs_wallet); - - ImportAddress(pwallet, pubKey.GetID(), strLabel); - ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); + { + LOCK2(cs_main, pwallet->cs_wallet); + ImportAddress(pwallet, pubKey.GetID(), strLabel); + ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); + } if (fRescan) { - pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); pwallet->ReacceptWalletTransactions(); } @@ -544,110 +576,116 @@ "Importing wallets is disabled in pruned mode"); } - LOCK2(cs_main, pwallet->cs_wallet); - - EnsureWalletIsUnlocked(pwallet); - - 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"); + WalletRescanReserver reserver(pwallet); + if (!reserver.reserve()) { + throw JSONRPCError( + RPC_WALLET_ERROR, + "Wallet is currently rescanning. Abort existing rescan or wait."); } - int64_t nTimeBegin = chainActive.Tip()->GetBlockTime(); - + int64_t nTimeBegin = 0; bool fGood = true; + { + LOCK2(cs_main, pwallet->cs_wallet); - int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); - file.seekg(0, file.beg); - - // show progress dialog in GUI - pwallet->ShowProgress(_("Importing..."), 0); - while (file.good()) { - pwallet->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; - } + EnsureWalletIsUnlocked(pwallet); - std::vector vstr; - boost::split(vstr, line, boost::is_any_of(" ")); - if (vstr.size() < 2) { - continue; + 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"); } - CBitcoinSecret vchSecret; - if (vchSecret.SetString(vstr[0])) { - CKey key = vchSecret.GetKey(); - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); - CKeyID keyid = pubkey.GetID(); - if (pwallet->HaveKey(keyid)) { - LogPrintf("Skipping import of %s (key already present)\n", - EncodeDestination(keyid)); + nTimeBegin = chainActive.Tip()->GetBlockTime(); + + int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); + file.seekg(0, file.beg); + + // show progress dialog in GUI + pwallet->ShowProgress(_("Importing..."), 0); + while (file.good()) { + pwallet->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; } - int64_t nTime = DecodeDumpTime(vstr[1]); - std::string strLabel; - bool fLabel = true; - for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { - if (boost::algorithm::starts_with(vstr[nStr], "#")) { - break; + + std::vector vstr; + boost::split(vstr, line, boost::is_any_of(" ")); + if (vstr.size() < 2) { + continue; + } + CBitcoinSecret vchSecret; + if (vchSecret.SetString(vstr[0])) { + CKey key = vchSecret.GetKey(); + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + CKeyID keyid = pubkey.GetID(); + if (pwallet->HaveKey(keyid)) { + LogPrintf("Skipping import of %s (key already present)\n", + EncodeDestination(keyid)); + continue; } - if (vstr[nStr] == "change=1") { - fLabel = false; + int64_t nTime = DecodeDumpTime(vstr[1]); + std::string strLabel; + bool fLabel = true; + for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { + if (boost::algorithm::starts_with(vstr[nStr], "#")) break; + if (vstr[nStr] == "change=1") { + fLabel = false; + } + if (vstr[nStr] == "reserve=1") { + fLabel = false; + } + if (boost::algorithm::starts_with(vstr[nStr], "label=")) { + strLabel = DecodeDumpString(vstr[nStr].substr(6)); + fLabel = true; + } } - if (vstr[nStr] == "reserve=1") { - fLabel = false; + LogPrintf("Importing %s...\n", EncodeDestination(keyid)); + if (!pwallet->AddKeyPubKey(key, pubkey)) { + fGood = false; + continue; } - if (boost::algorithm::starts_with(vstr[nStr], "label=")) { - strLabel = DecodeDumpString(vstr[nStr].substr(6)); - fLabel = true; + pwallet->mapKeyMetadata[keyid].nCreateTime = nTime; + if (fLabel) { + pwallet->SetAddressBook(keyid, strLabel, "receive"); + } + nTimeBegin = std::min(nTimeBegin, nTime); + } else if (IsHex(vstr[0])) { + std::vector vData(ParseHex(vstr[0])); + CScript script = CScript(vData.begin(), vData.end()); + if (pwallet->HaveCScript(script)) { + LogPrintf( + "Skipping import of %s (script already present)\n", + vstr[0]); + continue; + } + if (!pwallet->AddCScript(script)) { + LogPrintf("Error importing script %s\n", vstr[0]); + fGood = false; + continue; + } + int64_t birth_time = DecodeDumpTime(vstr[1]); + if (birth_time > 0) { + pwallet->m_script_metadata[CScriptID(script)].nCreateTime = + birth_time; + nTimeBegin = std::min(nTimeBegin, birth_time); } - } - LogPrintf("Importing %s...\n", EncodeDestination(keyid)); - if (!pwallet->AddKeyPubKey(key, pubkey)) { - fGood = false; - continue; - } - pwallet->mapKeyMetadata[keyid].nCreateTime = nTime; - if (fLabel) { - pwallet->SetAddressBook(keyid, strLabel, "receive"); - } - nTimeBegin = std::min(nTimeBegin, nTime); - } else if (IsHex(vstr[0])) { - std::vector vData(ParseHex(vstr[0])); - CScript script = CScript(vData.begin(), vData.end()); - if (pwallet->HaveCScript(script)) { - LogPrintf("Skipping import of %s (script already present)\n", - vstr[0]); - continue; - } - if (!pwallet->AddCScript(script)) { - LogPrintf("Error importing script %s\n", vstr[0]); - fGood = false; - continue; - } - int64_t birth_time = DecodeDumpTime(vstr[1]); - if (birth_time > 0) { - pwallet->m_script_metadata[CScriptID(script)].nCreateTime = - birth_time; - nTimeBegin = std::min(nTimeBegin, birth_time); } } - } - file.close(); + file.close(); - // hide progress dialog in GUI - pwallet->ShowProgress("", 100); - pwallet->UpdateTimeFirstKey(nTimeBegin); - - pwallet->RescanFromTime(nTimeBegin, false /* update */); + // hide progress dialog in GUI + pwallet->ShowProgress("", 100); + pwallet->UpdateTimeFirstKey(nTimeBegin); + } + pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */); pwallet->MarkDirty(); if (!fGood) { @@ -1281,6 +1319,8 @@ " {\n" " \"rescan\": , (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\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 }]'") + @@ -1307,52 +1347,59 @@ } } - LOCK2(cs_main, pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); - - // Verify all timestamps are present before importing any keys. - const int64_t now = - chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0; - for (const UniValue &data : requests.getValues()) { - GetImportTimestamp(data, now); + WalletRescanReserver reserver(pwallet); + 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, pwallet->cs_wallet); + EnsureWalletIsUnlocked(pwallet); - for (const UniValue &data : requests.getValues()) { - const int64_t timestamp = - std::max(GetImportTimestamp(data, now), minimumTimestamp); - const UniValue result = ProcessImport(pwallet, data, timestamp); - response.push_back(result); - - if (!fRescan) { - continue; + // Verify all timestamps are present before importing any keys. + 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; + + if (fRescan && chainActive.Tip()) { + nLowestTimestamp = chainActive.Tip()->GetBlockTime(); + } else { + fRescan = false; } - // Get the lowest timestamp. - if (timestamp < nLowestTimestamp) { - nLowestTimestamp = timestamp; + for (const UniValue &data : requests.getValues()) { + const int64_t timestamp = + std::max(GetImportTimestamp(data, now), minimumTimestamp); + const UniValue result = ProcessImport(pwallet, 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 = - pwallet->RescanFromTime(nLowestTimestamp, true /* update */); + int64_t scannedTime = pwallet->RescanFromTime( + nLowestTimestamp, reserver, true /* update */); pwallet->ReacceptWalletTransactions(); if (scannedTime > nLowestTimestamp) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3864,30 +3864,46 @@ HelpExampleRpc("rescanblockchain", "100000 120000")); } - LOCK2(cs_main, pwallet->cs_wallet); + WalletRescanReserver reserver(pwallet); + 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"); + } } } // We can't rescan beyond non-pruned blocks, stop and throw an error if (fPruneMode) { - CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip(); + LOCK(cs_main); + CBlockIndex *block = pindexStop ? pindexStop : pChainTip; while (block && block->nHeight >= pindexStart->nHeight) { if (!block->nStatus.hasData()) { throw JSONRPCError(RPC_MISC_ERROR, @@ -3899,20 +3915,19 @@ } } - CBlockIndex *stopBlock = - pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true); + CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions( + pindexStart, pindexStop, reserver, true); if (!stopBlock) { if (pwallet->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."); } - UniValue response(UniValue::VOBJ); response.pushKV("start_height", pindexStart->nHeight); response.pushKV("stop_height", stopBlock->nHeight); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -475,8 +475,10 @@ { CWallet wallet(Params()); 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(), 100 * COIN); } @@ -489,8 +491,10 @@ { CWallet wallet(Params()); AddKey(wallet, coinbaseKey); - BOOST_CHECK_EQUAL(oldTip, - wallet.ScanForWalletTransactions(oldTip, nullptr)); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions( + oldTip, nullptr, reserver)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } @@ -714,7 +718,10 @@ bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); - wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr); + WalletRescanReserver reserver(wallet.get()); + reserver.reserve(); + wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, + reserver); } ~ListCoinsTestingSetup() { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -615,6 +615,8 @@ std::vector _ssExtra; }; +// forward declarations for ScanForWalletTransactions/RescanFromTime +class WalletRescanReserver; /** * A CWallet is an extension of a keystore, which also maintains a set of * transactions and balances, and provides the ability to create new @@ -624,7 +626,10 @@ private: static std::atomic fFlushScheduled; std::atomic fAbortRescan; + // controlled by WalletRescanReserver std::atomic fScanningWallet; + std::mutex mutexScanning; + friend class WalletRescanReserver; /** * Select a set of coins such that nValueRet >= nTargetValue and at least @@ -958,9 +963,11 @@ bool AddToWalletIfInvolvingMe(const CTransactionRef &tx, const CBlockIndex *pIndex, int posInBlock, bool fUpdate); - int64_t RescanFromTime(int64_t startTime, bool update); + int64_t RescanFromTime(int64_t startTime, + const WalletRescanReserver &reserver, bool update); CBlockIndex *ScanForWalletTransactions(CBlockIndex *pindexStart, CBlockIndex *pindexStop, + const WalletRescanReserver &reserver, bool fUpdate = false); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions(); @@ -1273,4 +1280,37 @@ return true; } +/** 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_WALLET_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1688,21 +1688,25 @@ * @return Earliest timestamp that could be successfully scanned from. Timestamp * returned will be higher than startTime if relevant blocks could not be read. */ -int64_t CWallet::RescanFromTime(int64_t startTime, bool update) { - AssertLockHeld(cs_main); - AssertLockHeld(cs_wallet); - +int64_t CWallet::RescanFromTime(int64_t startTime, + const WalletRescanReserver &reserver, + bool update) { // Find starting block. May be null if nCreateTime is greater than the // highest blockchain timestamp, in which case there is nothing that needs // to be scanned. - CBlockIndex *const startBlock = - chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); - LogPrintf("%s: Rescanning last %i blocks\n", __func__, - startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); + CBlockIndex *startBlock = nullptr; + { + LOCK(cs_main); + startBlock = + chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); + LogPrintf("%s: Rescanning last %i blocks\n", __func__, + startBlock ? chainActive.Height() - startBlock->nHeight + 1 + : 0); + } if (startBlock) { const CBlockIndex *const failedBlock = - ScanForWalletTransactions(startBlock, nullptr, update); + ScanForWalletTransactions(startBlock, nullptr, reserver, update); if (failedBlock) { return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; } @@ -1719,14 +1723,19 @@ * 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. + * 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) { +CBlockIndex *CWallet::ScanForWalletTransactions( + CBlockIndex *pindexStart, CBlockIndex *pindexStop, + const WalletRescanReserver &reserver, bool fUpdate) { int64_t nNow = GetTime(); + assert(reserver.isReserved()); if (pindexStop) { assert(pindexStop->nHeight >= pindexStart->nHeight); } @@ -1734,31 +1743,40 @@ CBlockIndex *pindex = pindexStart; CBlockIndex *ret = nullptr; { - LOCK2(cs_main, cs_wallet); fAbortRescan = false; - fScanningWallet = true; // Show rescan progress in GUI as dialog or on splashscreen, if -rescan // on startup. ShowProgress(_("Rescanning..."), 0); - double dProgressStart = - GuessVerificationProgress(chainParams.TxData(), pindex); - double dProgressTip = - GuessVerificationProgress(chainParams.TxData(), chainActive.Tip()); + CBlockIndex *tip = nullptr; + double dProgressStart; + double dProgressTip; + { + LOCK(cs_main); + tip = chainActive.Tip(); + dProgressStart = + GuessVerificationProgress(chainParams.TxData(), pindex); + dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip); + } while (pindex && !fAbortRescan) { if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) { + double gvp = 0; + { + LOCK(cs_main); + gvp = + GuessVerificationProgress(chainParams.TxData(), pindex); + } ShowProgress( _("Rescanning..."), - std::max(1, std::min( - 99, (GuessVerificationProgress( - chainParams.TxData(), pindex) - - dProgressStart) / - (dProgressTip - dProgressStart) * - 100))); + std::max( + 1, std::min(99, (int)((gvp - dProgressStart) / + (dProgressTip - dProgressStart) * + 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); + LOCK(cs_main); LogPrintf( "Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, @@ -1767,6 +1785,14 @@ CBlock block; if (ReadBlockFromDisk(block, pindex, GetConfig())) { + LOCK2(cs_main, cs_wallet); + 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 (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, @@ -1778,8 +1804,16 @@ if (pindex == pindexStop) { break; } - - pindex = chainActive.Next(pindex); + { + LOCK(cs_main); + pindex = chainActive.Next(pindex); + if (tip != chainActive.Tip()) { + tip = chainActive.Tip(); + // in case the tip has changed, update progress max + dProgressTip = + GuessVerificationProgress(chainParams.TxData(), tip); + } + } } if (pindex && fAbortRescan) { @@ -1790,7 +1824,6 @@ // Hide progress dialog in GUI. ShowProgress(_("Rescanning..."), 100); - fScanningWallet = false; } return ret; } @@ -4346,7 +4379,16 @@ } nStart = GetTimeMillis(); - walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true); + { + WalletRescanReserver reserver(walletInstance); + if (!reserver.reserve()) { + InitError( + _("Failed to rescan the wallet during initialization")); + return nullptr; + } + walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, + reserver, true); + } LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); walletInstance->SetBestChain(chainActive.GetLocator()); walletInstance->dbw->IncrementUpdateCounter();