aboutsummaryrefslogtreecommitdiff
path: root/src/node
diff options
context:
space:
mode:
Diffstat (limited to 'src/node')
-rw-r--r--src/node/blockmanager_args.cpp19
-rw-r--r--src/node/blockmanager_args.h6
-rw-r--r--src/node/blockstorage.cpp117
-rw-r--r--src/node/blockstorage.h59
-rw-r--r--src/node/caches.cpp2
-rw-r--r--src/node/chainstate.cpp7
-rw-r--r--src/node/chainstate.h3
-rw-r--r--src/node/chainstatemanager_args.cpp12
-rw-r--r--src/node/chainstatemanager_args.h6
-rw-r--r--src/node/coins_view_args.cpp2
-rw-r--r--src/node/context.cpp1
-rw-r--r--src/node/context.h6
-rw-r--r--src/node/database_args.cpp2
-rw-r--r--src/node/interfaces.cpp67
-rw-r--r--src/node/kernel_notifications.cpp75
-rw-r--r--src/node/kernel_notifications.h31
-rw-r--r--src/node/mempool_args.cpp14
-rw-r--r--src/node/mempool_args.h4
-rw-r--r--src/node/mempool_persist_args.cpp4
-rw-r--r--src/node/mempool_persist_args.h2
-rw-r--r--src/node/miner.cpp2
-rw-r--r--src/node/miner.h3
-rw-r--r--src/node/mini_miner.cpp371
-rw-r--r--src/node/mini_miner.h121
-rw-r--r--src/node/transaction.cpp4
-rw-r--r--src/node/transaction.h4
-rw-r--r--src/node/txreconciliation.cpp3
-rw-r--r--src/node/utxo_snapshot.cpp7
-rw-r--r--src/node/utxo_snapshot.h4
-rw-r--r--src/node/validation_cache_args.cpp2
30 files changed, 801 insertions, 159 deletions
diff --git a/src/node/blockmanager_args.cpp b/src/node/blockmanager_args.cpp
index 5fb5c8beed..4b296db1b0 100644
--- a/src/node/blockmanager_args.cpp
+++ b/src/node/blockmanager_args.cpp
@@ -4,27 +4,36 @@
#include <node/blockmanager_args.h>
-#include <util/system.h>
+#include <common/args.h>
+#include <node/blockstorage.h>
+#include <tinyformat.h>
+#include <util/result.h>
+#include <util/translation.h>
#include <validation.h>
+#include <cstdint>
+
namespace node {
-std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts)
+util::Result<void> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts)
{
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
int64_t nPruneArg{args.GetIntArg("-prune", opts.prune_target)};
if (nPruneArg < 0) {
- return _("Prune cannot be configured with a negative value.");
+ return util::Error{_("Prune cannot be configured with a negative value.")};
}
uint64_t nPruneTarget{uint64_t(nPruneArg) * 1024 * 1024};
if (nPruneArg == 1) { // manual pruning: -prune=1
nPruneTarget = BlockManager::PRUNE_TARGET_MANUAL;
} else if (nPruneTarget) {
if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) {
- return strprintf(_("Prune configured below the minimum of %d MiB. Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024);
+ return util::Error{strprintf(_("Prune configured below the minimum of %d MiB. Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)};
}
}
opts.prune_target = nPruneTarget;
- return std::nullopt;
+ if (auto value{args.GetBoolArg("-fastprune")}) opts.fast_prune = *value;
+ if (auto value{args.GetBoolArg("-stopafterblockimport")}) opts.stop_after_block_import = *value;
+
+ return {};
}
} // namespace node
diff --git a/src/node/blockmanager_args.h b/src/node/blockmanager_args.h
index e657c6bb45..de9fe83a5c 100644
--- a/src/node/blockmanager_args.h
+++ b/src/node/blockmanager_args.h
@@ -7,14 +7,12 @@
#define BITCOIN_NODE_BLOCKMANAGER_ARGS_H
#include <node/blockstorage.h>
-
-#include <optional>
+#include <util/result.h>
class ArgsManager;
-struct bilingual_str;
namespace node {
-std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts);
+[[nodiscard]] util::Result<void> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts);
} // namespace node
#endif // BITCOIN_NODE_BLOCKMANAGER_ARGS_H
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index 53721b807c..87cd291c7e 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -8,18 +8,18 @@
#include <clientversion.h>
#include <consensus/validation.h>
#include <flatfile.h>
-#include <fs.h>
#include <hash.h>
-#include <logging.h>
#include <kernel/chainparams.h>
+#include <logging.h>
#include <pow.h>
#include <reverse_iterator.h>
#include <shutdown.h>
#include <signet.h>
#include <streams.h>
#include <undo.h>
+#include <util/batchpriority.h>
+#include <util/fs.h>
#include <util/syscall_sandbox.h>
-#include <util/system.h>
#include <validation.h>
#include <map>
@@ -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
{
@@ -52,10 +53,6 @@ bool CBlockIndexHeightOnlyComparator::operator()(const CBlockIndex* pa, const CB
return pa->nHeight < pb->nHeight;
}
-static FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false);
-static FlatFileSeq BlockFileSeq();
-static FlatFileSeq UndoFileSeq();
-
std::vector<CBlockIndex*> BlockManager::GetAllBlockIndices()
{
AssertLockHeld(cs_main);
@@ -252,9 +249,9 @@ CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
return pindex;
}
-bool BlockManager::LoadBlockIndex(const Consensus::Params& consensus_params)
+bool BlockManager::LoadBlockIndex()
{
- if (!m_block_tree_db->LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) {
+ if (!m_block_tree_db->LoadBlockIndexGuts(GetConsensus(), [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) {
return false;
}
@@ -317,9 +314,9 @@ bool BlockManager::WriteBlockIndexDB()
return true;
}
-bool BlockManager::LoadBlockIndexDB(const Consensus::Params& consensus_params)
+bool BlockManager::LoadBlockIndexDB()
{
- if (!LoadBlockIndex(consensus_params)) {
+ if (!LoadBlockIndex()) {
return false;
}
@@ -422,7 +419,7 @@ const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& start_bl
// rev files since they'll be rewritten by the reindex anyway. This ensures that m_blockfile_info
// is in sync with what's actually on disk by the time we start downloading, so that pruning
// works correctly.
-void CleanupBlockRevFiles()
+void BlockManager::CleanupBlockRevFiles() const
{
std::map<std::string, fs::path> mapBlockFiles;
@@ -430,8 +427,7 @@ void CleanupBlockRevFiles()
// Remove the rev files immediately and insert the blk file paths into an
// ordered map keyed by block file index.
LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n");
- const fs::path& blocksdir = gArgs.GetBlocksDirPath();
- for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) {
+ for (fs::directory_iterator it(m_opts.blocks_dir); it != fs::directory_iterator(); it++) {
const std::string path = fs::PathToString(it->path().filename());
if (fs::is_regular_file(*it) &&
path.length() == 12 &&
@@ -466,7 +462,7 @@ CBlockFileInfo* BlockManager::GetBlockFileInfo(size_t n)
return &m_blockfile_info.at(n);
}
-static bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock, const CMessageHeader::MessageStartChars& messageStart)
+bool BlockManager::UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock, const CMessageHeader::MessageStartChars& messageStart) const
{
// Open history file to append
AutoFile fileout{OpenUndoFile(pos)};
@@ -495,9 +491,9 @@ static bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const
return true;
}
-bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex)
+bool BlockManager::UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex& index) const
{
- const FlatFilePos pos{WITH_LOCK(::cs_main, return pindex->GetUndoPos())};
+ const FlatFilePos pos{WITH_LOCK(::cs_main, return index.GetUndoPos())};
if (pos.IsNull()) {
return error("%s: no undo data available", __func__);
@@ -513,7 +509,7 @@ bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex)
uint256 hashChecksum;
HashVerifier verifier{filein}; // Use HashVerifier as reserializing may lose data, c.f. commit d342424301013ec47dc146a4beb49d5c9319d80a
try {
- verifier << pindex->pprev->GetBlockHash();
+ verifier << index.pprev->GetBlockHash();
verifier >> blockundo;
filein >> hashChecksum;
} catch (const std::exception& e) {
@@ -569,7 +565,7 @@ uint64_t BlockManager::CalculateCurrentUsage()
return retval;
}
-void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune)
+void BlockManager::UnlinkPrunedFiles(const std::set<int>& setFilesToPrune) const
{
std::error_code ec;
for (std::set<int>::iterator it = setFilesToPrune.begin(); it != setFilesToPrune.end(); ++it) {
@@ -577,33 +573,33 @@ void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune)
const bool removed_blockfile{fs::remove(BlockFileSeq().FileName(pos), ec)};
const bool removed_undofile{fs::remove(UndoFileSeq().FileName(pos), ec)};
if (removed_blockfile || removed_undofile) {
- LogPrint(BCLog::BLOCKSTORE, "Prune: %s deleted blk/rev (%05u)\n", __func__, *it);
+ LogPrint(BCLog::BLOCKSTORAGE, "Prune: %s deleted blk/rev (%05u)\n", __func__, *it);
}
}
}
-static FlatFileSeq BlockFileSeq()
+FlatFileSeq BlockManager::BlockFileSeq() const
{
- return FlatFileSeq(gArgs.GetBlocksDirPath(), "blk", gArgs.GetBoolArg("-fastprune", false) ? 0x4000 /* 16kb */ : BLOCKFILE_CHUNK_SIZE);
+ return FlatFileSeq(m_opts.blocks_dir, "blk", m_opts.fast_prune ? 0x4000 /* 16kb */ : BLOCKFILE_CHUNK_SIZE);
}
-static FlatFileSeq UndoFileSeq()
+FlatFileSeq BlockManager::UndoFileSeq() const
{
- return FlatFileSeq(gArgs.GetBlocksDirPath(), "rev", UNDOFILE_CHUNK_SIZE);
+ return FlatFileSeq(m_opts.blocks_dir, "rev", UNDOFILE_CHUNK_SIZE);
}
-FILE* OpenBlockFile(const FlatFilePos& pos, bool fReadOnly)
+FILE* BlockManager::OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const
{
return BlockFileSeq().Open(pos, fReadOnly);
}
/** Open an undo file (rev?????.dat) */
-static FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly)
+FILE* BlockManager::OpenUndoFile(const FlatFilePos& pos, bool fReadOnly) const
{
return UndoFileSeq().Open(pos, fReadOnly);
}
-fs::path GetBlockPosFilename(const FlatFilePos& pos)
+fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const
{
return BlockFileSeq().FileName(pos);
}
@@ -619,7 +615,18 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
bool finalize_undo = false;
if (!fKnown) {
- while (m_blockfile_info[nFile].nSize + nAddSize >= (gArgs.GetBoolArg("-fastprune", false) ? 0x10000 /* 64kb */ : MAX_BLOCKFILE_SIZE)) {
+ unsigned int max_blockfile_size{MAX_BLOCKFILE_SIZE};
+ // Use smaller blockfiles in test-only -fastprune mode - but avoid
+ // the possibility of having a block not fit into the block file.
+ if (m_opts.fast_prune) {
+ max_blockfile_size = 0x10000; // 64kiB
+ if (nAddSize >= max_blockfile_size) {
+ // dynamically adjust the blockfile size to be larger than the added size
+ max_blockfile_size = nAddSize + 1;
+ }
+ }
+ assert(nAddSize < max_blockfile_size);
+ while (m_blockfile_info[nFile].nSize + nAddSize >= max_blockfile_size) {
// when the undo file is keeping up with the block file, we want to flush it explicitly
// when it is lagging behind (more blocks arrive than are being connected), we let the
// undo block write case handle it
@@ -635,7 +642,7 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
if ((int)nFile != m_last_blockfile) {
if (!fKnown) {
- LogPrint(BCLog::BLOCKSTORE, "Leaving block file %i: %s\n", m_last_blockfile, m_blockfile_info[m_last_blockfile].ToString());
+ LogPrint(BCLog::BLOCKSTORAGE, "Leaving block file %i: %s\n", m_last_blockfile, m_blockfile_info[m_last_blockfile].ToString());
}
FlushBlockFile(!fKnown, finalize_undo);
m_last_blockfile = nFile;
@@ -685,7 +692,7 @@ bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFileP
return true;
}
-static bool WriteBlockToDisk(const CBlock& block, FlatFilePos& pos, const CMessageHeader::MessageStartChars& messageStart)
+bool BlockManager::WriteBlockToDisk(const CBlock& block, FlatFilePos& pos, const CMessageHeader::MessageStartChars& messageStart) const
{
// Open history file to append
CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION);
@@ -708,16 +715,16 @@ static bool WriteBlockToDisk(const CBlock& block, FlatFilePos& pos, const CMessa
return true;
}
-bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams)
+bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
{
AssertLockHeld(::cs_main);
// Write undo information to disk
- if (pindex->GetUndoPos().IsNull()) {
+ if (block.GetUndoPos().IsNull()) {
FlatFilePos _pos;
- if (!FindUndoPos(state, pindex->nFile, _pos, ::GetSerializeSize(blockundo, CLIENT_VERSION) + 40)) {
+ if (!FindUndoPos(state, block.nFile, _pos, ::GetSerializeSize(blockundo, CLIENT_VERSION) + 40)) {
return error("ConnectBlock(): FindUndoPos failed");
}
- if (!UndoWriteToDisk(blockundo, _pos, pindex->pprev->GetBlockHash(), chainparams.MessageStart())) {
+ if (!UndoWriteToDisk(blockundo, _pos, block.pprev->GetBlockHash(), GetParams().MessageStart())) {
return AbortNode(state, "Failed to write undo data");
}
// rev files are written in block height order, whereas blk files are written as blocks come in (often out of order)
@@ -725,20 +732,20 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
// in the block file info as below; note that this does not catch the case where the undo writes are keeping up
// with the block writes (usually when a synced up node is getting newly mined blocks) -- this case is caught in
// the FindBlockPos function
- if (_pos.nFile < m_last_blockfile && static_cast<uint32_t>(pindex->nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) {
+ if (_pos.nFile < m_last_blockfile && static_cast<uint32_t>(block.nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) {
FlushUndoFile(_pos.nFile, true);
}
// update nUndoPos in block index
- pindex->nUndoPos = _pos.nPos;
- pindex->nStatus |= BLOCK_HAVE_UNDO;
- m_dirty_blockindex.insert(pindex);
+ block.nUndoPos = _pos.nPos;
+ block.nStatus |= BLOCK_HAVE_UNDO;
+ m_dirty_blockindex.insert(&block);
}
return true;
}
-bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams)
+bool BlockManager::ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos) const
{
block.SetNull();
@@ -756,33 +763,33 @@ bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::P
}
// Check the header
- if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) {
+ if (!CheckProofOfWork(block.GetHash(), block.nBits, GetConsensus())) {
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
}
// Signet only: check block solution
- if (consensusParams.signet_blocks && !CheckSignetBlockSolution(block, consensusParams)) {
+ if (GetConsensus().signet_blocks && !CheckSignetBlockSolution(block, GetConsensus())) {
return error("ReadBlockFromDisk: Errors in block solution at %s", pos.ToString());
}
return true;
}
-bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
+bool BlockManager::ReadBlockFromDisk(CBlock& block, const CBlockIndex& index) const
{
- const FlatFilePos block_pos{WITH_LOCK(cs_main, return pindex->GetBlockPos())};
+ const FlatFilePos block_pos{WITH_LOCK(cs_main, return index.GetBlockPos())};
- if (!ReadBlockFromDisk(block, block_pos, consensusParams)) {
+ if (!ReadBlockFromDisk(block, block_pos)) {
return false;
}
- if (block.GetHash() != pindex->GetBlockHash()) {
+ if (block.GetHash() != index.GetBlockHash()) {
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
- pindex->ToString(), block_pos.ToString());
+ index.ToString(), block_pos.ToString());
}
return true;
}
-bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start)
+bool BlockManager::ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start) const
{
FlatFilePos hpos = pos;
hpos.nPos -= 8; // Seek back 8 bytes for meta header
@@ -817,7 +824,7 @@ bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, c
return true;
}
-FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp)
+FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const FlatFilePos* dbp)
{
unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION);
FlatFilePos blockPos;
@@ -835,7 +842,7 @@ FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CCha
return FlatFilePos();
}
if (!position_known) {
- if (!WriteBlockToDisk(block, blockPos, chainparams.MessageStart())) {
+ if (!WriteBlockToDisk(block, blockPos, GetParams().MessageStart())) {
AbortNode("Failed to write block");
return FlatFilePos();
}
@@ -860,7 +867,7 @@ public:
}
};
-void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args, const fs::path& mempool_path)
+void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const fs::path& mempool_path)
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION_LOAD_BLOCKS);
ScheduleBatchPriority();
@@ -876,10 +883,10 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
while (true) {
FlatFilePos pos(nFile, 0);
- if (!fs::exists(GetBlockPosFilename(pos))) {
+ if (!fs::exists(chainman.m_blockman.GetBlockPosFilename(pos))) {
break; // No block files left to reindex
}
- FILE* file = OpenBlockFile(pos, true);
+ FILE* file = chainman.m_blockman.OpenBlockFile(pos, true);
if (!file) {
break; // This error is logged in OpenBlockFile
}
@@ -921,18 +928,18 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
- LogPrintf("Failed to connect best block (%s)\n", state.ToString());
- StartShutdown();
+ AbortNode(strprintf("Failed to connect best block (%s)", state.ToString()));
return;
}
}
- if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
+ if (chainman.m_blockman.StopAfterBlockImport()) {
LogPrintf("Stopping after block import\n");
StartShutdown();
return;
}
} // End scope of ImportingNow
chainman.ActiveChainstate().LoadMempool(mempool_path);
+ g_indexes_ready_to_sync = true;
}
} // namespace node
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index 5ba0045b8b..a1ebb0df0a 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -7,19 +7,19 @@
#include <attributes.h>
#include <chain.h>
-#include <fs.h>
#include <kernel/blockmanager_opts.h>
+#include <kernel/chainparams.h>
#include <kernel/cs_main.h>
#include <protocol.h>
#include <sync.h>
#include <txdb.h>
+#include <util/fs.h>
#include <atomic>
#include <cstdint>
#include <unordered_map>
#include <vector>
-class ArgsManager;
class BlockValidationState;
class CBlock;
class CBlockFileInfo;
@@ -35,7 +35,6 @@ struct Params;
}
namespace node {
-static constexpr bool DEFAULT_STOPAFTERBLOCKIMPORT{false};
/** The pre-allocation chunk size for blk?????.dat files (since 0.8) */
static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB
@@ -48,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
@@ -81,18 +81,28 @@ class BlockManager
friend ChainstateManager;
private:
+ const CChainParams& GetParams() const { return m_opts.chainparams; }
+ const Consensus::Params& GetConsensus() const { return m_opts.chainparams.GetConsensus(); }
/**
* Load the blocktree off disk and into memory. Populate certain metadata
* per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral
* collections like m_dirty_blockindex.
*/
- bool LoadBlockIndex(const Consensus::Params& consensus_params)
+ bool LoadBlockIndex()
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false);
void FlushUndoFile(int block_file, bool finalize = false);
bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown);
bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize);
+ FlatFileSeq BlockFileSeq() const;
+ FlatFileSeq UndoFileSeq() const;
+
+ FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const;
+
+ bool WriteBlockToDisk(const CBlock& block, FlatFilePos& pos, const CMessageHeader::MessageStartChars& messageStart) const;
+ bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock, const CMessageHeader::MessageStartChars& messageStart) const;
+
/* Calculate the block/rev files to delete based on height specified by user with RPC command pruneblockchain */
void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight, int chain_tip_height);
@@ -162,7 +172,7 @@ public:
std::unique_ptr<CBlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main);
bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
- bool LoadBlockIndexDB(const Consensus::Params& consensus_params) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* Remove any pruned block & undo files that are still on disk.
@@ -184,11 +194,11 @@ public:
/** Get block file info entry for one block file */
CBlockFileInfo* GetBlockFileInfo(size_t n);
- bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams)
+ bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Store block on disk. If dbp is not nullptr, then it provides the known position of the block within a block file on disk. */
- FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp);
+ FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const FlatFilePos* dbp);
/** Whether running in -prune mode. */
[[nodiscard]] bool IsPruneMode() const { return m_prune_mode; }
@@ -199,6 +209,8 @@ public:
[[nodiscard]] bool LoadingBlocks() const { return m_importing || fReindex; }
+ [[nodiscard]] bool StopAfterBlockImport() const { return m_opts.stop_after_block_import; }
+
/** Calculate the amount of disk space the block & undo files currently use */
uint64_t CalculateCurrentUsage();
@@ -216,28 +228,29 @@ public:
//! Create or update a prune lock identified by its name
void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
-};
-void CleanupBlockRevFiles();
+ /** Open a block file (blk?????.dat) */
+ FILE* OpenBlockFile(const FlatFilePos& pos, bool fReadOnly = false) const;
-/** Open a block file (blk?????.dat) */
-FILE* OpenBlockFile(const FlatFilePos& pos, bool fReadOnly = false);
-/** Translation to a filesystem path */
-fs::path GetBlockPosFilename(const FlatFilePos& pos);
+ /** Translation to a filesystem path */
+ fs::path GetBlockPosFilename(const FlatFilePos& pos) const;
-/**
- * Actually unlink the specified files
- */
-void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune);
+ /**
+ * Actually unlink the specified files
+ */
+ void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune) const;
-/** Functions for disk access for blocks */
-bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams);
-bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams);
-bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start);
+ /** Functions for disk access for blocks */
+ bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos) const;
+ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex& index) const;
+ bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start) const;
-bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex);
+ bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex& index) const;
+
+ void CleanupBlockRevFiles() const;
+};
-void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args, const fs::path& mempool_path);
+void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const fs::path& mempool_path);
} // namespace node
#endif // BITCOIN_NODE_BLOCKSTORAGE_H
diff --git a/src/node/caches.cpp b/src/node/caches.cpp
index 7622a03e19..7403f7ddea 100644
--- a/src/node/caches.cpp
+++ b/src/node/caches.cpp
@@ -4,9 +4,9 @@
#include <node/caches.h>
+#include <common/args.h>
#include <index/txindex.h>
#include <txdb.h>
-#include <util/system.h>
namespace node {
CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes)
diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp
index cfd3472592..3900d2e620 100644
--- a/src/node/chainstate.cpp
+++ b/src/node/chainstate.cpp
@@ -16,6 +16,7 @@
#include <tinyformat.h>
#include <txdb.h>
#include <uint256.h>
+#include <util/fs.h>
#include <util/time.h>
#include <util/translation.h>
#include <validation.h>
@@ -50,7 +51,7 @@ static ChainstateLoadResult CompleteChainstateInitialization(
pblocktree->WriteReindexing(true);
//If we're reindexing in prune mode, wipe away unusable block files and all undo data files
if (options.prune) {
- CleanupBlockRevFiles();
+ chainman.m_blockman.CleanupBlockRevFiles();
}
}
@@ -206,7 +207,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
} else if (snapshot_completion == SnapshotCompletionResult::SUCCESS) {
LogPrintf("[snapshot] cleaning up unneeded background chainstate, then reinitializing\n");
if (!chainman.ValidatedSnapshotCleanup()) {
- AbortNode("Background chainstate cleanup failed unexpectedly.");
+ return {ChainstateLoadStatus::FAILURE_FATAL, Untranslated("Background chainstate cleanup failed unexpectedly.")};
}
// Because ValidatedSnapshotCleanup() has torn down chainstates with
@@ -252,7 +253,7 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C
"Only rebuild the block database if you are sure that your computer's date and time are correct")};
}
- VerifyDBResult result = CVerifyDB().VerifyDB(
+ VerifyDBResult result = CVerifyDB(chainman.GetNotifications()).VerifyDB(
*chainstate, chainman.GetConsensus(), chainstate->CoinsDB(),
options.check_level,
options.check_blocks);
diff --git a/src/node/chainstate.h b/src/node/chainstate.h
index 77240cafe9..2e35035c28 100644
--- a/src/node/chainstate.h
+++ b/src/node/chainstate.h
@@ -42,7 +42,8 @@ struct ChainstateLoadOptions {
//! and exit cleanly in the interrupted case.
enum class ChainstateLoadStatus {
SUCCESS,
- FAILURE,
+ FAILURE, //!< Generic failure which reindexing may fix
+ FAILURE_FATAL, //!< Fatal error which should not prompt to reindex
FAILURE_INCOMPATIBLE_DB,
FAILURE_INSUFFICIENT_DBCACHE,
INTERRUPTED,
diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp
index 9801e6e959..a7f7303348 100644
--- a/src/node/chainstatemanager_args.cpp
+++ b/src/node/chainstatemanager_args.cpp
@@ -5,22 +5,22 @@
#include <node/chainstatemanager_args.h>
#include <arith_uint256.h>
+#include <common/args.h>
#include <kernel/chainstatemanager_opts.h>
#include <node/coins_view_args.h>
#include <node/database_args.h>
#include <tinyformat.h>
#include <uint256.h>
+#include <util/result.h>
#include <util/strencodings.h>
-#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <chrono>
-#include <optional>
#include <string>
namespace node {
-std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts)
+util::Result<void> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts)
{
if (auto value{args.GetBoolArg("-checkblockindex")}) opts.check_block_index = *value;
@@ -28,7 +28,7 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, Chains
if (auto value{args.GetArg("-minimumchainwork")}) {
if (!IsHexNumber(*value)) {
- return strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value);
+ return util::Error{strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value)};
}
opts.minimum_chain_work = UintToArith256(uint256S(*value));
}
@@ -37,10 +37,12 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, Chains
if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value};
+ if (auto value{args.GetIntArg("-stopatheight")}) opts.stop_at_height = *value;
+
ReadDatabaseArgs(args, opts.block_tree_db);
ReadDatabaseArgs(args, opts.coins_db);
ReadCoinsViewArgs(args, opts.coins_view);
- return std::nullopt;
+ return {};
}
} // namespace node
diff --git a/src/node/chainstatemanager_args.h b/src/node/chainstatemanager_args.h
index 6c46b998f2..701515953e 100644
--- a/src/node/chainstatemanager_args.h
+++ b/src/node/chainstatemanager_args.h
@@ -5,15 +5,13 @@
#ifndef BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H
#define BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H
+#include <util/result.h>
#include <validation.h>
-#include <optional>
-
class ArgsManager;
-struct bilingual_str;
namespace node {
-std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts);
+[[nodiscard]] util::Result<void> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts);
} // namespace node
#endif // BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H
diff --git a/src/node/coins_view_args.cpp b/src/node/coins_view_args.cpp
index 67c9b8dbac..5d55143e83 100644
--- a/src/node/coins_view_args.cpp
+++ b/src/node/coins_view_args.cpp
@@ -4,8 +4,8 @@
#include <node/coins_view_args.h>
+#include <common/args.h>
#include <txdb.h>
-#include <util/system.h>
namespace node {
void ReadCoinsViewArgs(const ArgsManager& args, CoinsViewOptions& options)
diff --git a/src/node/context.cpp b/src/node/context.cpp
index af59ab932b..ca56fa0b86 100644
--- a/src/node/context.cpp
+++ b/src/node/context.cpp
@@ -11,6 +11,7 @@
#include <net.h>
#include <net_processing.h>
#include <netgroup.h>
+#include <node/kernel_notifications.h>
#include <policy/fees.h>
#include <scheduler.h>
#include <txmempool.h>
diff --git a/src/node/context.h b/src/node/context.h
index 84f4053c84..91b68fa5bb 100644
--- a/src/node/context.h
+++ b/src/node/context.h
@@ -7,7 +7,9 @@
#include <kernel/context.h>
+#include <atomic>
#include <cassert>
+#include <cstdlib>
#include <functional>
#include <memory>
#include <vector>
@@ -30,6 +32,8 @@ class WalletLoader;
} // namespace interfaces
namespace node {
+class KernelNotifications;
+
//! NodeContext struct containing references to chain state and connection
//! state.
//!
@@ -62,6 +66,8 @@ struct NodeContext {
interfaces::WalletLoader* wallet_loader{nullptr};
std::unique_ptr<CScheduler> scheduler;
std::function<void()> rpc_interruption_point = [] {};
+ std::unique_ptr<KernelNotifications> notifications;
+ std::atomic<int> exit_status{EXIT_SUCCESS};
//! Declare default constructor and destructor that are not inline, so code
//! instantiating the NodeContext struct doesn't need to #include class
diff --git a/src/node/database_args.cpp b/src/node/database_args.cpp
index 2c53b4b47e..aba3c38ff3 100644
--- a/src/node/database_args.cpp
+++ b/src/node/database_args.cpp
@@ -4,8 +4,8 @@
#include <node/database_args.h>
+#include <common/args.h>
#include <dbwrapper.h>
-#include <util/system.h>
namespace node {
void ReadDatabaseArgs(const ArgsManager& args, DBOptions& options)
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index b397661df4..94b607b1de 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -7,6 +7,7 @@
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
+#include <common/args.h>
#include <deploymentstatus.h>
#include <external_signer.h>
#include <index/blockfilterindex.h>
@@ -43,7 +44,6 @@
#include <uint256.h>
#include <univalue.h>
#include <util/check.h>
-#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
@@ -89,10 +89,11 @@ public:
void initLogging() override { InitLogging(args()); }
void initParameterInteraction() override { InitParameterInteraction(args()); }
bilingual_str getWarnings() override { return GetWarnings(true); }
+ int getExitStatus() override { return Assert(m_context)->exit_status.load(); }
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
bool baseInitialize() override
{
- if (!AppInitBasicSetup(args())) return false;
+ if (!AppInitBasicSetup(args(), Assert(context())->exit_status)) return false;
if (!AppInitParameterInteraction(args(), /*use_syscall_sandbox=*/false)) return false;
m_context->kernel = std::make_unique<kernel::Context>();
@@ -105,7 +106,10 @@ public:
}
bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override
{
- return AppInitMain(*m_context, tip_info);
+ if (AppInitMain(*m_context, tip_info)) return true;
+ // Error during initialization, set exit status before continue
+ m_context->exit_status.store(EXIT_FAILURE);
+ return false;
}
void appShutdown() override
{
@@ -125,17 +129,17 @@ public:
bool isSettingIgnored(const std::string& name) override
{
bool ignored = false;
- args().LockSettings([&](util::Settings& settings) {
- if (auto* options = util::FindKey(settings.command_line_options, name)) {
+ args().LockSettings([&](common::Settings& settings) {
+ if (auto* options = common::FindKey(settings.command_line_options, name)) {
ignored = !options->empty();
}
});
return ignored;
}
- util::SettingsValue getPersistentSetting(const std::string& name) override { return args().GetPersistentSetting(name); }
- void updateRwSetting(const std::string& name, const util::SettingsValue& value) override
+ common::SettingsValue getPersistentSetting(const std::string& name) override { return args().GetPersistentSetting(name); }
+ void updateRwSetting(const std::string& name, const common::SettingsValue& value) override
{
- args().LockSettings([&](util::Settings& settings) {
+ args().LockSettings([&](common::Settings& settings) {
if (value.isNull()) {
settings.rw_settings.erase(name);
} else {
@@ -144,9 +148,9 @@ public:
});
args().WriteSettingsFile();
}
- void forceSetting(const std::string& name, const util::SettingsValue& value) override
+ void forceSetting(const std::string& name, const common::SettingsValue& value) override
{
- args().LockSettings([&](util::Settings& settings) {
+ args().LockSettings([&](common::Settings& settings) {
if (value.isNull()) {
settings.forced_settings.erase(name);
} else {
@@ -157,7 +161,7 @@ public:
void resetSettings() override
{
args().WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true);
- args().LockSettings([&](util::Settings& settings) {
+ args().LockSettings([&](common::Settings& settings) {
settings.rw_settings.clear();
});
args().WriteSettingsFile();
@@ -239,8 +243,9 @@ public:
std::vector<ExternalSigner> signers = {};
const std::string command = args().GetArg("-signer", "");
if (command == "") return {};
- ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
+ ExternalSigner::Enumerate(command, signers, Params().GetChainTypeString());
std::vector<std::unique_ptr<interfaces::ExternalSigner>> result;
+ result.reserve(signers.size());
for (auto& signer : signers) {
result.emplace_back(std::make_unique<ExternalSignerImpl>(std::move(signer)));
}
@@ -393,7 +398,7 @@ public:
NodeContext* m_context{nullptr};
};
-bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active)
+bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active, const BlockManager& blockman)
{
if (!index) return false;
if (block.m_hash) *block.m_hash = index->GetBlockHash();
@@ -403,10 +408,10 @@ bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<Rec
if (block.m_mtp_time) *block.m_mtp_time = index->GetMedianTimePast();
if (block.m_in_active_chain) *block.m_in_active_chain = active[index->nHeight] == index;
if (block.m_locator) { *block.m_locator = GetLocator(index); }
- if (block.m_next_block) FillBlock(active[index->nHeight] == index ? active[index->nHeight + 1] : nullptr, *block.m_next_block, lock, active);
+ if (block.m_next_block) FillBlock(active[index->nHeight] == index ? active[index->nHeight + 1] : nullptr, *block.m_next_block, lock, active, blockman);
if (block.m_data) {
REVERSE_LOCK(lock);
- if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) block.m_data->SetNull();
+ if (!blockman.ReadBlockFromDisk(*block.m_data, *index)) block.m_data->SetNull();
}
block.found = true;
return true;
@@ -556,13 +561,13 @@ public:
bool findBlock(const uint256& hash, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
- return FillBlock(chainman().m_blockman.LookupBlockIndex(hash), block, lock, chainman().ActiveChain());
+ return FillBlock(chainman().m_blockman.LookupBlockIndex(hash), block, lock, chainman().ActiveChain(), chainman().m_blockman);
}
bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
const CChain& active = chainman().ActiveChain();
- return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active);
+ return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active, chainman().m_blockman);
}
bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out) override
{
@@ -570,10 +575,10 @@ public:
const CChain& active = chainman().ActiveChain();
if (const CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) {
if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) {
- return FillBlock(ancestor, ancestor_out, lock, active);
+ return FillBlock(ancestor, ancestor_out, lock, active, chainman().m_blockman);
}
}
- return FillBlock(nullptr, ancestor_out, lock, active);
+ return FillBlock(nullptr, ancestor_out, lock, active, chainman().m_blockman);
}
bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, const FoundBlock& ancestor_out) override
{
@@ -581,7 +586,7 @@ public:
const CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash);
const CBlockIndex* ancestor = chainman().m_blockman.LookupBlockIndex(ancestor_hash);
if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr;
- return FillBlock(ancestor, ancestor_out, lock, chainman().ActiveChain());
+ return FillBlock(ancestor, ancestor_out, lock, chainman().ActiveChain(), chainman().m_blockman);
}
bool findCommonAncestor(const uint256& block_hash1, const uint256& block_hash2, const FoundBlock& ancestor_out, const FoundBlock& block1_out, const FoundBlock& block2_out) override
{
@@ -593,9 +598,9 @@ public:
// Using & instead of && below to avoid short circuiting and leaving
// output uninitialized. Cast bool to int to avoid -Wbitwise-instead-of-logical
// compiler warnings.
- return int{FillBlock(ancestor, ancestor_out, lock, active)} &
- int{FillBlock(block1, block1_out, lock, active)} &
- int{FillBlock(block2, block2_out, lock, active)};
+ return int{FillBlock(ancestor, ancestor_out, lock, active, chainman().m_blockman)} &
+ int{FillBlock(block1, block1_out, lock, active, chainman().m_blockman)} &
+ int{FillBlock(block2, block2_out, lock, active, chainman().m_blockman)};
}
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
double guessVerificationProgress(const uint256& block_hash) override
@@ -743,27 +748,27 @@ public:
RPCRunLater(name, std::move(fn), seconds);
}
int rpcSerializationFlags() override { return RPCSerializationFlags(); }
- util::SettingsValue getSetting(const std::string& name) override
+ common::SettingsValue getSetting(const std::string& name) override
{
return args().GetSetting(name);
}
- std::vector<util::SettingsValue> getSettingsList(const std::string& name) override
+ std::vector<common::SettingsValue> getSettingsList(const std::string& name) override
{
return args().GetSettingsList(name);
}
- util::SettingsValue getRwSetting(const std::string& name) override
+ common::SettingsValue getRwSetting(const std::string& name) override
{
- util::SettingsValue result;
- args().LockSettings([&](const util::Settings& settings) {
- if (const util::SettingsValue* value = util::FindKey(settings.rw_settings, name)) {
+ common::SettingsValue result;
+ args().LockSettings([&](const common::Settings& settings) {
+ if (const common::SettingsValue* value = common::FindKey(settings.rw_settings, name)) {
result = *value;
}
});
return result;
}
- bool updateRwSetting(const std::string& name, const util::SettingsValue& value, bool write) override
+ bool updateRwSetting(const std::string& name, const common::SettingsValue& value, bool write) override
{
- args().LockSettings([&](util::Settings& settings) {
+ args().LockSettings([&](common::Settings& settings) {
if (value.isNull()) {
settings.rw_settings.erase(name);
} else {
diff --git a/src/node/kernel_notifications.cpp b/src/node/kernel_notifications.cpp
new file mode 100644
index 0000000000..926b157f3b
--- /dev/null
+++ b/src/node/kernel_notifications.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <node/kernel_notifications.h>
+
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
+#include <common/args.h>
+#include <common/system.h>
+#include <node/interface_ui.h>
+#include <util/strencodings.h>
+#include <util/string.h>
+#include <util/translation.h>
+#include <warnings.h>
+
+#include <cstdint>
+#include <string>
+#include <thread>
+
+static void AlertNotify(const std::string& strMessage)
+{
+ uiInterface.NotifyAlertChanged();
+#if HAVE_SYSTEM
+ std::string strCmd = gArgs.GetArg("-alertnotify", "");
+ if (strCmd.empty()) return;
+
+ // Alert text should be plain ascii coming from a trusted source, but to
+ // be safe we first strip anything not in safeChars, then add single quotes around
+ // the whole string before passing it to the shell:
+ std::string singleQuote("'");
+ std::string safeStatus = SanitizeString(strMessage);
+ safeStatus = singleQuote+safeStatus+singleQuote;
+ ReplaceAll(strCmd, "%s", safeStatus);
+
+ std::thread t(runCommand, strCmd);
+ t.detach(); // thread runs free
+#endif
+}
+
+static void DoWarning(const bilingual_str& warning)
+{
+ static bool fWarned = false;
+ SetMiscWarning(warning);
+ if (!fWarned) {
+ AlertNotify(warning.original);
+ fWarned = true;
+ }
+}
+
+namespace node {
+
+void KernelNotifications::blockTip(SynchronizationState state, CBlockIndex& index)
+{
+ uiInterface.NotifyBlockTip(state, &index);
+}
+
+void KernelNotifications::headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync)
+{
+ uiInterface.NotifyHeaderTip(state, height, timestamp, presync);
+}
+
+void KernelNotifications::progress(const bilingual_str& title, int progress_percent, bool resume_possible)
+{
+ uiInterface.ShowProgress(title.translated, progress_percent, resume_possible);
+}
+
+void KernelNotifications::warning(const bilingual_str& warning)
+{
+ DoWarning(warning);
+}
+
+} // namespace node
diff --git a/src/node/kernel_notifications.h b/src/node/kernel_notifications.h
new file mode 100644
index 0000000000..3e665bbf14
--- /dev/null
+++ b/src/node/kernel_notifications.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_NODE_KERNEL_NOTIFICATIONS_H
+#define BITCOIN_NODE_KERNEL_NOTIFICATIONS_H
+
+#include <kernel/notifications_interface.h>
+
+#include <cstdint>
+#include <string>
+
+class CBlockIndex;
+enum class SynchronizationState;
+struct bilingual_str;
+
+namespace node {
+class KernelNotifications : public kernel::Notifications
+{
+public:
+ void blockTip(SynchronizationState state, CBlockIndex& index) override;
+
+ void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) override;
+
+ void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override;
+
+ void warning(const bilingual_str& warning) override;
+};
+} // namespace node
+
+#endif // BITCOIN_NODE_KERNEL_NOTIFICATIONS_H
diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp
index a0a2e43107..5381902263 100644
--- a/src/node/mempool_args.cpp
+++ b/src/node/mempool_args.cpp
@@ -7,6 +7,7 @@
#include <kernel/mempool_limits.h>
#include <kernel/mempool_options.h>
+#include <common/args.h>
#include <consensus/amount.h>
#include <kernel/chainparams.h>
#include <logging.h>
@@ -16,7 +17,6 @@
#include <tinyformat.h>
#include <util/error.h>
#include <util/moneystr.h>
-#include <util/system.h>
#include <util/translation.h>
#include <chrono>
@@ -38,7 +38,7 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limi
}
}
-std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts)
+util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts)
{
mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio);
@@ -52,7 +52,7 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, con
if (std::optional<CAmount> inc_relay_fee = ParseMoney(argsman.GetArg("-incrementalrelayfee", ""))) {
mempool_opts.incremental_relay_feerate = CFeeRate{inc_relay_fee.value()};
} else {
- return AmountErrMsg("incrementalrelayfee", argsman.GetArg("-incrementalrelayfee", ""));
+ return util::Error{AmountErrMsg("incrementalrelayfee", argsman.GetArg("-incrementalrelayfee", ""))};
}
}
@@ -61,7 +61,7 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, con
// High fee check is done afterward in CWallet::Create()
mempool_opts.min_relay_feerate = CFeeRate{min_relay_feerate.value()};
} else {
- return AmountErrMsg("minrelaytxfee", argsman.GetArg("-minrelaytxfee", ""));
+ return util::Error{AmountErrMsg("minrelaytxfee", argsman.GetArg("-minrelaytxfee", ""))};
}
} else if (mempool_opts.incremental_relay_feerate > mempool_opts.min_relay_feerate) {
// Allow only setting incremental fee to control both
@@ -75,7 +75,7 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, con
if (std::optional<CAmount> parsed = ParseMoney(argsman.GetArg("-dustrelayfee", ""))) {
mempool_opts.dust_relay_feerate = CFeeRate{parsed.value()};
} else {
- return AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""));
+ return util::Error{AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""))};
}
}
@@ -89,12 +89,12 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, con
mempool_opts.require_standard = !argsman.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
if (!chainparams.IsTestChain() && !mempool_opts.require_standard) {
- return strprintf(Untranslated("acceptnonstdtxn is not currently supported for %s chain"), chainparams.NetworkIDString());
+ return util::Error{strprintf(Untranslated("acceptnonstdtxn is not currently supported for %s chain"), chainparams.GetChainTypeString())};
}
mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf);
ApplyArgsManOptions(argsman, mempool_opts.limits);
- return std::nullopt;
+ return {};
}
diff --git a/src/node/mempool_args.h b/src/node/mempool_args.h
index 52d8b4f265..630fee6421 100644
--- a/src/node/mempool_args.h
+++ b/src/node/mempool_args.h
@@ -5,7 +5,7 @@
#ifndef BITCOIN_NODE_MEMPOOL_ARGS_H
#define BITCOIN_NODE_MEMPOOL_ARGS_H
-#include <optional>
+#include <util/result.h>
class ArgsManager;
class CChainParams;
@@ -21,7 +21,7 @@ struct MemPoolOptions;
* @param[in] argsman The ArgsManager in which to check set options.
* @param[in,out] mempool_opts The MemPoolOptions to modify according to \p argsman.
*/
-[[nodiscard]] std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, kernel::MemPoolOptions& mempool_opts);
+[[nodiscard]] util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, kernel::MemPoolOptions& mempool_opts);
#endif // BITCOIN_NODE_MEMPOOL_ARGS_H
diff --git a/src/node/mempool_persist_args.cpp b/src/node/mempool_persist_args.cpp
index 4e775869c6..97ecdd651b 100644
--- a/src/node/mempool_persist_args.cpp
+++ b/src/node/mempool_persist_args.cpp
@@ -4,8 +4,8 @@
#include <node/mempool_persist_args.h>
-#include <fs.h>
-#include <util/system.h>
+#include <common/args.h>
+#include <util/fs.h>
#include <validation.h>
namespace node {
diff --git a/src/node/mempool_persist_args.h b/src/node/mempool_persist_args.h
index f719ec62ab..3b101f0930 100644
--- a/src/node/mempool_persist_args.h
+++ b/src/node/mempool_persist_args.h
@@ -5,7 +5,7 @@
#ifndef BITCOIN_NODE_MEMPOOL_PERSIST_ARGS_H
#define BITCOIN_NODE_MEMPOOL_PERSIST_ARGS_H
-#include <fs.h>
+#include <util/fs.h>
class ArgsManager;
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index c7bc9a9a3d..aa1a9a155c 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -8,6 +8,7 @@
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
+#include <common/args.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <consensus/merkle.h>
@@ -20,7 +21,6 @@
#include <primitives/transaction.h>
#include <timedata.h>
#include <util/moneystr.h>
-#include <util/system.h>
#include <validation.h>
#include <algorithm>
diff --git a/src/node/miner.h b/src/node/miner.h
index f1ccffff55..70de9e1db0 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -14,7 +14,10 @@
#include <optional>
#include <stdint.h>
+#include <boost/multi_index/identity.hpp>
+#include <boost/multi_index/indexed_by.hpp>
#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/tag.hpp>
#include <boost/multi_index_container.hpp>
class ArgsManager;
diff --git a/src/node/mini_miner.cpp b/src/node/mini_miner.cpp
new file mode 100644
index 0000000000..6f253eddfa
--- /dev/null
+++ b/src/node/mini_miner.cpp
@@ -0,0 +1,371 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <node/mini_miner.h>
+
+#include <consensus/amount.h>
+#include <policy/feerate.h>
+#include <primitives/transaction.h>
+#include <timedata.h>
+#include <util/check.h>
+#include <util/moneystr.h>
+
+#include <algorithm>
+#include <numeric>
+#include <utility>
+
+namespace node {
+
+MiniMiner::MiniMiner(const CTxMemPool& mempool, const std::vector<COutPoint>& outpoints)
+{
+ LOCK(mempool.cs);
+ // Find which outpoints to calculate bump fees for.
+ // Anything that's spent by the mempool is to-be-replaced
+ // Anything otherwise unavailable just has a bump fee of 0
+ for (const auto& outpoint : outpoints) {
+ if (!mempool.exists(GenTxid::Txid(outpoint.hash))) {
+ // This UTXO is either confirmed or not yet submitted to mempool.
+ // If it's confirmed, no bump fee is required.
+ // If it's not yet submitted, we have no information, so return 0.
+ m_bump_fees.emplace(outpoint, 0);
+ continue;
+ }
+
+ // UXTO is created by transaction in mempool, add to map.
+ // Note: This will either create a missing entry or add the outpoint to an existing entry
+ m_requested_outpoints_by_txid[outpoint.hash].push_back(outpoint);
+
+ if (const auto ptx{mempool.GetConflictTx(outpoint)}) {
+ // This outpoint is already being spent by another transaction in the mempool. We
+ // assume that the caller wants to replace this transaction and its descendants. It
+ // would be unusual for the transaction to have descendants as the wallet won’t normally
+ // attempt to replace transactions with descendants. If the outpoint is from a mempool
+ // transaction, we still need to calculate its ancestors bump fees (added to
+ // m_requested_outpoints_by_txid below), but after removing the to-be-replaced entries.
+ //
+ // Note that the descendants of a transaction include the transaction itself. Also note,
+ // that this is only calculating bump fees. RBF fee rules should be handled separately.
+ CTxMemPool::setEntries descendants;
+ mempool.CalculateDescendants(mempool.GetIter(ptx->GetHash()).value(), descendants);
+ for (const auto& desc_txiter : descendants) {
+ m_to_be_replaced.insert(desc_txiter->GetTx().GetHash());
+ }
+ }
+ }
+
+ // No unconfirmed UTXOs, so nothing mempool-related needs to be calculated.
+ if (m_requested_outpoints_by_txid.empty()) return;
+
+ // Calculate the cluster and construct the entry map.
+ std::vector<uint256> txids_needed;
+ txids_needed.reserve(m_requested_outpoints_by_txid.size());
+ for (const auto& [txid, _]: m_requested_outpoints_by_txid) {
+ txids_needed.push_back(txid);
+ }
+ const auto cluster = mempool.GatherClusters(txids_needed);
+ if (cluster.empty()) {
+ // An empty cluster means that at least one of the transactions is missing from the mempool
+ // (should not be possible given processing above) or DoS limit was hit.
+ m_ready_to_calculate = false;
+ return;
+ }
+
+ // Add every entry to m_entries_by_txid and m_entries, except the ones that will be replaced.
+ for (const auto& txiter : cluster) {
+ if (!m_to_be_replaced.count(txiter->GetTx().GetHash())) {
+ auto [mapiter, success] = m_entries_by_txid.emplace(txiter->GetTx().GetHash(), MiniMinerMempoolEntry(txiter));
+ m_entries.push_back(mapiter);
+ } else {
+ auto outpoints_it = m_requested_outpoints_by_txid.find(txiter->GetTx().GetHash());
+ if (outpoints_it != m_requested_outpoints_by_txid.end()) {
+ // This UTXO is the output of a to-be-replaced transaction. Bump fee is 0; spending
+ // this UTXO is impossible as it will no longer exist after the replacement.
+ for (const auto& outpoint : outpoints_it->second) {
+ m_bump_fees.emplace(outpoint, 0);
+ }
+ m_requested_outpoints_by_txid.erase(outpoints_it);
+ }
+ }
+ }
+
+ // Build the m_descendant_set_by_txid cache.
+ for (const auto& txiter : cluster) {
+ const auto& txid = txiter->GetTx().GetHash();
+ // Cache descendants for future use. Unlike the real mempool, a descendant MiniMinerMempoolEntry
+ // will not exist without its ancestor MiniMinerMempoolEntry, so these sets won't be invalidated.
+ std::vector<MockEntryMap::iterator> cached_descendants;
+ const bool remove{m_to_be_replaced.count(txid) > 0};
+ CTxMemPool::setEntries descendants;
+ mempool.CalculateDescendants(txiter, descendants);
+ Assume(descendants.count(txiter) > 0);
+ for (const auto& desc_txiter : descendants) {
+ const auto txid_desc = desc_txiter->GetTx().GetHash();
+ const bool remove_desc{m_to_be_replaced.count(txid_desc) > 0};
+ auto desc_it{m_entries_by_txid.find(txid_desc)};
+ Assume((desc_it == m_entries_by_txid.end()) == remove_desc);
+ if (remove) Assume(remove_desc);
+ // It's possible that remove=false but remove_desc=true.
+ if (!remove && !remove_desc) {
+ cached_descendants.push_back(desc_it);
+ }
+ }
+ if (remove) {
+ Assume(cached_descendants.empty());
+ } else {
+ m_descendant_set_by_txid.emplace(txid, cached_descendants);
+ }
+ }
+
+ // Release the mempool lock; we now have all the information we need for a subset of the entries
+ // we care about. We will solely operate on the MiniMinerMempoolEntry map from now on.
+ Assume(m_in_block.empty());
+ Assume(m_requested_outpoints_by_txid.size() <= outpoints.size());
+ SanityCheck();
+}
+
+// Compare by min(ancestor feerate, individual feerate), then iterator
+//
+// Under the ancestor-based mining approach, high-feerate children can pay for parents, but high-feerate
+// parents do not incentive inclusion of their children. Therefore the mining algorithm only considers
+// transactions for inclusion on basis of the minimum of their own feerate or their ancestor feerate.
+struct AncestorFeerateComparator
+{
+ template<typename I>
+ bool operator()(const I& a, const I& b) const {
+ auto min_feerate = [](const MiniMinerMempoolEntry& e) -> CFeeRate {
+ const CAmount ancestor_fee{e.GetModFeesWithAncestors()};
+ const int64_t ancestor_size{e.GetSizeWithAncestors()};
+ const CAmount tx_fee{e.GetModifiedFee()};
+ const int64_t tx_size{e.GetTxSize()};
+ // Comparing ancestor feerate with individual feerate:
+ // ancestor_fee / ancestor_size <= tx_fee / tx_size
+ // Avoid division and possible loss of precision by
+ // multiplying both sides by the sizes:
+ return ancestor_fee * tx_size < tx_fee * ancestor_size ?
+ CFeeRate(ancestor_fee, ancestor_size) :
+ CFeeRate(tx_fee, tx_size);
+ };
+ CFeeRate a_feerate{min_feerate(a->second)};
+ CFeeRate b_feerate{min_feerate(b->second)};
+ if (a_feerate != b_feerate) {
+ return a_feerate > b_feerate;
+ }
+ // Use txid as tiebreaker for stable sorting
+ return a->first < b->first;
+ }
+};
+
+void MiniMiner::DeleteAncestorPackage(const std::set<MockEntryMap::iterator, IteratorComparator>& ancestors)
+{
+ Assume(ancestors.size() >= 1);
+ // "Mine" all transactions in this ancestor set.
+ for (auto& anc : ancestors) {
+ Assume(m_in_block.count(anc->first) == 0);
+ m_in_block.insert(anc->first);
+ m_total_fees += anc->second.GetModifiedFee();
+ m_total_vsize += anc->second.GetTxSize();
+ auto it = m_descendant_set_by_txid.find(anc->first);
+ // Each entry’s descendant set includes itself
+ Assume(it != m_descendant_set_by_txid.end());
+ for (auto& descendant : it->second) {
+ // If these fail, we must be double-deducting.
+ Assume(descendant->second.GetModFeesWithAncestors() >= anc->second.GetModifiedFee());
+ Assume(descendant->second.vsize_with_ancestors >= anc->second.GetTxSize());
+ descendant->second.fee_with_ancestors -= anc->second.GetModifiedFee();
+ descendant->second.vsize_with_ancestors -= anc->second.GetTxSize();
+ }
+ }
+ // Delete these entries.
+ for (const auto& anc : ancestors) {
+ m_descendant_set_by_txid.erase(anc->first);
+ // The above loop should have deducted each ancestor's size and fees from each of their
+ // respective descendants exactly once.
+ Assume(anc->second.GetModFeesWithAncestors() == 0);
+ Assume(anc->second.GetSizeWithAncestors() == 0);
+ auto vec_it = std::find(m_entries.begin(), m_entries.end(), anc);
+ Assume(vec_it != m_entries.end());
+ m_entries.erase(vec_it);
+ m_entries_by_txid.erase(anc);
+ }
+}
+
+void MiniMiner::SanityCheck() const
+{
+ // m_entries, m_entries_by_txid, and m_descendant_set_by_txid all same size
+ Assume(m_entries.size() == m_entries_by_txid.size());
+ Assume(m_entries.size() == m_descendant_set_by_txid.size());
+ // Cached ancestor values should be at least as large as the transaction's own fee and size
+ Assume(std::all_of(m_entries.begin(), m_entries.end(), [](const auto& entry) {
+ return entry->second.GetSizeWithAncestors() >= entry->second.GetTxSize() &&
+ entry->second.GetModFeesWithAncestors() >= entry->second.GetModifiedFee();}));
+ // None of the entries should be to-be-replaced transactions
+ Assume(std::all_of(m_to_be_replaced.begin(), m_to_be_replaced.end(),
+ [&](const auto& txid){return m_entries_by_txid.find(txid) == m_entries_by_txid.end();}));
+}
+
+void MiniMiner::BuildMockTemplate(const CFeeRate& target_feerate)
+{
+ while (!m_entries_by_txid.empty()) {
+ // Sort again, since transaction removal may change some m_entries' ancestor feerates.
+ std::sort(m_entries.begin(), m_entries.end(), AncestorFeerateComparator());
+
+ // Pick highest ancestor feerate entry.
+ auto best_iter = m_entries.begin();
+ Assume(best_iter != m_entries.end());
+ const auto ancestor_package_size = (*best_iter)->second.GetSizeWithAncestors();
+ const auto ancestor_package_fee = (*best_iter)->second.GetModFeesWithAncestors();
+ // Stop here. Everything that didn't "make it into the block" has bumpfee.
+ if (ancestor_package_fee < target_feerate.GetFee(ancestor_package_size)) {
+ break;
+ }
+
+ // Calculate ancestors on the fly. This lookup should be fairly cheap, and ancestor sets
+ // change at every iteration, so this is more efficient than maintaining a cache.
+ std::set<MockEntryMap::iterator, IteratorComparator> ancestors;
+ {
+ std::set<MockEntryMap::iterator, IteratorComparator> to_process;
+ to_process.insert(*best_iter);
+ while (!to_process.empty()) {
+ auto iter = to_process.begin();
+ Assume(iter != to_process.end());
+ ancestors.insert(*iter);
+ for (const auto& input : (*iter)->second.GetTx().vin) {
+ if (auto parent_it{m_entries_by_txid.find(input.prevout.hash)}; parent_it != m_entries_by_txid.end()) {
+ if (ancestors.count(parent_it) == 0) {
+ to_process.insert(parent_it);
+ }
+ }
+ }
+ to_process.erase(iter);
+ }
+ }
+ DeleteAncestorPackage(ancestors);
+ SanityCheck();
+ }
+ Assume(m_in_block.empty() || m_total_fees >= target_feerate.GetFee(m_total_vsize));
+ // Do not try to continue building the block template with a different feerate.
+ m_ready_to_calculate = false;
+}
+
+std::map<COutPoint, CAmount> MiniMiner::CalculateBumpFees(const CFeeRate& target_feerate)
+{
+ if (!m_ready_to_calculate) return {};
+ // Build a block template until the target feerate is hit.
+ BuildMockTemplate(target_feerate);
+
+ // Each transaction that "made it into the block" has a bumpfee of 0, i.e. they are part of an
+ // ancestor package with at least the target feerate and don't need to be bumped.
+ for (const auto& txid : m_in_block) {
+ // Not all of the block transactions were necessarily requested.
+ auto it = m_requested_outpoints_by_txid.find(txid);
+ if (it != m_requested_outpoints_by_txid.end()) {
+ for (const auto& outpoint : it->second) {
+ m_bump_fees.emplace(outpoint, 0);
+ }
+ m_requested_outpoints_by_txid.erase(it);
+ }
+ }
+
+ // A transactions and its ancestors will only be picked into a block when
+ // both the ancestor set feerate and the individual feerate meet the target
+ // feerate.
+ //
+ // We had to convince ourselves that after running the mini miner and
+ // picking all eligible transactions into our MockBlockTemplate, there
+ // could still be transactions remaining that have a lower individual
+ // feerate than their ancestor feerate. So here is an example:
+ //
+ // ┌─────────────────┐
+ // │ │
+ // │ Grandparent │
+ // │ 1700 vB │
+ // │ 1700 sats │ Target feerate: 10 s/vB
+ // │ 1 s/vB │ GP Ancestor Set Feerate (ASFR): 1 s/vB
+ // │ │ P1_ASFR: 9.84 s/vB
+ // └──────▲───▲──────┘ P2_ASFR: 2.47 s/vB
+ // │ │ C_ASFR: 10.27 s/vB
+ // ┌───────────────┐ │ │ ┌──────────────┐
+ // │ ├────┘ └────┤ │ ⇒ C_FR < TFR < C_ASFR
+ // │ Parent 1 │ │ Parent 2 │
+ // │ 200 vB │ │ 200 vB │
+ // │ 17000 sats │ │ 3000 sats │
+ // │ 85 s/vB │ │ 15 s/vB │
+ // │ │ │ │
+ // └───────────▲───┘ └───▲──────────┘
+ // │ │
+ // │ ┌───────────┐ │
+ // └────┤ ├────┘
+ // │ Child │
+ // │ 100 vB │
+ // │ 900 sats │
+ // │ 9 s/vB │
+ // │ │
+ // └───────────┘
+ //
+ // We therefore calculate both the bump fee that is necessary to elevate
+ // the individual transaction to the target feerate:
+ // target_feerate × tx_size - tx_fees
+ // and the bump fee that is necessary to bump the entire ancestor set to
+ // the target feerate:
+ // target_feerate × ancestor_set_size - ancestor_set_fees
+ // By picking the maximum from the two, we ensure that a transaction meets
+ // both criteria.
+ for (const auto& [txid, outpoints] : m_requested_outpoints_by_txid) {
+ auto it = m_entries_by_txid.find(txid);
+ Assume(it != m_entries_by_txid.end());
+ if (it != m_entries_by_txid.end()) {
+ Assume(target_feerate.GetFee(it->second.GetSizeWithAncestors()) > std::min(it->second.GetModifiedFee(), it->second.GetModFeesWithAncestors()));
+ CAmount bump_fee_with_ancestors = target_feerate.GetFee(it->second.GetSizeWithAncestors()) - it->second.GetModFeesWithAncestors();
+ CAmount bump_fee_individual = target_feerate.GetFee(it->second.GetTxSize()) - it->second.GetModifiedFee();
+ const CAmount bump_fee{std::max(bump_fee_with_ancestors, bump_fee_individual)};
+ Assume(bump_fee >= 0);
+ for (const auto& outpoint : outpoints) {
+ m_bump_fees.emplace(outpoint, bump_fee);
+ }
+ }
+ }
+ return m_bump_fees;
+}
+
+std::optional<CAmount> MiniMiner::CalculateTotalBumpFees(const CFeeRate& target_feerate)
+{
+ if (!m_ready_to_calculate) return std::nullopt;
+ // Build a block template until the target feerate is hit.
+ BuildMockTemplate(target_feerate);
+
+ // All remaining ancestors that are not part of m_in_block must be bumped, but no other relatives
+ std::set<MockEntryMap::iterator, IteratorComparator> ancestors;
+ std::set<MockEntryMap::iterator, IteratorComparator> to_process;
+ for (const auto& [txid, outpoints] : m_requested_outpoints_by_txid) {
+ // Skip any ancestors that already have a miner score higher than the target feerate
+ // (already "made it" into the block)
+ if (m_in_block.count(txid)) continue;
+ auto iter = m_entries_by_txid.find(txid);
+ if (iter == m_entries_by_txid.end()) continue;
+ to_process.insert(iter);
+ ancestors.insert(iter);
+ }
+
+ std::set<uint256> has_been_processed;
+ while (!to_process.empty()) {
+ auto iter = to_process.begin();
+ const CTransaction& tx = (*iter)->second.GetTx();
+ for (const auto& input : tx.vin) {
+ if (auto parent_it{m_entries_by_txid.find(input.prevout.hash)}; parent_it != m_entries_by_txid.end()) {
+ if (!has_been_processed.count(input.prevout.hash)) {
+ to_process.insert(parent_it);
+ }
+ ancestors.insert(parent_it);
+ }
+ }
+ has_been_processed.insert(tx.GetHash());
+ to_process.erase(iter);
+ }
+ const auto ancestor_package_size = std::accumulate(ancestors.cbegin(), ancestors.cend(), int64_t{0},
+ [](int64_t sum, const auto it) {return sum + it->second.GetTxSize();});
+ const auto ancestor_package_fee = std::accumulate(ancestors.cbegin(), ancestors.cend(), CAmount{0},
+ [](CAmount sum, const auto it) {return sum + it->second.GetModifiedFee();});
+ return target_feerate.GetFee(ancestor_package_size) - ancestor_package_fee;
+}
+} // namespace node
diff --git a/src/node/mini_miner.h b/src/node/mini_miner.h
new file mode 100644
index 0000000000..db07e6d1bf
--- /dev/null
+++ b/src/node/mini_miner.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_NODE_MINI_MINER_H
+#define BITCOIN_NODE_MINI_MINER_H
+
+#include <txmempool.h>
+
+#include <memory>
+#include <optional>
+#include <stdint.h>
+
+namespace node {
+
+// Container for tracking updates to ancestor feerate as we include ancestors in the "block"
+class MiniMinerMempoolEntry
+{
+ const CAmount fee_individual;
+ const CTransactionRef tx;
+ const int64_t vsize_individual;
+
+// This class must be constructed while holding mempool.cs. After construction, the object's
+// methods can be called without holding that lock.
+public:
+ CAmount fee_with_ancestors;
+ int64_t vsize_with_ancestors;
+ explicit MiniMinerMempoolEntry(CTxMemPool::txiter entry) :
+ fee_individual{entry->GetModifiedFee()},
+ tx{entry->GetSharedTx()},
+ vsize_individual(entry->GetTxSize()),
+ fee_with_ancestors{entry->GetModFeesWithAncestors()},
+ vsize_with_ancestors(entry->GetSizeWithAncestors())
+ { }
+
+ CAmount GetModifiedFee() const { return fee_individual; }
+ CAmount GetModFeesWithAncestors() const { return fee_with_ancestors; }
+ int64_t GetTxSize() const { return vsize_individual; }
+ int64_t GetSizeWithAncestors() const { return vsize_with_ancestors; }
+ const CTransaction& GetTx() const LIFETIMEBOUND { return *tx; }
+};
+
+// Comparator needed for std::set<MockEntryMap::iterator>
+struct IteratorComparator
+{
+ template<typename I>
+ bool operator()(const I& a, const I& b) const
+ {
+ return &(*a) < &(*b);
+ }
+};
+
+/** A minimal version of BlockAssembler. Allows us to run the mining algorithm on a subset of
+ * mempool transactions, ignoring consensus rules, to calculate mining scores. */
+class MiniMiner
+{
+ // When true, a caller may use CalculateBumpFees(). Becomes false if we failed to retrieve
+ // mempool entries (i.e. cluster size too large) or bump fees have already been calculated.
+ bool m_ready_to_calculate{true};
+
+ // Set once per lifetime, fill in during initialization.
+ // txids of to-be-replaced transactions
+ std::set<uint256> m_to_be_replaced;
+
+ // If multiple argument outpoints correspond to the same transaction, cache them together in
+ // a single entry indexed by txid. Then we can just work with txids since all outpoints from
+ // the same tx will have the same bumpfee. Excludes non-mempool transactions.
+ std::map<uint256, std::vector<COutPoint>> m_requested_outpoints_by_txid;
+
+ // What we're trying to calculate.
+ std::map<COutPoint, CAmount> m_bump_fees;
+
+ // The constructed block template
+ std::set<uint256> m_in_block;
+
+ // Information on the current status of the block
+ CAmount m_total_fees{0};
+ int32_t m_total_vsize{0};
+
+ /** Main data structure holding the entries, can be indexed by txid */
+ std::map<uint256, MiniMinerMempoolEntry> m_entries_by_txid;
+ using MockEntryMap = decltype(m_entries_by_txid);
+
+ /** Vector of entries, can be sorted by ancestor feerate. */
+ std::vector<MockEntryMap::iterator> m_entries;
+
+ /** Map of txid to its descendants. Should be inclusive. */
+ std::map<uint256, std::vector<MockEntryMap::iterator>> m_descendant_set_by_txid;
+
+ /** Consider this ancestor package "mined" so remove all these entries from our data structures. */
+ void DeleteAncestorPackage(const std::set<MockEntryMap::iterator, IteratorComparator>& ancestors);
+
+ /** Perform some checks. */
+ void SanityCheck() const;
+
+public:
+ /** Returns true if CalculateBumpFees may be called, false if not. */
+ bool IsReadyToCalculate() const { return m_ready_to_calculate; }
+
+ /** Build a block template until the target feerate is hit. */
+ void BuildMockTemplate(const CFeeRate& target_feerate);
+
+ /** Returns set of txids in the block template if one has been constructed. */
+ std::set<uint256> GetMockTemplateTxids() const { return m_in_block; }
+
+ MiniMiner(const CTxMemPool& mempool, const std::vector<COutPoint>& outpoints);
+
+ /** Construct a new block template and, for each outpoint corresponding to a transaction that
+ * did not make it into the block, calculate the cost of bumping those transactions (and their
+ * ancestors) to the minimum feerate. Returns a map from outpoint to bump fee, or an empty map
+ * if they cannot be calculated. */
+ std::map<COutPoint, CAmount> CalculateBumpFees(const CFeeRate& target_feerate);
+
+ /** Construct a new block template and, calculate the cost of bumping all transactions that did
+ * not make it into the block to the target feerate. Returns the total bump fee, or std::nullopt
+ * if it cannot be calculated. */
+ std::optional<CAmount> CalculateTotalBumpFees(const CFeeRate& target_feerate);
+};
+} // namespace node
+
+#endif // BITCOIN_NODE_MINI_MINER_H
diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp
index c7c8493f0c..026c8084dd 100644
--- a/src/node/transaction.cpp
+++ b/src/node/transaction.cpp
@@ -122,7 +122,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
return TransactionError::OK;
}
-CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)
+CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, uint256& hashBlock, const BlockManager& blockman)
{
if (mempool && !block_index) {
CTransactionRef ptx = mempool->get(hash);
@@ -143,7 +143,7 @@ CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMe
}
if (block_index) {
CBlock block;
- if (ReadBlockFromDisk(block, block_index, consensusParams)) {
+ if (blockman.ReadBlockFromDisk(block, *block_index)) {
for (const auto& tx : block.vtx) {
if (tx->GetHash() == hash) {
hashBlock = block_index->GetBlockHash();
diff --git a/src/node/transaction.h b/src/node/transaction.h
index 45f174f13c..168273594c 100644
--- a/src/node/transaction.h
+++ b/src/node/transaction.h
@@ -16,6 +16,7 @@ struct Params;
}
namespace node {
+class BlockManager;
struct NodeContext;
/** Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
@@ -53,11 +54,10 @@ static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
* @param[in] block_index The block to read from disk, or nullptr
* @param[in] mempool If provided, check mempool for tx
* @param[in] hash The txid
- * @param[in] consensusParams The params
* @param[out] hashBlock The block hash, if the tx was found via -txindex or block_index
* @returns The tx if found, otherwise nullptr
*/
-CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
+CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, uint256& hashBlock, const BlockManager& blockman);
} // namespace node
#endif // BITCOIN_NODE_TRANSACTION_H
diff --git a/src/node/txreconciliation.cpp b/src/node/txreconciliation.cpp
index ed04a78cec..d62046daaa 100644
--- a/src/node/txreconciliation.cpp
+++ b/src/node/txreconciliation.cpp
@@ -4,8 +4,9 @@
#include <node/txreconciliation.h>
+#include <common/system.h>
+#include <logging.h>
#include <util/check.h>
-#include <util/system.h>
#include <unordered_map>
#include <variant>
diff --git a/src/node/utxo_snapshot.cpp b/src/node/utxo_snapshot.cpp
index cccf95e552..036a25d0a5 100644
--- a/src/node/utxo_snapshot.cpp
+++ b/src/node/utxo_snapshot.cpp
@@ -4,14 +4,13 @@
#include <node/utxo_snapshot.h>
-#include <fs.h>
#include <logging.h>
#include <streams.h>
#include <sync.h>
#include <tinyformat.h>
#include <txdb.h>
#include <uint256.h>
-#include <util/system.h>
+#include <util/fs.h>
#include <validation.h>
#include <cassert>
@@ -82,10 +81,10 @@ std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
return base_blockhash;
}
-std::optional<fs::path> FindSnapshotChainstateDir()
+std::optional<fs::path> FindSnapshotChainstateDir(const fs::path& data_dir)
{
fs::path possible_dir =
- gArgs.GetDataDirNet() / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX));
+ data_dir / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX));
if (fs::exists(possible_dir)) {
return possible_dir;
diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h
index c5c018c9e8..a6dd3f3f13 100644
--- a/src/node/utxo_snapshot.h
+++ b/src/node/utxo_snapshot.h
@@ -6,11 +6,11 @@
#ifndef BITCOIN_NODE_UTXO_SNAPSHOT_H
#define BITCOIN_NODE_UTXO_SNAPSHOT_H
-#include <fs.h>
#include <kernel/cs_main.h>
#include <serialize.h>
#include <sync.h>
#include <uint256.h>
+#include <util/fs.h>
#include <cstdint>
#include <optional>
@@ -67,7 +67,7 @@ constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot";
//! Return a path to the snapshot-based chainstate dir, if one exists.
-std::optional<fs::path> FindSnapshotChainstateDir();
+std::optional<fs::path> FindSnapshotChainstateDir(const fs::path& data_dir);
} // namespace node
diff --git a/src/node/validation_cache_args.cpp b/src/node/validation_cache_args.cpp
index 5ea0a8ca0a..ddf24f798d 100644
--- a/src/node/validation_cache_args.cpp
+++ b/src/node/validation_cache_args.cpp
@@ -6,7 +6,7 @@
#include <kernel/validation_cache_sizes.h>
-#include <util/system.h>
+#include <common/args.h>
#include <algorithm>
#include <cstddef>