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));
Note I added the failure height here. Happy to remove, but I assume it could be helpful.
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)) {
Note this did not previously fail when ScanForWalletTransactions failed, so this removes a silent failure.
I think this is ok. This just adds another case where walletInstance is leaked on error.
Should we be switching 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);
Alternatively, this could be (!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;
I think you might want to drop this line and just return true here, to fix the regression described here: #12275 (comment).
Not sure about that - my inclination is to maintain stricter checking at the risk of spurious failures over less strict checking at the risk of silent failure. How about opening a separate PR for that so it can be considered separately?
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.
Not sure if in this PR, but would it make sense to just return an enum to better distinct between fail, ok and user-aborted?
Could be done here IMO, not a big change and would be a lot better IMO.
Updated to return an enum.
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)
Output arguments could be last?
Made it second-to-last due to default value for update arg.
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);
Could assert return value and stop_block value?
Concept ACK.
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
I'll address this todo in a follow-up.
Follow-up is here, I'll wait to open until this is accepted: https://github.com/Empact/bitcoin/tree/rescan-from-time
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,
nit: not sure if SUCCESS should use a non C-ish, non null positive value (1 probably)
note that there is no need for an enum class to derive from int or even specify values for the members.
Dropped explicit values and type from ScanResult enum.
Rebased and moved the shutdown / abort handling inline.
indicating the requested changes
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
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.
// no default case, so the compiler can warn about missing cases
Concept ACK. Needs rebase
Rebased. @MarcoFalke could you remove "Up for grabs"?
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;
nit: No need to initialize a return value. In fact this might look like you are passing in something.
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;
Why are you overriding the return value?
This has been the behavior since the rpc's introduction in c77170fbdb
Which doesn't mean it is correct. (The tip can change when you don't hold cs_main)
Buf if we switch to returning the last block in stopBlock on success, this replacement won't be necessary.
Since you rework what the function returns, you might as well make it return the last successfully scanned block.
I can pick this up and address my feedback middle of next week. (Let me know if you are currently busy or want to take a stab at this.)
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);
pIndexStop was nullptr, so this should be tip instead?
@MarcoFalke 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);
Should be tip instead of null_block?
Same here as the other case :)
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.
nit: could return last_block, which will either be "the most recent block that could not be scanned" (failure/abort) or "pindexStop/::chainActive.Tip()" (success)
Concept ACK, but I'd prefer it the block was returned in the case of success as well. (Otherwise it is up to the caller to guess which was the last successfully scanned block, since the tip can change without holding cs_main at the call site)
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):
struct ScanResult
{
const CBlockIndex* first_scanned = nullptr;
const CBlockIndex* last_scanned = nullptr;
const CBlockIndex* first_failed = nullptr;
const CBlockIndex* last_failed = nullptr;
};
Which I still think would be an improvement over the current code.
@ryanofsky I think I mis-explained, I mean in the case of success - the failure case behaviour should remain unchanged
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.
@MarcoFalke see the stop_block commit above. Any idea where the test failures are coming from? I don’t see the connection.
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:
********* Start testing of WalletTests *********
Config: Using QtTest library 5.9.6, Qt 5.9.6 (i386-little_endian-ilp32 static release build; by GCC 7.3.0)
PASS : WalletTests::initTestCase()
FAIL! : WalletTests::walletTests() Compared pointers are not the same
Loc: [qt/test/wallettests.cpp(154)]
PASS : WalletTests::cleanupTestCase()
Totals: 2 passed, 1 failed, 0 skipped, 0 blacklisted, 245ms
********* Finished testing of WalletTests *********
Thanks! Solved.
utACK bd3b0361d840bff95988a048abf70ade94d80524
utACK bd3b0361d840bff95988a048abf70ade94d80524
utACK bd3b0361d8
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 |
Code is not setting 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.
Ah, right, the code does contradict the comments on line 1634. Will open a PR.
Milestone
0.18.0