wallet: Ensure best block matches wallet scan state #30221

pull achow101 wants to merge 9 commits into bitcoin:master from achow101:wallet-no-chainstateflushed changing 9 files +81 −101
  1. achow101 commented at 9:58 pm on June 3, 2024: member

    Implements the idea discussed in #29652 (comment)

    Currently, m_last_block_processed and m_last_block_processed_height are not guaranteed to match the block locator stored in the wallet, nor do either of those fields actually represent the last block that the wallet is synced up to. This is confusing and unintuitive.

    This PR changes those last block fields to be updated whenever the wallet makes a change to the db for new transaction state found in new blocks. Whenever a block is received that contains a transaction relevant to the wallet, the last block locator will now be written to disk. Furthermore, every block disconnection will now write an updated locator.

    To ensure that the locator is relatively recent and loading rescans are fairly quick in the event of unplanned shutdown, it is also now written every 144 blocks (~1 day). Additionally it is now written when the wallet is unloaded so that it is accurate when the wallet is loaded again.

    Lastly, the chainstateFlushed notification in the wallet is changed to be a no-op. The best block locator record is no longer written when chainstateFlushed is received from the node since it should already be mostly up to date.

  2. DrahtBot commented at 9:58 pm on June 3, 2024: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/30221.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK fjahr, mzumsande
    Stale ACK ryanofsky

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #29680 (wallet: fix unrelated parent conflict doesn’t cause child tx to be marked as conflict by Eunovo)
    • #29652 (wallet: Avoid potentially writing incorrect best block locator by ryanofsky)
    • #28710 (Remove the legacy wallet and BDB dependency by achow101)
    • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)
    • #27286 (wallet: Keep track of the wallet’s own transaction outputs in memory by achow101)

    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.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    should recorded → should record

  3. DrahtBot added the label Wallet on Jun 3, 2024
  4. Thompson1985 approved
  5. ryanofsky commented at 3:34 pm on June 4, 2024: contributor

    I’m not sure, but it seems like a potentially good thing to me that last_block_processed and BESTBLOCK are two distinct concepts.

    The last block processed is the block that CWallet in-memory state has been synced to (particularly CWalletTx::m_state which includes mempool / abandoned information not serialized to disk).

    The BESTBLOCK record is the last block whose data has been flushed to disk, that the wallet should begin syncing from next time it is reloaded.

    It seems like the first commit ae3b8280a7dbb6cc87814ed97dbea5a122cf8215 could lead to a performance regression if it is writing the flushing the BESTBLOCK record on every single blockConnected event from the node. For example during reindexing it could write and sync the database each time a block is connected, even if the block is before the wallets birthday, or doesn’t have any relevant transactions?

    I think it probably makes sense to write BESTBLOCK in a smarter way, and probably write it more frequently, but it seems unnecessary to have to write it every block, and it might be bad for performance now or make optimizations harder in the future.

    I’m also curious about the idea of saving height with the best block record. I could imagine this being useful, since IIRC parts of wallet loading are complicated by not knowing heights of transactions before the chain is attached, but it doesn’t really seem like the height is used for much yet. Was this intended to be used by something else later?

  6. achow101 commented at 3:48 pm on June 4, 2024: member

    The last block processed is the block that CWallet in-memory state has been synced to (particularly CWalletTx::m_state which includes mempool / abandoned information not serialized to disk).

    But none of that state is relevant to the last block processed. Any state related to blocks (confirmed or conflicted) is written to disk.

    The BESTBLOCK record is the last block whose data has been flushed to disk, that the wallet should begin syncing from next time it is reloaded.

    The point is that this discrepancy can result in skipping blocks. It doesn’t make sense to me that we wouldn’t store the block the wallet’s state has been synced to, and it doesn’t make sense that we should rely on a field that means something else to determine what point to start rescanning from on the next load.

    If BESTBLOCK doesn’t match the chainstate, that’s fine because it’s a locator and we will just find the fork and sync from there. I don’t think it’s necessary to record which block we think the chainstate is synced to.

  7. ryanofsky commented at 3:56 pm on June 4, 2024: contributor

    It doesn’t make sense to me that we wouldn’t store the block the wallet’s state has been synced to

    You may be right this is the better approach. But I think the previous approach does make some sense, too. You might not want to write to the wallet database each time a block is connected if the block doesn’t contain any relevant transactions, especially during reindexing.

  8. achow101 commented at 9:03 pm on June 4, 2024: member

    You might not want to write to the wallet database each time a block is connected if the block doesn’t contain any relevant transactions

    I think you would since not writing it would result in possibly rescanning that block at the next loading which takes a little bit of time, regardless of whether any relevant transactions are in the block.

    especially during reindexing.

    Perhaps an easy solution to this is to just write the best block every 1000 blocks (or some other interval) when we are in IBD?

    I’m also curious about the idea of saving height with the best block record. I could imagine this being useful, since IIRC parts of wallet loading are complicated by not knowing heights of transactions before the chain is attached, but it doesn’t really seem like the height is used for much yet. Was this intended to be used by something else later?

    This was mainly done to avoid looking up the height every time we load since that was being problematic and causing a tests to fail. But it definitely could be more useful in the future.

  9. ryanofsky commented at 3:16 pm on June 5, 2024: contributor

    The idea of saving heights is interesting to me because wallet code assumes it know block heights many places, but it doesn’t actually store heights anywhere. So for example, if the wallet stored a mapping of block hashes to heights (or other block information) it might be useful for allowing the wallet to work in an offline mode or letting the wallet CLI tool have more capabilities. This is all pretty abstract though, so I’m not suggesting something specific.

    Perhaps an easy solution to this is to just write the best block every 1000 blocks (or some other interval) when we are in IBD?

    Yes but I think that just adds back the complexity you were trying to get rid of, in a form that seems worse than the status quo. If you implement that, the wallet will be back to tracking the last block processed separately from the best block written to the database. And now, instead of the node determining when data should be flushed to disk and having all wallets flush that data simultaneously, each wallet will have internal logic deciding when to flush. This would be more complicated, and could be worse for power consumption and performance if there are multiple wallets and flushes are happening more frequently at different times.

    I think overall this PR is doing some good things, but the goals seem either not clear, or not clearly good, so I wonder if maybe you could take these changes and make more targeted PRs for the things you want to achieve?

    Or, if this PR is definitely the direction you want to go, I’m happy to review it. I don’t like some things it is doing, but overall I think it is a reasonable change.

  10. DrahtBot added the label Needs rebase on Jul 22, 2024
  11. achow101 force-pushed on Jul 22, 2024
  12. DrahtBot removed the label Needs rebase on Jul 22, 2024
  13. DrahtBot added the label Needs rebase on Aug 12, 2024
  14. in test/functional/wallet_assumeutxo.py:111 in ebf2e2863c outdated
    84@@ -84,8 +85,6 @@ def run_test(self):
    85             assert_equal(n.getblockchaininfo()[
    86                          "headers"], SNAPSHOT_BASE_HEIGHT)
    87 
    88-        w.backupwallet("backup_w.dat")
    


    fjahr commented at 11:18 am on August 20, 2024:
    I’m thinking that it might make sense to keep this backup where it is and add second backup created at 199. Then this could test that both cases work as expected, i.e. 199 errors below and 299 doesn’t. 299 is an interesting edge case in general (snapshot height == backup height).
  15. fjahr commented at 11:28 am on August 20, 2024: contributor

    Concept ACK

    I can confirm that best block locator and last_processed_block being different is confusing, see also #30455 (review)

    Currently, this needs a rebase and I’m curious if @achow101 plans to make further changes based on @ryanofsky ’s comments.

  16. ryanofsky commented at 2:28 pm on August 20, 2024: contributor

    I can confirm that best block locator and last_processed_block being different is confusing

    I wonder if something else could be done to resolve this confusion other than writing to every wallet file every time a new block is connected, even if the block doesn’t contain any relevant transactions. To me, “last block processed” and “last block flushed” seem like different concepts, and we could force them to be the same but only if we:

    • Give up flexibility of not needing to flush each wallet each time a block is connected (which seems inefficient during sync)
    • Give up ability to do coordinated flushes across the chainstate database, index databases and wallet databases time (which seems like it could waste resources and hurt system performance)
  17. fjahr commented at 2:35 pm on August 20, 2024: contributor

    I wonder if something else could be done to resolve this confusion

    Ok, I would say it’s a problem when users see behavior that is inconsistent because of this difference. If the users don’t notice it I guess these don’t have to be the same at all times. There could be a more “pull-based” approach where they are aligned when the user interacts with the wallet, similar to what I tried to do for the backup case in #30678. But I am not clear yet on which approach is the better one all factors considered.

  18. ryanofsky commented at 2:48 pm on August 20, 2024: contributor
    It would be great if we could open an issue describing the user behavior that’s confusing. I read the comment you linked to but I didn’t really understand the context of what was going on with those backups. If there can be a github issue that simply describes steps to reproduce, expected behavior, and actual behavior, we can probably figure out if expected behavior is reasonable, and implement a fix if so.
  19. fjahr commented at 7:43 pm on August 20, 2024: contributor

    It would be great if we could open an issue describing the user behavior that’s confusing.

    I have created an issue here: #30686

  20. achow101 force-pushed on Aug 29, 2024
  21. Thompson1985 approved
  22. Thompson1985 commented at 8:08 pm on August 29, 2024: none
    Thank you 👍
  23. DrahtBot removed the label Needs rebase on Aug 29, 2024
  24. DrahtBot added the label Needs rebase on Sep 23, 2024
  25. achow101 force-pushed on Oct 4, 2024
  26. achow101 force-pushed on Oct 4, 2024
  27. DrahtBot removed the label Needs rebase on Oct 4, 2024
  28. DrahtBot commented at 9:52 pm on October 4, 2024: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/31102970538

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  29. DrahtBot added the label CI failed on Oct 4, 2024
  30. maflcko commented at 3:35 pm on October 16, 2024: member
    Maybe mark as draft while CI is red?
  31. DrahtBot added the label Needs rebase on Oct 24, 2024
  32. achow101 force-pushed on Oct 24, 2024
  33. DrahtBot removed the label Needs rebase on Oct 24, 2024
  34. DrahtBot added the label Needs rebase on Oct 25, 2024
  35. achow101 force-pushed on Oct 25, 2024
  36. DrahtBot removed the label Needs rebase on Oct 25, 2024
  37. DrahtBot removed the label CI failed on Oct 25, 2024
  38. DrahtBot added the label Needs rebase on Nov 15, 2024
  39. achow101 force-pushed on Nov 19, 2024
  40. DrahtBot removed the label Needs rebase on Nov 19, 2024
  41. DrahtBot added the label Needs rebase on Jan 15, 2025
  42. achow101 force-pushed on Jan 20, 2025
  43. DrahtBot removed the label Needs rebase on Jan 20, 2025
  44. DrahtBot added the label Needs rebase on Jan 31, 2025
  45. achow101 force-pushed on Feb 1, 2025
  46. DrahtBot removed the label Needs rebase on Feb 1, 2025
  47. DrahtBot added the label CI failed on Feb 1, 2025
  48. in src/wallet/wallet.cpp:1537 in ddc8095208 outdated
    1530@@ -1517,8 +1531,11 @@ void CWallet::blockConnected(ChainstateRole role, const interfaces::BlockInfo& b
    1531     assert(block.data);
    1532     LOCK(cs_wallet);
    1533 
    1534-    m_last_block_processed_height = block.height;
    1535-    m_last_block_processed = block.hash;
    1536+    // Update the best block first. This will set the best block's height, which is
    1537+    // needed by MarkConflicted.
    1538+    // Although this also writes the best block to disk, this is okay even if there is an unclean
    1539+    // shutdown since reloading the wallet will still rescan this block.
    


    mzumsande commented at 10:25 pm on February 6, 2025:

    this is okay even if there is an unclean shutdown since reloading the wallet will still rescan this block

    I don’t think that this is the case. We will be temporarily in a state in which the wallet thinks it has scanned the block (locator is written to disk) but haven’t actually done the SyncTransaction work yet. This is fragile, because in case of an unclean shutdown a future rescan may not include this block if it thinks, based on the locator, that the block has already been scanned.

    Therefore, it seems better to me to first do the SyncTransaction work and only then update the best block locator on disk - if there is an unclean shutdown with this order, we’ll be sure to sync the transactions again.

    Although the best solution would be to it all in one batch.

  49. in src/wallet/wallet.cpp:1600 in ddc8095208 outdated
    1585@@ -1573,6 +1586,9 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
    1586             }
    1587         }
    1588     }
    1589+
    1590+    // Update the best block
    1591+    SetLastBlockProcessed(block.height - 1, *Assert(block.prev_hash));
    


    mzumsande commented at 8:58 pm on February 7, 2025:
    To deal with unclean shutdowns, during disconnect the reverse order compared to blockConnected would be better: If the locator is updated first, an unclean shutdown will lead to a rescan of the disconnected block (which might do nothing but correct the locator). But if txns are set to Inactive first and have an unclean shutdown after that, the transactions will be missing from the wallet / balance. As mentioned above, doing it all in one batch would resolve this in a more elegant way.
  50. mzumsande commented at 11:29 pm on February 7, 2025: contributor

    Concept ACK

    I verified that this would solve the issues from #31824, only looked at the first couple commits in detail so far.

    With respect to the above discussion, I think that the concept of delayed flushing only makes sense if it applies to all relevant data for a given block connection /disconnection (which is the case for the indexes, for example). But when other changes to wallet txns are synced to disk immediately, it seems wrong to delay flushing the locator - this will lead to a mixed state where some relevant data is flushed and other data isn’t, which will lead to issues such as the one above.

    As for the point of IBD performance due to updating the locator at every block: Maybe this could be prevented by keeping track during block (dis)connection, if we did any other changes to the wallet db for this block. If nothing else needed changing ( wallet not affected by the block, only the locator would be updated) it should be safe to delay / skip the update.

  51. DrahtBot added the label Needs rebase on Feb 14, 2025
  52. achow101 force-pushed on Mar 6, 2025
  53. achow101 commented at 1:50 am on March 6, 2025: member

    As discussed at CoreDev, I’ve updated this PR to sync the best block periodically or when a connected block contains a relevant transaction.

    I’ve also had to make a couple of changes to AttachChain following #31629

  54. achow101 force-pushed on Mar 6, 2025
  55. DrahtBot removed the label Needs rebase on Mar 6, 2025
  56. achow101 force-pushed on Mar 6, 2025
  57. achow101 force-pushed on Mar 6, 2025
  58. achow101 commented at 7:54 pm on March 6, 2025: member
    I added a change to make sure that the most recent best block is written to disk on wallet unload. However, it seems like this uncovered some weirdness in the wallet unloading on shutdown. In particular, StopWallets() was being called before UnloadWallets() which I think is incorrect. I added a commit which flips this order, and it seems to work, although there may be side effects I have not discovered yet. This primarily affects BDB wallets as the shutdown for BDB wallets is much more involved and confusing due to the shared database environment. This is not an issue for SQLite.
  59. achow101 force-pushed on Mar 7, 2025
  60. achow101 force-pushed on Mar 7, 2025
  61. DrahtBot removed the label CI failed on Mar 7, 2025
  62. DrahtBot added the label Needs rebase on Mar 13, 2025
  63. achow101 force-pushed on Mar 14, 2025
  64. DrahtBot removed the label Needs rebase on Mar 14, 2025
  65. DrahtBot added the label CI failed on Mar 14, 2025
  66. in src/wallet/wallet.cpp:1552 in 8b8cb9f4e9 outdated
    1560         transactionRemovedFromMempool(block.data->vtx[index], MemPoolRemovalReason::BLOCK);
    1561     }
    1562+
    1563+    // Update on disk if this block resulted in us updating a tx, or periodically every 144 blocks (~1 day)
    1564+    if (wallet_updated || block.height % 144 == 0) {
    1565+        SetLastBlockProcessed(block.height, block.hash);
    


    mzumsande commented at 6:39 pm on March 25, 2025:
    8b8cb9f4e9055a3283805ed6ff9397236842bb34 : need to update wallet_reorgsrestore.py after #31757 introduced a test for the bug this PR fixes.

    achow101 commented at 10:03 pm on April 11, 2025:
    Done
  67. in src/wallet/wallet.cpp:651 in eecaca12f6 outdated
    648@@ -649,15 +649,6 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
    649     return false;
    650 }
    651 
    652-void CWallet::chainStateFlushed(ChainstateRole role, const CBlockLocator& loc)
    


    mzumsande commented at 9:28 pm on March 25, 2025:
    eecaca12f6979aefed51c7b19f886c9d7d4e9be4: Two calls to chainStateFlushed are left, in MigrateLegacyToDescriptor and bench/wallet_migration.cpp

    achow101 commented at 10:03 pm on April 11, 2025:
    Removed
  68. mzumsande commented at 9:46 pm on March 25, 2025: contributor
    I don’t completely understand what the second part of the PR (BESTBLOCK_NOMERKLE serialization) achieves with the current approach. With 748063a9820cb740c9b68f80b9e8c97d2a21e83d, chainStateFlushed has been made a no-op and could be removed. On the other hand, the expressed goal of having m_last_block_processed and m_last_block_processed_height always in sync with the block locator is no longer current, given that we only update every 144 blocks if there is nothing relevant for the wallet in these blocks. In that case, why not remove chainstateFlushed after 748063a9820cb740c9b68f80b9e8c97d2a21e83d (plus update the locator on unload) and call it a day, avoiding any possible compatibility issues and the complications of a walletdb format change?
  69. achow101 force-pushed on Apr 11, 2025
  70. achow101 commented at 10:05 pm on April 11, 2025: member

    I’ve removed the BestBlock struct refactor and walletdb changes so that this only changes how m_last_block_processed is updated. Those changes will be put into a separate PR at a later time.

    I also noticed that the concurrent writes test in wallet_descriptor.py no longer tests the issue since it relied on chainStateFlushed() writing to the wallet. I’m pretty sure that there are no other cases of concurrent writes, so I think it is fine to remove that test now.

  71. in test/functional/wallet_descriptor.py:1 in 229a159390


    maflcko commented at 7:59 am on April 12, 2025:
     0[22:04:03.113] test/functional/wallet_descriptor.py:12:8: F401 [*] `concurrent.futures` imported but unused
     1[22:04:03.113]    |
     2[22:04:03.113] 10 |     pass
     3[22:04:03.113] 11 | 
     4[22:04:03.113] 12 | import concurrent.futures
     5[22:04:03.113]    |        ^^^^^^^^^^^^^^^^^^ F401
     6[22:04:03.113] 13 | 
     7[22:04:03.113] 14 | from test_framework.blocktools import COINBASE_MATURITY
     8[22:04:03.113]    |
     9[22:04:03.113]    = help: Remove unused import: `concurrent.futures`
    10[22:04:03.113] 
    11[22:04:03.113] test/functional/wallet_descriptor.py:15:40: F401 [*] `test_framework.descriptors.descsum_create` imported but unused
    12[22:04:03.113]    |
    13[22:04:03.113] 14 | from test_framework.blocktools import COINBASE_MATURITY
    14[22:04:03.113] 15 | from test_framework.descriptors import descsum_create
    15[22:04:03.113]    |                                        ^^^^^^^^^^^^^^ F401
    16[22:04:03.113] 16 | from test_framework.test_framework import BitcoinTestFramework
    17[22:04:03.113] 17 | from test_framework.util import (
    18[22:04:03.113]    |
    19[22:04:03.113]    = help: Remove unused import: `test_framework.descriptors.descsum_create`
    20[22:04:03.113] 
    21[22:04:03.113] Found 2 errors.
    22[22:04:03.113] [*] 2 fixable with the `--fix` option.
    23[22:04:03.115] ^^^
    24[22:04:03.116] `ruff` found errors!
    25[22:04:03.116] ^---- ⚠️ Failure generated from lint check 'py_lint' (Lint Python code)!
    26[22:04:03.116] 
    27[22:04:03.116] 
    
  72. achow101 force-pushed on Apr 12, 2025
  73. DrahtBot removed the label CI failed on Apr 12, 2025
  74. in src/wallet/load.cpp:178 in fa7d90ff02 outdated
    172@@ -173,7 +173,7 @@ void FlushWallets(WalletContext& context)
    173     }
    174 }
    175 
    176-void StopWallets(WalletContext& context)
    177+void CloseWallets(WalletContext& context)
    178 {
    179     for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
    


    ryanofsky commented at 6:23 pm on April 14, 2025:

    In commit “wallet: Swap order of Unload and Close on shutdown” (fa7d90ff025c13d33342e2f36be6b2b60ddca558)

    I’m confused about this change. If UnloadWallets, which deletes CWallet objects, is being called before CloseWallets (formerly StopWallets) then calling CloseWallets will have no effect because there are no wallets to call CWallet::Close on.

    It seems like the thing this commit is really doing is avoiding calling CWallet::Close so that a new best block write added in RemoveWallet in following commit can work (commit “wallet: Write best block record on unload” (b48be4b17929e540b6c093fa868b8aca6e01266e).

    If that’s the goal I think I’d suggest just completely dropping the CloseWallets function and the ~WalletLoaderImpl() destructor. If calling CWallet::Close call explicitly is needed it could be done in RemoveWallet, but if this change is working, it seems like that was never actually necessary and could be skipped.


    achow101 commented at 8:02 pm on April 14, 2025:

    Dropped it as suggested.

    I believe CloseWallets did actually have an effect in that it is the step that deletes the /databases directory for legacy wallets. However, this step is skipped by unloadwallet, and afaict, is mostly unnecessary. So I’ve gone ahead and removed it. Since BDB is going to be removed, I see no reason to try to preserve this behavior especially when the two methods of unloading differ.

    For sqlite wallets, this makes no difference.

  75. ryanofsky approved
  76. ryanofsky commented at 7:11 pm on April 14, 2025: contributor

    Code review ACK 4882e71403111f11651f97a6b1cf7d861e619d8f. This change seems ok now that it is not writing to disk on every block, and there are some nice cleanups here.

    I think PR description should be rewritten to say what the benefits of this change are now that statements like “This PR changes the those last block fields to actually be in sync with the record stored on disk.” are no longer true.

    I guess my thoughts are:

    • I like all the small cleanups here making operations more explicit.
    • I like that blockConnected notification now calls WriteBestBlock to update the wallet locator whenever the block contains any relevant transactions. That seems like a clear win.
    • I think it’s probably good that blockDisconnected notification calls WriteBestBlock every block. Might be a little cleaner to only do it when the wallet is actually updated like blockConnected, but doubt that matters.
    • Calling WriteBestBlock every 144 blocks seems like it might write too frequently during initial sync and not write frequently enough after that. Seems like it might be smarter to update the best block based on clock time like the rescan implementation, which seems to write it every 60 seconds.
    • I don’t like not updating the best block when the chainStateFlushed notification is received. It seems nice to be able to tell a daemon to flush all its state to disk at once, and have some high level control over flushing for efficiency so each component is not just doing its own thing. But this is a theoretical objection because I don’t think chainStateFlushed was a practically useful notification before, even though it could have been.

    Overall LGTM.

  77. DrahtBot requested review from mzumsande on Apr 14, 2025
  78. DrahtBot requested review from fjahr on Apr 14, 2025
  79. achow101 force-pushed on Apr 14, 2025
  80. achow101 commented at 8:03 pm on April 14, 2025: member
    Updated the PR description.
  81. ryanofsky approved
  82. ryanofsky commented at 6:27 pm on April 16, 2025: contributor

    Code review ACK 480186d7093117932bb1f672932813a8936b6c42. Only change since last review is dropping extra wallet shutdown step, which seems like a nice simplification.

    PR description has also been updated and looks good. I did find last two paragraphs a little confusing because when it mentions “last block” fields and records it sounds might be referring to m_last_block_processed and m_last_block_processed_height some places where it’s actually referring to the best block locator. Would maybe suggest:


    This PR changes those last block fields to be updated whenever the wallet makes a change to the db for new transaction state found in new blocks. Whenever a block is received that contains a transaction relevant to the wallet, the last block locator will now be written to disk. Furthermore, every block disconnection will now write an updated locator.

    To ensure that the locator is relatively recent and loading rescans are fairly quick in the event of unplanned shutdown, it is also now written every 144 blocks (~1 day). Additionally it is now written when the wallet is unloaded so that it is accurate when the wallet is loaded again.


    Could also add “The wallet locator is no longer written when a chainstateFlushed notification is received from the node, since it should already be mostly up to date”

  83. achow101 commented at 6:54 pm on April 16, 2025: member
    Updated description mostly as suggested.
  84. in src/wallet/load.h:35 in 00fe040eca outdated
    30@@ -31,10 +31,10 @@ void StartWallets(WalletContext& context);
    31 //! Flush all wallets in preparation for shutdown.
    32 void FlushWallets(WalletContext& context);
    33 
    34-//! Stop all wallets. Wallets will be flushed first.
    35-void StopWallets(WalletContext& context);
    36+//! Close all wallet databases.
    37+void CloseWallets(WalletContext& context);
    


    mzumsande commented at 6:30 pm on April 23, 2025:
    This function is declared here, but not implemented or used anywhere. Is this meant to be called somewhere in UnloadWallets? Or does UnloadWallets already close the db somewhere (where?) and this can be deleted?

    achow101 commented at 8:20 pm on April 23, 2025:

    Just forgot to remove it, done now.

    The db is closed when a CWallet is destroyed.

  85. mzumsande commented at 8:08 pm on April 23, 2025: contributor
    Looks good, except for an issue with commit 00fe040eca4b77d05ec5652b4d494b0ded183653
  86. DrahtBot requested review from mzumsande on Apr 23, 2025
  87. achow101 force-pushed on Apr 23, 2025
  88. mzumsande commented at 9:14 pm on April 23, 2025: contributor
    Looks like WalletMigration bench fails on this branch (which is now run in the CI).
  89. wallet: Update best block record after block dis/connect
    When a block is connected, if the new block had anything relevant to the
    wallet, update the best block record on disk. If not, also sync the best
    block record to disk every 144 blocks.
    7efa6a4fb4
  90. wallet: Replace chainStateFlushed in loading with WriteBestBlock
    The only reason to call chainStateFlushed during wallet loading is to
    ensure that the best block is written. Do these writes explicitly to
    prepare for removing chainStateFlushed.
    8b180358fb
  91. DrahtBot added the label CI failed on Apr 23, 2025
  92. DrahtBot commented at 10:06 pm on April 23, 2025: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/41041933804

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  93. wallet, bench: Write a bestblock record in WalletMigration
    Migrating a wallet requires having a bestblock record. This is always
    the case in normal operation as such a record is always written on
    wallet loading if it didn't already exist. However, within the unit
    tests and benchmarks, this is not guaranteed. Since migration requires
    the record, WalletMigration needs to also add this record before the
    benchmark.
    2150a268c2
  94. wallet: Remove WriteBestBlock from chainStateFlushed 35b6364699
  95. wallet: Write the last block scanned instead of tip after init rescan
    After rescanning on wallet loading, instead of writing the locator for
    the current chain tip, write the locator for the last block that the
    rescan had scanned. This ensures that the stored best block record
    matches the wallet's current state.
    
    Any blocks dis/connected during the rescan are processed after the
    rescan and the last block processed will be updated accordingly.
    0866827203
  96. achow101 commented at 10:08 pm on April 23, 2025: member
    Fixed CI
  97. achow101 force-pushed on Apr 23, 2025
  98. DrahtBot removed the label CI failed on Apr 23, 2025
  99. wallet: Remove unnecessary database Close step on shutdown
    StopWallets, which was being called prior to UnloadWallets, performs an
    unnecessary database closing step. This causes issues in UnloadWallets
    which does additional database cleanups. Since the database closing step
    is unnecessary, StopWallets is removed, and UnloadWallets is now called
    from WalletLoaderImpl::stop.
    9113e60f55
  100. wallet: Write best block record on unload eceb848494
  101. wallet: Remove chainStateFlushed
    chainStateFlushed is no longer needed since the best block is updated
    after a block is scanned. Since the chainstate being flushed does not
    necessarily coincide with the wallet having processed said block, it
    does not entirely make sense for the wallet to be recording that block
    as its best block, and this can cause race conditions where some blocks
    are not processed. Thus, remove this notification.
    2d72a8894c
  102. test, wallet: Remove concurrent writes test
    Since CWallet::chainStateFlushed is now no-op, this test no longer tests
    the concurrent writes scenario. There are no other cases where multiple
    DatabaseBatches are open at the same time.
    de7b5eda7e
  103. achow101 force-pushed on Apr 24, 2025

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-04-27 18:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me