diff options
Diffstat (limited to 'src/node')
-rw-r--r-- | src/node/blockmanager_args.cpp | 1 | ||||
-rw-r--r-- | src/node/blockstorage.cpp | 155 | ||||
-rw-r--r-- | src/node/blockstorage.h | 49 | ||||
-rw-r--r-- | src/node/caches.cpp | 1 | ||||
-rw-r--r-- | src/node/chainstatemanager_args.cpp | 25 | ||||
-rw-r--r-- | src/node/context.h | 2 | ||||
-rw-r--r-- | src/node/interface_ui.h | 3 | ||||
-rw-r--r-- | src/node/interfaces.cpp | 127 | ||||
-rw-r--r-- | src/node/kernel_notifications.cpp | 6 | ||||
-rw-r--r-- | src/node/kernel_notifications.h | 12 | ||||
-rw-r--r-- | src/node/mempool_args.cpp | 3 | ||||
-rw-r--r-- | src/node/mempool_persist.cpp | 221 | ||||
-rw-r--r-- | src/node/mempool_persist.h | 34 | ||||
-rw-r--r-- | src/node/miner.cpp | 33 | ||||
-rw-r--r-- | src/node/miner.h | 36 | ||||
-rw-r--r-- | src/node/mini_miner.cpp | 22 | ||||
-rw-r--r-- | src/node/transaction.cpp | 2 | ||||
-rw-r--r-- | src/node/txreconciliation.cpp | 2 | ||||
-rw-r--r-- | src/node/types.h | 22 | ||||
-rw-r--r-- | src/node/utxo_snapshot.cpp | 6 | ||||
-rw-r--r-- | src/node/utxo_snapshot.h | 13 | ||||
-rw-r--r-- | src/node/validation_cache_args.cpp | 34 | ||||
-rw-r--r-- | src/node/validation_cache_args.h | 17 | ||||
-rw-r--r-- | src/node/warnings.cpp | 3 |
24 files changed, 619 insertions, 210 deletions
diff --git a/src/node/blockmanager_args.cpp b/src/node/blockmanager_args.cpp index fa76566652..0fc4e1646a 100644 --- a/src/node/blockmanager_args.cpp +++ b/src/node/blockmanager_args.cpp @@ -16,6 +16,7 @@ namespace node { util::Result<void> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts) { + if (auto value{args.GetBoolArg("-blocksxor")}) opts.use_xor = *value; // block pruning; get the amount of disk space (in MiB) to allot for block & undo files int64_t nPruneArg{args.GetIntArg("-prune", opts.prune_target)}; if (nPruneArg < 0) { diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index fb62e78138..07878a5602 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -19,7 +19,7 @@ #include <pow.h> #include <primitives/block.h> #include <primitives/transaction.h> -#include <reverse_iterator.h> +#include <random.h> #include <serialize.h> #include <signet.h> #include <span.h> @@ -37,6 +37,7 @@ #include <validation.h> #include <map> +#include <ranges> #include <unordered_map> namespace kernel { @@ -366,7 +367,7 @@ void BlockManager::FindFilesToPrune( } } - LogPrint(BCLog::PRUNE, "[%s] target=%dMiB actual=%dMiB diff=%dMiB min_height=%d max_prune_height=%d removed %d blk/rev pairs\n", + LogDebug(BCLog::PRUNE, "[%s] target=%dMiB actual=%dMiB diff=%dMiB min_height=%d max_prune_height=%d removed %d blk/rev pairs\n", chain.GetRole(), target / 1024 / 1024, nCurrentUsage / 1024 / 1024, (int64_t(target) - int64_t(nCurrentUsage)) / 1024 / 1024, min_block_to_prune, last_block_can_prune, count); @@ -410,11 +411,11 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha m_snapshot_height = au_data.height; CBlockIndex* base{LookupBlockIndex(*snapshot_blockhash)}; - // Since nChainTx (responsible for estimated progress) isn't persisted + // Since m_chain_tx_count (responsible for estimated progress) isn't persisted // to disk, we must bootstrap the value for assumedvalid chainstates // from the hardcoded assumeutxo chainparams. - base->nChainTx = au_data.nChainTx; - LogPrintf("[snapshot] set nChainTx=%d for %s\n", au_data.nChainTx, snapshot_blockhash->ToString()); + base->m_chain_tx_count = au_data.m_chain_tx_count; + LogPrintf("[snapshot] set m_chain_tx_count=%d for %s\n", au_data.m_chain_tx_count, snapshot_blockhash->ToString()); } else { // If this isn't called with a snapshot blockhash, make sure the cached snapshot height // is null. This is relevant during snapshot completion, when the blockman may be loaded @@ -449,15 +450,15 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha if (m_snapshot_height && pindex->nHeight == *m_snapshot_height && pindex->GetBlockHash() == *snapshot_blockhash) { // Should have been set above; don't disturb it with code below. - Assert(pindex->nChainTx > 0); - } else if (pindex->pprev->nChainTx > 0) { - pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx; + Assert(pindex->m_chain_tx_count > 0); + } else if (pindex->pprev->m_chain_tx_count > 0) { + pindex->m_chain_tx_count = pindex->pprev->m_chain_tx_count + pindex->nTx; } else { - pindex->nChainTx = 0; + pindex->m_chain_tx_count = 0; m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex)); } } else { - pindex->nChainTx = pindex->nTx; + pindex->m_chain_tx_count = pindex->nTx; } } if (!(pindex->nStatus & BLOCK_FAILED_MASK) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_MASK)) { @@ -578,7 +579,7 @@ const CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) { const MapCheckpoints& checkpoints = data.mapCheckpoints; - for (const MapCheckpoints::value_type& i : reverse_iterate(checkpoints)) { + for (const MapCheckpoints::value_type& i : checkpoints | std::views::reverse) { const uint256& hash = i.second; const CBlockIndex* pindex = LookupBlockIndex(hash); if (pindex) { @@ -588,18 +589,18 @@ const CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) return nullptr; } -bool BlockManager::IsBlockPruned(const CBlockIndex& block) +bool BlockManager::IsBlockPruned(const CBlockIndex& block) const { AssertLockHeld(::cs_main); return m_have_pruned && !(block.nStatus & BLOCK_HAVE_DATA) && (block.nTx > 0); } -const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_block, const CBlockIndex* lower_block) +const CBlockIndex* BlockManager::GetFirstBlock(const CBlockIndex& upper_block, uint32_t status_mask, const CBlockIndex* lower_block) const { AssertLockHeld(::cs_main); const CBlockIndex* last_block = &upper_block; - assert(last_block->nStatus & BLOCK_HAVE_DATA); // 'upper_block' must have data - while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) { + assert((last_block->nStatus & status_mask) == status_mask); // 'upper_block' must satisfy the status mask + while (last_block->pprev && ((last_block->pprev->nStatus & status_mask) == status_mask)) { if (lower_block) { // Return if we reached the lower_block if (last_block == lower_block) return lower_block; @@ -616,7 +617,7 @@ const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& upper_bl bool BlockManager::CheckBlockDataAvailability(const CBlockIndex& upper_block, const CBlockIndex& lower_block) { if (!(upper_block.nStatus & BLOCK_HAVE_DATA)) return false; - return GetFirstStoredBlock(upper_block, &lower_block) == &lower_block; + return GetFirstBlock(upper_block, BLOCK_HAVE_DATA, &lower_block) == &lower_block; } // If we're using -prune with -reindex, then delete block files that will be ignored by the @@ -682,11 +683,7 @@ bool BlockManager::UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos fileout << GetParams().MessageStart() << nSize; // Write undo data - long fileOutPos = ftell(fileout.Get()); - if (fileOutPos < 0) { - LogError("%s: ftell failed\n", __func__); - return false; - } + long fileOutPos = fileout.tell(); pos.nPos = (unsigned int)fileOutPos; fileout << blockundo; @@ -703,15 +700,10 @@ bool BlockManager::UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex& in { const FlatFilePos pos{WITH_LOCK(::cs_main, return index.GetUndoPos())}; - if (pos.IsNull()) { - LogError("%s: no undo data available\n", __func__); - return false; - } - // Open history file to read AutoFile filein{OpenUndoFile(pos, true)}; if (filein.IsNull()) { - LogError("%s: OpenUndoFile failed\n", __func__); + LogError("%s: OpenUndoFile failed for %s\n", __func__, pos.ToString()); return false; } @@ -723,13 +715,13 @@ bool BlockManager::UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex& in verifier >> blockundo; filein >> hashChecksum; } catch (const std::exception& e) { - LogError("%s: Deserialize or I/O error - %s\n", __func__, e.what()); + LogError("%s: Deserialize or I/O error - %s at %s\n", __func__, e.what(), pos.ToString()); return false; } // Verify checksum if (hashChecksum != verifier.GetHash()) { - LogError("%s: Checksum mismatch\n", __func__); + LogError("%s: Checksum mismatch at %s\n", __func__, pos.ToString()); return false; } @@ -739,7 +731,7 @@ bool BlockManager::UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex& in bool BlockManager::FlushUndoFile(int block_file, bool finalize) { FlatFilePos undo_pos_old(block_file, m_blockfile_info[block_file].nUndoSize); - if (!UndoFileSeq().Flush(undo_pos_old, finalize)) { + if (!m_undo_file_seq.Flush(undo_pos_old, finalize)) { m_opts.notifications.flushError(_("Flushing undo file to disk failed. This is likely the result of an I/O error.")); return false; } @@ -761,7 +753,7 @@ bool BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize, bool finali assert(static_cast<int>(m_blockfile_info.size()) > blockfile_num); FlatFilePos block_pos_old(blockfile_num, m_blockfile_info[blockfile_num].nSize); - if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) { + if (!m_block_file_seq.Flush(block_pos_old, fFinalize)) { m_opts.notifications.flushError(_("Flushing block file to disk failed. This is likely the result of an I/O error.")); success = false; } @@ -813,38 +805,28 @@ void BlockManager::UnlinkPrunedFiles(const std::set<int>& setFilesToPrune) const std::error_code ec; for (std::set<int>::iterator it = setFilesToPrune.begin(); it != setFilesToPrune.end(); ++it) { FlatFilePos pos(*it, 0); - const bool removed_blockfile{fs::remove(BlockFileSeq().FileName(pos), ec)}; - const bool removed_undofile{fs::remove(UndoFileSeq().FileName(pos), ec)}; + const bool removed_blockfile{fs::remove(m_block_file_seq.FileName(pos), ec)}; + const bool removed_undofile{fs::remove(m_undo_file_seq.FileName(pos), ec)}; if (removed_blockfile || removed_undofile) { - LogPrint(BCLog::BLOCKSTORAGE, "Prune: %s deleted blk/rev (%05u)\n", __func__, *it); + LogDebug(BCLog::BLOCKSTORAGE, "Prune: %s deleted blk/rev (%05u)\n", __func__, *it); } } } -FlatFileSeq BlockManager::BlockFileSeq() const -{ - return FlatFileSeq(m_opts.blocks_dir, "blk", m_opts.fast_prune ? 0x4000 /* 16kb */ : BLOCKFILE_CHUNK_SIZE); -} - -FlatFileSeq BlockManager::UndoFileSeq() const -{ - return FlatFileSeq(m_opts.blocks_dir, "rev", UNDOFILE_CHUNK_SIZE); -} - AutoFile BlockManager::OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const { - return AutoFile{BlockFileSeq().Open(pos, fReadOnly)}; + return AutoFile{m_block_file_seq.Open(pos, fReadOnly), m_xor_key}; } /** Open an undo file (rev?????.dat) */ AutoFile BlockManager::OpenUndoFile(const FlatFilePos& pos, bool fReadOnly) const { - return AutoFile{UndoFileSeq().Open(pos, fReadOnly)}; + return AutoFile{m_undo_file_seq.Open(pos, fReadOnly), m_xor_key}; } fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const { - return BlockFileSeq().FileName(pos); + return m_block_file_seq.FileName(pos); } FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime) @@ -858,7 +840,7 @@ FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int n assert(chain_type == BlockfileType::ASSUMED); const auto new_cursor = BlockfileCursor{this->MaxBlockfileNum() + 1}; m_blockfile_cursors[chain_type] = new_cursor; - LogPrint(BCLog::BLOCKSTORAGE, "[%s] initializing blockfile cursor to %s\n", chain_type, new_cursor); + LogDebug(BCLog::BLOCKSTORAGE, "[%s] initializing blockfile cursor to %s\n", chain_type, new_cursor); } const int last_blockfile = m_blockfile_cursors[chain_type]->file_num; @@ -901,7 +883,7 @@ FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int n pos.nPos = m_blockfile_info[nFile].nSize; if (nFile != last_blockfile) { - LogPrint(BCLog::BLOCKSTORAGE, "Leaving block file %i: %s (onto %i) (height %i)\n", + LogDebug(BCLog::BLOCKSTORAGE, "Leaving block file %i: %s (onto %i) (height %i)\n", last_blockfile, m_blockfile_info[last_blockfile].ToString(), nFile, nHeight); // Do not propagate the return code. The flush concerns a previous block @@ -924,7 +906,7 @@ FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int n m_blockfile_info[nFile].nSize += nAddSize; bool out_of_space; - size_t bytes_allocated = BlockFileSeq().Allocate(pos, nAddSize, out_of_space); + size_t bytes_allocated = m_block_file_seq.Allocate(pos, nAddSize, out_of_space); if (out_of_space) { m_opts.notifications.fatalError(_("Disk space is too low!")); return {}; @@ -970,7 +952,7 @@ bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFileP m_dirty_fileinfo.insert(nFile); bool out_of_space; - size_t bytes_allocated = UndoFileSeq().Allocate(pos, nAddSize, out_of_space); + size_t bytes_allocated = m_undo_file_seq.Allocate(pos, nAddSize, out_of_space); if (out_of_space) { return FatalError(m_opts.notifications, state, _("Disk space is too low!")); } @@ -986,7 +968,7 @@ bool BlockManager::WriteBlockToDisk(const CBlock& block, FlatFilePos& pos) const // Open history file to append AutoFile fileout{OpenBlockFile(pos)}; if (fileout.IsNull()) { - LogError("WriteBlockToDisk: OpenBlockFile failed\n"); + LogError("%s: OpenBlockFile failed\n", __func__); return false; } @@ -995,11 +977,7 @@ bool BlockManager::WriteBlockToDisk(const CBlock& block, FlatFilePos& pos) const fileout << GetParams().MessageStart() << nSize; // Write block - long fileOutPos = ftell(fileout.Get()); - if (fileOutPos < 0) { - LogError("WriteBlockToDisk: ftell failed\n"); - return false; - } + long fileOutPos = fileout.tell(); pos.nPos = (unsigned int)fileOutPos; fileout << TX_WITH_WITNESS(block); @@ -1016,7 +994,7 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid if (block.GetUndoPos().IsNull()) { FlatFilePos _pos; if (!FindUndoPos(state, block.nFile, _pos, ::GetSerializeSize(blockundo) + 40)) { - LogError("ConnectBlock(): FindUndoPos failed\n"); + LogError("%s: FindUndoPos failed\n", __func__); return false; } if (!UndoWriteToDisk(blockundo, _pos, block.pprev->GetBlockHash())) { @@ -1055,7 +1033,7 @@ bool BlockManager::ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos) cons // Open history file to read AutoFile filein{OpenBlockFile(pos, true)}; if (filein.IsNull()) { - LogError("ReadBlockFromDisk: OpenBlockFile failed for %s\n", pos.ToString()); + LogError("%s: OpenBlockFile failed for %s\n", __func__, pos.ToString()); return false; } @@ -1069,13 +1047,13 @@ bool BlockManager::ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos) cons // Check the header if (!CheckProofOfWork(block.GetHash(), block.nBits, GetConsensus())) { - LogError("ReadBlockFromDisk: Errors in block header at %s\n", pos.ToString()); + LogError("%s: Errors in block header at %s\n", __func__, pos.ToString()); return false; } // Signet only: check block solution if (GetConsensus().signet_blocks && !CheckSignetBlockSolution(block, GetConsensus())) { - LogError("ReadBlockFromDisk: Errors in block solution at %s\n", pos.ToString()); + LogError("%s: Errors in block solution at %s\n", __func__, pos.ToString()); return false; } @@ -1090,8 +1068,7 @@ bool BlockManager::ReadBlockFromDisk(CBlock& block, const CBlockIndex& index) co return false; } if (block.GetHash() != index.GetBlockHash()) { - LogError("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s\n", - index.ToString(), block_pos.ToString()); + LogError("%s: GetHash() doesn't match index for %s at %s\n", __func__, index.ToString(), block_pos.ToString()); return false; } return true; @@ -1160,6 +1137,54 @@ FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight) return blockPos; } +static auto InitBlocksdirXorKey(const BlockManager::Options& opts) +{ + // Bytes are serialized without length indicator, so this is also the exact + // size of the XOR-key file. + std::array<std::byte, 8> xor_key{}; + + if (opts.use_xor && fs::is_empty(opts.blocks_dir)) { + // Only use random fresh key when the boolean option is set and on the + // very first start of the program. + FastRandomContext{}.fillrand(xor_key); + } + + const fs::path xor_key_path{opts.blocks_dir / "xor.dat"}; + if (fs::exists(xor_key_path)) { + // A pre-existing xor key file has priority. + AutoFile xor_key_file{fsbridge::fopen(xor_key_path, "rb")}; + xor_key_file >> xor_key; + } else { + // Create initial or missing xor key file + AutoFile xor_key_file{fsbridge::fopen(xor_key_path, +#ifdef __MINGW64__ + "wb" // Temporary workaround for https://github.com/bitcoin/bitcoin/issues/30210 +#else + "wbx" +#endif + )}; + xor_key_file << xor_key; + } + // If the user disabled the key, it must be zero. + if (!opts.use_xor && xor_key != decltype(xor_key){}) { + throw std::runtime_error{ + strprintf("The blocksdir XOR-key can not be disabled when a random key was already stored! " + "Stored key: '%s', stored path: '%s'.", + HexStr(xor_key), fs::PathToString(xor_key_path)), + }; + } + LogInfo("Using obfuscation key for blocksdir *.dat files (%s): '%s'\n", fs::PathToString(opts.blocks_dir), HexStr(xor_key)); + return std::vector<std::byte>{xor_key.begin(), xor_key.end()}; +} + +BlockManager::BlockManager(const util::SignalInterrupt& interrupt, Options opts) + : m_prune_mode{opts.prune_target > 0}, + m_xor_key{InitBlocksdirXorKey(opts)}, + m_opts{std::move(opts)}, + m_block_file_seq{FlatFileSeq{m_opts.blocks_dir, "blk", m_opts.fast_prune ? 0x4000 /* 16kB */ : BLOCKFILE_CHUNK_SIZE}}, + m_undo_file_seq{FlatFileSeq{m_opts.blocks_dir, "rev", UNDOFILE_CHUNK_SIZE}}, + m_interrupt{interrupt} {} + class ImportingNow { std::atomic<bool>& m_importing; @@ -1177,7 +1202,7 @@ public: } }; -void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFiles) +void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths) { ImportingNow imp{chainman.m_blockman.m_importing}; @@ -1212,7 +1237,7 @@ void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFile } // -loadblock= - for (const fs::path& path : vImportFiles) { + for (const fs::path& path : import_paths) { AutoFile file{fsbridge::fopen(path, "rb")}; if (!file.IsNull()) { LogPrintf("Importing blocks file %s...\n", fs::PathToString(path)); diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 108a08a72b..03bc5f4600 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -29,6 +29,7 @@ #include <memory> #include <optional> #include <set> +#include <span> #include <string> #include <unordered_map> #include <utility> @@ -166,9 +167,6 @@ private: [[nodiscard]] bool FlushChainstateBlockFile(int tip_height); bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize); - FlatFileSeq BlockFileSeq() const; - FlatFileSeq UndoFileSeq() const; - AutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const; /** @@ -243,6 +241,8 @@ private: const bool m_prune_mode; + const std::vector<std::byte> m_xor_key; + /** Dirty block index entries. */ std::set<CBlockIndex*> m_dirty_blockindex; @@ -261,13 +261,13 @@ private: const kernel::BlockManagerOpts m_opts; + const FlatFileSeq m_block_file_seq; + const FlatFileSeq m_undo_file_seq; + public: using Options = kernel::BlockManagerOpts; - explicit BlockManager(const util::SignalInterrupt& interrupt, Options opts) - : m_prune_mode{opts.prune_target > 0}, - m_opts{std::move(opts)}, - m_interrupt{interrupt} {} + explicit BlockManager(const util::SignalInterrupt& interrupt, Options opts); const util::SignalInterrupt& m_interrupt; std::atomic<bool> m_importing{false}; @@ -372,16 +372,39 @@ public: //! (part of the same chain). bool CheckBlockDataAvailability(const CBlockIndex& upper_block LIFETIMEBOUND, const CBlockIndex& lower_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - //! Find the first stored ancestor of start_block immediately after the last - //! pruned ancestor. Return value will never be null. Caller is responsible - //! for ensuring that start_block has data is not pruned. - const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND, const CBlockIndex* lower_block=nullptr) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** + * @brief Returns the earliest block with specified `status_mask` flags set after + * the latest block _not_ having those flags. + * + * This function starts from `upper_block`, which must have all + * `status_mask` flags set, and iterates backwards through its ancestors. It + * continues as long as each block has all `status_mask` flags set, until + * reaching the oldest ancestor or `lower_block`. + * + * @pre `upper_block` must have all `status_mask` flags set. + * @pre `lower_block` must be null or an ancestor of `upper_block` + * + * @param upper_block The starting block for the search, which must have all + * `status_mask` flags set. + * @param status_mask Bitmask specifying required status flags. + * @param lower_block The earliest possible block to return. If null, the + * search can extend to the genesis block. + * + * @return A non-null pointer to the earliest block between `upper_block` + * and `lower_block`, inclusive, such that every block between the + * returned block and `upper_block` has `status_mask` flags set. + */ + const CBlockIndex* GetFirstBlock( + const CBlockIndex& upper_block LIFETIMEBOUND, + uint32_t status_mask, + const CBlockIndex* lower_block = nullptr + ) const 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& block) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool IsBlockPruned(const CBlockIndex& block) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); //! 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); @@ -407,7 +430,7 @@ public: void CleanupBlockRevFiles() const; }; -void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFiles); +void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths); } // namespace node #endif // BITCOIN_NODE_BLOCKSTORAGE_H diff --git a/src/node/caches.cpp b/src/node/caches.cpp index 7403f7ddea..dc4d98f592 100644 --- a/src/node/caches.cpp +++ b/src/node/caches.cpp @@ -13,7 +13,6 @@ CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes) { int64_t nTotalCache = (args.GetIntArg("-dbcache", nDefaultDbCache) << 20); nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache - nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache CacheSizes sizes; sizes.block_tree_db = std::min(nTotalCache / 8, nMaxBlockDBCache << 20); nTotalCache -= sizes.block_tree_db; diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp index bc4a815a3e..b86d0b2991 100644 --- a/src/node/chainstatemanager_args.cpp +++ b/src/node/chainstatemanager_args.cpp @@ -32,13 +32,20 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& args, ChainstateManage if (auto value{args.GetBoolArg("-checkpoints")}) opts.checkpoints_enabled = *value; if (auto value{args.GetArg("-minimumchainwork")}) { - if (!IsHexNumber(*value)) { - return util::Error{strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value)}; + if (auto min_work{uint256::FromUserHex(*value)}) { + opts.minimum_chain_work = UintToArith256(*min_work); + } else { + return util::Error{strprintf(Untranslated("Invalid minimum work specified (%s), must be up to %d hex digits"), *value, uint256::size() * 2)}; } - opts.minimum_chain_work = UintToArith256(uint256S(*value)); } - if (auto value{args.GetArg("-assumevalid")}) opts.assumed_valid_block = uint256S(*value); + if (auto value{args.GetArg("-assumevalid")}) { + if (auto block_hash{uint256::FromUserHex(*value)}) { + opts.assumed_valid_block = *block_hash; + } else { + return util::Error{strprintf(Untranslated("Invalid assumevalid block hash specified (%s), must be up to %d hex digits (or 0 to disable)"), *value, uint256::size() * 2)}; + } + } if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value}; @@ -56,6 +63,16 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& args, ChainstateManage opts.worker_threads_num = std::clamp(script_threads - 1, 0, MAX_SCRIPTCHECK_THREADS); LogPrintf("Script verification uses %d additional threads\n", opts.worker_threads_num); + if (auto max_size = args.GetIntArg("-maxsigcachesize")) { + // 1. When supplied with a max_size of 0, both the signature cache and + // script execution cache 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; + opts.script_execution_cache_bytes = clamped_size_each; + opts.signature_cache_bytes = clamped_size_each; + } + return {}; } } // namespace node diff --git a/src/node/context.h b/src/node/context.h index a664fad80b..398089ff3c 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -9,6 +9,7 @@ #include <cstdlib> #include <functional> #include <memory> +#include <thread> #include <vector> class ArgsManager; @@ -86,6 +87,7 @@ struct NodeContext { std::atomic<int> exit_status{EXIT_SUCCESS}; //! Manages all the node warnings std::unique_ptr<node::Warnings> warnings; + std::thread background_init_thread; //! Declare default constructor and destructor that are not inline, so code //! instantiating the NodeContext struct doesn't need to #include class diff --git a/src/node/interface_ui.h b/src/node/interface_ui.h index 22c241cb78..85c34f5834 100644 --- a/src/node/interface_ui.h +++ b/src/node/interface_ui.h @@ -6,9 +6,10 @@ #ifndef BITCOIN_NODE_INTERFACE_UI_H #define BITCOIN_NODE_INTERFACE_UI_H +#include <cstdint> #include <functional> -#include <memory> #include <string> +#include <vector> class CBlockIndex; enum class SynchronizationState; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index fa151407fa..febd968313 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -17,6 +17,7 @@ #include <interfaces/handler.h> #include <interfaces/mining.h> #include <interfaces/node.h> +#include <interfaces/types.h> #include <interfaces/wallet.h> #include <kernel/chain.h> #include <kernel/context.h> @@ -33,6 +34,7 @@ #include <node/interface_ui.h> #include <node/mini_miner.h> #include <node/miner.h> +#include <node/kernel_notifications.h> #include <node/transaction.h> #include <node/types.h> #include <node/warnings.h> @@ -67,6 +69,8 @@ #include <boost/signals2/signal.hpp> +using interfaces::BlockRef; +using interfaces::BlockTemplate; using interfaces::BlockTip; using interfaces::Chain; using interfaces::FoundBlock; @@ -100,7 +104,7 @@ public: void initParameterInteraction() override { InitParameterInteraction(args()); } bilingual_str getWarnings() override { return Join(Assert(m_context->warnings)->GetMessages(), Untranslated("<hr />")); } int getExitStatus() override { return Assert(m_context)->exit_status.load(); } - uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); } + BCLog::CategoryMask getLogCategories() override { return LogInstance().GetCategoryMask(); } bool baseInitialize() override { if (!AppInitBasicSetup(args(), Assert(context())->exit_status)) return false; @@ -136,7 +140,7 @@ public: // Stop RPC for clean shutdown if any of waitfor* commands is executed. if (args().GetBoolArg("-server", false)) { InterruptRPC(); - StopRPC(); + StopRPC(m_context); } } bool shutdownRequested() override { return ShutdownRequested(*Assert(m_context)); }; @@ -180,7 +184,7 @@ public: }); args().WriteSettingsFile(); } - void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } + void mapPort(bool use_upnp, bool use_pcp) override { StartMapPort(use_upnp, use_pcp); } bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(ConnectionDirection flags) override { @@ -278,6 +282,7 @@ public: int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; } size_t getMempoolDynamicUsage() override { return m_context->mempool ? m_context->mempool->DynamicMemoryUsage() : 0; } + size_t getMempoolMaxUsage() override { return m_context->mempool ? m_context->mempool->m_opts.max_size_bytes : 0; } bool getHeaderTip(int& height, int64_t& block_time) override { LOCK(::cs_main); @@ -289,6 +294,13 @@ public: } return false; } + std::map<CNetAddr, LocalServiceInfo> getNetLocalAddresses() override + { + if (m_context->connman) + return m_context->connman->getNetLocalAddresses(); + else + return {}; + } int getNumBlocks() override { LOCK(::cs_main); @@ -806,16 +818,34 @@ public: }); return result; } - bool updateRwSetting(const std::string& name, const common::SettingsValue& value, bool write) override + bool updateRwSetting(const std::string& name, + const interfaces::SettingsUpdate& update_settings_func) override { + std::optional<interfaces::SettingsAction> action; args().LockSettings([&](common::Settings& settings) { - if (value.isNull()) { - settings.rw_settings.erase(name); + if (auto* value = common::FindKey(settings.rw_settings, name)) { + action = update_settings_func(*value); + if (value->isNull()) settings.rw_settings.erase(name); } else { - settings.rw_settings[name] = value; + UniValue new_value; + action = update_settings_func(new_value); + if (!new_value.isNull()) settings.rw_settings[name] = std::move(new_value); } }); - return !write || args().WriteSettingsFile(); + if (!action) return false; + // Now dump value to disk if requested + return *action != interfaces::SettingsAction::WRITE || args().WriteSettingsFile(); + } + bool overwriteRwSetting(const std::string& name, common::SettingsValue value, interfaces::SettingsAction action) override + { + return updateRwSetting(name, [&](common::SettingsValue& settings) { + settings = std::move(value); + return action; + }); + } + bool deleteRwSettings(const std::string& name, interfaces::SettingsAction action) override + { + return overwriteRwSetting(name, {}, action); } void requestMempoolTransactions(Notifications& notifications) override { @@ -837,6 +867,52 @@ public: NodeContext& m_node; }; +class BlockTemplateImpl : public BlockTemplate +{ +public: + explicit BlockTemplateImpl(std::unique_ptr<CBlockTemplate> block_template) : m_block_template(std::move(block_template)) + { + assert(m_block_template); + } + + CBlockHeader getBlockHeader() override + { + return m_block_template->block; + } + + CBlock getBlock() override + { + return m_block_template->block; + } + + std::vector<CAmount> getTxFees() override + { + return m_block_template->vTxFees; + } + + std::vector<int64_t> getTxSigops() override + { + return m_block_template->vTxSigOpsCost; + } + + CTransactionRef getCoinbaseTx() override + { + return m_block_template->block.vtx[0]; + } + + std::vector<unsigned char> getCoinbaseCommitment() override + { + return m_block_template->vchCoinbaseCommitment; + } + + int getWitnessCommitmentIndex() override + { + return GetWitnessCommitmentIndex(m_block_template->block); + } + + const std::unique_ptr<CBlockTemplate> m_block_template; +}; + class MinerImpl : public Mining { public: @@ -852,12 +928,33 @@ public: return chainman().IsInitialBlockDownload(); } - std::optional<uint256> getTipHash() override + std::optional<BlockRef> getTip() override { LOCK(::cs_main); CBlockIndex* tip{chainman().ActiveChain().Tip()}; if (!tip) return {}; - return tip->GetBlockHash(); + return BlockRef{tip->GetBlockHash(), tip->nHeight}; + } + + BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override + { + // Interrupt check interval + const MillisecondsDouble tick{1000}; + auto now{std::chrono::steady_clock::now()}; + auto deadline = now + timeout; + // std::chrono does not check against overflow + if (deadline < now) deadline = std::chrono::steady_clock::time_point::max(); + { + WAIT_LOCK(notifications().m_tip_block_mutex, lock); + while ((notifications().m_tip_block == uint256() || notifications().m_tip_block == current_tip) && !chainman().m_interrupt) { + now = std::chrono::steady_clock::now(); + if (now >= deadline) break; + notifications().m_tip_block_cv.wait_until(lock, std::min(deadline, now + tick)); + } + } + // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks. + LOCK(::cs_main); + return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight}; } bool processNewBlock(const std::shared_ptr<const CBlock>& block, bool* new_block) override @@ -883,16 +980,16 @@ public: return TestBlockValidity(state, chainman().GetParams(), chainman().ActiveChainstate(), block, tip, /*fCheckPOW=*/false, check_merkle_root); } - std::unique_ptr<CBlockTemplate> createNewBlock(const CScript& script_pub_key, bool use_mempool) override + std::unique_ptr<BlockTemplate> createNewBlock(const CScript& script_pub_key, const BlockCreateOptions& options) override { - BlockAssembler::Options options; - ApplyArgsManOptions(gArgs, options); - - return BlockAssembler{chainman().ActiveChainstate(), use_mempool ? context()->mempool.get() : nullptr, options}.CreateNewBlock(script_pub_key); + BlockAssembler::Options assemble_options{options}; + ApplyArgsManOptions(*Assert(m_node.args), assemble_options); + return std::make_unique<BlockTemplateImpl>(BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key)); } NodeContext* context() override { return &m_node; } ChainstateManager& chainman() { return *Assert(m_node.chainman); } + KernelNotifications& notifications() { return *Assert(m_node.notifications); } NodeContext& m_node; }; } // namespace diff --git a/src/node/kernel_notifications.cpp b/src/node/kernel_notifications.cpp index 9894052a3a..40d45c8c2b 100644 --- a/src/node/kernel_notifications.cpp +++ b/src/node/kernel_notifications.cpp @@ -50,6 +50,12 @@ namespace node { kernel::InterruptResult KernelNotifications::blockTip(SynchronizationState state, CBlockIndex& index) { + { + LOCK(m_tip_block_mutex); + m_tip_block = index.GetBlockHash(); + m_tip_block_cv.notify_all(); + } + uiInterface.NotifyBlockTip(state, &index); if (m_stop_at_height && index.nHeight >= m_stop_at_height) { if (!m_shutdown()) { diff --git a/src/node/kernel_notifications.h b/src/node/kernel_notifications.h index e37f4d4e1e..9ff980ea56 100644 --- a/src/node/kernel_notifications.h +++ b/src/node/kernel_notifications.h @@ -7,6 +7,10 @@ #include <kernel/notifications_interface.h> +#include <sync.h> +#include <threadsafety.h> +#include <uint256.h> + #include <atomic> #include <cstdint> @@ -34,7 +38,7 @@ public: KernelNotifications(util::SignalInterrupt& shutdown, std::atomic<int>& exit_status, node::Warnings& warnings) : m_shutdown(shutdown), m_exit_status{exit_status}, m_warnings{warnings} {} - [[nodiscard]] kernel::InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) override; + [[nodiscard]] kernel::InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) override EXCLUSIVE_LOCKS_REQUIRED(!m_tip_block_mutex); void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) override; @@ -52,6 +56,12 @@ public: int m_stop_at_height{DEFAULT_STOPATHEIGHT}; //! Useful for tests, can be set to false to avoid shutdown on fatal error. bool m_shutdown_on_fatal_error{true}; + + Mutex m_tip_block_mutex; + std::condition_variable m_tip_block_cv; + //! The block for which the last blockTip notification was received for. + uint256 m_tip_block; + private: util::SignalInterrupt& m_shutdown; std::atomic<int>& m_exit_status; diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp index f329affb1d..a488c1b149 100644 --- a/src/node/mempool_args.cpp +++ b/src/node/mempool_args.cpp @@ -93,6 +93,9 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP } mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf); + if (!mempool_opts.full_rbf) { + LogInfo("Warning: mempoolfullrbf=0 set but deprecated and will be removed in a future release\n"); + } mempool_opts.persist_v1_dat = argsman.GetBoolArg("-persistmempoolv1", mempool_opts.persist_v1_dat); diff --git a/src/node/mempool_persist.cpp b/src/node/mempool_persist.cpp new file mode 100644 index 0000000000..ff7de8c64a --- /dev/null +++ b/src/node/mempool_persist.cpp @@ -0,0 +1,221 @@ +// 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.h> + +#include <clientversion.h> +#include <consensus/amount.h> +#include <logging.h> +#include <primitives/transaction.h> +#include <random.h> +#include <serialize.h> +#include <streams.h> +#include <sync.h> +#include <txmempool.h> +#include <uint256.h> +#include <util/fs.h> +#include <util/fs_helpers.h> +#include <util/signalinterrupt.h> +#include <util/time.h> +#include <validation.h> + +#include <cstdint> +#include <cstdio> +#include <exception> +#include <functional> +#include <map> +#include <memory> +#include <set> +#include <stdexcept> +#include <utility> +#include <vector> + +using fsbridge::FopenFn; + +namespace node { + +static const uint64_t MEMPOOL_DUMP_VERSION_NO_XOR_KEY{1}; +static const uint64_t MEMPOOL_DUMP_VERSION{2}; + +bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts) +{ + if (load_path.empty()) return false; + + AutoFile file{opts.mockable_fopen_function(load_path, "rb")}; + if (file.IsNull()) { + LogInfo("Failed to open mempool file. Continuing anyway.\n"); + return false; + } + + int64_t count = 0; + int64_t expired = 0; + int64_t failed = 0; + int64_t already_there = 0; + int64_t unbroadcast = 0; + const auto now{NodeClock::now()}; + + try { + uint64_t version; + file >> version; + std::vector<std::byte> xor_key; + if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) { + // Leave XOR-key empty + } else if (version == MEMPOOL_DUMP_VERSION) { + file >> xor_key; + } else { + return false; + } + file.SetXor(xor_key); + uint64_t total_txns_to_load; + file >> total_txns_to_load; + uint64_t txns_tried = 0; + LogInfo("Loading %u mempool transactions from file...\n", total_txns_to_load); + int next_tenth_to_report = 0; + while (txns_tried < total_txns_to_load) { + const int percentage_done(100.0 * txns_tried / total_txns_to_load); + if (next_tenth_to_report < percentage_done / 10) { + LogInfo("Progress loading mempool transactions from file: %d%% (tried %u, %u remaining)\n", + percentage_done, txns_tried, total_txns_to_load - txns_tried); + next_tenth_to_report = percentage_done / 10; + } + ++txns_tried; + + CTransactionRef tx; + int64_t nTime; + int64_t nFeeDelta; + file >> TX_WITH_WITNESS(tx); + file >> nTime; + file >> nFeeDelta; + + if (opts.use_current_time) { + nTime = TicksSinceEpoch<std::chrono::seconds>(now); + } + + CAmount amountdelta = nFeeDelta; + if (amountdelta && opts.apply_fee_delta_priority) { + pool.PrioritiseTransaction(tx->GetHash(), amountdelta); + } + if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) { + LOCK(cs_main); + const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false); + if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) { + ++count; + } else { + // mempool may contain the transaction already, e.g. from + // wallet(s) having loaded it while we were processing + // mempool transactions; consider these as valid, instead of + // failed, but mark them as 'already there' + if (pool.exists(GenTxid::Txid(tx->GetHash()))) { + ++already_there; + } else { + ++failed; + } + } + } else { + ++expired; + } + if (active_chainstate.m_chainman.m_interrupt) + return false; + } + std::map<uint256, CAmount> mapDeltas; + file >> mapDeltas; + + if (opts.apply_fee_delta_priority) { + for (const auto& i : mapDeltas) { + pool.PrioritiseTransaction(i.first, i.second); + } + } + + std::set<uint256> unbroadcast_txids; + file >> unbroadcast_txids; + if (opts.apply_unbroadcast_set) { + unbroadcast = unbroadcast_txids.size(); + for (const auto& txid : unbroadcast_txids) { + // Ensure transactions were accepted to mempool then add to + // unbroadcast set. + if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid); + } + } + } catch (const std::exception& e) { + LogInfo("Failed to deserialize mempool data on file: %s. Continuing anyway.\n", e.what()); + return false; + } + + LogInfo("Imported mempool transactions from file: %i succeeded, %i failed, %i expired, %i already there, %i waiting for initial broadcast\n", count, failed, expired, already_there, unbroadcast); + return true; +} + +bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit) +{ + auto start = SteadyClock::now(); + + std::map<uint256, CAmount> mapDeltas; + std::vector<TxMempoolInfo> vinfo; + std::set<uint256> unbroadcast_txids; + + static Mutex dump_mutex; + LOCK(dump_mutex); + + { + LOCK(pool.cs); + for (const auto &i : pool.mapDeltas) { + mapDeltas[i.first] = i.second; + } + vinfo = pool.infoAll(); + unbroadcast_txids = pool.GetUnbroadcastTxs(); + } + + auto mid = SteadyClock::now(); + + AutoFile file{mockable_fopen_function(dump_path + ".new", "wb")}; + if (file.IsNull()) { + return false; + } + + try { + const uint64_t version{pool.m_opts.persist_v1_dat ? MEMPOOL_DUMP_VERSION_NO_XOR_KEY : MEMPOOL_DUMP_VERSION}; + file << version; + + std::vector<std::byte> xor_key(8); + if (!pool.m_opts.persist_v1_dat) { + FastRandomContext{}.fillrand(xor_key); + file << xor_key; + } + file.SetXor(xor_key); + + uint64_t mempool_transactions_to_write(vinfo.size()); + file << mempool_transactions_to_write; + LogInfo("Writing %u mempool transactions to file...\n", mempool_transactions_to_write); + for (const auto& i : vinfo) { + file << TX_WITH_WITNESS(*(i.tx)); + file << int64_t{count_seconds(i.m_time)}; + file << int64_t{i.nFeeDelta}; + mapDeltas.erase(i.tx->GetHash()); + } + + file << mapDeltas; + + LogInfo("Writing %d unbroadcast transactions to file.\n", unbroadcast_txids.size()); + file << unbroadcast_txids; + + if (!skip_file_commit && !file.Commit()) + throw std::runtime_error("Commit failed"); + file.fclose(); + if (!RenameOver(dump_path + ".new", dump_path)) { + throw std::runtime_error("Rename failed"); + } + auto last = SteadyClock::now(); + + LogInfo("Dumped mempool: %.3fs to copy, %.3fs to dump, %d bytes dumped to file\n", + Ticks<SecondsDouble>(mid - start), + Ticks<SecondsDouble>(last - mid), + fs::file_size(dump_path)); + } catch (const std::exception& e) { + LogInfo("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); + return false; + } + return true; +} + +} // namespace node diff --git a/src/node/mempool_persist.h b/src/node/mempool_persist.h new file mode 100644 index 0000000000..7c5754a90c --- /dev/null +++ b/src/node/mempool_persist.h @@ -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. + +#ifndef BITCOIN_NODE_MEMPOOL_PERSIST_H +#define BITCOIN_NODE_MEMPOOL_PERSIST_H + +#include <util/fs.h> + +class Chainstate; +class CTxMemPool; + +namespace node { + +/** Dump the mempool to a file. */ +bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, + fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen, + bool skip_file_commit = false); + +struct ImportMempoolOptions { + fsbridge::FopenFn mockable_fopen_function{fsbridge::fopen}; + bool use_current_time{false}; + bool apply_fee_delta_priority{true}; + bool apply_unbroadcast_set{true}; +}; +/** Import the file and attempt to add its contents to the mempool. */ +bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, + Chainstate& active_chainstate, + ImportMempoolOptions&& opts); + +} // namespace node + + +#endif // BITCOIN_NODE_MEMPOOL_PERSIST_H diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 03c6d74deb..181ae2ef05 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -33,6 +33,14 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam int64_t nOldTime = pblock->nTime; int64_t nNewTime{std::max<int64_t>(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch<std::chrono::seconds>(NodeClock::now()))}; + if (consensusParams.enforce_BIP94) { + // Height of block to be mined. + const int height{pindexPrev->nHeight + 1}; + if (height % consensusParams.DifficultyAdjustmentInterval() == 0) { + nNewTime = std::max<int64_t>(nNewTime, pindexPrev->GetBlockTime() - MAX_TIMEWARP); + } + } + if (nOldTime < nNewTime) { pblock->nTime = nNewTime; } @@ -59,14 +67,17 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman) static BlockAssembler::Options ClampOptions(BlockAssembler::Options options) { - // Limit weight to between 4K and DEFAULT_BLOCK_MAX_WEIGHT for sanity: - options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, 4000, DEFAULT_BLOCK_MAX_WEIGHT); + Assert(options.coinbase_max_additional_weight <= DEFAULT_BLOCK_MAX_WEIGHT); + Assert(options.coinbase_output_max_additional_sigops <= MAX_BLOCK_SIGOPS_COST); + // Limit weight to between coinbase_max_additional_weight and DEFAULT_BLOCK_MAX_WEIGHT for sanity: + // Coinbase (reserved) outputs can safely exceed -blockmaxweight, but the rest of the block template will be empty. + options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, options.coinbase_max_additional_weight, DEFAULT_BLOCK_MAX_WEIGHT); return options; } BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options) : chainparams{chainstate.m_chainman.GetParams()}, - m_mempool{mempool}, + m_mempool{options.use_mempool ? mempool : nullptr}, m_chainstate{chainstate}, m_options{ClampOptions(options)} { @@ -79,6 +90,7 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio if (const auto blockmintxfee{args.GetArg("-blockmintxfee")}) { if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed}; } + options.print_modified_fee = args.GetBoolArg("-printpriority", options.print_modified_fee); } void BlockAssembler::resetBlock() @@ -86,8 +98,8 @@ void BlockAssembler::resetBlock() inBlock.clear(); // Reserve space for coinbase tx - nBlockWeight = 4000; - nBlockSigOpsCost = 400; + nBlockWeight = m_options.coinbase_max_additional_weight; + nBlockSigOpsCost = m_options.coinbase_output_max_additional_sigops; // These counters do not include coinbase tx nBlockTx = 0; @@ -101,10 +113,6 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc resetBlock(); pblocktemplate.reset(new CBlockTemplate()); - - if (!pblocktemplate.get()) { - return nullptr; - } CBlock* const pblock = &pblocktemplate->block; // pointer for convenience // Add dummy coinbase tx as first transaction @@ -167,7 +175,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc } const auto time_2{SteadyClock::now()}; - LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", + LogDebug(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)); @@ -222,8 +230,7 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) nFees += iter->GetFee(); inBlock.insert(iter->GetSharedTx()->GetHash()); - bool fPrintPriority = gArgs.GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY); - if (fPrintPriority) { + if (m_options.print_modified_fee) { LogPrintf("fee rate %s txid %s\n", CFeeRate(iter->GetModifiedFee(), iter->GetTxSize()).ToString(), iter->GetTx().GetHash().ToString()); @@ -379,7 +386,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele ++nConsecutiveFailed; if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight > - m_options.nBlockMaxWeight - 4000) { + m_options.nBlockMaxWeight - m_options.coinbase_max_additional_weight) { // Give up if we're close to full and haven't succeeded in a while break; } diff --git a/src/node/miner.h b/src/node/miner.h index c3178a7532..1b82943766 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_NODE_MINER_H #define BITCOIN_NODE_MINER_H +#include <node/types.h> #include <policy/policy.h> #include <primitives/block.h> #include <txmempool.h> @@ -30,7 +31,7 @@ class ChainstateManager; namespace Consensus { struct Params; }; namespace node { -static const bool DEFAULT_PRINTPRIORITY = false; +static const bool DEFAULT_PRINT_MODIFIED_FEE = false; struct CBlockTemplate { @@ -96,21 +97,25 @@ struct CompareTxIterByAncestorCount { } }; + +struct CTxMemPoolModifiedEntry_Indices final : boost::multi_index::indexed_by< + boost::multi_index::ordered_unique< + modifiedentry_iter, + CompareCTxMemPoolIter + >, + // sorted by modified ancestor fee rate + boost::multi_index::ordered_non_unique< + // Reuse same tag from CTxMemPool's similar index + boost::multi_index::tag<ancestor_score>, + boost::multi_index::identity<CTxMemPoolModifiedEntry>, + CompareTxMemPoolEntryByAncestorFee + > +> +{}; + typedef boost::multi_index_container< CTxMemPoolModifiedEntry, - boost::multi_index::indexed_by< - boost::multi_index::ordered_unique< - modifiedentry_iter, - CompareCTxMemPoolIter - >, - // sorted by modified ancestor fee rate - boost::multi_index::ordered_non_unique< - // Reuse same tag from CTxMemPool's similar index - boost::multi_index::tag<ancestor_score>, - boost::multi_index::identity<CTxMemPoolModifiedEntry>, - CompareTxMemPoolEntryByAncestorFee - > - > + CTxMemPoolModifiedEntry_Indices > indexed_modified_transaction_set; typedef indexed_modified_transaction_set::nth_index<0>::type::iterator modtxiter; @@ -153,12 +158,13 @@ private: Chainstate& m_chainstate; public: - struct Options { + struct Options : BlockCreateOptions { // Configuration parameters for the block size size_t nBlockMaxWeight{DEFAULT_BLOCK_MAX_WEIGHT}; CFeeRate blockMinFeeRate{DEFAULT_BLOCK_MIN_TX_FEE}; // Whether to call TestBlockValidity() at the end of CreateNewBlock(). bool test_block_validity{true}; + bool print_modified_fee{DEFAULT_PRINT_MODIFIED_FEE}; }; explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options); diff --git a/src/node/mini_miner.cpp b/src/node/mini_miner.cpp index 58422c4439..d7d15554b3 100644 --- a/src/node/mini_miner.cpp +++ b/src/node/mini_miner.cpp @@ -174,7 +174,7 @@ MiniMiner::MiniMiner(const std::vector<MiniMinerMempoolEntry>& manual_entries, SanityCheck(); } -// Compare by min(ancestor feerate, individual feerate), then iterator +// Compare by min(ancestor feerate, individual feerate), then txid // // Under the ancestor-based mining approach, high-feerate children can pay for parents, but high-feerate // parents do not incentive inclusion of their children. Therefore the mining algorithm only considers @@ -183,21 +183,13 @@ struct AncestorFeerateComparator { template<typename I> bool operator()(const I& a, const I& b) const { - auto min_feerate = [](const MiniMinerMempoolEntry& e) -> CFeeRate { - const CAmount ancestor_fee{e.GetModFeesWithAncestors()}; - const int64_t ancestor_size{e.GetSizeWithAncestors()}; - const CAmount tx_fee{e.GetModifiedFee()}; - const int64_t tx_size{e.GetTxSize()}; - // Comparing ancestor feerate with individual feerate: - // ancestor_fee / ancestor_size <= tx_fee / tx_size - // Avoid division and possible loss of precision by - // multiplying both sides by the sizes: - return ancestor_fee * tx_size < tx_fee * ancestor_size ? - CFeeRate(ancestor_fee, ancestor_size) : - CFeeRate(tx_fee, tx_size); + auto min_feerate = [](const MiniMinerMempoolEntry& e) -> FeeFrac { + FeeFrac self_feerate(e.GetModifiedFee(), e.GetTxSize()); + FeeFrac ancestor_feerate(e.GetModFeesWithAncestors(), e.GetSizeWithAncestors()); + return std::min(ancestor_feerate, self_feerate); }; - CFeeRate a_feerate{min_feerate(a->second)}; - CFeeRate b_feerate{min_feerate(b->second)}; + FeeFrac a_feerate{min_feerate(a->second)}; + FeeFrac b_feerate{min_feerate(b->second)}; if (a_feerate != b_feerate) { return a_feerate > b_feerate; } diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 591dcd698d..0f45da45db 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -55,7 +55,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t const Coin& existingCoin = view.AccessCoin(COutPoint(txid, o)); // IsSpent doesn't mean the coin is spent, it means the output doesn't exist. // So if the output does exist, then this transaction exists in the chain. - if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN; + if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_UTXO_SET; } if (auto mempool_tx = node.mempool->get(txid); mempool_tx) { diff --git a/src/node/txreconciliation.cpp b/src/node/txreconciliation.cpp index d62046daaa..e6e19c5756 100644 --- a/src/node/txreconciliation.cpp +++ b/src/node/txreconciliation.cpp @@ -85,7 +85,7 @@ public: 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)}; + const uint64_t local_salt{FastRandomContext().rand64()}; // 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. diff --git a/src/node/types.h b/src/node/types.h index 0461e85f43..1302f1b127 100644 --- a/src/node/types.h +++ b/src/node/types.h @@ -13,17 +13,37 @@ #ifndef BITCOIN_NODE_TYPES_H #define BITCOIN_NODE_TYPES_H +#include <cstddef> + namespace node { enum class TransactionError { OK, //!< No error MISSING_INPUTS, - ALREADY_IN_CHAIN, + ALREADY_IN_UTXO_SET, MEMPOOL_REJECTED, MEMPOOL_ERROR, MAX_FEE_EXCEEDED, MAX_BURN_EXCEEDED, INVALID_PACKAGE, }; + +struct BlockCreateOptions { + /** + * Set false to omit mempool transactions + */ + bool use_mempool{true}; + /** + * The maximum additional weight which the pool will add to the coinbase + * scriptSig, witness and outputs. This must include any additional + * weight needed for larger CompactSize encoded lengths. + */ + size_t coinbase_max_additional_weight{4000}; + /** + * The maximum additional sigops which the pool will add in coinbase + * transaction outputs. + */ + size_t coinbase_output_max_additional_sigops{400}; +}; } // namespace node #endif // BITCOIN_NODE_TYPES_H diff --git a/src/node/utxo_snapshot.cpp b/src/node/utxo_snapshot.cpp index 976421e455..ca5491bdc2 100644 --- a/src/node/utxo_snapshot.cpp +++ b/src/node/utxo_snapshot.cpp @@ -73,10 +73,10 @@ std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir) } afile >> base_blockhash; - if (std::fgetc(afile.Get()) != EOF) { + int64_t position = afile.tell(); + afile.seek(0, SEEK_END); + if (position != afile.tell()) { 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; } diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h index a7c4135787..e4eb6d60ad 100644 --- a/src/node/utxo_snapshot.h +++ b/src/node/utxo_snapshot.h @@ -28,16 +28,17 @@ class Chainstate; namespace node { //! Metadata describing a serialized version of a UTXO set from which an //! assumeutxo Chainstate can be constructed. +//! All metadata fields come from an untrusted file, so must be validated +//! before being used. Thus, new fields should be added only if needed. class SnapshotMetadata { - const uint16_t m_version{1}; - const std::set<uint16_t> m_supported_versions{1}; + inline static const uint16_t VERSION{2}; + const std::set<uint16_t> m_supported_versions{VERSION}; const MessageStartChars m_network_magic; public: //! The hash of the block that reflects the tip of the chain for the //! UTXO set contained in this snapshot. uint256 m_base_blockhash; - uint32_t m_base_blockheight; //! The number of coins in the UTXO set contained in this snapshot. Used @@ -50,19 +51,16 @@ public: SnapshotMetadata( const MessageStartChars network_magic, const uint256& base_blockhash, - const int base_blockheight, uint64_t coins_count) : m_network_magic(network_magic), m_base_blockhash(base_blockhash), - m_base_blockheight(base_blockheight), m_coins_count(coins_count) { } template <typename Stream> inline void Serialize(Stream& s) const { s << SNAPSHOT_MAGIC_BYTES; - s << m_version; + s << VERSION; s << m_network_magic; - s << m_base_blockheight; s << m_base_blockhash; s << m_coins_count; } @@ -98,7 +96,6 @@ public: } } - s >> m_base_blockheight; s >> m_base_blockhash; s >> m_coins_count; } diff --git a/src/node/validation_cache_args.cpp b/src/node/validation_cache_args.cpp deleted file mode 100644 index ddf24f798d..0000000000 --- a/src/node/validation_cache_args.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// 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 <common/args.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 deleted file mode 100644 index f447c13b49..0000000000 --- a/src/node/validation_cache_args.h +++ /dev/null @@ -1,17 +0,0 @@ -// 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 diff --git a/src/node/warnings.cpp b/src/node/warnings.cpp index b99c845900..87389e472b 100644 --- a/src/node/warnings.cpp +++ b/src/node/warnings.cpp @@ -28,8 +28,7 @@ Warnings::Warnings() } bool Warnings::Set(warning_type id, bilingual_str message) { - LOCK(m_mutex); - const auto& [_, inserted]{m_warnings.insert({id, std::move(message)})}; + const auto& [_, inserted]{WITH_LOCK(m_mutex, return m_warnings.insert({id, std::move(message)}))}; if (inserted) uiInterface.NotifyAlertChanged(); return inserted; } |