Merge #9773: Return errors from importmulti if complete rescans are not successful

e2e2f4c Return errors from importmulti if complete rescans are not successful (Russell Yanofsky)
This commit is contained in:
Wladimir J. van der Laan 2017-02-22 12:42:55 +01:00
commit ba7220b5e8
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
6 changed files with 114 additions and 13 deletions

View File

@ -3331,7 +3331,7 @@ void PruneOneBlockFile(const int fileNumber)
}
void UnlinkPrunedFiles(std::set<int>& setFilesToPrune)
void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune)
{
for (std::set<int>::iterator it = setFilesToPrune.begin(); it != setFilesToPrune.end(); ++it) {
CDiskBlockPos pos(*it, 0);
@ -4163,6 +4163,11 @@ std::string CBlockFileInfo::ToString() const
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, DateTimeStrFormat("%Y-%m-%d", nTimeFirst), DateTimeStrFormat("%Y-%m-%d", nTimeLast));
}
CBlockFileInfo* GetBlockFileInfo(size_t n)
{
return &vinfoBlockFile.at(n);
}
ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos)
{
LOCK(cs_main);

View File

@ -299,10 +299,15 @@ double GuessVerificationProgress(const ChainTxData& data, CBlockIndex* pindex);
*/
void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight);
/**
* Mark one block file as pruned.
*/
void PruneOneBlockFile(const int fileNumber);
/**
* Actually unlink the specified files
*/
void UnlinkPrunedFiles(std::set<int>& setFilesToPrune);
void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune);
/** Create a new block index entry for a given block hash */
CBlockIndex * InsertBlockIndex(uint256 hash);
@ -562,6 +567,9 @@ static const unsigned int REJECT_ALREADY_KNOWN = 0x101;
/** Transaction conflicts with a transaction already known */
static const unsigned int REJECT_CONFLICT = 0x102;
/** Get block file info entry for one block file */
CBlockFileInfo* GetBlockFileInfo(size_t n);
/** Dump the mempool to disk. */
void DumpMempool();

View File

@ -1074,11 +1074,32 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
if (fRescan && fRunScan && requests.size()) {
CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindEarliestAtLeast(std::max<int64_t>(nLowestTimestamp - 7200, 0)) : chainActive.Genesis();
CBlockIndex* scannedRange = nullptr;
if (pindex) {
pwalletMain->ScanForWalletTransactions(pindex, true);
scannedRange = pwalletMain->ScanForWalletTransactions(pindex, true);
pwalletMain->ReacceptWalletTransactions();
}
if (!scannedRange || scannedRange->nHeight > pindex->nHeight) {
std::vector<UniValue> results = response.getValues();
response.clear();
response.setArray();
size_t i = 0;
for (const UniValue& request : requests.getValues()) {
// If key creation date is within the successfully scanned
// 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) - 7200 >= scannedRange->GetBlockTimeMax() || results.at(i).exists("error")) {
response.push_back(results.at(i));
} else {
UniValue result = UniValue(UniValue::VOBJ);
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())));
response.push_back(std::move(result));
}
}
}
}
return response;

View File

@ -9,10 +9,16 @@
#include <utility>
#include <vector>
#include "rpc/server.h"
#include "test/test_bitcoin.h"
#include "validation.h"
#include "wallet/test/wallet_test_fixture.h"
#include <boost/foreach.hpp>
#include <boost/test/unit_test.hpp>
#include <univalue.h>
extern UniValue importmulti(const JSONRPCRequest& request);
// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
#define RUN_TESTS 100
@ -355,4 +361,58 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
empty_wallet();
}
BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
{
LOCK(cs_main);
// Cap last block file size, and mine new block in a new block file.
CBlockIndex* oldTip = chainActive.Tip();
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = chainActive.Tip();
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
CWallet wallet;
LOCK(wallet.cs_wallet);
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
}
// Prune the older block file.
PruneOneBlockFile(oldTip->GetBlockPos().nFile);
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
CWallet wallet;
LOCK(wallet.cs_wallet);
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(oldTip));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
}
{
CWallet wallet;
::pwalletMain = &wallet;
UniValue key;
key.setObject();
key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey())));
key.pushKV("timestamp", 0);
key.pushKV("internal", UniValue(true));
UniValue keys;
keys.setArray();
keys.push_back(key);
JSONRPCRequest request;
request.params.setArray();
request.params.push_back(keys);
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.\"}}]", newTip->GetBlockTimeMax()));
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -1545,10 +1545,14 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, CAmount& nReceived,
* 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.
*
* Returns pointer to the first block in the last contiguous range that was
* successfully scanned.
*
*/
int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
{
int ret = 0;
CBlockIndex* ret = nullptr;
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
@ -1570,12 +1574,15 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((GuessVerificationProgress(chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
CBlock block;
ReadBlockFromDisk(block, pindex, Params().GetConsensus());
int posInBlock;
for (posInBlock = 0; posInBlock < (int)block.vtx.size(); posInBlock++)
{
if (AddToWalletIfInvolvingMe(*block.vtx[posInBlock], pindex, posInBlock, fUpdate))
ret++;
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
AddToWalletIfInvolvingMe(*block.vtx[posInBlock], pindex, posInBlock, fUpdate);
}
if (!ret) {
ret = pindex;
}
} else {
ret = nullptr;
}
pindex = chainActive.Next(pindex);
if (GetTime() >= nNow + 60) {

View File

@ -788,7 +788,7 @@ public:
bool LoadToWallet(const CWalletTx& wtxIn);
void SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock) override;
bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman);