aboutsummaryrefslogtreecommitdiff
path: root/src/node
diff options
context:
space:
mode:
Diffstat (limited to 'src/node')
-rw-r--r--src/node/blockstorage.cpp76
-rw-r--r--src/node/blockstorage.h59
-rw-r--r--src/node/caches.cpp2
-rw-r--r--src/node/chainstate.cpp169
-rw-r--r--src/node/chainstate.h88
-rw-r--r--src/node/chainstatemanager_args.cpp39
-rw-r--r--src/node/chainstatemanager_args.h19
-rw-r--r--src/node/coinstats.cpp185
-rw-r--r--src/node/coinstats.h84
-rw-r--r--src/node/connection_types.cpp26
-rw-r--r--src/node/connection_types.h82
-rw-r--r--src/node/context.cpp5
-rw-r--r--src/node/context.h4
-rw-r--r--src/node/eviction.cpp240
-rw-r--r--src/node/eviction.h69
-rw-r--r--src/node/interface_ui.cpp (renamed from src/node/ui_interface.cpp)4
-rw-r--r--src/node/interface_ui.h (renamed from src/node/ui_interface.h)8
-rw-r--r--src/node/interfaces.cpp248
-rw-r--r--src/node/mempool_args.cpp100
-rw-r--r--src/node/mempool_args.h27
-rw-r--r--src/node/mempool_persist_args.cpp23
-rw-r--r--src/node/mempool_persist_args.h25
-rw-r--r--src/node/miner.cpp119
-rw-r--r--src/node/miner.h19
-rw-r--r--src/node/psbt.cpp4
-rw-r--r--src/node/transaction.h1
-rw-r--r--src/node/txreconciliation.cpp169
-rw-r--r--src/node/txreconciliation.h91
-rw-r--r--src/node/utxo_snapshot.cpp91
-rw-r--r--src/node/utxo_snapshot.h35
-rw-r--r--src/node/validation_cache_args.cpp34
-rw-r--r--src/node/validation_cache_args.h17
32 files changed, 1548 insertions, 614 deletions
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index 21cb0250d8..04d46f4361 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -21,6 +21,9 @@
#include <util/system.h>
#include <validation.h>
+#include <map>
+#include <unordered_map>
+
namespace node {
std::atomic_bool fImporting(false);
std::atomic_bool fReindex(false);
@@ -224,12 +227,17 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
}
}
- LogPrint(BCLog::PRUNE, "Prune: target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n",
+ LogPrint(BCLog::PRUNE, "target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n",
nPruneTarget/1024/1024, nCurrentUsage/1024/1024,
((int64_t)nPruneTarget - (int64_t)nCurrentUsage)/1024/1024,
nLastBlockWeCanPrune, count);
}
+void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) {
+ AssertLockHeld(::cs_main);
+ m_prune_locks[name] = lock_info;
+}
+
CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
{
AssertLockHeld(cs_main);
@@ -290,20 +298,6 @@ bool BlockManager::LoadBlockIndex(const Consensus::Params& consensus_params)
return true;
}
-void BlockManager::Unload()
-{
- m_blocks_unlinked.clear();
-
- m_block_index.clear();
-
- m_blockfile_info.clear();
- m_last_blockfile = 0;
- m_dirty_blockindex.clear();
- m_dirty_fileinfo.clear();
-
- m_have_pruned = false;
-}
-
bool BlockManager::WriteBlockIndexDB()
{
AssertLockHeld(::cs_main);
@@ -325,9 +319,9 @@ bool BlockManager::WriteBlockIndexDB()
return true;
}
-bool BlockManager::LoadBlockIndexDB()
+bool BlockManager::LoadBlockIndexDB(const Consensus::Params& consensus_params)
{
- if (!LoadBlockIndex(::Params().GetConsensus())) {
+ if (!LoadBlockIndex(consensus_params)) {
return false;
}
@@ -397,6 +391,16 @@ bool BlockManager::IsBlockPruned(const CBlockIndex* pblockindex)
return (m_have_pruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0);
}
+const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& start_block)
+{
+ AssertLockHeld(::cs_main);
+ const CBlockIndex* last_block = &start_block;
+ while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) {
+ last_block = last_block->pprev;
+ }
+ return last_block;
+}
+
// If we're using -prune with -reindex, then delete block files that will be ignored by the
// reindex. Since reindexing works by starting at block file 0 and looping until a blockfile
// is missing, do the same here to delete any later block files after a gap. Also delete all
@@ -411,7 +415,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");
- fs::path blocksdir = gArgs.GetBlocksDirPath();
+ const fs::path& blocksdir = gArgs.GetBlocksDirPath();
for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) {
const std::string path = fs::PathToString(it->path().filename());
if (fs::is_regular_file(*it) &&
@@ -468,7 +472,7 @@ static bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const
fileout << blockundo;
// calculate & write checksum
- CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
+ HashWriter hasher{};
hasher << hashBlock;
hasher << blockundo;
fileout << hasher.GetHash();
@@ -520,6 +524,16 @@ void BlockManager::FlushUndoFile(int block_file, bool finalize)
void BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo)
{
LOCK(cs_LastBlockFile);
+
+ if (m_blockfile_info.size() < 1) {
+ // Return if we haven't loaded any blockfiles yet. This happens during
+ // chainstate init, when we call ChainstateManager::MaybeRebalanceCaches() (which
+ // then calls FlushStateToDisk()), resulting in a call to this function before we
+ // have populated `m_blockfile_info` via LoadBlockIndexDB().
+ return;
+ }
+ assert(static_cast<int>(m_blockfile_info.size()) > m_last_blockfile);
+
FlatFilePos block_pos_old(m_last_blockfile, m_blockfile_info[m_last_blockfile].nSize);
if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
AbortNode("Flushing block file to disk failed. This is likely the result of an I/O error.");
@@ -785,19 +799,24 @@ bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, c
return true;
}
-/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp)
{
unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION);
FlatFilePos blockPos;
- if (dbp != nullptr) {
+ const auto position_known {dbp != nullptr};
+ if (position_known) {
blockPos = *dbp;
+ } else {
+ // when known, blockPos.nPos points at the offset of the block data in the blk file. that already accounts for
+ // the serialization header present in the file (the 4 magic message start bytes + the 4 length bytes = 8 bytes = BLOCK_SERIALIZATION_HEADER_SIZE).
+ // we add BLOCK_SERIALIZATION_HEADER_SIZE only for new blocks since they will have the serialization header added when written to disk.
+ nBlockSize += static_cast<unsigned int>(BLOCK_SERIALIZATION_HEADER_SIZE);
}
- if (!FindBlockPos(blockPos, nBlockSize + 8, nHeight, active_chain, block.GetBlockTime(), dbp != nullptr)) {
+ if (!FindBlockPos(blockPos, nBlockSize, nHeight, active_chain, block.GetBlockTime(), position_known)) {
error("%s: FindBlockPos failed", __func__);
return FlatFilePos();
}
- if (dbp == nullptr) {
+ if (!position_known) {
if (!WriteBlockToDisk(block, blockPos, chainparams.MessageStart())) {
AbortNode("Failed to write block");
return FlatFilePos();
@@ -820,7 +839,7 @@ struct CImportingNow {
}
};
-void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args)
+void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args, const fs::path& mempool_path)
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION_LOAD_BLOCKS);
ScheduleBatchPriority();
@@ -831,6 +850,9 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
// -reindex
if (fReindex) {
int nFile = 0;
+ // Map of disk positions for blocks with unknown parent (only used for reindex);
+ // parent hash -> child disk position, multiple children can have the same parent.
+ std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
while (true) {
FlatFilePos pos(nFile, 0);
if (!fs::exists(GetBlockPosFilename(pos))) {
@@ -841,7 +863,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
break; // This error is logged in OpenBlockFile
}
LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
- chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos);
+ chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
return;
@@ -875,7 +897,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
// We can't hold cs_main during ActivateBestChain even though we're accessing
// the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
// the relevant pointers before the ABC call.
- for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
+ 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());
@@ -890,6 +912,6 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
return;
}
} // End scope of CImportingNow
- chainman.ActiveChainstate().LoadMempool(args);
+ chainman.ActiveChainstate().LoadMempool(mempool_path);
}
} // namespace node
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index 11445aa22e..29501c1959 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -5,14 +5,16 @@
#ifndef BITCOIN_NODE_BLOCKSTORAGE_H
#define BITCOIN_NODE_BLOCKSTORAGE_H
+#include <attributes.h>
#include <chain.h>
#include <fs.h>
-#include <protocol.h> // For CMessageHeader::MessageStartChars
+#include <protocol.h>
#include <sync.h>
#include <txdb.h>
#include <atomic>
#include <cstdint>
+#include <unordered_map>
#include <vector>
extern RecursiveMutex cs_main;
@@ -24,7 +26,7 @@ class CBlockFileInfo;
class CBlockUndo;
class CChain;
class CChainParams;
-class CChainState;
+class Chainstate;
class ChainstateManager;
struct CCheckpointData;
struct FlatFilePos;
@@ -42,12 +44,15 @@ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
/** The maximum size of a blk?????.dat file (since 0.8) */
static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
+/** Size of header written by WriteBlockToDisk before a serialized CBlock */
+static constexpr size_t BLOCK_SERIALIZATION_HEADER_SIZE = CMessageHeader::MESSAGE_START_SIZE + sizeof(unsigned int);
+
extern std::atomic_bool fImporting;
extern std::atomic_bool fReindex;
/** Pruning-related variables and constants */
/** True if we're running in -prune mode. */
extern bool fPruneMode;
-/** Number of MiB of block files that we're trying to stay below. */
+/** Number of bytes of block files that we're trying to stay below. */
extern uint64_t nPruneTarget;
// Because validation code takes pointers to the map's CBlockIndex objects, if
@@ -65,19 +70,30 @@ struct CBlockIndexHeightOnlyComparator {
bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
};
+struct PruneLockInfo {
+ int height_first{std::numeric_limits<int>::max()}; //! Height of earliest block that should be kept and not pruned
+};
+
/**
* Maintains a tree of blocks (stored in `m_block_index`) which is consulted
* to determine where the most-work tip is.
*
- * This data is used mostly in `CChainState` - information about, e.g.,
+ * This data is used mostly in `Chainstate` - information about, e.g.,
* candidate tips is not maintained here.
*/
class BlockManager
{
- friend CChainState;
+ friend Chainstate;
friend ChainstateManager;
private:
+ /**
+ * 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)
+ 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);
@@ -118,6 +134,14 @@ private:
/** Dirty block file entries. */
std::set<int> m_dirty_fileinfo;
+ /**
+ * Map from external index name to oldest block that must not be pruned.
+ *
+ * @note Internally, only blocks at height (height_first - PRUNE_LOCK_BUFFER - 1) and
+ * below will be pruned, but callers should avoid assuming any particular buffer size.
+ */
+ std::unordered_map<std::string, PruneLockInfo> m_prune_locks GUARDED_BY(::cs_main);
+
public:
BlockMap m_block_index GUARDED_BY(cs_main);
@@ -132,18 +156,7 @@ public:
std::unique_ptr<CBlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main);
bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
- bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
-
- /**
- * 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)
- EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-
- /** Clear all data members. */
- void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool LoadBlockIndexDB(const Consensus::Params& consensus_params) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
CBlockIndex* AddToBlockIndex(const CBlockHeader& block, CBlockIndex*& best_header) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Create a new block index entry for a given block hash */
@@ -161,6 +174,7 @@ public:
bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams)
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);
/** Calculate the amount of disk space the block & undo files currently use */
@@ -169,16 +183,17 @@ public:
//! Returns last CBlockIndex* that is a checkpoint
const CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ //! Find the first block that is not pruned
+ const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
/** True if any block files have ever been pruned. */
bool m_have_pruned = false;
//! Check whether the block associated with this index entry is pruned or not.
bool IsBlockPruned(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
- ~BlockManager()
- {
- Unload();
- }
+ //! 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();
@@ -200,7 +215,7 @@ bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, c
bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex);
-void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args);
+void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args, 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 f168332ee6..a39ad7aeb6 100644
--- a/src/node/caches.cpp
+++ b/src/node/caches.cpp
@@ -4,9 +4,9 @@
#include <node/caches.h>
+#include <index/txindex.h>
#include <txdb.h>
#include <util/system.h>
-#include <validation.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 e43211402c..60acb614b4 100644
--- a/src/node/chainstate.cpp
+++ b/src/node/chainstate.cpp
@@ -4,69 +4,98 @@
#include <node/chainstate.h>
+#include <arith_uint256.h>
+#include <chain.h>
+#include <coins.h>
#include <consensus/params.h>
+#include <logging.h>
#include <node/blockstorage.h>
+#include <node/caches.h>
+#include <sync.h>
+#include <threadsafety.h>
+#include <tinyformat.h>
+#include <txdb.h>
+#include <uint256.h>
+#include <util/time.h>
+#include <util/translation.h>
#include <validation.h>
+#include <algorithm>
+#include <atomic>
+#include <cassert>
+#include <limits>
+#include <memory>
+#include <vector>
+
namespace node {
-std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
- ChainstateManager& chainman,
- CTxMemPool* mempool,
- bool fPruneMode,
- const Consensus::Params& consensus_params,
- bool fReindexChainState,
- int64_t nBlockTreeDBCache,
- int64_t nCoinDBCache,
- int64_t nCoinCacheUsage,
- bool block_tree_db_in_memory,
- bool coins_db_in_memory,
- std::function<bool()> shutdown_requested,
- std::function<void()> coins_error_cb)
+ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
+ const ChainstateLoadOptions& options)
{
- auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
- return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
+ auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+ return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
};
+ if (!chainman.AssumedValidBlock().IsNull()) {
+ LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex());
+ } else {
+ LogPrintf("Validating signatures for all blocks.\n");
+ }
+ LogPrintf("Setting nMinimumChainWork=%s\n", chainman.MinimumChainWork().GetHex());
+ if (chainman.MinimumChainWork() < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) {
+ LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainman.GetConsensus().nMinimumChainWork.GetHex());
+ }
+ if (nPruneTarget == std::numeric_limits<uint64_t>::max()) {
+ LogPrintf("Block pruning enabled. Use RPC call pruneblockchain(height) to manually prune block and undo files.\n");
+ } else if (nPruneTarget) {
+ LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024);
+ }
+
LOCK(cs_main);
- chainman.InitializeChainstate(mempool);
- chainman.m_total_coinstip_cache = nCoinCacheUsage;
- chainman.m_total_coinsdb_cache = nCoinDBCache;
+ chainman.m_total_coinstip_cache = cache_sizes.coins;
+ chainman.m_total_coinsdb_cache = cache_sizes.coins_db;
+
+ // Load the fully validated chainstate.
+ chainman.InitializeChainstate(options.mempool);
- UnloadBlockIndex(mempool, chainman);
+ // Load a chain created from a UTXO snapshot, if any exist.
+ chainman.DetectSnapshotChainstate(options.mempool);
auto& pblocktree{chainman.m_blockman.m_block_tree_db};
// new CBlockTreeDB tries to delete the existing file, which
// fails if it's still open from the previous loop. Close it first:
pblocktree.reset();
- pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, block_tree_db_in_memory, fReset));
+ pblocktree.reset(new CBlockTreeDB(cache_sizes.block_tree_db, options.block_tree_db_in_memory, options.reindex));
- if (fReset) {
+ if (options.reindex) {
pblocktree->WriteReindexing(true);
//If we're reindexing in prune mode, wipe away unusable block files and all undo data files
- if (fPruneMode)
+ if (options.prune) {
CleanupBlockRevFiles();
+ }
}
- if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED;
+ if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}};
// LoadBlockIndex will load m_have_pruned if we've ever removed a
// block file from disk.
- // Note that it also sets fReindex based on the disk flag!
- // From here on out fReindex and fReset mean something different!
+ // Note that it also sets fReindex global based on the disk flag!
+ // From here on, fReindex and options.reindex values may be different!
if (!chainman.LoadBlockIndex()) {
- if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED;
- return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB;
+ if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}};
+ return {ChainstateLoadStatus::FAILURE, _("Error loading block database")};
}
if (!chainman.BlockIndex().empty() &&
- !chainman.m_blockman.LookupBlockIndex(consensus_params.hashGenesisBlock)) {
- return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK;
+ !chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) {
+ // If the loaded chain has a wrong genesis, bail out immediately
+ // (we're likely using a testnet datadir, or the other way around).
+ return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Incorrect or no genesis block found. Wrong datadir for network?")};
}
// Check for changed -prune state. What we are concerned about is a user who has pruned blocks
// in the past, but is now trying to run unpruned.
- if (chainman.m_blockman.m_have_pruned && !fPruneMode) {
- return ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX;
+ if (chainman.m_blockman.m_have_pruned && !options.prune) {
+ return {ChainstateLoadStatus::FAILURE, _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")};
}
// At this point blocktree args are consistent with what's on disk.
@@ -74,87 +103,99 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
// (otherwise we use the one already on disk).
// This is called again in ThreadImport after the reindex completes.
if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) {
- return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED;
+ return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
}
+ // Conservative value which is arbitrarily chosen, as it will ultimately be changed
+ // by a call to `chainman.MaybeRebalanceCaches()`. We just need to make sure
+ // that the sum of the two caches (40%) does not exceed the allowable amount
+ // during this temporary initialization state.
+ double init_cache_fraction = 0.2;
+
// At this point we're either in reindex or we've loaded a useful
// block tree into BlockIndex()!
- for (CChainState* chainstate : chainman.GetAll()) {
+ for (Chainstate* chainstate : chainman.GetAll()) {
+ LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
+
chainstate->InitCoinsDB(
- /*cache_size_bytes=*/nCoinDBCache,
- /*in_memory=*/coins_db_in_memory,
- /*should_wipe=*/fReset || fReindexChainState);
+ /*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction,
+ /*in_memory=*/options.coins_db_in_memory,
+ /*should_wipe=*/options.reindex || options.reindex_chainstate);
- if (coins_error_cb) {
- chainstate->CoinsErrorCatcher().AddReadErrCallback(coins_error_cb);
+ if (options.coins_error_cb) {
+ chainstate->CoinsErrorCatcher().AddReadErrCallback(options.coins_error_cb);
}
// Refuse to load unsupported database format.
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (chainstate->CoinsDB().NeedsUpgrade()) {
- return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED;
+ return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Unsupported chainstate database format found. "
+ "Please restart with -reindex-chainstate. This will "
+ "rebuild the chainstate database.")};
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!chainstate->ReplayBlocks()) {
- return ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED;
+ return {ChainstateLoadStatus::FAILURE, _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")};
}
// The on-disk coinsdb is now in a good state, create the cache
- chainstate->InitCoinsCache(nCoinCacheUsage);
+ chainstate->InitCoinsCache(chainman.m_total_coinstip_cache * init_cache_fraction);
assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on CoinsTip()'s best block
if (!chainstate->LoadChainTip()) {
- return ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED;
+ return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
}
assert(chainstate->m_chain.Tip() != nullptr);
}
}
- if (!fReset) {
+ if (!options.reindex) {
auto chainstates{chainman.GetAll()};
if (std::any_of(chainstates.begin(), chainstates.end(),
- [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
- return ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED;
- }
+ [](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
+ return {ChainstateLoadStatus::FAILURE, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
+ chainman.GetConsensus().SegwitHeight)};
+ };
}
- return std::nullopt;
+ // Now that chainstates are loaded and we're able to flush to
+ // disk, rebalance the coins caches to desired levels based
+ // on the condition of each chainstate.
+ chainman.MaybeRebalanceCaches();
+
+ return {ChainstateLoadStatus::SUCCESS, {}};
}
-std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman,
- bool fReset,
- bool fReindexChainState,
- const Consensus::Params& consensus_params,
- int check_blocks,
- int check_level,
- std::function<int64_t()> get_unix_time_seconds)
+ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
{
- auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
- return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
+ auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+ return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
};
LOCK(cs_main);
- for (CChainState* chainstate : chainman.GetAll()) {
+ for (Chainstate* chainstate : chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
const CBlockIndex* tip = chainstate->m_chain.Tip();
- if (tip && tip->nTime > get_unix_time_seconds() + MAX_FUTURE_BLOCK_TIME) {
- return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE;
+ if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) {
+ return {ChainstateLoadStatus::FAILURE, _("The block database contains a block which appears to be from the future. "
+ "This may be due to your computer's date and time being set incorrectly. "
+ "Only rebuild the block database if you are sure that your computer's date and time are correct")};
}
if (!CVerifyDB().VerifyDB(
- *chainstate, consensus_params, chainstate->CoinsDB(),
- check_level,
- check_blocks)) {
- return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB;
+ *chainstate, chainman.GetConsensus(), chainstate->CoinsDB(),
+ options.check_level,
+ options.check_blocks)) {
+ return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")};
}
}
}
- return std::nullopt;
+ return {ChainstateLoadStatus::SUCCESS, {}};
}
} // namespace node
diff --git a/src/node/chainstate.h b/src/node/chainstate.h
index 8ba04f1436..2289310ece 100644
--- a/src/node/chainstate.h
+++ b/src/node/chainstate.h
@@ -5,30 +5,41 @@
#ifndef BITCOIN_NODE_CHAINSTATE_H
#define BITCOIN_NODE_CHAINSTATE_H
+#include <util/translation.h>
+#include <validation.h>
+
#include <cstdint>
#include <functional>
-#include <optional>
+#include <tuple>
-class ChainstateManager;
class CTxMemPool;
-namespace Consensus {
-struct Params;
-} // namespace Consensus
namespace node {
-enum class ChainstateLoadingError {
- ERROR_LOADING_BLOCK_DB,
- ERROR_BAD_GENESIS_BLOCK,
- ERROR_PRUNED_NEEDS_REINDEX,
- ERROR_LOAD_GENESIS_BLOCK_FAILED,
- ERROR_CHAINSTATE_UPGRADE_FAILED,
- ERROR_REPLAYBLOCKS_FAILED,
- ERROR_LOADCHAINTIP_FAILED,
- ERROR_GENERIC_BLOCKDB_OPEN_FAILED,
- ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED,
- SHUTDOWN_PROBED,
+
+struct CacheSizes;
+
+struct ChainstateLoadOptions {
+ CTxMemPool* mempool{nullptr};
+ bool block_tree_db_in_memory{false};
+ bool coins_db_in_memory{false};
+ bool reindex{false};
+ bool reindex_chainstate{false};
+ bool prune{false};
+ int64_t check_blocks{DEFAULT_CHECKBLOCKS};
+ int64_t check_level{DEFAULT_CHECKLEVEL};
+ std::function<bool()> check_interrupt;
+ std::function<void()> coins_error_cb;
};
+//! Chainstate load status. Simple applications can just check for the success
+//! case, and treat other cases as errors. More complex applications may want to
+//! try reindexing in the generic failure case, and pass an interrupt callback
+//! and exit cleanly in the interrupted case.
+enum class ChainstateLoadStatus { SUCCESS, FAILURE, FAILURE_INCOMPATIBLE_DB, INTERRUPTED };
+
+//! Chainstate load status code and optional error string.
+using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>;
+
/** This sequence can have 4 types of outcomes:
*
* 1. Success
@@ -40,48 +51,11 @@ enum class ChainstateLoadingError {
* 4. Hard failure
* - a failure that definitively cannot be recovered from with a reindex
*
- * Currently, LoadChainstate returns a std::optional<ChainstateLoadingError>
- * which:
- *
- * - if has_value()
- * - Either "Soft failure", "Hard failure", or "Shutdown requested",
- * differentiable by the specific enumerator.
- *
- * Note that a return value of SHUTDOWN_PROBED means ONLY that "during
- * this sequence, when we explicitly checked shutdown_requested() at
- * arbitrary points, one of those calls returned true". Therefore, a
- * return value other than SHUTDOWN_PROBED does not guarantee that
- * shutdown hasn't been called indirectly.
- * - else
- * - Success!
+ * LoadChainstate returns a (status code, error string) tuple.
*/
-std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
- ChainstateManager& chainman,
- CTxMemPool* mempool,
- bool fPruneMode,
- const Consensus::Params& consensus_params,
- bool fReindexChainState,
- int64_t nBlockTreeDBCache,
- int64_t nCoinDBCache,
- int64_t nCoinCacheUsage,
- bool block_tree_db_in_memory,
- bool coins_db_in_memory,
- std::function<bool()> shutdown_requested = nullptr,
- std::function<void()> coins_error_cb = nullptr);
-
-enum class ChainstateLoadVerifyError {
- ERROR_BLOCK_FROM_FUTURE,
- ERROR_CORRUPTED_BLOCK_DB,
- ERROR_GENERIC_FAILURE,
-};
-
-std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman,
- bool fReset,
- bool fReindexChainState,
- const Consensus::Params& consensus_params,
- int check_blocks,
- int check_level,
- std::function<int64_t()> get_unix_time_seconds);
+ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
+ const ChainstateLoadOptions& options);
+ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
} // namespace node
#endif // BITCOIN_NODE_CHAINSTATE_H
diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp
new file mode 100644
index 0000000000..b0d929626b
--- /dev/null
+++ b/src/node/chainstatemanager_args.cpp
@@ -0,0 +1,39 @@
+// 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.
+
+#include <node/chainstatemanager_args.h>
+
+#include <arith_uint256.h>
+#include <tinyformat.h>
+#include <uint256.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)
+{
+ if (auto value{args.GetBoolArg("-checkblockindex")}) opts.check_block_index = *value;
+
+ if (auto value{args.GetBoolArg("-checkpoints")}) opts.checkpoints_enabled = *value;
+
+ if (auto value{args.GetArg("-minimumchainwork")}) {
+ if (!IsHexNumber(*value)) {
+ return strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value);
+ }
+ opts.minimum_chain_work = UintToArith256(uint256S(*value));
+ }
+
+ if (auto value{args.GetArg("-assumevalid")}) opts.assumed_valid_block = uint256S(*value);
+
+ if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value};
+
+ return std::nullopt;
+}
+} // namespace node
diff --git a/src/node/chainstatemanager_args.h b/src/node/chainstatemanager_args.h
new file mode 100644
index 0000000000..6c46b998f2
--- /dev/null
+++ b/src/node/chainstatemanager_args.h
@@ -0,0 +1,19 @@
+// 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_CHAINSTATEMANAGER_ARGS_H
+#define BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H
+
+#include <validation.h>
+
+#include <optional>
+
+class ArgsManager;
+struct bilingual_str;
+
+namespace node {
+std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts);
+} // namespace node
+
+#endif // BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H
diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp
deleted file mode 100644
index eed43a1bc7..0000000000
--- a/src/node/coinstats.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2021 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/coinstats.h>
-
-#include <coins.h>
-#include <crypto/muhash.h>
-#include <hash.h>
-#include <index/coinstatsindex.h>
-#include <serialize.h>
-#include <uint256.h>
-#include <util/overflow.h>
-#include <util/system.h>
-#include <validation.h>
-
-#include <map>
-
-namespace node {
-// Database-independent metric indicating the UTXO set size
-uint64_t GetBogoSize(const CScript& script_pub_key)
-{
- return 32 /* txid */ +
- 4 /* vout index */ +
- 4 /* height + coinbase */ +
- 8 /* amount */ +
- 2 /* scriptPubKey len */ +
- script_pub_key.size() /* scriptPubKey */;
-}
-
-CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) {
- CDataStream ss(SER_DISK, PROTOCOL_VERSION);
- ss << outpoint;
- ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
- ss << coin.out;
- return ss;
-}
-
-//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
-//! validation commitments are reliant on the hash constructed by this
-//! function.
-//!
-//! If the construction of this hash is changed, it will invalidate
-//! existing UTXO snapshots. This will not result in any kind of consensus
-//! failure, but it will force clients that were expecting to make use of
-//! assumeutxo to do traditional IBD instead.
-//!
-//! It is also possible, though very unlikely, that a change in this
-//! construction could cause a previously invalid (and potentially malicious)
-//! UTXO snapshot to be considered valid.
-static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
-{
- for (auto it = outputs.begin(); it != outputs.end(); ++it) {
- if (it == outputs.begin()) {
- ss << hash;
- ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
- }
-
- ss << VARINT(it->first + 1);
- ss << it->second.out.scriptPubKey;
- ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
-
- if (it == std::prev(outputs.end())) {
- ss << VARINT(0u);
- }
- }
-}
-
-static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {}
-
-static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
-{
- for (auto it = outputs.begin(); it != outputs.end(); ++it) {
- COutPoint outpoint = COutPoint(hash, it->first);
- Coin coin = it->second;
- muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
- }
-}
-
-static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
-{
- assert(!outputs.empty());
- stats.nTransactions++;
- for (auto it = outputs.begin(); it != outputs.end(); ++it) {
- stats.nTransactionOutputs++;
- if (stats.total_amount.has_value()) {
- stats.total_amount = CheckedAdd(*stats.total_amount, it->second.out.nValue);
- }
- stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
- }
-}
-
-//! Calculate statistics about the unspent transaction output set
-template <typename T>
-static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
-{
- std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
- assert(pcursor);
-
- if (!pindex) {
- LOCK(cs_main);
- pindex = blockman.LookupBlockIndex(view->GetBestBlock());
- }
- stats.nHeight = Assert(pindex)->nHeight;
- stats.hashBlock = pindex->GetBlockHash();
-
- // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
- if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index && stats.index_requested) {
- stats.index_used = true;
- return g_coin_stats_index->LookUpStats(pindex, stats);
- }
-
- PrepareHash(hash_obj, stats);
-
- uint256 prevkey;
- std::map<uint32_t, Coin> outputs;
- while (pcursor->Valid()) {
- interruption_point();
- COutPoint key;
- Coin coin;
- if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
- if (!outputs.empty() && key.hash != prevkey) {
- ApplyStats(stats, prevkey, outputs);
- ApplyHash(hash_obj, prevkey, outputs);
- outputs.clear();
- }
- prevkey = key.hash;
- outputs[key.n] = std::move(coin);
- stats.coins_count++;
- } else {
- return error("%s: unable to read value", __func__);
- }
- pcursor->Next();
- }
- if (!outputs.empty()) {
- ApplyStats(stats, prevkey, outputs);
- ApplyHash(hash_obj, prevkey, outputs);
- }
-
- FinalizeHash(hash_obj, stats);
-
- stats.nDiskSize = view->EstimateSize();
- return true;
-}
-
-bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
-{
- switch (stats.m_hash_type) {
- case(CoinStatsHashType::HASH_SERIALIZED): {
- CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
- return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex);
- }
- case(CoinStatsHashType::MUHASH): {
- MuHash3072 muhash;
- return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex);
- }
- case(CoinStatsHashType::NONE): {
- return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex);
- }
- } // no default case, so the compiler can warn about missing cases
- assert(false);
-}
-
-// The legacy hash serializes the hashBlock
-static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats)
-{
- ss << stats.hashBlock;
-}
-// MuHash does not need the prepare step
-static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {}
-static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {}
-
-static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats)
-{
- stats.hashSerialized = ss.GetHash();
-}
-static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats)
-{
- uint256 out;
- muhash.Finalize(out);
- stats.hashSerialized = out;
-}
-static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {}
-} // namespace node
diff --git a/src/node/coinstats.h b/src/node/coinstats.h
deleted file mode 100644
index aa771b18b0..0000000000
--- a/src/node/coinstats.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2021 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_COINSTATS_H
-#define BITCOIN_NODE_COINSTATS_H
-
-#include <chain.h>
-#include <coins.h>
-#include <consensus/amount.h>
-#include <streams.h>
-#include <uint256.h>
-
-#include <cstdint>
-#include <functional>
-
-class CCoinsView;
-namespace node {
-class BlockManager;
-} // namespace node
-
-namespace node {
-enum class CoinStatsHashType {
- HASH_SERIALIZED,
- MUHASH,
- NONE,
-};
-
-struct CCoinsStats {
- //! Which hash type to use
- const CoinStatsHashType m_hash_type;
-
- int nHeight{0};
- uint256 hashBlock{};
- uint64_t nTransactions{0};
- uint64_t nTransactionOutputs{0};
- uint64_t nBogoSize{0};
- uint256 hashSerialized{};
- uint64_t nDiskSize{0};
- //! The total amount, or nullopt if an overflow occurred calculating it
- std::optional<CAmount> total_amount{0};
-
- //! The number of coins contained.
- uint64_t coins_count{0};
-
- //! Signals if the coinstatsindex should be used (when available).
- bool index_requested{true};
- //! Signals if the coinstatsindex was used to retrieve the statistics.
- bool index_used{false};
-
- // Following values are only available from coinstats index
-
- //! Total cumulative amount of block subsidies up to and including this block
- CAmount total_subsidy{0};
- //! Total cumulative amount of unspendable coins up to and including this block
- CAmount total_unspendable_amount{0};
- //! Total cumulative amount of prevouts spent up to and including this block
- CAmount total_prevout_spent_amount{0};
- //! Total cumulative amount of outputs created up to and including this block
- CAmount total_new_outputs_ex_coinbase_amount{0};
- //! Total cumulative amount of coinbase outputs up to and including this block
- CAmount total_coinbase_amount{0};
- //! The unspendable coinbase amount from the genesis block
- CAmount total_unspendables_genesis_block{0};
- //! The two unspendable coinbase outputs total amount caused by BIP30
- CAmount total_unspendables_bip30{0};
- //! Total cumulative amount of outputs sent to unspendable scripts (OP_RETURN for example) up to and including this block
- CAmount total_unspendables_scripts{0};
- //! Total cumulative amount of coins lost due to unclaimed miner rewards up to and including this block
- CAmount total_unspendables_unclaimed_rewards{0};
-
- CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
-};
-
-//! Calculate statistics about the unspent transaction output set
-bool GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {}, const CBlockIndex* pindex = nullptr);
-
-uint64_t GetBogoSize(const CScript& script_pub_key);
-
-CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin);
-} // namespace node
-
-#endif // BITCOIN_NODE_COINSTATS_H
diff --git a/src/node/connection_types.cpp b/src/node/connection_types.cpp
new file mode 100644
index 0000000000..904f4371aa
--- /dev/null
+++ b/src/node/connection_types.cpp
@@ -0,0 +1,26 @@
+// 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.
+
+#include <node/connection_types.h>
+#include <cassert>
+
+std::string ConnectionTypeAsString(ConnectionType conn_type)
+{
+ switch (conn_type) {
+ case ConnectionType::INBOUND:
+ return "inbound";
+ case ConnectionType::MANUAL:
+ return "manual";
+ case ConnectionType::FEELER:
+ return "feeler";
+ case ConnectionType::OUTBOUND_FULL_RELAY:
+ return "outbound-full-relay";
+ case ConnectionType::BLOCK_RELAY:
+ return "block-relay-only";
+ case ConnectionType::ADDR_FETCH:
+ return "addr-fetch";
+ } // no default case, so the compiler can warn about missing cases
+
+ assert(false);
+}
diff --git a/src/node/connection_types.h b/src/node/connection_types.h
new file mode 100644
index 0000000000..5e1abcace6
--- /dev/null
+++ b/src/node/connection_types.h
@@ -0,0 +1,82 @@
+// 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_CONNECTION_TYPES_H
+#define BITCOIN_NODE_CONNECTION_TYPES_H
+
+#include <string>
+
+/** Different types of connections to a peer. This enum encapsulates the
+ * information we have available at the time of opening or accepting the
+ * connection. Aside from INBOUND, all types are initiated by us.
+ *
+ * If adding or removing types, please update CONNECTION_TYPE_DOC in
+ * src/rpc/net.cpp and src/qt/rpcconsole.cpp, as well as the descriptions in
+ * src/qt/guiutil.cpp and src/bitcoin-cli.cpp::NetinfoRequestHandler. */
+enum class ConnectionType {
+ /**
+ * Inbound connections are those initiated by a peer. This is the only
+ * property we know at the time of connection, until P2P messages are
+ * exchanged.
+ */
+ INBOUND,
+
+ /**
+ * These are the default connections that we use to connect with the
+ * network. There is no restriction on what is relayed; by default we relay
+ * blocks, addresses & transactions. We automatically attempt to open
+ * MAX_OUTBOUND_FULL_RELAY_CONNECTIONS using addresses from our AddrMan.
+ */
+ OUTBOUND_FULL_RELAY,
+
+
+ /**
+ * We open manual connections to addresses that users explicitly requested
+ * via the addnode RPC or the -addnode/-connect configuration options. Even if a
+ * manual connection is misbehaving, we do not automatically disconnect or
+ * add it to our discouragement filter.
+ */
+ MANUAL,
+
+ /**
+ * Feeler connections are short-lived connections made to check that a node
+ * is alive. They can be useful for:
+ * - test-before-evict: if one of the peers is considered for eviction from
+ * our AddrMan because another peer is mapped to the same slot in the tried table,
+ * evict only if this longer-known peer is offline.
+ * - move node addresses from New to Tried table, so that we have more
+ * connectable addresses in our AddrMan.
+ * Note that in the literature ("Eclipse Attacks on Bitcoin’s Peer-to-Peer Network")
+ * only the latter feature is referred to as "feeler connections",
+ * although in our codebase feeler connections encompass test-before-evict as well.
+ * We make these connections approximately every FEELER_INTERVAL:
+ * first we resolve previously found collisions if they exist (test-before-evict),
+ * otherwise we connect to a node from the new table.
+ */
+ FEELER,
+
+ /**
+ * We use block-relay-only connections to help prevent against partition
+ * attacks. By not relaying transactions or addresses, these connections
+ * are harder to detect by a third party, thus helping obfuscate the
+ * network topology. We automatically attempt to open
+ * MAX_BLOCK_RELAY_ONLY_ANCHORS using addresses from our anchors.dat. Then
+ * addresses from our AddrMan if MAX_BLOCK_RELAY_ONLY_CONNECTIONS
+ * isn't reached yet.
+ */
+ BLOCK_RELAY,
+
+ /**
+ * AddrFetch connections are short lived connections used to solicit
+ * addresses from peers. These are initiated to addresses submitted via the
+ * -seednode command line argument, or under certain conditions when the
+ * AddrMan is empty.
+ */
+ ADDR_FETCH,
+};
+
+/** Convert ConnectionType enum to a string value */
+std::string ConnectionTypeAsString(ConnectionType conn_type);
+
+#endif // BITCOIN_NODE_CONNECTION_TYPES_H
diff --git a/src/node/context.cpp b/src/node/context.cpp
index 0b31c10f44..d80b8ca7a7 100644
--- a/src/node/context.cpp
+++ b/src/node/context.cpp
@@ -7,6 +7,7 @@
#include <addrman.h>
#include <banman.h>
#include <interfaces/chain.h>
+#include <kernel/context.h>
#include <net.h>
#include <net_processing.h>
#include <netgroup.h>
@@ -16,6 +17,6 @@
#include <validation.h>
namespace node {
-NodeContext::NodeContext() {}
-NodeContext::~NodeContext() {}
+NodeContext::NodeContext() = default;
+NodeContext::~NodeContext() = default;
} // namespace node
diff --git a/src/node/context.h b/src/node/context.h
index 91ba456219..31be308787 100644
--- a/src/node/context.h
+++ b/src/node/context.h
@@ -5,6 +5,8 @@
#ifndef BITCOIN_NODE_CONTEXT_H
#define BITCOIN_NODE_CONTEXT_H
+#include <kernel/context.h>
+
#include <cassert>
#include <functional>
#include <memory>
@@ -39,6 +41,8 @@ namespace node {
//! any member functions. It should just be a collection of references that can
//! be used without pulling in unwanted dependencies or functionality.
struct NodeContext {
+ //! libbitcoin_kernel context
+ std::unique_ptr<kernel::Context> kernel;
//! Init interface for initializing current process and connecting to other processes.
interfaces::Init* init{nullptr};
std::unique_ptr<AddrMan> addrman;
diff --git a/src/node/eviction.cpp b/src/node/eviction.cpp
new file mode 100644
index 0000000000..33406931d4
--- /dev/null
+++ b/src/node/eviction.cpp
@@ -0,0 +1,240 @@
+// 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.
+
+#include <node/eviction.h>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <vector>
+
+
+static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ return a.m_min_ping_time > b.m_min_ping_time;
+}
+
+static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ return a.m_connected > b.m_connected;
+}
+
+static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
+ return a.nKeyedNetGroup < b.nKeyedNetGroup;
+}
+
+static bool CompareNodeBlockTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ // There is a fall-through here because it is common for a node to have many peers which have not yet relayed a block.
+ if (a.m_last_block_time != b.m_last_block_time) return a.m_last_block_time < b.m_last_block_time;
+ if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices;
+ return a.m_connected > b.m_connected;
+}
+
+static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ // There is a fall-through here because it is common for a node to have more than a few peers that have not yet relayed txn.
+ if (a.m_last_tx_time != b.m_last_tx_time) return a.m_last_tx_time < b.m_last_tx_time;
+ if (a.m_relay_txs != b.m_relay_txs) return b.m_relay_txs;
+ if (a.fBloomFilter != b.fBloomFilter) return a.fBloomFilter;
+ return a.m_connected > b.m_connected;
+}
+
+// Pick out the potential block-relay only peers, and sort them by last block time.
+static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ if (a.m_relay_txs != b.m_relay_txs) return a.m_relay_txs;
+ if (a.m_last_block_time != b.m_last_block_time) return a.m_last_block_time < b.m_last_block_time;
+ if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices;
+ return a.m_connected > b.m_connected;
+}
+
+/**
+ * Sort eviction candidates by network/localhost and connection uptime.
+ * Candidates near the beginning are more likely to be evicted, and those
+ * near the end are more likely to be protected, e.g. less likely to be evicted.
+ * - First, nodes that are not `is_local` and that do not belong to `network`,
+ * sorted by increasing uptime (from most recently connected to connected longer).
+ * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime.
+ */
+struct CompareNodeNetworkTime {
+ const bool m_is_local;
+ const Network m_network;
+ CompareNodeNetworkTime(bool is_local, Network network) : m_is_local(is_local), m_network(network) {}
+ bool operator()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const
+ {
+ if (m_is_local && a.m_is_local != b.m_is_local) return b.m_is_local;
+ if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network;
+ return a.m_connected > b.m_connected;
+ };
+};
+
+//! Sort an array by the specified comparator, then erase the last K elements where predicate is true.
+template <typename T, typename Comparator>
+static void EraseLastKElements(
+ std::vector<T>& elements, Comparator comparator, size_t k,
+ std::function<bool(const NodeEvictionCandidate&)> predicate = [](const NodeEvictionCandidate& n) { return true; })
+{
+ std::sort(elements.begin(), elements.end(), comparator);
+ size_t eraseSize = std::min(k, elements.size());
+ elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end());
+}
+
+void ProtectNoBanConnections(std::vector<NodeEvictionCandidate>& eviction_candidates)
+{
+ eviction_candidates.erase(std::remove_if(eviction_candidates.begin(), eviction_candidates.end(),
+ [](NodeEvictionCandidate const& n) {
+ return n.m_noban;
+ }),
+ eviction_candidates.end());
+}
+
+void ProtectOutboundConnections(std::vector<NodeEvictionCandidate>& eviction_candidates)
+{
+ eviction_candidates.erase(std::remove_if(eviction_candidates.begin(), eviction_candidates.end(),
+ [](NodeEvictionCandidate const& n) {
+ return n.m_conn_type != ConnectionType::INBOUND;
+ }),
+ eviction_candidates.end());
+}
+
+void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates)
+{
+ // Protect the half of the remaining nodes which have been connected the longest.
+ // This replicates the non-eviction implicit behavior, and precludes attacks that start later.
+ // To favorise the diversity of our peer connections, reserve up to half of these protected
+ // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime
+ // overall. This helps protect these higher-latency peers that tend to be otherwise
+ // disadvantaged under our eviction criteria.
+ const size_t initial_size = eviction_candidates.size();
+ const size_t total_protect_size{initial_size / 2};
+
+ // Disadvantaged networks to protect. In the case of equal counts, earlier array members
+ // have the first opportunity to recover unused slots from the previous iteration.
+ struct Net { bool is_local; Network id; size_t count; };
+ std::array<Net, 4> networks{
+ {{false, NET_CJDNS, 0}, {false, NET_I2P, 0}, {/*localhost=*/true, NET_MAX, 0}, {false, NET_ONION, 0}}};
+
+ // Count and store the number of eviction candidates per network.
+ for (Net& n : networks) {
+ n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(),
+ [&n](const NodeEvictionCandidate& c) {
+ return n.is_local ? c.m_is_local : c.m_network == n.id;
+ });
+ }
+ // Sort `networks` by ascending candidate count, to give networks having fewer candidates
+ // the first opportunity to recover unused protected slots from the previous iteration.
+ std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; });
+
+ // Protect up to 25% of the eviction candidates by disadvantaged network.
+ const size_t max_protect_by_network{total_protect_size / 2};
+ size_t num_protected{0};
+
+ while (num_protected < max_protect_by_network) {
+ // Count the number of disadvantaged networks from which we have peers to protect.
+ auto num_networks = std::count_if(networks.begin(), networks.end(), [](const Net& n) { return n.count; });
+ if (num_networks == 0) {
+ break;
+ }
+ const size_t disadvantaged_to_protect{max_protect_by_network - num_protected};
+ const size_t protect_per_network{std::max(disadvantaged_to_protect / num_networks, static_cast<size_t>(1))};
+ // Early exit flag if there are no remaining candidates by disadvantaged network.
+ bool protected_at_least_one{false};
+
+ for (Net& n : networks) {
+ if (n.count == 0) continue;
+ const size_t before = eviction_candidates.size();
+ EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id),
+ protect_per_network, [&n](const NodeEvictionCandidate& c) {
+ return n.is_local ? c.m_is_local : c.m_network == n.id;
+ });
+ const size_t after = eviction_candidates.size();
+ if (before > after) {
+ protected_at_least_one = true;
+ const size_t delta{before - after};
+ num_protected += delta;
+ if (num_protected >= max_protect_by_network) {
+ break;
+ }
+ n.count -= delta;
+ }
+ }
+ if (!protected_at_least_one) {
+ break;
+ }
+ }
+
+ // Calculate how many we removed, and update our total number of peers that
+ // we want to protect based on uptime accordingly.
+ assert(num_protected == initial_size - eviction_candidates.size());
+ const size_t remaining_to_protect{total_protect_size - num_protected};
+ EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect);
+}
+
+[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
+{
+ // Protect connections with certain characteristics
+
+ ProtectNoBanConnections(vEvictionCandidates);
+
+ ProtectOutboundConnections(vEvictionCandidates);
+
+ // Deterministically select 4 peers to protect by netgroup.
+ // An attacker cannot predict which netgroups will be protected
+ EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
+ // Protect the 8 nodes with the lowest minimum ping time.
+ // An attacker cannot manipulate this metric without physically moving nodes closer to the target.
+ EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
+ // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool.
+ // An attacker cannot manipulate this metric without performing useful work.
+ EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
+ // Protect up to 8 non-tx-relay peers that have sent us novel blocks.
+ EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8,
+ [](const NodeEvictionCandidate& n) { return !n.m_relay_txs && n.fRelevantServices; });
+
+ // Protect 4 nodes that most recently sent us novel blocks.
+ // An attacker cannot manipulate this metric without performing useful work.
+ EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
+
+ // Protect some of the remaining eviction candidates by ratios of desirable
+ // or disadvantaged characteristics.
+ ProtectEvictionCandidatesByRatio(vEvictionCandidates);
+
+ if (vEvictionCandidates.empty()) return std::nullopt;
+
+ // If any remaining peers are preferred for eviction consider only them.
+ // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks)
+ // then we probably don't want to evict it no matter what.
+ if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) {
+ vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(),
+ [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end());
+ }
+
+ // Identify the network group with the most connections and youngest member.
+ // (vEvictionCandidates is already sorted by reverse connect time)
+ uint64_t naMostConnections;
+ unsigned int nMostConnections = 0;
+ std::chrono::seconds nMostConnectionsTime{0};
+ std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes;
+ for (const NodeEvictionCandidate &node : vEvictionCandidates) {
+ std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup];
+ group.push_back(node);
+ const auto grouptime{group[0].m_connected};
+
+ if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) {
+ nMostConnections = group.size();
+ nMostConnectionsTime = grouptime;
+ naMostConnections = node.nKeyedNetGroup;
+ }
+ }
+
+ // Reduce to the network group with the most connections
+ vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]);
+
+ // Disconnect from the network group with the most connections
+ return vEvictionCandidates.front().id;
+}
diff --git a/src/node/eviction.h b/src/node/eviction.h
new file mode 100644
index 0000000000..1bb32e5327
--- /dev/null
+++ b/src/node/eviction.h
@@ -0,0 +1,69 @@
+// 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_EVICTION_H
+#define BITCOIN_NODE_EVICTION_H
+
+#include <node/connection_types.h>
+#include <net_permissions.h>
+
+#include <chrono>
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+typedef int64_t NodeId;
+
+struct NodeEvictionCandidate {
+ NodeId id;
+ std::chrono::seconds m_connected;
+ std::chrono::microseconds m_min_ping_time;
+ std::chrono::seconds m_last_block_time;
+ std::chrono::seconds m_last_tx_time;
+ bool fRelevantServices;
+ bool m_relay_txs;
+ bool fBloomFilter;
+ uint64_t nKeyedNetGroup;
+ bool prefer_evict;
+ bool m_is_local;
+ Network m_network;
+ bool m_noban;
+ ConnectionType m_conn_type;
+};
+
+/**
+ * Select an inbound peer to evict after filtering out (protecting) peers having
+ * distinct, difficult-to-forge characteristics. The protection logic picks out
+ * fixed numbers of desirable peers per various criteria, followed by (mostly)
+ * ratios of desirable or disadvantaged peers. If any eviction candidates
+ * remain, the selection logic chooses a peer to evict.
+ */
+[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates);
+
+/** Protect desirable or disadvantaged inbound peers from eviction by ratio.
+ *
+ * This function protects half of the peers which have been connected the
+ * longest, to replicate the non-eviction implicit behavior and preclude attacks
+ * that start later.
+ *
+ * Half of these protected spots (1/4 of the total) are reserved for the
+ * following categories of peers, sorted by longest uptime, even if they're not
+ * longest uptime overall:
+ *
+ * - onion peers connected via our tor control service
+ *
+ * - localhost peers, as manually configured hidden services not using
+ * `-bind=addr[:port]=onion` will not be detected as inbound onion connections
+ *
+ * - I2P peers
+ *
+ * - CJDNS peers
+ *
+ * This helps protect these privacy network peers, which tend to be otherwise
+ * disadvantaged under our eviction criteria for their higher min ping times
+ * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections.
+ */
+void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates);
+
+#endif // BITCOIN_NODE_EVICTION_H
diff --git a/src/node/ui_interface.cpp b/src/node/interface_ui.cpp
index a3a6ede39a..fa90d6fda7 100644
--- a/src/node/ui_interface.cpp
+++ b/src/node/interface_ui.cpp
@@ -2,7 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <node/ui_interface.h>
+#include <node/interface_ui.h>
#include <util/translation.h>
@@ -53,7 +53,7 @@ void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return
void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); }
void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); }
void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); }
-void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyHeaderTip(s, i); }
+void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, int64_t height, int64_t timestamp, bool presync) { return g_ui_signals.NotifyHeaderTip(s, height, timestamp, presync); }
void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); }
bool InitError(const bilingual_str& str)
diff --git a/src/node/ui_interface.h b/src/node/interface_ui.h
index d02238b549..316d75167e 100644
--- a/src/node/ui_interface.h
+++ b/src/node/interface_ui.h
@@ -3,8 +3,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#ifndef BITCOIN_NODE_UI_INTERFACE_H
-#define BITCOIN_NODE_UI_INTERFACE_H
+#ifndef BITCOIN_NODE_INTERFACE_UI_H
+#define BITCOIN_NODE_INTERFACE_UI_H
#include <functional>
#include <memory>
@@ -105,7 +105,7 @@ public:
ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*);
/** Best header has changed */
- ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, const CBlockIndex*);
+ ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, int64_t height, int64_t timestamp, bool presync);
/** Banlist did change. */
ADD_SIGNALS_DECL_WRAPPER(BannedListChanged, void, void);
@@ -120,4 +120,4 @@ constexpr auto AbortError = InitError;
extern CClientUIInterface uiInterface;
-#endif // BITCOIN_NODE_UI_INTERFACE_H
+#endif // BITCOIN_NODE_INTERFACE_UI_H
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 954bd1c31d..7a15f3649b 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -4,15 +4,19 @@
#include <addrdb.h>
#include <banman.h>
+#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <deploymentstatus.h>
#include <external_signer.h>
+#include <index/blockfilterindex.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <interfaces/wallet.h>
+#include <kernel/chain.h>
+#include <kernel/mempool_entry.h>
#include <mapport.h>
#include <net.h>
#include <net_processing.h>
@@ -21,8 +25,8 @@
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
+#include <node/interface_ui.h>
#include <node/transaction.h>
-#include <node/ui_interface.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <policy/policy.h>
@@ -35,7 +39,6 @@
#include <shutdown.h>
#include <support/allocators/secure.h>
#include <sync.h>
-#include <timedata.h>
#include <txmempool.h>
#include <uint256.h>
#include <univalue.h>
@@ -61,11 +64,13 @@ using interfaces::BlockTip;
using interfaces::Chain;
using interfaces::FoundBlock;
using interfaces::Handler;
-using interfaces::MakeHandler;
+using interfaces::MakeSignalHandler;
using interfaces::Node;
using interfaces::WalletLoader;
namespace node {
+// All members of the classes in this namespace are intentionally public, as the
+// classes themselves are private.
namespace {
#ifdef ENABLE_EXTERNAL_SIGNER
class ExternalSignerImpl : public interfaces::ExternalSigner
@@ -73,15 +78,12 @@ class ExternalSignerImpl : public interfaces::ExternalSigner
public:
ExternalSignerImpl(::ExternalSigner signer) : m_signer(std::move(signer)) {}
std::string getName() override { return m_signer.m_name; }
-private:
::ExternalSigner m_signer;
};
#endif
class NodeImpl : public Node
{
-private:
- ChainstateManager& chainman() { return *Assert(m_context->chainman); }
public:
explicit NodeImpl(NodeContext& context) { setContext(&context); }
void initLogging() override { InitLogging(*Assert(m_context->args)); }
@@ -90,8 +92,16 @@ public:
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
bool baseInitialize() override
{
- return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(gArgs, /*use_syscall_sandbox=*/false) && AppInitSanityChecks() &&
- AppInitLockDataDirectory() && AppInitInterfaces(*m_context);
+ if (!AppInitBasicSetup(gArgs)) return false;
+ if (!AppInitParameterInteraction(gArgs, /*use_syscall_sandbox=*/false)) return false;
+
+ m_context->kernel = std::make_unique<kernel::Context>();
+ if (!AppInitSanityChecks(*m_context->kernel)) return false;
+
+ if (!AppInitLockDataDirectory()) return false;
+ if (!AppInitInterfaces(*m_context)) return false;
+
+ return true;
}
bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override
{
@@ -112,6 +122,46 @@ public:
}
}
bool shutdownRequested() override { return ShutdownRequested(); }
+ bool isSettingIgnored(const std::string& name) override
+ {
+ bool ignored = false;
+ gArgs.LockSettings([&](util::Settings& settings) {
+ if (auto* options = util::FindKey(settings.command_line_options, name)) {
+ ignored = !options->empty();
+ }
+ });
+ return ignored;
+ }
+ util::SettingsValue getPersistentSetting(const std::string& name) override { return gArgs.GetPersistentSetting(name); }
+ void updateRwSetting(const std::string& name, const util::SettingsValue& value) override
+ {
+ gArgs.LockSettings([&](util::Settings& settings) {
+ if (value.isNull()) {
+ settings.rw_settings.erase(name);
+ } else {
+ settings.rw_settings[name] = value;
+ }
+ });
+ gArgs.WriteSettingsFile();
+ }
+ void forceSetting(const std::string& name, const util::SettingsValue& value) override
+ {
+ gArgs.LockSettings([&](util::Settings& settings) {
+ if (value.isNull()) {
+ settings.forced_settings.erase(name);
+ } else {
+ settings.forced_settings[name] = value;
+ }
+ });
+ }
+ void resetSettings() override
+ {
+ gArgs.WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true);
+ gArgs.LockSettings([&](util::Settings& settings) {
+ settings.rw_settings.clear();
+ });
+ gArgs.WriteSettingsFile();
+ }
void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); }
bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); }
size_t getNodeCount(ConnectionDirection flags) override
@@ -228,7 +278,7 @@ public:
uint256 getBestBlockHash() override
{
const CBlockIndex* tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip());
- return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash();
+ return tip ? tip->GetBlockHash() : chainman().GetParams().GenesisBlock().GetHash();
}
int64_t getLastBlockTime() override
{
@@ -236,16 +286,11 @@ public:
if (chainman().ActiveChain().Tip()) {
return chainman().ActiveChain().Tip()->GetBlockTime();
}
- return Params().GenesisBlock().GetBlockTime(); // Genesis block's time of current network
+ return chainman().GetParams().GenesisBlock().GetBlockTime(); // Genesis block's time of current network
}
double getVerificationProgress() override
{
- const CBlockIndex* tip;
- {
- LOCK(::cs_main);
- tip = chainman().ActiveChain().Tip();
- }
- return GuessVerificationProgress(Params().TxData(), tip);
+ return GuessVerificationProgress(chainman().GetParams().TxData(), WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()));
}
bool isInitialBlockDownload() override {
return chainman().ActiveChainstate().IsInitialBlockDownload();
@@ -259,7 +304,11 @@ public:
}
}
bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); }
- CFeeRate getDustRelayFee() override { return ::dustRelayFee; }
+ CFeeRate getDustRelayFee() override
+ {
+ if (!m_context->mempool) return CFeeRate{DUST_RELAY_TX_FEE};
+ return m_context->mempool->m_dust_relay_feerate;
+ }
UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override
{
JSONRPCRequest req;
@@ -287,53 +336,52 @@ public:
}
std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override
{
- return MakeHandler(::uiInterface.InitMessage_connect(fn));
+ return MakeSignalHandler(::uiInterface.InitMessage_connect(fn));
}
std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) override
{
- return MakeHandler(::uiInterface.ThreadSafeMessageBox_connect(fn));
+ return MakeSignalHandler(::uiInterface.ThreadSafeMessageBox_connect(fn));
}
std::unique_ptr<Handler> handleQuestion(QuestionFn fn) override
{
- return MakeHandler(::uiInterface.ThreadSafeQuestion_connect(fn));
+ return MakeSignalHandler(::uiInterface.ThreadSafeQuestion_connect(fn));
}
std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override
{
- return MakeHandler(::uiInterface.ShowProgress_connect(fn));
+ return MakeSignalHandler(::uiInterface.ShowProgress_connect(fn));
}
std::unique_ptr<Handler> handleInitWallet(InitWalletFn fn) override
{
- return MakeHandler(::uiInterface.InitWallet_connect(fn));
+ return MakeSignalHandler(::uiInterface.InitWallet_connect(fn));
}
std::unique_ptr<Handler> handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) override
{
- return MakeHandler(::uiInterface.NotifyNumConnectionsChanged_connect(fn));
+ return MakeSignalHandler(::uiInterface.NotifyNumConnectionsChanged_connect(fn));
}
std::unique_ptr<Handler> handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn fn) override
{
- return MakeHandler(::uiInterface.NotifyNetworkActiveChanged_connect(fn));
+ return MakeSignalHandler(::uiInterface.NotifyNetworkActiveChanged_connect(fn));
}
std::unique_ptr<Handler> handleNotifyAlertChanged(NotifyAlertChangedFn fn) override
{
- return MakeHandler(::uiInterface.NotifyAlertChanged_connect(fn));
+ return MakeSignalHandler(::uiInterface.NotifyAlertChanged_connect(fn));
}
std::unique_ptr<Handler> handleBannedListChanged(BannedListChangedFn fn) override
{
- return MakeHandler(::uiInterface.BannedListChanged_connect(fn));
+ return MakeSignalHandler(::uiInterface.BannedListChanged_connect(fn));
}
std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) override
{
- return MakeHandler(::uiInterface.NotifyBlockTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
+ return MakeSignalHandler(::uiInterface.NotifyBlockTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()},
GuessVerificationProgress(Params().TxData(), block));
}));
}
std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override
{
- return MakeHandler(
- ::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
- fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()},
- /* verification progress is unused when a header was received */ 0);
+ return MakeSignalHandler(
+ ::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, int64_t height, int64_t timestamp, bool presync) {
+ fn(sync_state, BlockTip{(int)height, timestamp, uint256{}}, presync);
}));
}
NodeContext* context() override { return m_context; }
@@ -341,6 +389,7 @@ public:
{
m_context = context;
}
+ ChainstateManager& chainman() { return *Assert(m_context->chainman); }
NodeContext* m_context{nullptr};
};
@@ -353,6 +402,7 @@ bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<Rec
if (block.m_max_time) *block.m_max_time = index->GetBlockTimeMax();
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_data) {
REVERSE_LOCK(lock);
@@ -378,11 +428,11 @@ public:
}
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override
{
- m_notifications->blockConnected(*block, index->nHeight);
+ m_notifications->blockConnected(kernel::MakeBlockInfo(index, block.get()));
}
void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override
{
- m_notifications->blockDisconnected(*block, index->nHeight);
+ m_notifications->blockDisconnected(kernel::MakeBlockInfo(index, block.get()));
}
void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
{
@@ -426,7 +476,7 @@ public:
// try to handle the request. Otherwise, reraise the exception.
if (!last_handler) {
const UniValue& code = e["code"];
- if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) {
+ if (code.isNum() && code.getInt<int>() == RPC_WALLET_NOT_FOUND) {
return false;
}
}
@@ -452,67 +502,73 @@ public:
class ChainImpl : public Chain
{
-private:
- ChainstateManager& chainman() { return *Assert(m_node.chainman); }
public:
explicit ChainImpl(NodeContext& node) : m_node(node) {}
std::optional<int> getHeight() override
{
- LOCK(::cs_main);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- int height = active.Height();
- if (height >= 0) {
- return height;
- }
- return std::nullopt;
+ const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())};
+ return height >= 0 ? std::optional{height} : std::nullopt;
}
uint256 getBlockHash(int height) override
{
LOCK(::cs_main);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- CBlockIndex* block = active[height];
- assert(block);
- return block->GetBlockHash();
+ return Assert(chainman().ActiveChain()[height])->GetBlockHash();
}
bool haveBlockOnDisk(int height) override
{
- LOCK(cs_main);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- CBlockIndex* block = active[height];
+ LOCK(::cs_main);
+ const CBlockIndex* block{chainman().ActiveChain()[height]};
return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0;
}
CBlockLocator getTipLocator() override
{
- LOCK(cs_main);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- return active.GetLocator();
+ LOCK(::cs_main);
+ return chainman().ActiveChain().GetLocator();
+ }
+ CBlockLocator getActiveChainLocator(const uint256& block_hash) override
+ {
+ LOCK(::cs_main);
+ const CBlockIndex* index = chainman().m_blockman.LookupBlockIndex(block_hash);
+ return GetLocator(index);
}
std::optional<int> findLocatorFork(const CBlockLocator& locator) override
{
- LOCK(cs_main);
- const CChainState& active = Assert(m_node.chainman)->ActiveChainstate();
- if (const CBlockIndex* fork = active.FindForkInGlobalIndex(locator)) {
+ LOCK(::cs_main);
+ if (const CBlockIndex* fork = chainman().ActiveChainstate().FindForkInGlobalIndex(locator)) {
return fork->nHeight;
}
return std::nullopt;
}
+ bool hasBlockFilterIndex(BlockFilterType filter_type) override
+ {
+ return GetBlockFilterIndex(filter_type) != nullptr;
+ }
+ std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) override
+ {
+ const BlockFilterIndex* block_filter_index{GetBlockFilterIndex(filter_type)};
+ if (!block_filter_index) return std::nullopt;
+
+ BlockFilter filter;
+ const CBlockIndex* index{WITH_LOCK(::cs_main, return chainman().m_blockman.LookupBlockIndex(block_hash))};
+ if (index == nullptr || !block_filter_index->LookupFilter(index, filter)) return std::nullopt;
+ return filter.GetFilter().MatchAny(filter_set);
+ }
bool findBlock(const uint256& hash, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active);
+ return FillBlock(chainman().m_blockman.LookupBlockIndex(hash), block, lock, chainman().ActiveChain());
}
bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
+ const CChain& active = chainman().ActiveChain();
return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active);
}
bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out) override
{
WAIT_LOCK(cs_main, lock);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) {
+ 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);
}
@@ -522,18 +578,17 @@ public:
bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, const FoundBlock& ancestor_out) override
{
WAIT_LOCK(cs_main, lock);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash);
- const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash);
+ 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, active);
+ return FillBlock(ancestor, ancestor_out, lock, chainman().ActiveChain());
}
bool findCommonAncestor(const uint256& block_hash1, const uint256& block_hash2, const FoundBlock& ancestor_out, const FoundBlock& block1_out, const FoundBlock& block2_out) override
{
WAIT_LOCK(cs_main, lock);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1);
- const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2);
+ const CChain& active = chainman().ActiveChain();
+ const CBlockIndex* block1 = chainman().m_blockman.LookupBlockIndex(block_hash1);
+ const CBlockIndex* block2 = chainman().m_blockman.LookupBlockIndex(block_hash2);
const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr;
// Using & instead of && below to avoid short circuiting and leaving
// output uninitialized. Cast bool to int to avoid -Wbitwise-instead-of-logical
@@ -545,8 +600,8 @@ public:
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
double guessVerificationProgress(const uint256& block_hash) override
{
- LOCK(cs_main);
- return GuessVerificationProgress(Params().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash));
+ LOCK(::cs_main);
+ return GuessVerificationProgress(chainman().GetParams().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash));
}
bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override
{
@@ -605,8 +660,12 @@ public:
}
void getPackageLimits(unsigned int& limit_ancestor_count, unsigned int& limit_descendant_count) override
{
- limit_ancestor_count = gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
- limit_descendant_count = gArgs.GetIntArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
+ const CTxMemPool::Limits default_limits{};
+
+ const CTxMemPool::Limits& limits{m_node.mempool ? m_node.mempool->m_limits : default_limits};
+
+ limit_ancestor_count = limits.ancestor_count;
+ limit_descendant_count = limits.descendant_count;
}
bool checkChainLimits(const CTransactionRef& tx) override
{
@@ -614,15 +673,11 @@ public:
LockPoints lp;
CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp);
CTxMemPool::setEntries ancestors;
- auto limit_ancestor_count = gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
- auto limit_ancestor_size = gArgs.GetIntArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000;
- auto limit_descendant_count = gArgs.GetIntArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
- auto limit_descendant_size = gArgs.GetIntArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000;
+ const CTxMemPool::Limits& limits{m_node.mempool->m_limits};
std::string unused_error_string;
LOCK(m_node.mempool->cs);
return m_node.mempool->CalculateMemPoolAncestors(
- entry, ancestors, limit_ancestor_count, limit_ancestor_size,
- limit_descendant_count, limit_descendant_size, unused_error_string);
+ entry, ancestors, limits, unused_error_string);
}
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
{
@@ -637,15 +692,27 @@ public:
CFeeRate mempoolMinFee() override
{
if (!m_node.mempool) return {};
- return m_node.mempool->GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ return m_node.mempool->GetMinFee();
+ }
+ CFeeRate relayMinFee() override
+ {
+ if (!m_node.mempool) return CFeeRate{DEFAULT_MIN_RELAY_TX_FEE};
+ return m_node.mempool->m_min_relay_feerate;
+ }
+ CFeeRate relayIncrementalFee() override
+ {
+ if (!m_node.mempool) return CFeeRate{DEFAULT_INCREMENTAL_RELAY_FEE};
+ return m_node.mempool->m_incremental_relay_feerate;
+ }
+ CFeeRate relayDustFee() override
+ {
+ if (!m_node.mempool) return CFeeRate{DUST_RELAY_TX_FEE};
+ return m_node.mempool->m_dust_relay_feerate;
}
- CFeeRate relayMinFee() override { return ::minRelayTxFee; }
- CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; }
- CFeeRate relayDustFee() override { return ::dustRelayFee; }
bool havePruned() override
{
- LOCK(cs_main);
- return m_node.chainman->m_blockman.m_have_pruned;
+ LOCK(::cs_main);
+ return chainman().m_blockman.m_have_pruned;
}
bool isReadyToBroadcast() override { return !node::fImporting && !node::fReindex && !isInitialBlockDownload(); }
bool isInitialBlockDownload() override {
@@ -665,11 +732,7 @@ public:
}
void waitForNotificationsIfTipChanged(const uint256& old_tip) override
{
- if (!old_tip.IsNull()) {
- LOCK(::cs_main);
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
- if (old_tip == active.Tip()->GetBlockHash()) return;
- }
+ if (!old_tip.IsNull() && old_tip == WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()->GetBlockHash())) return;
SyncWithValidationInterfaceQueue();
}
std::unique_ptr<Handler> handleRpc(const CRPCCommand& command) override
@@ -719,6 +782,13 @@ public:
notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */);
}
}
+ bool hasAssumedValidChain() override
+ {
+ return chainman().IsSnapshotActive();
+ }
+
+ NodeContext* context() override { return &m_node; }
+ ChainstateManager& chainman() { return *Assert(m_node.chainman); }
NodeContext& m_node;
};
} // namespace
diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp
new file mode 100644
index 0000000000..8c929e5e0d
--- /dev/null
+++ b/src/node/mempool_args.cpp
@@ -0,0 +1,100 @@
+// 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.
+
+#include <node/mempool_args.h>
+
+#include <kernel/mempool_limits.h>
+#include <kernel/mempool_options.h>
+
+#include <chainparams.h>
+#include <consensus/amount.h>
+#include <logging.h>
+#include <policy/feerate.h>
+#include <policy/policy.h>
+#include <script/standard.h>
+#include <tinyformat.h>
+#include <util/error.h>
+#include <util/moneystr.h>
+#include <util/system.h>
+#include <util/translation.h>
+
+#include <chrono>
+#include <memory>
+
+using kernel::MemPoolLimits;
+using kernel::MemPoolOptions;
+
+namespace {
+void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limits)
+{
+ mempool_limits.ancestor_count = argsman.GetIntArg("-limitancestorcount", mempool_limits.ancestor_count);
+
+ if (auto vkb = argsman.GetIntArg("-limitancestorsize")) mempool_limits.ancestor_size_vbytes = *vkb * 1'000;
+
+ mempool_limits.descendant_count = argsman.GetIntArg("-limitdescendantcount", mempool_limits.descendant_count);
+
+ if (auto vkb = argsman.GetIntArg("-limitdescendantsize")) mempool_limits.descendant_size_vbytes = *vkb * 1'000;
+}
+}
+
+std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts)
+{
+ mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio);
+
+ if (auto mb = argsman.GetIntArg("-maxmempool")) mempool_opts.max_size_bytes = *mb * 1'000'000;
+
+ if (auto hours = argsman.GetIntArg("-mempoolexpiry")) mempool_opts.expiry = std::chrono::hours{*hours};
+
+ // incremental relay fee sets the minimum feerate increase necessary for replacement in the mempool
+ // and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting.
+ if (argsman.IsArgSet("-incrementalrelayfee")) {
+ 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", ""));
+ }
+ }
+
+ if (argsman.IsArgSet("-minrelaytxfee")) {
+ if (std::optional<CAmount> min_relay_feerate = ParseMoney(argsman.GetArg("-minrelaytxfee", ""))) {
+ // 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", ""));
+ }
+ } else if (mempool_opts.incremental_relay_feerate > mempool_opts.min_relay_feerate) {
+ // Allow only setting incremental fee to control both
+ mempool_opts.min_relay_feerate = mempool_opts.incremental_relay_feerate;
+ LogPrintf("Increasing minrelaytxfee to %s to match incrementalrelayfee\n", mempool_opts.min_relay_feerate.ToString());
+ }
+
+ // Feerate used to define dust. Shouldn't be changed lightly as old
+ // implementations may inadvertently create non-standard transactions
+ if (argsman.IsArgSet("-dustrelayfee")) {
+ if (std::optional<CAmount> parsed = ParseMoney(argsman.GetArg("-dustrelayfee", ""))) {
+ mempool_opts.dust_relay_feerate = CFeeRate{parsed.value()};
+ } else {
+ return AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""));
+ }
+ }
+
+ mempool_opts.permit_bare_multisig = argsman.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
+
+ if (argsman.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER)) {
+ mempool_opts.max_datacarrier_bytes = argsman.GetIntArg("-datacarriersize", MAX_OP_RETURN_RELAY);
+ } else {
+ mempool_opts.max_datacarrier_bytes = std::nullopt;
+ }
+
+ 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());
+ }
+
+ mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf);
+
+ ApplyArgsManOptions(argsman, mempool_opts.limits);
+
+ return std::nullopt;
+}
diff --git a/src/node/mempool_args.h b/src/node/mempool_args.h
new file mode 100644
index 0000000000..52d8b4f265
--- /dev/null
+++ b/src/node/mempool_args.h
@@ -0,0 +1,27 @@
+// 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_MEMPOOL_ARGS_H
+#define BITCOIN_NODE_MEMPOOL_ARGS_H
+
+#include <optional>
+
+class ArgsManager;
+class CChainParams;
+struct bilingual_str;
+namespace kernel {
+struct MemPoolOptions;
+};
+
+/**
+ * Overlay the options set in \p argsman on top of corresponding members in \p mempool_opts.
+ * Returns an error if one was encountered.
+ *
+ * @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);
+
+
+#endif // BITCOIN_NODE_MEMPOOL_ARGS_H
diff --git a/src/node/mempool_persist_args.cpp b/src/node/mempool_persist_args.cpp
new file mode 100644
index 0000000000..4e775869c6
--- /dev/null
+++ b/src/node/mempool_persist_args.cpp
@@ -0,0 +1,23 @@
+// 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.
+
+#include <node/mempool_persist_args.h>
+
+#include <fs.h>
+#include <util/system.h>
+#include <validation.h>
+
+namespace node {
+
+bool ShouldPersistMempool(const ArgsManager& argsman)
+{
+ return argsman.GetBoolArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL);
+}
+
+fs::path MempoolPath(const ArgsManager& argsman)
+{
+ return argsman.GetDataDirNet() / "mempool.dat";
+}
+
+} // namespace node
diff --git a/src/node/mempool_persist_args.h b/src/node/mempool_persist_args.h
new file mode 100644
index 0000000000..f719ec62ab
--- /dev/null
+++ b/src/node/mempool_persist_args.h
@@ -0,0 +1,25 @@
+// 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_MEMPOOL_PERSIST_ARGS_H
+#define BITCOIN_NODE_MEMPOOL_PERSIST_ARGS_H
+
+#include <fs.h>
+
+class ArgsManager;
+
+namespace node {
+
+/**
+ * Default for -persistmempool, indicating whether the node should attempt to
+ * automatically load the mempool on start and save to disk on shutdown
+ */
+static constexpr bool DEFAULT_PERSIST_MEMPOOL{true};
+
+bool ShouldPersistMempool(const ArgsManager& argsman);
+fs::path MempoolPath(const ArgsManager& argsman);
+
+} // namespace node
+
+#endif // BITCOIN_NODE_MEMPOOL_PERSIST_ARGS_H
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index be5d58527b..e11ec5b0f1 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -30,7 +30,7 @@ namespace node {
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev)
{
int64_t nOldTime = pblock->nTime;
- int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast() + 1, GetAdjustedTime());
+ int64_t nNewTime{std::max<int64_t>(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch<std::chrono::seconds>(GetAdjustedTime()))};
if (nOldTime < nNewTime) {
pblock->nTime = nNewTime;
@@ -51,7 +51,7 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
block.vtx.at(0) = MakeTransactionRef(tx);
const CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock));
- GenerateCoinbaseCommitment(block, prev_block, Params().GetConsensus());
+ chainman.GenerateCoinbaseCommitment(block, prev_block);
block.hashMerkleRoot = BlockMerkleRoot(block);
}
@@ -62,8 +62,8 @@ BlockAssembler::Options::Options()
nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;
}
-BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options)
- : chainparams(params),
+BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options)
+ : chainparams{chainstate.m_chainman.GetParams()},
m_mempool(mempool),
m_chainstate(chainstate)
{
@@ -87,8 +87,8 @@ static BlockAssembler::Options DefaultOptions()
return options;
}
-BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params)
- : BlockAssembler(chainstate, mempool, params, DefaultOptions()) {}
+BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool)
+ : BlockAssembler(chainstate, mempool, DefaultOptions()) {}
void BlockAssembler::resetBlock()
{
@@ -105,7 +105,7 @@ void BlockAssembler::resetBlock()
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
{
- int64_t nTimeStart = GetTimeMicros();
+ const auto time_start{SteadyClock::now()};
resetBlock();
@@ -121,26 +121,29 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
pblocktemplate->vTxFees.push_back(-1); // updated at end
pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end
- LOCK2(cs_main, m_mempool.cs);
+ LOCK(::cs_main);
CBlockIndex* pindexPrev = m_chainstate.m_chain.Tip();
assert(pindexPrev != nullptr);
nHeight = pindexPrev->nHeight + 1;
- pblock->nVersion = g_versionbitscache.ComputeBlockVersion(pindexPrev, chainparams.GetConsensus());
+ pblock->nVersion = m_chainstate.m_chainman.m_versionbitscache.ComputeBlockVersion(pindexPrev, chainparams.GetConsensus());
// -regtest only: allow overriding block.nVersion with
// -blockversion=N to test forking scenarios
if (chainparams.MineBlocksOnDemand()) {
pblock->nVersion = gArgs.GetIntArg("-blockversion", pblock->nVersion);
}
- pblock->nTime = GetAdjustedTime();
+ pblock->nTime = TicksSinceEpoch<std::chrono::seconds>(GetAdjustedTime());
m_lock_time_cutoff = pindexPrev->GetMedianTimePast();
int nPackagesSelected = 0;
int nDescendantsUpdated = 0;
- addPackageTxs(nPackagesSelected, nDescendantsUpdated);
+ if (m_mempool) {
+ LOCK(m_mempool->cs);
+ addPackageTxs(*m_mempool, nPackagesSelected, nDescendantsUpdated);
+ }
- int64_t nTime1 = GetTimeMicros();
+ const auto time_1{SteadyClock::now()};
m_last_block_num_txs = nBlockTx;
m_last_block_weight = nBlockWeight;
@@ -154,7 +157,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
- pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
+ pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev);
pblocktemplate->vTxFees[0] = -nFees;
LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost);
@@ -167,12 +170,15 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]);
BlockValidationState state;
- if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) {
+ if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, GetAdjustedTime, false, false)) {
throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString()));
}
- int64_t nTime2 = GetTimeMicros();
+ const auto time_2{SteadyClock::now()};
- LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart));
+ LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n",
+ Ticks<MillisecondsDouble>(time_1 - time_start), nPackagesSelected, nDescendantsUpdated,
+ Ticks<MillisecondsDouble>(time_2 - time_1),
+ Ticks<MillisecondsDouble>(time_2 - time_start));
return std::move(pblocktemplate);
}
@@ -232,15 +238,19 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter)
}
}
-int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded,
- indexed_modified_transaction_set &mapModifiedTx)
+/** Add descendants of given transactions to mapModifiedTx with ancestor
+ * state updated assuming given transactions are inBlock. Returns number
+ * of updated descendants. */
+static int UpdatePackagesForAdded(const CTxMemPool& mempool,
+ const CTxMemPool::setEntries& alreadyAdded,
+ indexed_modified_transaction_set& mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs)
{
- AssertLockHeld(m_mempool.cs);
+ AssertLockHeld(mempool.cs);
int nDescendantsUpdated = 0;
for (CTxMemPool::txiter it : alreadyAdded) {
CTxMemPool::setEntries descendants;
- m_mempool.CalculateDescendants(it, descendants);
+ mempool.CalculateDescendants(it, descendants);
// Insert all descendants (not yet in block) into the modified set
for (CTxMemPool::txiter desc : descendants) {
if (alreadyAdded.count(desc)) {
@@ -250,35 +260,14 @@ int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& already
modtxiter mit = mapModifiedTx.find(desc);
if (mit == mapModifiedTx.end()) {
CTxMemPoolModifiedEntry modEntry(desc);
- modEntry.nSizeWithAncestors -= it->GetTxSize();
- modEntry.nModFeesWithAncestors -= it->GetModifiedFee();
- modEntry.nSigOpCostWithAncestors -= it->GetSigOpCost();
- mapModifiedTx.insert(modEntry);
- } else {
- mapModifiedTx.modify(mit, update_for_parent_inclusion(it));
+ mit = mapModifiedTx.insert(modEntry).first;
}
+ mapModifiedTx.modify(mit, update_for_parent_inclusion(it));
}
}
return nDescendantsUpdated;
}
-// Skip entries in mapTx that are already in a block or are present
-// in mapModifiedTx (which implies that the mapTx ancestor state is
-// stale due to ancestor inclusion in the block)
-// Also skip transactions that we've already failed to add. This can happen if
-// we consider a transaction in mapModifiedTx and it fails: we can then
-// potentially consider it again while walking mapTx. It's currently
-// guaranteed to fail again, but as a belt-and-suspenders check we put it in
-// failedTx and avoid re-evaluation, since the re-evaluation would be using
-// cached size/sigops/fee values that are not actually correct.
-bool BlockAssembler::SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set& mapModifiedTx, CTxMemPool::setEntries& failedTx)
-{
- AssertLockHeld(m_mempool.cs);
-
- assert(it != m_mempool.mapTx.end());
- return mapModifiedTx.count(it) || inBlock.count(it) || failedTx.count(it);
-}
-
void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, std::vector<CTxMemPool::txiter>& sortedEntries)
{
// Sort package by ancestor count
@@ -300,9 +289,9 @@ void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, std::ve
// Each time through the loop, we compare the best transaction in
// mapModifiedTxs with the next transaction in the mempool to decide what
// transaction package to work on next.
-void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated)
+void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSelected, int& nDescendantsUpdated)
{
- AssertLockHeld(m_mempool.cs);
+ AssertLockHeld(mempool.cs);
// mapModifiedTx will store sorted packages after they are modified
// because some of their txs are already in the block
@@ -310,11 +299,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
// Keep track of entries that failed inclusion, to avoid duplicate work
CTxMemPool::setEntries failedTx;
- // Start by adding all descendants of previously added txs to mapModifiedTx
- // and modifying them for their already included ancestors
- UpdatePackagesForAdded(inBlock, mapModifiedTx);
-
- CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = m_mempool.mapTx.get<ancestor_score>().begin();
+ CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = mempool.mapTx.get<ancestor_score>().begin();
CTxMemPool::txiter iter;
// Limit the number of attempts to add transactions to the block when it is
@@ -323,12 +308,27 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
int64_t nConsecutiveFailed = 0;
- while (mi != m_mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) {
+ while (mi != mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) {
// First try to find a new transaction in mapTx to evaluate.
- if (mi != m_mempool.mapTx.get<ancestor_score>().end() &&
- SkipMapTxEntry(m_mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) {
- ++mi;
- continue;
+ //
+ // Skip entries in mapTx that are already in a block or are present
+ // in mapModifiedTx (which implies that the mapTx ancestor state is
+ // stale due to ancestor inclusion in the block)
+ // Also skip transactions that we've already failed to add. This can happen if
+ // we consider a transaction in mapModifiedTx and it fails: we can then
+ // potentially consider it again while walking mapTx. It's currently
+ // guaranteed to fail again, but as a belt-and-suspenders check we put it in
+ // failedTx and avoid re-evaluation, since the re-evaluation would be using
+ // cached size/sigops/fee values that are not actually correct.
+ /** Return true if given transaction from mapTx has already been evaluated,
+ * or if the transaction's cached data in mapTx is incorrect. */
+ if (mi != mempool.mapTx.get<ancestor_score>().end()) {
+ auto it = mempool.mapTx.project<0>(mi);
+ assert(it != mempool.mapTx.end());
+ if (mapModifiedTx.count(it) || inBlock.count(it) || failedTx.count(it)) {
+ ++mi;
+ continue;
+ }
}
// Now that mi is not stale, determine which transaction to evaluate:
@@ -336,13 +336,13 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
bool fUsingModified = false;
modtxscoreiter modit = mapModifiedTx.get<ancestor_score>().begin();
- if (mi == m_mempool.mapTx.get<ancestor_score>().end()) {
+ if (mi == mempool.mapTx.get<ancestor_score>().end()) {
// We're out of entries in mapTx; use the entry from mapModifiedTx
iter = modit->iter;
fUsingModified = true;
} else {
// Try to compare the mapTx entry to the mapModifiedTx entry
- iter = m_mempool.mapTx.project<0>(mi);
+ iter = mempool.mapTx.project<0>(mi);
if (modit != mapModifiedTx.get<ancestor_score>().end() &&
CompareTxMemPoolEntryByAncestorFee()(*modit, CTxMemPoolModifiedEntry(iter))) {
// The best entry in mapModifiedTx has higher score
@@ -395,9 +395,8 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
}
CTxMemPool::setEntries ancestors;
- uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
- m_mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
+ mempool.CalculateMemPoolAncestors(*iter, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
onlyUnconfirmed(ancestors);
ancestors.insert(iter);
@@ -427,7 +426,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
++nPackagesSelected;
// Update transactions that depend on each of these
- nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx);
+ nDescendantsUpdated += UpdatePackagesForAdded(mempool, ancestors, mapModifiedTx);
}
}
} // namespace node
diff --git a/src/node/miner.h b/src/node/miner.h
index c8093ec883..7269ce1186 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -116,7 +116,7 @@ struct update_for_parent_inclusion
void operator() (CTxMemPoolModifiedEntry &e)
{
- e.nModFeesWithAncestors -= iter->GetFee();
+ e.nModFeesWithAncestors -= iter->GetModifiedFee();
e.nSizeWithAncestors -= iter->GetTxSize();
e.nSigOpCostWithAncestors -= iter->GetSigOpCost();
}
@@ -147,8 +147,8 @@ private:
int64_t m_lock_time_cutoff;
const CChainParams& chainparams;
- const CTxMemPool& m_mempool;
- CChainState& m_chainstate;
+ const CTxMemPool* const m_mempool;
+ Chainstate& m_chainstate;
public:
struct Options {
@@ -157,8 +157,8 @@ public:
CFeeRate blockMinFeeRate;
};
- explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params);
- explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options);
+ explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool);
+ explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options);
/** Construct a new block template with coinbase to scriptPubKeyIn */
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn);
@@ -177,7 +177,7 @@ private:
/** Add transactions based on feerate including unconfirmed ancestors
* Increments nPackagesSelected / nDescendantsUpdated with corresponding
* statistics from the package selection (for logging statistics). */
- void addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs);
+ void addPackageTxs(const CTxMemPool& mempool, int& nPackagesSelected, int& nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs);
// helper functions for addPackageTxs()
/** Remove confirmed (inBlock) entries from given set */
@@ -189,15 +189,8 @@ private:
* These checks should always succeed, and they're here
* only as an extra check in case of suboptimal node configuration */
bool TestPackageTransactions(const CTxMemPool::setEntries& package) const;
- /** Return true if given transaction from mapTx has already been evaluated,
- * or if the transaction's cached data in mapTx is incorrect. */
- bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set& mapModifiedTx, CTxMemPool::setEntries& failedTx) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs);
/** Sort the package in an order that is valid to appear in a block */
void SortForBlock(const CTxMemPool::setEntries& package, std::vector<CTxMemPool::txiter>& sortedEntries);
- /** Add descendants of given transactions to mapModifiedTx with ancestor
- * state updated assuming given transactions are inBlock. Returns number
- * of updated descendants. */
- int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set& mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs);
};
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev);
diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp
index 5a932f435d..ca3fc0955d 100644
--- a/src/node/psbt.cpp
+++ b/src/node/psbt.cpp
@@ -59,7 +59,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
}
// Check if it is final
- if (!utxo.IsNull() && !PSBTInputSigned(input)) {
+ if (!PSBTInputSignedAndVerified(psbtx, i, &txdata)) {
input_analysis.is_final = false;
// Figure out what is missing
@@ -137,7 +137,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
if (success) {
CTransaction ctx = CTransaction(mtx);
- size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
+ size_t size(GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS), ::nBytesPerSigOp));
result.estimated_vsize = size;
// Estimate fee rate
CFeeRate feerate(fee, size);
diff --git a/src/node/transaction.h b/src/node/transaction.h
index b7cf225636..0604754a46 100644
--- a/src/node/transaction.h
+++ b/src/node/transaction.h
@@ -5,7 +5,6 @@
#ifndef BITCOIN_NODE_TRANSACTION_H
#define BITCOIN_NODE_TRANSACTION_H
-#include <attributes.h>
#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <util/error.h>
diff --git a/src/node/txreconciliation.cpp b/src/node/txreconciliation.cpp
new file mode 100644
index 0000000000..ed04a78cec
--- /dev/null
+++ b/src/node/txreconciliation.cpp
@@ -0,0 +1,169 @@
+// 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.
+
+#include <node/txreconciliation.h>
+
+#include <util/check.h>
+#include <util/system.h>
+
+#include <unordered_map>
+#include <variant>
+
+
+namespace {
+
+/** Static salt component used to compute short txids for sketch construction, see BIP-330. */
+const std::string RECON_STATIC_SALT = "Tx Relay Salting";
+const HashWriter RECON_SALT_HASHER = TaggedHash(RECON_STATIC_SALT);
+
+/**
+ * Salt (specified by BIP-330) constructed from contributions from both peers. It is used
+ * to compute transaction short IDs, which are then used to construct a sketch representing a set
+ * of transactions we want to announce to the peer.
+ */
+uint256 ComputeSalt(uint64_t salt1, uint64_t salt2)
+{
+ // According to BIP-330, salts should be combined in ascending order.
+ return (HashWriter(RECON_SALT_HASHER) << std::min(salt1, salt2) << std::max(salt1, salt2)).GetSHA256();
+}
+
+/**
+ * Keeps track of txreconciliation-related per-peer state.
+ */
+class TxReconciliationState
+{
+public:
+ /**
+ * TODO: This field is public to ignore -Wunused-private-field. Make private once used in
+ * the following commits.
+ *
+ * Reconciliation protocol assumes using one role consistently: either a reconciliation
+ * initiator (requesting sketches), or responder (sending sketches). This defines our role,
+ * based on the direction of the p2p connection.
+ *
+ */
+ bool m_we_initiate;
+
+ /**
+ * TODO: These fields are public to ignore -Wunused-private-field. Make private once used in
+ * the following commits.
+ *
+ * These values are used to salt short IDs, which is necessary for transaction reconciliations.
+ */
+ uint64_t m_k0, m_k1;
+
+ TxReconciliationState(bool we_initiate, uint64_t k0, uint64_t k1) : m_we_initiate(we_initiate), m_k0(k0), m_k1(k1) {}
+};
+
+} // namespace
+
+/** Actual implementation for TxReconciliationTracker's data structure. */
+class TxReconciliationTracker::Impl
+{
+private:
+ mutable Mutex m_txreconciliation_mutex;
+
+ // Local protocol version
+ uint32_t m_recon_version;
+
+ /**
+ * Keeps track of txreconciliation states of eligible peers.
+ * For pre-registered peers, the locally generated salt is stored.
+ * For registered peers, the locally generated salt is forgotten, and the state (including
+ * "full" salt) is stored instead.
+ */
+ std::unordered_map<NodeId, std::variant<uint64_t, TxReconciliationState>> m_states GUARDED_BY(m_txreconciliation_mutex);
+
+public:
+ explicit Impl(uint32_t recon_version) : m_recon_version(recon_version) {}
+
+ uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+
+ LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Pre-register peer=%d\n", peer_id);
+ const uint64_t local_salt{GetRand(UINT64_MAX)};
+
+ // We do this exactly once per peer (which are unique by NodeId, see GetNewNodeId) so it's
+ // safe to assume we don't have this record yet.
+ Assume(m_states.emplace(peer_id, local_salt).second);
+ return local_salt;
+ }
+
+ ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, uint32_t peer_recon_version,
+ uint64_t remote_salt) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+ auto recon_state = m_states.find(peer_id);
+
+ if (recon_state == m_states.end()) return ReconciliationRegisterResult::NOT_FOUND;
+
+ if (std::holds_alternative<TxReconciliationState>(recon_state->second)) {
+ return ReconciliationRegisterResult::ALREADY_REGISTERED;
+ }
+
+ uint64_t local_salt = *std::get_if<uint64_t>(&recon_state->second);
+
+ // If the peer supports the version which is lower than ours, we downgrade to the version
+ // it supports. For now, this only guarantees that nodes with future reconciliation
+ // versions have the choice of reconciling with this current version. However, they also
+ // have the choice to refuse supporting reconciliations if the common version is not
+ // satisfactory (e.g. too low).
+ const uint32_t recon_version{std::min(peer_recon_version, m_recon_version)};
+ // v1 is the lowest version, so suggesting something below must be a protocol violation.
+ if (recon_version < 1) return ReconciliationRegisterResult::PROTOCOL_VIOLATION;
+
+ LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Register peer=%d (inbound=%i)\n",
+ peer_id, is_peer_inbound);
+
+ const uint256 full_salt{ComputeSalt(local_salt, remote_salt)};
+ recon_state->second = TxReconciliationState(!is_peer_inbound, full_salt.GetUint64(0), full_salt.GetUint64(1));
+ return ReconciliationRegisterResult::SUCCESS;
+ }
+
+ void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+ if (m_states.erase(peer_id)) {
+ LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Forget txreconciliation state of peer=%d\n", peer_id);
+ }
+ }
+
+ bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+ auto recon_state = m_states.find(peer_id);
+ return (recon_state != m_states.end() &&
+ std::holds_alternative<TxReconciliationState>(recon_state->second));
+ }
+};
+
+TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {}
+
+TxReconciliationTracker::~TxReconciliationTracker() = default;
+
+uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
+{
+ return m_impl->PreRegisterPeer(peer_id);
+}
+
+ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound,
+ uint32_t peer_recon_version, uint64_t remote_salt)
+{
+ return m_impl->RegisterPeer(peer_id, is_peer_inbound, peer_recon_version, remote_salt);
+}
+
+void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
+{
+ m_impl->ForgetPeer(peer_id);
+}
+
+bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const
+{
+ return m_impl->IsPeerRegistered(peer_id);
+}
diff --git a/src/node/txreconciliation.h b/src/node/txreconciliation.h
new file mode 100644
index 0000000000..4591dd5df7
--- /dev/null
+++ b/src/node/txreconciliation.h
@@ -0,0 +1,91 @@
+// 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_TXRECONCILIATION_H
+#define BITCOIN_NODE_TXRECONCILIATION_H
+
+#include <net.h>
+#include <sync.h>
+
+#include <memory>
+#include <tuple>
+
+/** Whether transaction reconciliation protocol should be enabled by default. */
+static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false};
+/** Supported transaction reconciliation protocol version */
+static constexpr uint32_t TXRECONCILIATION_VERSION{1};
+
+enum class ReconciliationRegisterResult {
+ NOT_FOUND,
+ SUCCESS,
+ ALREADY_REGISTERED,
+ PROTOCOL_VIOLATION,
+};
+
+/**
+ * Transaction reconciliation is a way for nodes to efficiently announce transactions.
+ * This object keeps track of all txreconciliation-related communications with the peers.
+ * The high-level protocol is:
+ * 0. Txreconciliation protocol handshake.
+ * 1. Once we receive a new transaction, add it to the set instead of announcing immediately.
+ * 2. At regular intervals, a txreconciliation initiator requests a sketch from a peer, where a
+ * sketch is a compressed representation of short form IDs of the transactions in their set.
+ * 3. Once the initiator received a sketch from the peer, the initiator computes a local sketch,
+ * and combines the two sketches to attempt finding the difference in *sets*.
+ * 4a. If the difference was not larger than estimated, see SUCCESS below.
+ * 4b. If the difference was larger than estimated, initial txreconciliation fails. The initiator
+ * requests a larger sketch via an extension round (allowed only once).
+ * - If extension succeeds (a larger sketch is sufficient), see SUCCESS below.
+ * - If extension fails (a larger sketch is insufficient), see FAILURE below.
+ *
+ * SUCCESS. The initiator knows full symmetrical difference and can request what the initiator is
+ * missing and announce to the peer what the peer is missing.
+ *
+ * FAILURE. The initiator notifies the peer about the failure and announces all transactions from
+ * the corresponding set. Once the peer received the failure notification, the peer
+ * announces all transactions from their set.
+
+ * This is a modification of the Erlay protocol (https://arxiv.org/abs/1905.10518) with two
+ * changes (sketch extensions instead of bisections, and an extra INV exchange round), both
+ * are motivated in BIP-330.
+ */
+class TxReconciliationTracker
+{
+private:
+ class Impl;
+ const std::unique_ptr<Impl> m_impl;
+
+public:
+ explicit TxReconciliationTracker(uint32_t recon_version);
+ ~TxReconciliationTracker();
+
+ /**
+ * Step 0. Generates initial part of the state (salt) required to reconcile txs with the peer.
+ * The salt is used for short ID computation required for txreconciliation.
+ * The function returns the salt.
+ * A peer can't participate in future txreconciliations without this call.
+ * This function must be called only once per peer.
+ */
+ uint64_t PreRegisterPeer(NodeId peer_id);
+
+ /**
+ * Step 0. Once the peer agreed to reconcile txs with us, generate the state required to track
+ * ongoing reconciliations. Must be called only after pre-registering the peer and only once.
+ */
+ ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound,
+ uint32_t peer_recon_version, uint64_t remote_salt);
+
+ /**
+ * Attempts to forget txreconciliation-related state of the peer (if we previously stored any).
+ * After this, we won't be able to reconcile transactions with the peer.
+ */
+ void ForgetPeer(NodeId peer_id);
+
+ /**
+ * Check if a peer is registered to reconcile transactions with us.
+ */
+ bool IsPeerRegistered(NodeId peer_id) const;
+};
+
+#endif // BITCOIN_NODE_TXRECONCILIATION_H
diff --git a/src/node/utxo_snapshot.cpp b/src/node/utxo_snapshot.cpp
new file mode 100644
index 0000000000..bab1b75211
--- /dev/null
+++ b/src/node/utxo_snapshot.cpp
@@ -0,0 +1,91 @@
+// 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.
+
+#include <node/utxo_snapshot.h>
+
+#include <fs.h>
+#include <logging.h>
+#include <streams.h>
+#include <uint256.h>
+#include <util/system.h>
+#include <validation.h>
+
+#include <cstdio>
+#include <optional>
+
+namespace node {
+
+bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
+{
+ AssertLockHeld(::cs_main);
+ assert(snapshot_chainstate.m_from_snapshot_blockhash);
+
+ const std::optional<fs::path> chaindir = snapshot_chainstate.CoinsDB().StoragePath();
+ assert(chaindir); // Sanity check that chainstate isn't in-memory.
+ const fs::path write_to = *chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;
+
+ FILE* file{fsbridge::fopen(write_to, "wb")};
+ AutoFile afile{file};
+ if (afile.IsNull()) {
+ LogPrintf("[snapshot] failed to open base blockhash file for writing: %s\n",
+ fs::PathToString(write_to));
+ return false;
+ }
+ afile << *snapshot_chainstate.m_from_snapshot_blockhash;
+
+ if (afile.fclose() != 0) {
+ LogPrintf("[snapshot] failed to close base blockhash file %s after writing\n",
+ fs::PathToString(write_to));
+ return false;
+ }
+ return true;
+}
+
+std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
+{
+ if (!fs::exists(chaindir)) {
+ LogPrintf("[snapshot] cannot read base blockhash: no chainstate dir " /* Continued */
+ "exists at path %s\n", fs::PathToString(chaindir));
+ return std::nullopt;
+ }
+ const fs::path read_from = chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;
+ const std::string read_from_str = fs::PathToString(read_from);
+
+ if (!fs::exists(read_from)) {
+ LogPrintf("[snapshot] snapshot chainstate dir is malformed! no base blockhash file " /* Continued */
+ "exists at path %s. Try deleting %s and calling loadtxoutset again?\n",
+ fs::PathToString(chaindir), read_from_str);
+ return std::nullopt;
+ }
+
+ uint256 base_blockhash;
+ FILE* file{fsbridge::fopen(read_from, "rb")};
+ AutoFile afile{file};
+ if (afile.IsNull()) {
+ LogPrintf("[snapshot] failed to open base blockhash file for reading: %s\n",
+ read_from_str);
+ return std::nullopt;
+ }
+ afile >> base_blockhash;
+
+ if (std::fgetc(afile.Get()) != EOF) {
+ LogPrintf("[snapshot] warning: unexpected trailing data in %s\n", read_from_str);
+ } else if (std::ferror(afile.Get())) {
+ LogPrintf("[snapshot] warning: i/o error reading %s\n", read_from_str);
+ }
+ return base_blockhash;
+}
+
+std::optional<fs::path> FindSnapshotChainstateDir()
+{
+ fs::path possible_dir =
+ gArgs.GetDataDirNet() / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX));
+
+ if (fs::exists(possible_dir)) {
+ return possible_dir;
+ }
+ return std::nullopt;
+}
+
+} // namespace node
diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h
index 401d4baaeb..c94521792f 100644
--- a/src/node/utxo_snapshot.h
+++ b/src/node/utxo_snapshot.h
@@ -6,12 +6,18 @@
#ifndef BITCOIN_NODE_UTXO_SNAPSHOT_H
#define BITCOIN_NODE_UTXO_SNAPSHOT_H
+#include <fs.h>
#include <uint256.h>
#include <serialize.h>
+#include <validation.h>
+
+#include <optional>
+
+extern RecursiveMutex cs_main;
namespace node {
//! Metadata describing a serialized version of a UTXO set from which an
-//! assumeutxo CChainState can be constructed.
+//! assumeutxo Chainstate can be constructed.
class SnapshotMetadata
{
public:
@@ -33,6 +39,33 @@ public:
SERIALIZE_METHODS(SnapshotMetadata, obj) { READWRITE(obj.m_base_blockhash, obj.m_coins_count); }
};
+
+//! The file in the snapshot chainstate dir which stores the base blockhash. This is
+//! needed to reconstruct snapshot chainstates on init.
+//!
+//! Because we only allow loading a single snapshot at a time, there will only be one
+//! chainstate directory with this filename present within it.
+const fs::path SNAPSHOT_BLOCKHASH_FILENAME{"base_blockhash"};
+
+//! Write out the blockhash of the snapshot base block that was used to construct
+//! this chainstate. This value is read in during subsequent initializations and
+//! used to reconstruct snapshot-based chainstates.
+bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+//! Read the blockhash of the snapshot base block that was used to construct the
+//! chainstate.
+std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+//! Suffix appended to the chainstate (leveldb) dir when created based upon
+//! a snapshot.
+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();
+
} // namespace node
#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H
diff --git a/src/node/validation_cache_args.cpp b/src/node/validation_cache_args.cpp
new file mode 100644
index 0000000000..5ea0a8ca0a
--- /dev/null
+++ b/src/node/validation_cache_args.cpp
@@ -0,0 +1,34 @@
+// 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.
+
+#include <node/validation_cache_args.h>
+
+#include <kernel/validation_cache_sizes.h>
+
+#include <util/system.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+
+using kernel::ValidationCacheSizes;
+
+namespace node {
+void ApplyArgsManOptions(const ArgsManager& argsman, ValidationCacheSizes& cache_sizes)
+{
+ if (auto max_size = argsman.GetIntArg("-maxsigcachesize")) {
+ // 1. When supplied with a max_size of 0, both InitSignatureCache and
+ // InitScriptExecutionCache create the minimum possible cache (2
+ // elements). Therefore, we can use 0 as a floor here.
+ // 2. Multiply first, divide after to avoid integer truncation.
+ size_t clamped_size_each = std::max<int64_t>(*max_size, 0) * (1 << 20) / 2;
+ cache_sizes = {
+ .signature_cache_bytes = clamped_size_each,
+ .script_execution_cache_bytes = clamped_size_each,
+ };
+ }
+}
+} // namespace node
diff --git a/src/node/validation_cache_args.h b/src/node/validation_cache_args.h
new file mode 100644
index 0000000000..f447c13b49
--- /dev/null
+++ b/src/node/validation_cache_args.h
@@ -0,0 +1,17 @@
+// 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_VALIDATION_CACHE_ARGS_H
+#define BITCOIN_NODE_VALIDATION_CACHE_ARGS_H
+
+class ArgsManager;
+namespace kernel {
+struct ValidationCacheSizes;
+};
+
+namespace node {
+void ApplyArgsManOptions(const ArgsManager& argsman, kernel::ValidationCacheSizes& cache_sizes);
+} // namespace node
+
+#endif // BITCOIN_NODE_VALIDATION_CACHE_ARGS_H