Skip to content

index: move disk read lookups to base class - #32694

Merged
achow101 merged 5 commits into
bitcoin:masterfrom
furszy:2025_indexes_remove_CBlockIndex_access
Jun 12, 2025
Merged

achow101 merged 5 commits into
bitcoin:masterfrom
furszy:2025_indexes_remove_CBlockIndex_access

Conversation

@furszy

@furszy furszy commented Jun 6, 2025

Copy link
Copy Markdown
Member

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.

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.
@DrahtBot

DrahtBot commented Jun 6, 2025 •

Copy link
Copy Markdown
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/32694.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK TheCharlatan, maflcko, davidgumberg, mzumsande, achow101
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:

  • #30469 (index: Fix coinstats overflow and introduce index versioning by fjahr)
  • #29307 (util: explicitly close all AutoFiles that have been written by vasild)
  • #26966 (index: initial sync speedup, parallelize process by furszy)

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 ryanofsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@furszy furszy Jun 10, 2025 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/index/base.cpp Outdated
Comment thread src/index/base.cpp Outdated
Comment thread src/index/coinstatsindex.cpp Outdated

@sedited sedited left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach ACK

This all felt very familiar :)

Can you double-check the commit message of 50696f0. I think there is a word too much in the first and second sentence.

Comment thread src/index/base.cpp
@furszy
furszy force-pushed the 2025_indexes_remove_CBlockIndex_access branch from 3f390ef to 0ce3d9b Compare June 10, 2025 16:51
…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.
@furszy
furszy force-pushed the 2025_indexes_remove_CBlockIndex_access branch from 0ce3d9b to 4c5ec3a Compare June 10, 2025 16:58
@furszy

furszy commented Jun 10, 2025

Copy link
Copy Markdown
Member Author

Thanks for the review ryanofsky and TheCharlatan!
Updated per feedback.

Can you double-check the commit message of 50696f0. I think there is a word too much in the first and second sentence.

Done.

@sedited sedited left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 4c5ec3a70a47798d8451da65c9c0059955cde937

@DrahtBot
DrahtBot requested a review from ryanofsky June 10, 2025 18:43

@mzumsande mzumsande left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

Comment thread src/index/blockfilterindex.cpp Outdated
furszy added 2 commits June 10, 2025 20:22
…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.
@furszy
furszy force-pushed the 2025_indexes_remove_CBlockIndex_access branch from 4c5ec3a to 029ba1a Compare June 11, 2025 00:22

@sedited sedited left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ACK 029ba1a

@DrahtBot
DrahtBot requested a review from mzumsande June 11, 2025 08:12
@maflcko

maflcko commented Jun 11, 2025

Copy link
Copy Markdown
Member

review ACK 029ba1a 👡

Show signature

Signature:

untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
trusted comment: review ACK 029ba1a21d570f7db6c4366ec9a30a381b56d6fb 👡
OQ13TwEEQORasoU+qCZ+jMGAPXKnsCJhTt8atkoWt5nRWhCn9ueXFilV6W5B9iknOUBhP6B2nDQJXduuKa0nAA==

ghost left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 029ba1a

Left some nits, and general observations that don't need to be addressed in this PR or at all.

Comment thread src/test/util/index.cpp

UninterruptibleSleep(100ms);
}
assert(index.GetSummary().synced);

ghost Jun 11, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

ghost Jun 12, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, only if retouching: it would probably be nice to retain the m_synced check from here.

the m_init check wasn't retained either, but I didn't mention it, as this is test-only code

ghost Jun 12, 2025 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

ghost Jun 11, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

ghost Jun 11, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

ghost Jun 12, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

ghost Jun 12, 2025 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iterator is invalid if no entry is found matching the key I believe.

ghost Jun 12, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

ghost Jun 12, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/index/base.cpp
}
}

// In the case of a reorg, ensure persisted block locator is not stale.

ghost Jun 11, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockRef& block, DBVal& result)
{
// First check if the result is stored under the height index and the value
// there matches the block hash. This should be the case if the block is on
// the active chain.
std::pair<uint256, DBVal> read_out;
if (!db.Read(DBHeightKey(block.height), read_out)) {
return false;
}
if (read_out.first == block.hash) {
result = std::move(read_out.second);
return true;
}
// If value at the height index corresponds to an different block, the
// result will be stored in the hash index.
return db.Read(DBHashKey(block.hash), result);
}

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?

Comment thread src/interfaces/chain.h
virtual void chainStateFlushed(ChainstateRole role, const CBlockLocator& locator) {}
};

//! Options specifying which chain notifications are required.

ghost Jun 11, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6f1392c (indexes, refactor: Remove remaining CBlockIndex* uses in index Rewind methods)


nit:

Suggested change
//! Options specifying which chain notifications are required.
//! Options specifying data required for chain notifications.

Comment thread src/interfaces/chain.h
struct NotifyOptions
{
//! Include block data with block disconnected notifications.
bool disconnect_data = false;

ghost Jun 11, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
bool disconnect_data = false;
bool disconnect_block_data = false;

@DrahtBot
DrahtBot requested a review from davidgumberg June 11, 2025 23:07
@furszy

ghost commented Jun 12, 2025

Copy link
Copy Markdown
Member Author

Will try to not touch the code while we are all happy with the current state. Moving #26966 and #24230 forward after ~3 years sounds good enough to me :)

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review ACK 029ba1a

@achow101

ghost commented Jun 12, 2025

Copy link
Copy Markdown
Member

ACK 029ba1a

@achow101
achow101 merged commit 19765dc into bitcoin:master Jun 12, 2025
@furszy
furszy deleted the 2025_indexes_remove_CBlockIndex_access branch June 13, 2025 00:10
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants