When a connected block updates the wallet (a transaction relevant to the wallet is observed, or the periodic 144-block sync triggers), each individual write currently runs in its own implicit SQLite autocommit transaction. That forces an fsync per WriteKey, which is the proximate cause of the v29 -> v30 regression reported in #33618: in generatetoaddress-style workloads where every coinbase pays the wallet, each block goes from "no on-disk write" (pre-7bacabb204) to "two or three fsynced writes" (post-7bacabb204), making the RPC ~1.5-2x slower.
Reverting 7bacabb204 is not an option: it fixes a correctness bug (#31824 — best-block locator was lagging across unclean shutdown during a reorg, leaving wallet transactions stuck as abandoned).
This change keeps the correctness fix and addresses the perf regression by wrapping the per-block writes in a single explicit SQLite transaction opened at the top of CWallet::blockConnected and committed once at the end. All writes performed for the block (the new/updated wallet transactions, the best-block locator, conflict descendants, and address book updates for newly-seen receiving addresses) share the same WalletBatch, so SQLite issues one fsync at COMMIT time instead of one per individual key write.
To make this safe, the same WalletBatch* is propagated through the relevant call paths:
- SyncTransaction
- AddToWalletIfInvolvingMe
- AddToWallet
- MarkConflicted (-> RecursiveUpdateTxState's batch overload)
- WriteBestBlock
- SetAddressBook (rerouted to SetAddressBookWithDB when a batch is active, since reusing a fresh WalletBatch on the same connection would block on m_write_semaphore that the active txn already holds)
All new parameters default to nullptr, preserving call-site behavior for every other caller (rescan, mempool path, RPC, tests, etc.). Only the blockConnected path is batched.
If TxnBegin() fails for any reason, the code transparently falls back to the previous unbatched behavior — correctness is unchanged. If TxnCommit() fails, the in-memory tip is preserved (it was already updated) and the on-disk best-block locator may briefly lag; the periodic 144-block sync and chainStateFlushed callback will reconcile on the next opportunity, mirroring how the wallet already tolerated crash-driven divergence prior to 7bacabb204.
Fixes #33618.
<!-- *** Please remove the following help text before submitting: *** Pull requests without a rationale and clear improvement may be closed immediately. GUI-related pull requests should be opened against https://github.com/bitcoin-core/gui first. See CONTRIBUTING.md -->
<!-- Please provide clear motivation for your patch and explain how it improves Bitcoin Core user experience or Bitcoin Core developer experience significantly: * Any test improvements or new tests that improve coverage are always welcome. * All other changes should have accompanying unit tests (see `src/test/`) or functional tests (see `test/`). Contributors should note which tests cover modified code. If no tests exist for a region of modified code, new tests should accompany the change. * Bug fixes are most welcome when they come with steps to reproduce or an explanation of the potential issue as well as reasoning for the way the bug was fixed. * Features are welcome, but might be rejected due to design or scope issues. If a feature is based on a lot of dependencies, contributors should first consider building the system outside of Bitcoin Core, if possible. * Refactoring changes are only accepted if they are required for a feature or bug fix or otherwise improve developer experience significantly. For example, most "code style" refactoring changes require a thorough explanation why they are useful, what downsides they have and why they *significantly* improve developer experience or avoid serious programming bugs. Note that code style is often a subjective matter. Unless they are explicitly mentioned to be preferred in the [developer notes](/doc/developer-notes.md), stylistic code changes are usually rejected. -->
<!-- Bitcoin Core has a thorough review process and even the most trivial change needs to pass a lot of eyes and requires non-zero or even substantial time effort to review. There is a huge lack of active reviewers on the project, so patches often sit for a long time. -->