Fix importmulti failure to return rescan errors
An off-by-one-block bug in importmulti rescan logic could cause it to return success in an edge case even when a rescan was not successful. The case where this would happen is if there were multiple blocks in a row with the same GetBlockTimeMax() value, and the last block was scanned successfully, but one or more of the earlier blocks was not readable.
This commit is contained in:
parent
41987aa92f
commit
4d2d6045a4
3 changed files with 34 additions and 29 deletions
|
@ -1121,13 +1121,13 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
||||||
|
|
||||||
if (fRescan && fRunScan && requests.size()) {
|
if (fRescan && fRunScan && requests.size()) {
|
||||||
CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindEarliestAtLeast(std::max<int64_t>(nLowestTimestamp - TIMESTAMP_WINDOW, 0)) : chainActive.Genesis();
|
CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindEarliestAtLeast(std::max<int64_t>(nLowestTimestamp - TIMESTAMP_WINDOW, 0)) : chainActive.Genesis();
|
||||||
CBlockIndex* scannedRange = nullptr;
|
CBlockIndex* scanFailed = nullptr;
|
||||||
if (pindex) {
|
if (pindex) {
|
||||||
scannedRange = pwallet->ScanForWalletTransactions(pindex, true);
|
scanFailed = pwallet->ScanForWalletTransactions(pindex, true);
|
||||||
pwallet->ReacceptWalletTransactions();
|
pwallet->ReacceptWalletTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scannedRange || scannedRange->nHeight > pindex->nHeight) {
|
if (scanFailed) {
|
||||||
std::vector<UniValue> results = response.getValues();
|
std::vector<UniValue> results = response.getValues();
|
||||||
response.clear();
|
response.clear();
|
||||||
response.setArray();
|
response.setArray();
|
||||||
|
@ -1137,12 +1137,23 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
||||||
// range, or if the import result already has an error set, let
|
// range, or if the import result already has an error set, let
|
||||||
// the result stand unmodified. Otherwise replace the result
|
// the result stand unmodified. Otherwise replace the result
|
||||||
// with an error message.
|
// with an error message.
|
||||||
if (GetImportTimestamp(request, now) - TIMESTAMP_WINDOW >= scannedRange->GetBlockTimeMax() || results.at(i).exists("error")) {
|
if (GetImportTimestamp(request, now) - TIMESTAMP_WINDOW > scanFailed->GetBlockTimeMax() || results.at(i).exists("error")) {
|
||||||
response.push_back(results.at(i));
|
response.push_back(results.at(i));
|
||||||
} else {
|
} else {
|
||||||
UniValue result = UniValue(UniValue::VOBJ);
|
UniValue result = UniValue(UniValue::VOBJ);
|
||||||
result.pushKV("success", UniValue(false));
|
result.pushKV("success", UniValue(false));
|
||||||
result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, strprintf("Failed to rescan before time %d, transactions may be missing.", scannedRange->GetBlockTimeMax())));
|
result.pushKV(
|
||||||
|
"error",
|
||||||
|
JSONRPCError(
|
||||||
|
RPC_MISC_ERROR,
|
||||||
|
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));
|
response.push_back(std::move(result));
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
|
|
|
@ -364,6 +364,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
|
|
||||||
// Cap last block file size, and mine new block in a new block file.
|
// Cap last block file size, and mine new block in a new block file.
|
||||||
|
CBlockIndex* const nullBlock = nullptr;
|
||||||
CBlockIndex* oldTip = chainActive.Tip();
|
CBlockIndex* oldTip = chainActive.Tip();
|
||||||
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
|
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
|
||||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||||
|
@ -375,7 +376,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||||
CWallet wallet;
|
CWallet wallet;
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
|
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);
|
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,7 +390,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||||
CWallet wallet;
|
CWallet wallet;
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
|
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);
|
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +414,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||||
CKey futureKey;
|
CKey futureKey;
|
||||||
futureKey.MakeNewKey(true);
|
futureKey.MakeNewKey(true);
|
||||||
key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey())));
|
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));
|
key.pushKV("internal", UniValue(true));
|
||||||
keys.push_back(key);
|
keys.push_back(key);
|
||||||
JSONRPCRequest request;
|
JSONRPCRequest request;
|
||||||
|
@ -421,20 +422,17 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||||
request.params.push_back(keys);
|
request.params.push_back(keys);
|
||||||
|
|
||||||
UniValue response = importmulti(request);
|
UniValue response = importmulti(request);
|
||||||
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()));
|
BOOST_CHECK_EQUAL(response.write(),
|
||||||
|
strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"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));
|
||||||
::pwalletMain = backup;
|
::pwalletMain = backup;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify ScanForWalletTransactions does not return null when the scan is
|
|
||||||
// elided due to the nTimeFirstKey optimization.
|
|
||||||
{
|
|
||||||
CWallet wallet;
|
|
||||||
{
|
|
||||||
LOCK(wallet.cs_wallet);
|
|
||||||
wallet.UpdateTimeFirstKey(newTip->GetBlockTime() + 7200 + 1);
|
|
||||||
}
|
|
||||||
BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(newTip));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that GetImmatureCredit() returns a newly calculated value instead of
|
// Check that GetImmatureCredit() returns a newly calculated value instead of
|
||||||
|
|
|
@ -1456,10 +1456,9 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
|
||||||
* from or to us. If fUpdate is true, found transactions that already
|
* from or to us. If fUpdate is true, found transactions that already
|
||||||
* exist in the wallet will be updated.
|
* exist in the wallet will be updated.
|
||||||
*
|
*
|
||||||
* Returns pointer to the first block in the last contiguous range that was
|
* Returns null if scan was successful. Otherwise, if a complete rescan was not
|
||||||
* successfully scanned or elided (elided if pIndexStart points at a block
|
* possible (due to pruning or corruption), returns pointer to the most recent
|
||||||
* before CWallet::nTimeFirstKey). Returns null if there is no such range, or
|
* block that could not be scanned.
|
||||||
* the range doesn't include chainActive.Tip().
|
|
||||||
*/
|
*/
|
||||||
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
|
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
|
||||||
{
|
{
|
||||||
|
@ -1467,7 +1466,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f
|
||||||
const CChainParams& chainParams = Params();
|
const CChainParams& chainParams = Params();
|
||||||
|
|
||||||
CBlockIndex* pindex = pindexStart;
|
CBlockIndex* pindex = pindexStart;
|
||||||
CBlockIndex* ret = pindexStart;
|
CBlockIndex* ret = nullptr;
|
||||||
{
|
{
|
||||||
LOCK2(cs_main, cs_wallet);
|
LOCK2(cs_main, cs_wallet);
|
||||||
fAbortRescan = false;
|
fAbortRescan = false;
|
||||||
|
@ -1495,11 +1494,8 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f
|
||||||
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
||||||
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
|
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
|
||||||
}
|
}
|
||||||
if (!ret) {
|
|
||||||
ret = pindex;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ret = nullptr;
|
ret = pindex;
|
||||||
}
|
}
|
||||||
pindex = chainActive.Next(pindex);
|
pindex = chainActive.Next(pindex);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue