Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/bench/rpc_blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <chainlock/chainlock.h>
#include <consensus/validation.h>
#include <instantsend/instantsend.h>
#include <llmq/context.h>
#include <rpc/blockchain.h>
#include <streams.h>
#include <test/util/setup_common.h>
Expand Down Expand Up @@ -43,9 +42,9 @@ struct TestBlockAndIndex {
static void BlockToJsonVerbose(benchmark::Bench& bench)
{
TestBlockAndIndex data;
const LLMQContext& llmq_ctx = *data.testing_setup->m_node.llmq_ctx;
const llmq::CInstantSendManager& isman = *data.testing_setup->m_node.isman;
bench.run([&] {
auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *data.testing_setup->m_node.chainlocks, *llmq_ctx.isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *data.testing_setup->m_node.chainlocks, isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
ankerl::nanobench::doNotOptimizeAway(univalue);
});
}
Expand All @@ -55,8 +54,8 @@ BENCHMARK(BlockToJsonVerbose, benchmark::PriorityLevel::HIGH);
static void BlockToJsonVerboseWrite(benchmark::Bench& bench)
{
TestBlockAndIndex data;
const LLMQContext& llmq_ctx = *data.testing_setup->m_node.llmq_ctx;
auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *data.testing_setup->m_node.chainlocks, *llmq_ctx.isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
const llmq::CInstantSendManager& isman = *data.testing_setup->m_node.isman;
auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *data.testing_setup->m_node.chainlocks, isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
bench.run([&] {
auto str = univalue.write();
ankerl::nanobench::doNotOptimizeAway(str);
Expand Down
14 changes: 7 additions & 7 deletions src/bitcoin-chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <evo/deterministicmns.h>
#include <evo/evodb.h>
#include <init/common.h>
#include <instantsend/instantsend.h>
#include <llmq/context.h>
#include <masternode/meta.h>
#include <masternode/sync.h>
Expand Down Expand Up @@ -93,11 +94,13 @@ int main(int argc, char* argv[])
ChainstateManager chainman{chainman_opts};

CMasternodeMetaMan metaman;
std::unique_ptr<CEvoDB> evodb;
std::unique_ptr<CDeterministicMNManager> dmnman;
CEvoDB evodb{util::DbWrapperParams{.path = gArgs.GetDataDirNet(), .memory = false, .wipe = false}};
CDeterministicMNManager dmnman{evodb, metaman};
CMasternodeSync mn_sync{std::make_unique<NullNodeSyncNotifier>()};
CSporkManager sporkman;
chainlock::Chainlocks chainlocks(sporkman);
// TODO: remove isman from bitcoin-chainstate and make it nullable for node::ChainstateLoadOptions same as mempool
llmq::CInstantSendManager isman{sporkman, util::DbWrapperParams{.path = gArgs.GetDataDirNet(), .memory = false, .wipe = false}};

std::unique_ptr<LLMQContext> llmq_ctx;
std::unique_ptr<CChainstateHelper> chain_helper;
Expand All @@ -107,8 +110,7 @@ int main(int argc, char* argv[])
cache_sizes.coins_db = 2 << 22;
cache_sizes.coins = (450 << 20) - (2 << 20) - (2 << 22);
node::ChainstateLoadOptions options;
options.mn_metaman = &metaman;
options.sporkman = &sporkman;
options.isman = &isman;
options.chainlocks = &chainlocks;
options.mn_sync = &mn_sync;
options.data_dir = gArgs.GetDataDirNet();
Expand All @@ -119,7 +121,7 @@ int main(int argc, char* argv[])
std::cerr << "Failed to load Chain state from your datadir." << std::endl;
goto epilogue;
} else {
std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options, *evodb);
std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options, evodb);
if (status != node::ChainstateLoadStatus::SUCCESS) {
std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
goto epilogue;
Expand Down Expand Up @@ -278,6 +280,4 @@ int main(int argc, char* argv[])
// Tear down Dash kernel objects before kernel::~Context().
chain_helper.reset();
llmq_ctx.reset();
dmnman.reset();
evodb.reset();
}
4 changes: 4 additions & 0 deletions src/evo/chainhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ std::optional<std::pair</*islock_hash=*/uint256, /*txid=*/uint256>> CChainstateH
return std::make_pair(::SerializeHash(*islock), islock->txid);
}

bool CChainstateHelper::IsInstantSendEnabled() const { return isman.IsInstantSendEnabled(); }

bool CChainstateHelper::IsInstantSendLocked(const uint256& hash) const { return isman.IsLocked(hash); }

bool CChainstateHelper::IsInstantSendWaitingForTx(const uint256& hash) const { return isman.IsWaitingForTx(hash); }

bool CChainstateHelper::RemoveConflictingISLockByTx(const CTransaction& tx)
Expand Down
2 changes: 2 additions & 0 deletions src/evo/chainhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class CChainstateHelper

/** Passthrough functions to CInstantSendManager */
std::optional<std::pair</*islock_hash=*/uint256, /*txid=*/uint256>> ConflictingISLockIfAny(const CTransaction& tx) const;
bool IsInstantSendEnabled() const;
bool IsInstantSendLocked(const uint256& hash) const;
bool IsInstantSendWaitingForTx(const uint256& hash) const;
bool RemoveConflictingISLockByTx(const CTransaction& tx);

Expand Down
40 changes: 25 additions & 15 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,12 @@ void PrepareShutdown(NodeContext& node)
chainstate->ResetCoinsViews();
}
}
// The mempool holds raw pointers to dmnman and llmq_ctx->isman, so it has to
// let go of them before either manager is destroyed.
if (node.mempool) {
node.mempool->DisconnectManagers();
}
// The mempool holds raw pointers to dmnman and isman, so it must be
// destroyed before either manager.
node.mempool.reset();
node.chain_helper.reset();
node.llmq_ctx.reset();
node.isman.reset();
node.dmnman.reset();
node.evodb.reset();
}
Expand Down Expand Up @@ -900,7 +899,7 @@ static void PeriodicStats(NodeContext& node)
assert(::g_stats_client->active());
ChainstateManager& chainman = *Assert(node.chainman);
const CTxMemPool& mempool = *Assert(node.mempool);
const llmq::CInstantSendManager& isman = *Assert(node.llmq_ctx->isman);
const llmq::CInstantSendManager& isman = *Assert(node.isman);
chainman.ActiveChainstate().ForceFlushStateToDisk();
const auto maybe_stats = WITH_LOCK(::cs_main, return GetUTXOStats(&chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, /*hash_type=*/CoinStatsHashType::NONE, node.rpc_interruption_point, chainman.ActiveChain().Tip(), /*index_requested=*/true));
if (maybe_stats.has_value()) {
Expand Down Expand Up @@ -1949,6 +1948,18 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
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));

for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) {
// On a retry iteration the previous instances still hold the on-disk
// database locks, so release them before opening the databases again.
node.mempool.reset();
node.isman.reset();
node.dmnman.reset();
node.evodb.reset();
node.evodb = std::make_unique<CEvoDB>(util::DbWrapperParams{.path = args.GetDataDirNet(), .memory = false, .wipe = node::fReindex || fReindexChainState});
node.dmnman = std::make_unique<CDeterministicMNManager>(*node.evodb, *node.mn_metaman);
node.isman = std::make_unique<llmq::CInstantSendManager>(*node.sporkman, util::DbWrapperParams{.path = args.GetDataDirNet(), .memory = false, .wipe = node::fReindex || fReindexChainState});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +1957 to +1959

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep Dash DB opens inside the startup error path

When evodb or the InstantSend DB is corrupt or still has a stale LevelDB lock, these constructors can throw dbwrapper_error, but they now run before the catch_exceptions wrapper below is entered. Before this refactor the EvoDB open happened inside LoadChainstate(), so startup converted the error into a recoverable block-database failure and could offer the reindex prompt; now the exception escapes to the outer AppInit catch and the node exits with only a generic initialization failure for those datadir states.

AGENTS.md reference: AGENTS.md:L170-L172

Useful? React with 👍 / 👎.

Comment on lines +1957 to +1959

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Blocking: Keep Dash database opens inside the recoverable startup path

The CEvoDB and CInstantSendManager constructors both open LevelDB databases and can throw dbwrapper_error, but these calls now run before the catch_exceptions boundary at lines 2013-2021. Before this refactor, EvoDB was opened inside LoadChainstate() and the InstantSend database was opened while constructing LLMQContext there, so an open or corruption error became ChainstateLoadStatus::FAILURE and followed the normal reindex-recovery prompt. At the current location, the exception escapes AppInitMain() to the outer application catch in bitcoind.cpp, terminating startup with a generic initialization failure instead. Construct these database-backed managers, followed by the mempool that references them, within the same exception boundary used for LoadChainstate() so database failures retain the established recovery flow.

source: ['codex']


mempool_opts.dmnman = node.dmnman.get();
mempool_opts.isman = node.isman.get();
node.mempool = std::make_unique<CTxMemPool>(mempool_opts);

const ChainstateManager::Options chainman_opts{
Expand All @@ -1969,8 +1980,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

node::ChainstateLoadOptions options;
options.mempool = Assert(node.mempool.get());
options.mn_metaman = Assert(node.mn_metaman.get());
options.sporkman = Assert(node.sporkman.get());
options.isman = Assert(node.isman.get());
options.chainlocks = Assert(node.chainlocks.get());
options.mn_sync = Assert(node.mn_sync.get());
options.data_dir = args.GetDataDirNet();
Expand Down Expand Up @@ -2008,7 +2018,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database"));
}
};
auto [status, error] = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options, node.evodb, node.dmnman, node.llmq_ctx, node.chain_helper); });
auto [status, error] = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options, *node.evodb, *node.dmnman, node.llmq_ctx, node.chain_helper); });
if (status == node::ChainstateLoadStatus::SUCCESS) {
uiInterface.InitMessage(_("Verifying blocks…").translated);
if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) {
Expand Down Expand Up @@ -2083,7 +2093,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// Will init later in ThreadImport
node.active_ctx = std::make_unique<ActiveContext>(*node.llmq_ctx->bls_worker, chainman, *node.connman, *node.dmnman,
*node.govman, *node.chain_helper->superblocks,
*node.sporkman, *node.chainlocks, *node.mempool, *node.clhandler, *node.llmq_ctx->isman,
*node.sporkman, *node.chainlocks, *node.mempool, *node.clhandler, *node.isman,
*node.llmq_ctx->qman, *node.llmq_ctx->qsnapman, *node.llmq_ctx->sigman,
*node.mn_sync, operator_sk, dash_db_params, quorums_watch);
RegisterValidationInterface(node.active_ctx.get());
Expand All @@ -2098,7 +2108,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
chainman, *node.mempool, *node.mn_metaman, *node.mn_sync,
*node.sporkman, *node.chainlocks, *node.clhandler,
node.active_ctx ? node.active_ctx->nodeman.get() : nullptr,
node.dmnman, node.cj_walletman, node.llmq_ctx, ignores_incoming_txs);
node.dmnman, node.cj_walletman, *node.isman, node.llmq_ctx, ignores_incoming_txs);
RegisterValidationInterface(node.peerman.get());

node.ds_notification_interface = std::make_unique<CDSNotificationInterface>(
Expand All @@ -2108,7 +2118,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

// ********************************************************* Step 7d: Setup other Dash services

node.peerman->AddExtraHandler(std::make_unique<NetInstantSend>(node.peerman.get(), *node.llmq_ctx->isman, node.active_ctx ? node.active_ctx->is_signer.get() : nullptr, *node.llmq_ctx->sigman, *node.llmq_ctx->qman, *node.chainlocks, chainman, *node.mempool, *node.mn_sync));
node.peerman->AddExtraHandler(std::make_unique<NetInstantSend>(node.peerman.get(), *node.isman, node.active_ctx ? node.active_ctx->is_signer.get() : nullptr, *node.llmq_ctx->sigman, *node.llmq_ctx->qman, *node.chainlocks, chainman, *node.mempool, *node.mn_sync));
node.peerman->AddExtraHandler(std::make_unique<llmq::NetSigning>(node.peerman.get(), *node.llmq_ctx->sigman, node.active_ctx ? node.active_ctx->shareman.get() : nullptr, *node.sporkman));

{
Expand Down Expand Up @@ -2140,15 +2150,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

if (node.active_ctx) {
auto cj_server = std::make_unique<CCoinJoinServer>(node.peerman.get(), chainman, *node.connman, *node.dmnman, *node.dstxman, *node.mn_metaman,
*node.mempool, *node.active_ctx->nodeman, *node.mn_sync, *node.llmq_ctx->isman);
*node.mempool, *node.active_ctx->nodeman, *node.mn_sync, *node.isman);
node.active_ctx->SetCJServer(cj_server.get());
node.peerman->AddExtraHandler(std::move(cj_server));
} else {
assert(!node.cj_walletman);
// Only constructed in wallet-enabled builds; stays null otherwise, must check before use
#ifdef ENABLE_WALLET
node.cj_walletman = CJWalletManager::make(chainman, *node.dmnman, *node.mn_metaman, *node.mempool, *node.mn_sync,
*node.llmq_ctx->isman, !ignores_incoming_txs);
*node.isman, !ignores_incoming_txs);
#endif
}

Expand Down Expand Up @@ -2386,7 +2396,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// Seed InstantSend tip-height cache; NetInstantSend receives future
// updates via CValidationInterface but misses InitializeCurrentBlockTip.
// TODO: move cache updates from NetInstantSend to g_ds_notification due to specific of Tip's processing
node.llmq_ctx->isman->CacheTipHeight(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()));
node.isman->CacheTipHeight(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()));

{
// Get all UTXOs for each MN collateral in one go so that we can fill coin cache early
Expand Down
9 changes: 9 additions & 0 deletions src/kernel/mempool_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
#include <optional>

class CBlockPolicyEstimator;
class CDeterministicMNManager;

namespace llmq {
class CInstantSendManager;
} // namespace llmq

/** Default for -maxmempool, maximum megabytes of mempool memory usage */
static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300};
Expand All @@ -32,6 +37,10 @@ namespace kernel {
struct MemPoolOptions {
/* Used to estimate appropriate transaction fees. */
CBlockPolicyEstimator* estimator{nullptr};
/* Used to validate special transactions; required, must outlive the mempool. */
CDeterministicMNManager* dmnman{nullptr};
/* Used to protect InstantSend-locked transactions; required, must outlive the mempool. */
llmq::CInstantSendManager* isman{nullptr};
/* The ratio used to determine how often sanity checks will run. */
int check_ratio{0};
int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000};
Expand Down
10 changes: 4 additions & 6 deletions src/llmq/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,22 @@
#include <llmq/context.h>

#include <bls/bls_worker.h>
#include <instantsend/instantsend.h>
#include <llmq/blockprocessor.h>
#include <llmq/quorumsman.h>
#include <llmq/signing.h>
#include <llmq/snapshot.h>
#include <validation.h>

LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman,
ChainstateManager& chainman, const util::DbWrapperParams& db_params, int8_t bls_threads,
int16_t worker_count, int64_t max_recsigs_age) :
LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, ChainstateManager& chainman,
const util::DbWrapperParams& db_params, int8_t bls_threads, int16_t worker_count,
int64_t max_recsigs_age) :
bls_worker{std::make_shared<CBLSWorker>()},
qsnapman{std::make_unique<llmq::CQuorumSnapshotManager>(evo_db)},
quorum_block_processor{
std::make_unique<llmq::CQuorumBlockProcessor>(chainman, dmnman, evo_db, *qsnapman, bls_threads)},
qman{std::make_unique<llmq::CQuorumManager>(*bls_worker, dmnman, evo_db, *quorum_block_processor, *qsnapman,
chainman, db_params)},
sigman{std::make_unique<llmq::CSigningManager>(*qman, db_params, max_recsigs_age)},
isman{std::make_unique<llmq::CInstantSendManager>(sporkman, db_params)}
sigman{std::make_unique<llmq::CSigningManager>(*qman, db_params, max_recsigs_age)}
{
// Have to start it early to let VerifyDB check ChainLock signatures in coinbase
bls_worker->Start(worker_count);
Expand Down
9 changes: 3 additions & 6 deletions src/llmq/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ class CBLSWorker;
class ChainstateManager;
class CDeterministicMNManager;
class CEvoDB;
class CSporkManager;
class PeerManager;

namespace llmq {
class CInstantSendManager;
class CQuorumBlockProcessor;
class CQuorumManager;
class CQuorumSnapshotManager;
Expand All @@ -32,9 +30,9 @@ struct LLMQContext {
LLMQContext() = delete;
LLMQContext(const LLMQContext&) = delete;
LLMQContext& operator=(const LLMQContext&) = delete;
explicit LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman,
ChainstateManager& chainman, const util::DbWrapperParams& db_params, int8_t bls_threads,
int16_t worker_count, int64_t max_recsigs_age);
explicit LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, ChainstateManager& chainman,
const util::DbWrapperParams& db_params, int8_t bls_threads, int16_t worker_count,
int64_t max_recsigs_age);
~LLMQContext();

/** Guaranteed if LLMQContext is initialized then all members are valid too
Expand All @@ -48,7 +46,6 @@ struct LLMQContext {
const std::unique_ptr<llmq::CQuorumBlockProcessor> quorum_block_processor;
const std::unique_ptr<llmq::CQuorumManager> qman;
const std::unique_ptr<llmq::CSigningManager> sigman;
const std::unique_ptr<llmq::CInstantSendManager> isman;
};

#endif // BITCOIN_LLMQ_CONTEXT_H
Loading
Loading