Return the failed block as an out arg.
Fixes #11450.
/cc #12275
3780- else {
3781- throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
3782+ } else if (pwallet->IsAbortingRescan()) {
3783+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
3784+ } else {
3785+ throw JSONRPCError(RPC_MISC_ERROR, strprintf("Rescan failed. Potentially corrupted data files. Failed at height %i.", stopBlock->nHeight));
4166@@ -4167,11 +4167,11 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path&
4167 nStart = GetTimeMillis();
4168 {
4169 WalletRescanReserver reserver(walletInstance);
4170- if (!reserver.reserve()) {
4171+ const CBlockIndex* stop_block = nullptr;
4172+ if (!reserver.reserve() || !walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, stop_block, reserver, true)) {
walletInstance
is leaked on error.
walletInstance
to RAII then?
1790@@ -1791,7 +1791,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
1791 }
1792 ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
1793 }
1794- return ret;
1795+ return (!fAbortRescan && !failed_block);
(!pindex && !failed_block)
1763@@ -1763,14 +1764,14 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
1764 if (pindex && !chainActive.Contains(pindex)) {
1765 // Abort scan if current block is no longer active, to prevent
1766 // marking transactions as coming from the wrong block.
1767- ret = pindex;
1768+ failed_block = pindex;
1713- * possible (due to pruning or corruption), returns pointer to the most recent
1714- * block that could not be scanned.
1715+ * Returns true if scan was successful. If a complete rescan was not
1716+ * possible (due to pruning or corruption), returns false and sets failed_block
1717+ * to the most recent block that could not be scanned. If the rescan was aborted
1718+ * before it could complete, returns false and does not set failed_block.
enum
to better distinct between fail
, ok
and user-aborted
?
1720@@ -1719,7 +1721,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
1721 * the main chain after to the addition of any new keys you want to detect
1722 * transactions for.
1723 */
1724-CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate)
1725+bool CWallet::ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const CBlockIndex*& failed_block, const WalletRescanReserver& reserver, bool fUpdate)
155@@ -156,7 +156,8 @@ void TestGUI()
156 LOCK(cs_main);
157 WalletRescanReserver reserver(&wallet);
158 reserver.reserve();
159- wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
160+ const CBlockIndex* stop_block = nullptr;
161+ wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, stop_block, reserver, true);
stop_block
value?
1694@@ -1695,8 +1695,9 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
1695 }
1696
1697 if (startBlock) {
1698- const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
1699- if (failedBlock) {
1700+ const CBlockIndex* failedBlock = nullptr;
1701+ // TODO: this should take into account failure by ScanResult::USER_ABORTED
913@@ -914,7 +914,13 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
914 void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
915 bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
916 int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
917- CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false);
918+
919+ enum class ScanResult : int {
920+ SUCCESS = 0,
enum class
to derive from int or even specify values for the members.
ScanResult
enum.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
3930+ case CWallet::ScanResult::FAILURE:
3931 throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
3932+ case CWallet::ScanResult::USER_ABORT:
3933+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
3934+ default:
3935+ throw JSONRPCError(RPC_MISC_ERROR, strprintf("Unexpected rescan result: %i.", static_cast<int>(result)));
Should remove the default case, so instead of a runtime-error the compiler can warn about missing cases.
0 // no default case, so the compiler can warn about missing cases
143@@ -144,7 +144,12 @@ void TestGUI()
144 LOCK(cs_main);
145 WalletRescanReserver reserver(wallet.get());
146 reserver.reserve();
147- wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
148+ const CBlockIndex* const null_block = nullptr;
149+ const CBlockIndex* stop_block = nullptr;
nit: No need to initialize a return value. In fact this might look like you are passing in something.
Also could inline nullptr for null_block below for brevity.
Removed the initialization of the out var, though this required setting the out var to null in the method for consistent behavior under test. Seems like a good change to me as this seems more predictable.
Can’t inline the null_block
vars as that results in template ambiguity around <ostream, nullptr_t>
3301- if (!stopBlock) {
3302- if (pwallet->IsAbortingRescan()) {
3303- throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
3304- }
3305- // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
3306+ const CBlockIndex* stopBlock = nullptr;
3306+ const CBlockIndex* stopBlock = nullptr;
3307+ CWallet::ScanResult result =
3308+ pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, stopBlock, true);
3309+ switch (result) {
3310+ case CWallet::ScanResult::SUCCESS:
3311 stopBlock = pindexStop ? pindexStop : pChainTip;
stopBlock
on success, this replacement won’t be necessary.
49@@ -50,7 +50,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
50 AddKey(wallet, coinbaseKey);
51 WalletRescanReserver reserver(&wallet);
52 reserver.reserve();
53- BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
54+ const CBlockIndex* stop_block = nullptr;
55+ BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, stop_block), CWallet::ScanResult::SUCCESS);
56+ BOOST_CHECK_EQUAL(nullBlock, stop_block);
stop_block
is only set if the scan failed, it isn’t set to tip if the scan succeeds
282@@ -279,7 +283,10 @@ class ListCoinsTestingSetup : public TestChain100Setup
283 AddKey(*wallet, coinbaseKey);
284 WalletRescanReserver reserver(wallet.get());
285 reserver.reserve();
286- wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver);
287+ const CBlockIndex* const null_block = nullptr;
288+ const CBlockIndex* stop_block = nullptr;
289+ BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, stop_block), CWallet::ScanResult::SUCCESS);
290+ BOOST_CHECK_EQUAL(stop_block, null_block);
1619@@ -1619,18 +1620,20 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
1620 * from or to us. If fUpdate is true, found transactions that already
1621 * exist in the wallet will be updated.
1622 *
1623- * Returns null if scan was successful. Otherwise, if a complete rescan was not
1624- * possible (due to pruning or corruption), returns pointer to the most recent
1625- * block that could not be scanned.
1626+ * @param[in] pindexStop if not a nullptr, the scan will stop at this block-index
1627+ * @param[out] failed_block if FAILURE is returned, will be set to the most
1628+ * recent block that could not be scanned.
last_block
, which will either be “the most recent block that could not be scanned” (failure/abort) or “pindexStop/::chainActive.Tip()” (success)
Concept ACK / light-utACK https://github.com/bitcoin/bitcoin/pull/13076/commits/b52abc7164ba6403124e409af2e3ee2e2dad7f24
I agree with Marco that returning the last successful block is probably a good idea, but with that and the other comments, at least this doesn’t appear to make things worse, so it could be addressed in follow-ups if not here :)
I agree with Marco that returning the last successful block is probably a good idea
Returning the last failed block instead of the last successful block is needed for importmulti. Importmulti uses the last failed block to return success for keys with birthdays after that block, and failures for keys with older birthdays.
I tried to clarify this in #12275 by returning both values (last_scanned
and last_failed
):
0 struct ScanResult
1 {
2 const CBlockIndex* first_scanned = nullptr;
3 const CBlockIndex* last_scanned = nullptr;
4 const CBlockIndex* first_failed = nullptr;
5 const CBlockIndex* last_failed = nullptr;
6 };
Which I still think would be an improvement over the current code.
Return the failed block as an out var.
This clarifies the outcome as the prior return value could
be null due to user abort or failure.
Accurately reports the last block successfully scanned, replacing a return of
the chain tip, which represented possibly inaccurated data in a race condition.
150+ const CBlockIndex* const null_block = nullptr;
151+ const CBlockIndex *stop_block, *failed_block;
152+ QCOMPARE(
153+ wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block, true /* fUpdate */),
154+ CWallet::ScanResult::SUCCESS);
155+ QCOMPARE(stop_block, null_block);
Travis failure:
0********* Start testing of WalletTests *********
1Config: Using QtTest library 5.9.6, Qt 5.9.6 (i386-little_endian-ilp32 static release build; by GCC 7.3.0)
2PASS : WalletTests::initTestCase()
3FAIL! : WalletTests::walletTests() Compared pointers are not the same
4 Loc: [qt/test/wallettests.cpp(154)]
5PASS : WalletTests::cleanupTestCase()
6Totals: 2 passed, 1 failed, 0 skipped, 0 blacklisted, 245ms
7********* Finished testing of WalletTests *********
1651@@ -1647,8 +1652,8 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
1652 assert(pindexStop->nHeight >= pindexStart->nHeight);
1653 }
1654
1655- CBlockIndex* pindex = pindexStart;
1656- CBlockIndex* ret = nullptr;
1657+ const CBlockIndex* pindex = pindexStart;
1658+ failed_block = nullptr;
1659
stop_block
null here, which seems unintended and could lead to uninitialized variables. Would be good to fix this or add documentation if it was done intentionally.
Empact
promag
ryanofsky
jonasschnelli
MarcoFalke
sabran125
DocOBITCOIN
DrahtBot
meshcollider
Labels
Refactoring
Wallet
Milestone
0.18.0