diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1294,13 +1294,13 @@ ? chainActive.FindEarliestAtLeast( std::max(nLowestTimestamp - TIMESTAMP_WINDOW, 0)) : chainActive.Genesis(); - CBlockIndex *scannedRange = nullptr; + CBlockIndex *scanFailed = nullptr; if (pindex) { - scannedRange = pwallet->ScanForWalletTransactions(pindex, true); + scanFailed = pwallet->ScanForWalletTransactions(pindex, true); pwallet->ReacceptWalletTransactions(); } - if (!scannedRange || scannedRange->nHeight > pindex->nHeight) { + if (scanFailed) { std::vector results = response.getValues(); response.clear(); response.setArray(); @@ -1310,8 +1310,8 @@ // 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 >= - scannedRange->GetBlockTimeMax() || + if (GetImportTimestamp(request, now) - TIMESTAMP_WINDOW > + scanFailed->GetBlockTimeMax() || results.at(i).exists("error")) { response.push_back(results.at(i)); } else { @@ -1321,9 +1321,22 @@ "error", JSONRPCError( RPC_MISC_ERROR, - strprintf("Failed to rescan before time %d, " - "transactions may be missing.", - scannedRange->GetBlockTimeMax()))); + strprintf( + "Rescan failed for key with creation timestamp " + "%d. There was an error reading a block from " + "time %d, which is after or within %d seconds " + "of key creation, and could contain " + "transactions pertaining to the key. As a " + "result, transactions and coins using this key " + "may not appear in the wallet. This error " + "could be caused by pruning or data corruption " + "(see bitcoind 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))); response.push_back(std::move(result)); } ++i; 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 @@ -396,7 +396,9 @@ setCoinsRet, nValueRet)); BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); - if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; + if (equal_sets(setCoinsRet, setCoinsRet2)) { + fails++; + } } BOOST_CHECK_NE(fails, RANDOM_REPEATS); @@ -418,7 +420,9 @@ 90 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK(wallet.SelectCoinsMinConf( 90 * CENT, 1, 6, 0, vCoins, setCoinsRet2, nValueRet)); - if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; + if (equal_sets(setCoinsRet, setCoinsRet2)) { + fails++; + } } BOOST_CHECK_NE(fails, RANDOM_REPEATS); } @@ -453,6 +457,7 @@ LOCK(cs_main); // Cap last block file size, and mine new block in a new block file. + CBlockIndex *const nullBlock = nullptr; CBlockIndex *oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); @@ -464,7 +469,7 @@ CWallet wallet(Params()); LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); - BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip)); + BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } @@ -478,7 +483,7 @@ CWallet wallet(Params()); LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); - BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(oldTip)); + BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } @@ -503,7 +508,8 @@ futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); - key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW); + key.pushKV("timestamp", + newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); JSONRPCRequest request; @@ -514,22 +520,19 @@ BOOST_CHECK_EQUAL( response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":" - "\"Failed to rescan before time %d, transactions may be " - "missing.\"}},{\"success\":true}]", - newTip->GetBlockTimeMax())); + "\"Rescan failed for key with creation timestamp %d. " + "There was an error reading a block from time %d, which " + "is after or within %d seconds of key creation, and " + "could contain transactions pertaining to the key. As a " + "result, transactions and coins using this key may not " + "appear in the wallet. This error could be caused by " + "pruning or data corruption (see bitcoind log for " + "details) and could be dealt with by downloading and " + "rescanning the relevant blocks (see -reindex and " + "-rescan options).\"}},{\"success\":true}]", + 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); vpwallets.erase(vpwallets.begin()); } - - // Verify ScanForWalletTransactions does not return null when the scan is - // elided due to the nTimeFirstKey optimization. - { - CWallet wallet(Params()); - { - LOCK(wallet.cs_wallet); - wallet.UpdateTimeFirstKey(newTip->GetBlockTime() + 7200 + 1); - } - BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(newTip)); - } } // Verify importwallet RPC starts rescan at earliest block with timestamp diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1700,19 +1700,18 @@ * us. If fUpdate is true, found transactions that already exist in the wallet * will be updated. * - * Returns pointer to the first block in the last contiguous range that was - * successfully scanned or elided (elided if pIndexStart points at a block - * before CWallet::nTimeFirstKey). Returns null if there is no such range, or - * the range doesn't include chainActive.Tip(). + * 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. */ CBlockIndex *CWallet::ScanForWalletTransactions(CBlockIndex *pindexStart, bool fUpdate) { - LOCK2(cs_main, cs_wallet); - int64_t nNow = GetTime(); CBlockIndex *pindex = pindexStart; - CBlockIndex *ret = pindexStart; + CBlockIndex *ret = nullptr; + + LOCK2(cs_main, cs_wallet); // No need to read and scan block, if block was created before our wallet // birthday (as adjusted for block time variability) @@ -1747,11 +1746,8 @@ AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate); } - if (!ret) { - ret = pindex; - } } else { - ret = nullptr; + ret = pindex; } pindex = chainActive.Next(pindex);