Skip to content

Commit

Permalink
Merge #25193: indexes: Read the locator's top block during init, allo…
Browse files Browse the repository at this point in the history
…w interaction with reindex-chainstate

97844d9 index: Enable reindex-chainstate with active indexes (Martin Zumsande)
60bec3c index: Use first block from locator instead of looking for fork point (Martin Zumsande)

Pull request description:

  This makes two improvements to the index init phase:

  **1) Prevent index corruption in case a reorg happens when the index was switched off**:
  This is done by reading in the top block stored in the locator instead of looking for a fork point already in `BaseIndex::Init()`.
  Before, we'd just go back to the fork point by calling `FindForkInGlobalIndex()`, which would have corrupted the coinstatsindex because its saved muhash needs to be reverted step by step by un-applying all blocks in between, which wasn't done before. This is now being done a bit later in  `ThreadSync()`, which has existing logic to call the custom `Rewind()` method when going back along the chain to the forking point (thanks ryanofsky for pointing this out to me!).

  **2) Allow using the `-reindex-chainstate` option without needing to disabling indexes**:
  With `BaseIndex::Init()` not calling `FindForkInGlobalIndex()` anymore, we can allow `reindex-chainstate` with active indexes. `reindex-chainstate` deletes the chain and rebuilds it later in `ThreadImport`, so there is no chain available during `BaseIndex::Init()`, which would lead to problems (see #24789).
  But now we'll only need the chain a bit later in `BaseIndex::ThreadSync`, which will wait for the reindex-chainstate in `ThreadImport` to finish and will continue syncing after that.

ACKs for top commit:
  ryanofsky:
    Code review ACK 97844d9. Just simple rebase since last review

Tree-SHA512: e24973fc22e0b87a49026f4820aecb0a4e415f4d381bade9969dd31cf97afecfea0449dce7fcc797343b792199cc8287276d1f5ffa4433dcb54fb24a808db6fb
  • Loading branch information
ryanofsky committed May 17, 2023
2 parents 594f05d + 97844d9 commit 4e8a765
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 31 deletions.
20 changes: 18 additions & 2 deletions src/index/base.cpp
Expand Up @@ -23,6 +23,8 @@
#include <string>
#include <utility>

using node::g_indexes_ready_to_sync;

constexpr uint8_t DB_BEST_BLOCK{'B'};

constexpr auto SYNC_LOG_INTERVAL{30s};
Expand Down Expand Up @@ -92,14 +94,22 @@ bool BaseIndex::Init()
if (locator.IsNull()) {
SetBestBlockIndex(nullptr);
} else {
SetBestBlockIndex(m_chainstate->FindForkInGlobalIndex(locator));
// Setting the best block to the locator's top block. If it is not part of the
// best chain, we will rewind to the fork point during index sync
const CBlockIndex* locator_index{m_chainstate->m_blockman.LookupBlockIndex(locator.vHave.at(0))};
if (!locator_index) {
return InitError(strprintf(Untranslated("%s: best block of the index not found. Please rebuild the index."), GetName()));
}
SetBestBlockIndex(locator_index);
}

// Note: this will latch to true immediately if the user starts up with an empty
// datadir and an index enabled. If this is the case, indexation will happen solely
// via `BlockConnected` signals until, possibly, the next restart.
m_synced = m_best_block_index.load() == active_chain.Tip();
if (!m_synced) {

// Skip pruning check if indexes are not ready to sync (because reindex-chainstate has wiped the chain).
if (!m_synced && g_indexes_ready_to_sync) {
bool prune_violation = false;
if (!m_best_block_index) {
// index is not built yet
Expand Down Expand Up @@ -155,6 +165,12 @@ static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev, CChain&
void BaseIndex::ThreadSync()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::TX_INDEX);
// Wait for a possible reindex-chainstate to finish until continuing
// with the index sync
while (!g_indexes_ready_to_sync) {
if (!m_interrupt.sleep_for(std::chrono::milliseconds(500))) return;
}

const CBlockIndex* pindex = m_best_block_index.load();
if (!m_synced) {
std::chrono::steady_clock::time_point last_log_time{0s};
Expand Down
19 changes: 5 additions & 14 deletions src/init.cpp
Expand Up @@ -123,6 +123,7 @@ using node::CalculateCacheSizes;
using node::DEFAULT_PERSIST_MEMPOOL;
using node::DEFAULT_PRINTPRIORITY;
using node::fReindex;
using node::g_indexes_ready_to_sync;
using node::LoadChainstate;
using node::MempoolPath;
using node::NodeContext;
Expand Down Expand Up @@ -456,7 +457,7 @@ void SetupServerArgs(ArgsManager& argsman)
"Warning: Reverting this setting requires re-downloading the entire blockchain. "
"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk. This will also rebuild active optional indexes.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead. Deactivate all optional indexes before running this.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-settings=<file>", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#if HAVE_SYSTEM
argsman.AddArg("-startupnotify=<cmd>", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
Expand Down Expand Up @@ -982,19 +983,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
if (args.GetIntArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 1)
return InitError(Untranslated("Unknown rpcserialversion requested."));

if (args.GetBoolArg("-reindex-chainstate", false)) {
// indexes that must be deactivated to prevent index corruption, see #24630
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
return InitError(_("-reindex-chainstate option is not compatible with -coinstatsindex. Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes."));
}
if (g_enabled_filter_types.count(BlockFilterType::BASIC)) {
return InitError(_("-reindex-chainstate option is not compatible with -blockfilterindex. Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes."));
}
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
return InitError(_("-reindex-chainstate option is not compatible with -txindex. Please temporarily disable txindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes."));
}
}

#if defined(USE_SYSCALL_SANDBOX)
if (args.IsArgSet("-sandbox") && !args.IsArgNegated("-sandbox")) {
const std::string sandbox_arg{args.GetArg("-sandbox", "")};
Expand Down Expand Up @@ -1571,6 +1559,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
RegisterValidationInterface(node.peerman.get());

// ********************************************************* Step 8: start indexers

// If reindex-chainstate was specified, delay syncing indexes until ThreadImport has reindexed the chain
if (!fReindexChainState) g_indexes_ready_to_sync = true;
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
if (const auto error{WITH_LOCK(cs_main, return CheckLegacyTxindex(*Assert(chainman.m_blockman.m_block_tree_db)))}) {
return InitError(*error);
Expand Down
2 changes: 2 additions & 0 deletions src/node/blockstorage.cpp
Expand Up @@ -27,6 +27,7 @@

namespace node {
std::atomic_bool fReindex(false);
std::atomic_bool g_indexes_ready_to_sync{false};

bool CBlockIndexWorkComparator::operator()(const CBlockIndex* pa, const CBlockIndex* pb) const
{
Expand Down Expand Up @@ -940,5 +941,6 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
}
} // End scope of ImportingNow
chainman.ActiveChainstate().LoadMempool(mempool_path);
g_indexes_ready_to_sync = true;
}
} // namespace node
1 change: 1 addition & 0 deletions src/node/blockstorage.h
Expand Up @@ -47,6 +47,7 @@ static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
static constexpr size_t BLOCK_SERIALIZATION_HEADER_SIZE = CMessageHeader::MESSAGE_START_SIZE + sizeof(unsigned int);

extern std::atomic_bool fReindex;
extern std::atomic_bool g_indexes_ready_to_sync;

// Because validation code takes pointers to the map's CBlockIndex objects, if
// we ever switch to another associative container, we need to either use a
Expand Down
1 change: 1 addition & 0 deletions src/test/util/setup_common.cpp
Expand Up @@ -155,6 +155,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto
noui_connect();
noui_connected = true;
}
node::g_indexes_ready_to_sync = true;
}

BasicTestingSetup::~BasicTestingSetup()
Expand Down
39 changes: 31 additions & 8 deletions test/functional/feature_coinstatsindex.py
Expand Up @@ -52,6 +52,7 @@ def run_test(self):
self._test_use_index_option()
self._test_reorg_index()
self._test_index_rejects_hash_serialized()
self._test_init_index_after_reorg()

def block_sanity_check(self, block_info):
block_subsidy = 50
Expand All @@ -60,6 +61,9 @@ def block_sanity_check(self, block_info):
block_info['new_outputs_ex_coinbase'] + block_info['coinbase'] + block_info['unspendable']
)

def sync_index_node(self):
self.wait_until(lambda: self.nodes[1].getindexinfo()['coinstatsindex']['synced'] is True)

def _test_coin_stats_index(self):
node = self.nodes[0]
index_node = self.nodes[1]
Expand Down Expand Up @@ -227,18 +231,16 @@ def _test_coin_stats_index(self):
self.log.info("Test that the index works with -reindex")

self.restart_node(1, extra_args=["-coinstatsindex", "-reindex"])
self.sync_index_node()
res11 = index_node.gettxoutsetinfo('muhash')
assert_equal(res11, res10)

self.log.info("Test that -reindex-chainstate is disallowed with coinstatsindex")
self.log.info("Test that the index works with -reindex-chainstate")

self.stop_node(1)
self.nodes[1].assert_start_raises_init_error(
expected_msg='Error: -reindex-chainstate option is not compatible with -coinstatsindex. '
'Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.',
extra_args=['-coinstatsindex', '-reindex-chainstate'],
)
self.restart_node(1, extra_args=["-coinstatsindex"])
self.restart_node(1, extra_args=["-coinstatsindex", "-reindex-chainstate"])
self.sync_index_node()
res12 = index_node.gettxoutsetinfo('muhash')
assert_equal(res12, res10)

def _test_use_index_option(self):
self.log.info("Test use_index option for nodes running the index")
Expand All @@ -257,6 +259,7 @@ def _test_reorg_index(self):
index_node = self.nodes[1]
reorg_blocks = self.generatetoaddress(index_node, 2, getnewdestination()[2])
reorg_block = reorg_blocks[1]
self.sync_index_node()
res_invalid = index_node.gettxoutsetinfo('muhash')
index_node.invalidateblock(reorg_blocks[0])
assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110)
Expand Down Expand Up @@ -296,6 +299,26 @@ def _test_index_rejects_hash_serialized(self):
for use_index in {True, False, None}:
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_2', hash_or_height=111, use_index=use_index)

def _test_init_index_after_reorg(self):
self.log.info("Test a reorg while the index is deactivated")
index_node = self.nodes[1]
block = self.nodes[0].getbestblockhash()
self.generate(index_node, 2, sync_fun=self.no_op)
self.sync_index_node()

# Restart without index
self.restart_node(1, extra_args=[])
self.connect_nodes(0, 1)
index_node.invalidateblock(block)
self.generatetoaddress(index_node, 5, getnewdestination()[2])
res = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)

# Restart with index that still has its best block on the old chain
self.restart_node(1, extra_args=self.extra_args[1])
self.sync_index_node()
res1 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=True)
assert_equal(res["muhash"], res1["muhash"])


if __name__ == '__main__':
CoinStatsIndexTest().main()
7 changes: 0 additions & 7 deletions test/functional/p2p_blockfilters.py
Expand Up @@ -255,13 +255,6 @@ def run_test(self):
msg = "Error: Unknown -blockfilterindex value abc."
self.nodes[0].assert_start_raises_init_error(expected_msg=msg)

self.log.info("Test -blockfilterindex with -reindex-chainstate raises an error")
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: -reindex-chainstate option is not compatible with -blockfilterindex. '
'Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.',
extra_args=['-blockfilterindex', '-reindex-chainstate'],
)

def compute_last_header(prev_header, hashes):
"""Compute the last filter header from a starting header and a sequence of filter hashes."""
header = ser_uint256(prev_header)
Expand Down

0 comments on commit 4e8a765

Please sign in to comment.