Skip to content

Commit d7f956a

Browse files
committed
Merge bitcoin#30968: init: Remove retry for loop
e9d60af refactor: Replace init retry for loop with if statement (TheCharlatan) c1d8870 refactor: Move most of init retry for loop to a function (TheCharlatan) 781c01f init: Check mempool arguments in AppInitParameterInteractions (TheCharlatan) Pull request description: The for loop around the chain loading logic in `init.cpp` allows users of the GUI to retry once on failure with reindexing without having to manually set the reindex flag on startup. However this current mechanism has problems: * It is badly documented and has led to confusion among developers and bugs making it into master. Examples: * https://github.com/bitcoin/bitcoin/pull/28830/files#r1598392660 * bitcoin#30132 (comment) * It can only ever iterate once, making the choice of a for loop questionable. * With its large scope it is easy for re-entry bugs to sneak in. Example: * bitcoin#28830 (comment) Attempt to fix this by moving the bulk of the logic into a separate function and replacing the for loop with a simpler `if` statement. The diff's in this pull request are best reviewed with `--color-moved-ws=ignore-all-space --color-moved=dimmed-zebra`. The error behaviour can be tested by either manually making `LoadChainstate` return a failure, or deleting some of the block index database files. ACKs for top commit: maflcko: review ACK e9d60af 🚸 josibake: crACK bitcoin@e9d60af achow101: ACK e9d60af ryanofsky: Code review ACK e9d60af. Nice change to make AppInitMain shorter and more understandable. Tree-SHA512: 5e5c0a5fd1b32225346450f8482f0ae8792e1557cdab1518112c1a3ec3a4400b64f5796692245cc5bf2f9010bb97b3a9558f07626a285ccd6ae525dd671ead13
2 parents c33eb23 + e9d60af commit d7f956a

File tree

1 file changed

+137
-124
lines changed

1 file changed

+137
-124
lines changed

src/init.cpp

+137-124
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,19 @@ using node::ApplyArgsManOptions;
123123
using node::BlockManager;
124124
using node::CacheSizes;
125125
using node::CalculateCacheSizes;
126+
using node::ChainstateLoadResult;
127+
using node::ChainstateLoadStatus;
126128
using node::DEFAULT_PERSIST_MEMPOOL;
127129
using node::DEFAULT_PRINT_MODIFIED_FEE;
128130
using node::DEFAULT_STOPATHEIGHT;
129131
using node::DumpMempool;
130-
using node::LoadMempool;
132+
using node::ImportBlocks;
131133
using node::KernelNotifications;
132134
using node::LoadChainstate;
135+
using node::LoadMempool;
133136
using node::MempoolPath;
134137
using node::NodeContext;
135138
using node::ShouldPersistMempool;
136-
using node::ImportBlocks;
137139
using node::VerifyLoadedChainstate;
138140
using util::Join;
139141
using util::ReplaceAll;
@@ -1068,6 +1070,13 @@ bool AppInitParameterInteraction(const ArgsManager& args)
10681070
if (!blockman_result) {
10691071
return InitError(util::ErrorString(blockman_result));
10701072
}
1073+
CTxMemPool::Options mempool_opts{
1074+
.check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
1075+
};
1076+
auto mempool_result{ApplyArgsManOptions(args, chainparams, mempool_opts)};
1077+
if (!mempool_result) {
1078+
return InitError(util::ErrorString(mempool_result));
1079+
}
10711080
}
10721081

10731082
return true;
@@ -1172,6 +1181,104 @@ bool CheckHostPortOptions(const ArgsManager& args) {
11721181
return true;
11731182
}
11741183

1184+
// A GUI user may opt to retry once if there is a failure during chainstate initialization.
1185+
// The function therefore has to support re-entry.
1186+
static ChainstateLoadResult InitAndLoadChainstate(
1187+
NodeContext& node,
1188+
bool do_reindex,
1189+
const bool do_reindex_chainstate,
1190+
CacheSizes& cache_sizes,
1191+
const ArgsManager& args)
1192+
{
1193+
const CChainParams& chainparams = Params();
1194+
CTxMemPool::Options mempool_opts{
1195+
.check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
1196+
.signals = node.validation_signals.get(),
1197+
};
1198+
Assert(ApplyArgsManOptions(args, chainparams, mempool_opts)); // no error can happen, already checked in AppInitParameterInteraction
1199+
bilingual_str mempool_error;
1200+
node.mempool = std::make_unique<CTxMemPool>(mempool_opts, mempool_error);
1201+
if (!mempool_error.empty()) {
1202+
return {ChainstateLoadStatus::FAILURE_FATAL, mempool_error};
1203+
}
1204+
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
1205+
ChainstateManager::Options chainman_opts{
1206+
.chainparams = chainparams,
1207+
.datadir = args.GetDataDirNet(),
1208+
.notifications = *node.notifications,
1209+
.signals = node.validation_signals.get(),
1210+
};
1211+
Assert(ApplyArgsManOptions(args, chainman_opts)); // no error can happen, already checked in AppInitParameterInteraction
1212+
BlockManager::Options blockman_opts{
1213+
.chainparams = chainman_opts.chainparams,
1214+
.blocks_dir = args.GetBlocksDirPath(),
1215+
.notifications = chainman_opts.notifications,
1216+
};
1217+
Assert(ApplyArgsManOptions(args, blockman_opts)); // no error can happen, already checked in AppInitParameterInteraction
1218+
try {
1219+
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
1220+
} catch (std::exception& e) {
1221+
return {ChainstateLoadStatus::FAILURE_FATAL, strprintf(Untranslated("Failed to initialize ChainstateManager: %s"), e.what())};
1222+
}
1223+
ChainstateManager& chainman = *node.chainman;
1224+
// This is defined and set here instead of inline in validation.h to avoid a hard
1225+
// dependency between validation and index/base, since the latter is not in
1226+
// libbitcoinkernel.
1227+
chainman.snapshot_download_completed = [&node]() {
1228+
if (!node.chainman->m_blockman.IsPruneMode()) {
1229+
LogPrintf("[snapshot] re-enabling NODE_NETWORK services\n");
1230+
node.connman->AddLocalServices(NODE_NETWORK);
1231+
}
1232+
LogPrintf("[snapshot] restarting indexes\n");
1233+
// Drain the validation interface queue to ensure that the old indexes
1234+
// don't have any pending work.
1235+
Assert(node.validation_signals)->SyncWithValidationInterfaceQueue();
1236+
for (auto* index : node.indexes) {
1237+
index->Interrupt();
1238+
index->Stop();
1239+
if (!(index->Init() && index->StartBackgroundSync())) {
1240+
LogPrintf("[snapshot] WARNING failed to restart index %s on snapshot chain\n", index->GetName());
1241+
}
1242+
}
1243+
};
1244+
node::ChainstateLoadOptions options;
1245+
options.mempool = Assert(node.mempool.get());
1246+
options.wipe_block_tree_db = do_reindex;
1247+
options.wipe_chainstate_db = do_reindex || do_reindex_chainstate;
1248+
options.prune = chainman.m_blockman.IsPruneMode();
1249+
options.check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
1250+
options.check_level = args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
1251+
options.require_full_verification = args.IsArgSet("-checkblocks") || args.IsArgSet("-checklevel");
1252+
options.coins_error_cb = [] {
1253+
uiInterface.ThreadSafeMessageBox(
1254+
_("Error reading from database, shutting down."),
1255+
"", CClientUIInterface::MSG_ERROR);
1256+
};
1257+
uiInterface.InitMessage(_("Loading block index…").translated);
1258+
const auto load_block_index_start_time{SteadyClock::now()};
1259+
auto catch_exceptions = [](auto&& f) {
1260+
try {
1261+
return f();
1262+
} catch (const std::exception& e) {
1263+
LogError("%s\n", e.what());
1264+
return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database"));
1265+
}
1266+
};
1267+
auto [status, error] = catch_exceptions([&] { return LoadChainstate(chainman, cache_sizes, options); });
1268+
if (status == node::ChainstateLoadStatus::SUCCESS) {
1269+
uiInterface.InitMessage(_("Verifying blocks…").translated);
1270+
if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) {
1271+
LogWarning("pruned datadir may not have more than %d blocks; only checking available blocks\n",
1272+
MIN_BLOCKS_TO_KEEP);
1273+
}
1274+
std::tie(status, error) = catch_exceptions([&] { return VerifyLoadedChainstate(chainman, options); });
1275+
if (status == node::ChainstateLoadStatus::SUCCESS) {
1276+
LogPrintf(" block index %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - load_block_index_start_time));
1277+
}
1278+
}
1279+
return {status, error};
1280+
};
1281+
11751282
bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
11761283
{
11771284
const ArgsManager& args = *Assert(node.args);
@@ -1503,20 +1610,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15031610

15041611
node.notifications = std::make_unique<KernelNotifications>(*Assert(node.shutdown), node.exit_status, *Assert(node.warnings));
15051612
ReadNotificationArgs(args, *node.notifications);
1506-
ChainstateManager::Options chainman_opts{
1507-
.chainparams = chainparams,
1508-
.datadir = args.GetDataDirNet(),
1509-
.notifications = *node.notifications,
1510-
.signals = &validation_signals,
1511-
};
1512-
Assert(ApplyArgsManOptions(args, chainman_opts)); // no error can happen, already checked in AppInitParameterInteraction
1513-
1514-
BlockManager::Options blockman_opts{
1515-
.chainparams = chainman_opts.chainparams,
1516-
.blocks_dir = args.GetBlocksDirPath(),
1517-
.notifications = chainman_opts.notifications,
1518-
};
1519-
Assert(ApplyArgsManOptions(args, blockman_opts)); // no error can happen, already checked in AppInitParameterInteraction
15201613

15211614
// cache size calculations
15221615
CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size());
@@ -1535,119 +1628,39 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15351628
assert(!node.mempool);
15361629
assert(!node.chainman);
15371630

1538-
CTxMemPool::Options mempool_opts{
1539-
.check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
1540-
.signals = &validation_signals,
1541-
};
1542-
auto result{ApplyArgsManOptions(args, chainparams, mempool_opts)};
1543-
if (!result) {
1544-
return InitError(util::ErrorString(result));
1545-
}
1546-
15471631
bool do_reindex{args.GetBoolArg("-reindex", false)};
15481632
const bool do_reindex_chainstate{args.GetBoolArg("-reindex-chainstate", false)};
15491633

1550-
for (bool fLoaded = false; !fLoaded && !ShutdownRequested(node);) {
1551-
bilingual_str mempool_error;
1552-
node.mempool = std::make_unique<CTxMemPool>(mempool_opts, mempool_error);
1553-
if (!mempool_error.empty()) {
1554-
return InitError(mempool_error);
1555-
}
1556-
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
1557-
1558-
try {
1559-
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
1560-
} catch (std::exception& e) {
1561-
return InitError(strprintf(Untranslated("Failed to initialize ChainstateManager: %s"), e.what()));
1562-
}
1563-
ChainstateManager& chainman = *node.chainman;
1564-
1565-
// This is defined and set here instead of inline in validation.h to avoid a hard
1566-
// dependency between validation and index/base, since the latter is not in
1567-
// libbitcoinkernel.
1568-
chainman.snapshot_download_completed = [&node]() {
1569-
if (!node.chainman->m_blockman.IsPruneMode()) {
1570-
LogPrintf("[snapshot] re-enabling NODE_NETWORK services\n");
1571-
node.connman->AddLocalServices(NODE_NETWORK);
1572-
}
1573-
1574-
LogPrintf("[snapshot] restarting indexes\n");
1575-
1576-
// Drain the validation interface queue to ensure that the old indexes
1577-
// don't have any pending work.
1578-
Assert(node.validation_signals)->SyncWithValidationInterfaceQueue();
1579-
1580-
for (auto* index : node.indexes) {
1581-
index->Interrupt();
1582-
index->Stop();
1583-
if (!(index->Init() && index->StartBackgroundSync())) {
1584-
LogPrintf("[snapshot] WARNING failed to restart index %s on snapshot chain\n", index->GetName());
1585-
}
1586-
}
1587-
};
1588-
1589-
node::ChainstateLoadOptions options;
1590-
options.mempool = Assert(node.mempool.get());
1591-
options.wipe_block_tree_db = do_reindex;
1592-
options.wipe_chainstate_db = do_reindex || do_reindex_chainstate;
1593-
options.prune = chainman.m_blockman.IsPruneMode();
1594-
options.check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
1595-
options.check_level = args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
1596-
options.require_full_verification = args.IsArgSet("-checkblocks") || args.IsArgSet("-checklevel");
1597-
options.coins_error_cb = [] {
1598-
uiInterface.ThreadSafeMessageBox(
1599-
_("Error reading from database, shutting down."),
1600-
"", CClientUIInterface::MSG_ERROR);
1601-
};
1602-
1603-
uiInterface.InitMessage(_("Loading block index…").translated);
1604-
const auto load_block_index_start_time{SteadyClock::now()};
1605-
auto catch_exceptions = [](auto&& f) {
1606-
try {
1607-
return f();
1608-
} catch (const std::exception& e) {
1609-
LogError("%s\n", e.what());
1610-
return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database"));
1611-
}
1612-
};
1613-
auto [status, error] = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options); });
1614-
if (status == node::ChainstateLoadStatus::SUCCESS) {
1615-
uiInterface.InitMessage(_("Verifying blocks…").translated);
1616-
if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) {
1617-
LogWarning("pruned datadir may not have more than %d blocks; only checking available blocks\n",
1618-
MIN_BLOCKS_TO_KEEP);
1619-
}
1620-
std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);});
1621-
if (status == node::ChainstateLoadStatus::SUCCESS) {
1622-
fLoaded = true;
1623-
LogPrintf(" block index %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - load_block_index_start_time));
1624-
}
1625-
}
1626-
1627-
if (status == node::ChainstateLoadStatus::FAILURE_FATAL || status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB || status == node::ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE) {
1628-
return InitError(error);
1634+
// Chainstate initialization and loading may be retried once with reindexing by GUI users
1635+
auto [status, error] = InitAndLoadChainstate(
1636+
node,
1637+
do_reindex,
1638+
do_reindex_chainstate,
1639+
cache_sizes,
1640+
args);
1641+
if (status == ChainstateLoadStatus::FAILURE && !do_reindex && !ShutdownRequested(node)) {
1642+
// suggest a reindex
1643+
bool do_retry = uiInterface.ThreadSafeQuestion(
1644+
error + Untranslated(".\n\n") + _("Do you want to rebuild the block database now?"),
1645+
error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.",
1646+
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT);
1647+
if (!do_retry) {
1648+
LogError("Aborted block database rebuild. Exiting.\n");
1649+
return false;
16291650
}
1630-
1631-
if (!fLoaded && !ShutdownRequested(node)) {
1632-
// first suggest a reindex
1633-
if (!do_reindex) {
1634-
bool fRet = uiInterface.ThreadSafeQuestion(
1635-
error + Untranslated(".\n\n") + _("Do you want to rebuild the block database now?"),
1636-
error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.",
1637-
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT);
1638-
if (fRet) {
1639-
do_reindex = true;
1640-
if (!Assert(node.shutdown)->reset()) {
1641-
LogError("Internal error: failed to reset shutdown signal.\n");
1642-
}
1643-
} else {
1644-
LogError("Aborted block database rebuild. Exiting.\n");
1645-
return false;
1646-
}
1647-
} else {
1648-
return InitError(error);
1649-
}
1651+
do_reindex = true;
1652+
if (!Assert(node.shutdown)->reset()) {
1653+
LogError("Internal error: failed to reset shutdown signal.\n");
16501654
}
1655+
std::tie(status, error) = InitAndLoadChainstate(
1656+
node,
1657+
do_reindex,
1658+
do_reindex_chainstate,
1659+
cache_sizes,
1660+
args);
1661+
}
1662+
if (status != ChainstateLoadStatus::SUCCESS && status != ChainstateLoadStatus::INTERRUPTED) {
1663+
return InitError(error);
16511664
}
16521665

16531666
// As LoadBlockIndex can take several minutes, it's possible the user

0 commit comments

Comments
 (0)