Repository navigation
index: move disk read lookups to base class - #32694
Conversation
The indexes test call StartBackgroundSync(), which spawns a thread to run Sync(), only for the test thread to wait for it to complete by calling IndexWaitSynced(). So, since the sync is performed synchronously, we can skip the extra thread creation entirely and call Sync() directly.
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32694. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, 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. |
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK 3f390ef. Nice changes! This gets rid of blockstorage and CBlockIndex* accesses from index subclasses during sync to decouple the indexes from node internals and make new indexes more straightforward to write in the future. It should also simplify the parallelization PR and avoid the need for refactoring there.
| // BlockUntilSyncedToCurrentChain should return false before index is started. | ||
| BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain()); | ||
|
|
||
| BOOST_REQUIRE(filter_index.StartBackgroundSync()); |
There was a problem hiding this comment.
In commit "test: indexes, avoid creating threads when sync runs synchronously" (331a25c)
Motivation for this commit seems unclear. It seems to initialize indexes in unit tests differently than they are initialized in the node and reduce test coverage a little. Maybe commit message can be updated to mention reasons for wanting to do this?
There was a problem hiding this comment.
In commit "test: indexes, avoid creating threads when sync runs synchronously" (331a25c)
Motivation for this commit seems unclear. It seems to initialize indexes in unit tests differently than they are initialized in the node and reduce test coverage a little. Maybe commit message can be updated to mention reasons for wanting to do this?
Having unit tests waiting for secondary thread creation seemed odd to me; especially since these tests are meant to only run and verify the initial index synchronization output once (once the initial sync is completed, they sync new blocks only via block connection signals). Besides that, the functionality should already be covered by the functional tests.
That being said, could drop it too if preferred. I think I just dislike having the thread inside the index class.
3f390ef to
0ce3d9b
Compare
…pendable This commit does not change behavior in any way.
… methods Move ReadBlock code from CoinStatsIndex::CustomRewind to BaseIndex::Rewind Move ReadUndo code from CoinStatsIndex::ReverseBlock to BaseIndex::Rewind This commit does change behavior slightly. Since the new CustomRemove methods only take a single block at a time instead of a range of disconnected blocks, when they call CopyHeightIndexToHashIndex they will now do an index seek for each removed block instead of only seeking once to the height of the earliest removed block. Seeking instead of scanning is a little worse for performance if there is a >1 block reorg, but probably not noticeable unless the reorg is very deep.
0ce3d9b to
4c5ec3a
Compare
|
Thanks for the review ryanofsky and TheCharlatan!
Done. |
sedited
left a comment
There was a problem hiding this comment.
ACK 4c5ec3a70a47798d8451da65c9c0059955cde937
…e block The function previously handled a range of heights but now copies only a single entry. This change simplifies the code to match its current usage.
Moved CBlockUndo disk read lookups from child index classes to the base index class. The goal is for child index classes to synchronize only through events, without directly accessing the chain database. This change will enable future parallel synchronization mechanisms, reduce database access (when batched), and contribute toward the goal of running indexes in a separate process (with no chain database access). Besides that, this commit also documents how NextSyncBlock() behaves. It is not immediately clear this function could return the first block after the fork point during a reorg.
4c5ec3a to
029ba1a
Compare
|
review ACK 029ba1a 👡 Show signatureSignature: |
left a comment
•
There was a problem hiding this comment.
ACK 029ba1a
Left some nits, and general observations that don't need to be addressed in this PR or at all.
|
|
||
| UninterruptibleSleep(100ms); | ||
| } | ||
| assert(index.GetSummary().synced); |
There was a problem hiding this comment.
331a25c (test: indexes, avoid creating threads when sync runs synchronously)
nit, only if retouching: it would probably be nice to retain the m_synced check from here.
There was a problem hiding this comment.
nit, only if retouching: it would probably be nice to retain the
m_syncedcheck from here.
the m_init check wasn't retained either, but I didn't mention it, as this is test-only code
There was a problem hiding this comment.
Sure, will do it if it needs a re-touch.
I don't see it as that important because if the flag wouldn't being set, the test would be failing due to the index ignoring the BlockConnected signals anyway.
| { | ||
| DBHeightKey key(start_height); | ||
| DBHeightKey key(height); | ||
| db_it.Seek(key); |
There was a problem hiding this comment.
Out of scope for this PR since it is incorrect on master, but db_it needs a Valid() check before GetKey(), since leveldb assert()'s on trying to read the key of an invalid iterator:
https://github.com/bitcoin-core/leveldb-subtree/tree/bitcoin-fork/db/db_iter.cc#L64-L67
There was a problem hiding this comment.
Alternatively, this should probably do m_db.ReadKey(), but that would require this function to become a member of the index or to be passed a db handle, so maybe better suited for a refactor that also addresses @mzumsande's ask to de-deduplicate this (and maybe LookupOne?) in blockfilterindex and coinstatsindex
There was a problem hiding this comment.
Out of scope for this PR since it is incorrect on master, but db_it needs a Valid() check before GetKey(), since leveldb assert()'s on trying to read the key of an invalid iterator:
I assume that the invalid iterator case could only happen when it cannot access db? Which is not common unless the user deletes/moves the index db directory after startup?
There was a problem hiding this comment.
The iterator is invalid if no entry is found matching the key I believe.
There was a problem hiding this comment.
Correction, the iterator is invalid if no entry is found which has a key greater than or equal to the "Seek"'ed key:
https://github.com/bitcoin-core/leveldb-subtree/blob/bitcoin-fork/db/db_test.cc#L861-L870
There was a problem hiding this comment.
The GetKey() call and key.height check below protects against the situation where an entry with a height greater than the key we're seeking exists, but the height we're seeking doesn't exist. In the instance where the height we're seeking is greater than any entry which exists, which should never happen,db_it.GetKey() will assert and crash.
Just to be clear though, I think this is out-of-scope and the PR is ready to merge as-is.
| } | ||
| } | ||
|
|
||
| // In the case of a reorg, ensure persisted block locator is not stale. |
There was a problem hiding this comment.
Just an observation:
While reviewing this PR's changes to CopyHeightIndexToHashIndex(), I wanted to understand LookUpOne from coinstatsindex.cpp and from blockfilterindex.cpp since this is where the lookups by block hashes are relevant:
bitcoin/src/index/coinstatsindex.cpp
Lines 307 to 325 in 0a24870
I was a little confused about whether or not LookupOne() is correct, since it makes the assumption that if no entry exists for a given block height then there is no entry for that block hash, this is because CustomRewind()/CustomRemove() does not actually remove the rewound block from the DB, it just writes a hash-keyed copy of it, and then when the block is replaced by its sibling from a heavier chain, the height-keyed entry is overwritten.
But, I do struggle a little bit to understand why we even care about preserving the rewound block at all, since if we ever reconnect the block we don't even take advantage of already having written it to the DB, is it because users should be able to do gettxoutsetinfo and getblockfilter() on stale blocks that have already been indexed?
| virtual void chainStateFlushed(ChainstateRole role, const CBlockLocator& locator) {} | ||
| }; | ||
|
|
||
| //! Options specifying which chain notifications are required. |
There was a problem hiding this comment.
6f1392c (indexes, refactor: Remove remaining CBlockIndex* uses in index Rewind methods)
nit:
| //! Options specifying which chain notifications are required. | |
| //! Options specifying data required for chain notifications. |
| struct NotifyOptions | ||
| { | ||
| //! Include block data with block disconnected notifications. | ||
| bool disconnect_data = false; |
There was a problem hiding this comment.
6f1392c (indexes, refactor: Remove remaining CBlockIndex* uses in index Rewind methods)
feel-free-to-disregard-style-nit:
I feel this would be more consistent as:
| bool disconnect_data = false; | |
| bool disconnect_block_data = false; |
commented
Jun 12, 2025
commented
Jun 12, 2025
|
ACK 029ba1a |
Combining common refactors from #24230 and #26966, aiming to move both efforts forward while reducing their size and review burden.
Broadly, #24230 focuses on enabling indexes to run in a separate process, and #26966 aims to parallelize the indexes initial synchronization process. A shared prerequisite for both is ensuring that only the base index class interacts with the node’s chain internals - child index classes should instead operate solely through chain events.
This PR moves disk read lookups from child index classes to the base index class. It also includes a few documentation improvements and a test-only code cleanup.