diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 6cd8876d4..33e89e2bc 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -1,984 +1,985 @@ // Copyright (c) 2011-2022 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace node { -std::atomic_bool fImporting(false); std::atomic_bool fReindex(false); static FILE *OpenUndoFile(const FlatFilePos &pos, bool fReadOnly = false); static FlatFileSeq BlockFileSeq(); static FlatFileSeq UndoFileSeq(); std::vector BlockManager::GetAllBlockIndices() { AssertLockHeld(cs_main); std::vector rv; rv.reserve(m_block_index.size()); for (auto &[_, block_index] : m_block_index) { rv.push_back(&block_index); } return rv; } CBlockIndex *BlockManager::LookupBlockIndex(const BlockHash &hash) { AssertLockHeld(cs_main); BlockMap::iterator it = m_block_index.find(hash); return it == m_block_index.end() ? nullptr : &it->second; } const CBlockIndex *BlockManager::LookupBlockIndex(const BlockHash &hash) const { AssertLockHeld(cs_main); BlockMap::const_iterator it = m_block_index.find(hash); return it == m_block_index.end() ? nullptr : &it->second; } CBlockIndex *BlockManager::AddToBlockIndex(const CBlockHeader &block, CBlockIndex *&best_header) { AssertLockHeld(cs_main); const auto [mi, inserted] = m_block_index.try_emplace(block.GetHash(), block); if (!inserted) { return &mi->second; } CBlockIndex *pindexNew = &(*mi).second; // We assign the sequence id to blocks only when the full data is available, // to avoid miners withholding blocks but broadcasting headers, to get a // competitive advantage. pindexNew->nSequenceId = 0; pindexNew->phashBlock = &((*mi).first); BlockMap::iterator miPrev = m_block_index.find(block.hashPrevBlock); if (miPrev != m_block_index.end()) { pindexNew->pprev = &(*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; pindexNew->BuildSkip(); } pindexNew->nTimeReceived = GetTime(); pindexNew->nTimeMax = (pindexNew->pprev ? std::max(pindexNew->pprev->nTimeMax, pindexNew->nTime) : pindexNew->nTime); pindexNew->nChainWork = (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) + GetBlockProof(*pindexNew); pindexNew->RaiseValidity(BlockValidity::TREE); if (best_header == nullptr || best_header->nChainWork < pindexNew->nChainWork) { best_header = pindexNew; } m_dirty_blockindex.insert(pindexNew); return pindexNew; } void BlockManager::PruneOneBlockFile(const int fileNumber) { AssertLockHeld(cs_main); LOCK(cs_LastBlockFile); for (auto &entry : m_block_index) { CBlockIndex *pindex = &entry.second; if (pindex->nFile == fileNumber) { pindex->nStatus = pindex->nStatus.withData(false).withUndo(false); pindex->nFile = 0; pindex->nDataPos = 0; pindex->nUndoPos = 0; m_dirty_blockindex.insert(pindex); // Prune from m_blocks_unlinked -- any block we prune would have // to be downloaded again in order to consider its chain, at which // point it would be considered as a candidate for // m_blocks_unlinked or setBlockIndexCandidates. auto range = m_blocks_unlinked.equal_range(pindex->pprev); while (range.first != range.second) { std::multimap::iterator _it = range.first; range.first++; if (_it->second == pindex) { m_blocks_unlinked.erase(_it); } } } } m_blockfile_info[fileNumber].SetNull(); m_dirty_fileinfo.insert(fileNumber); } void BlockManager::FindFilesToPruneManual(std::set &setFilesToPrune, int nManualPruneHeight, int chain_tip_height) { assert(IsPruneMode() && nManualPruneHeight > 0); LOCK2(cs_main, cs_LastBlockFile); if (chain_tip_height < 0) { return; } // last block to prune is the lesser of (user-specified height, // MIN_BLOCKS_TO_KEEP from the tip) unsigned int nLastBlockWeCanPrune{std::min( (unsigned)nManualPruneHeight, chain_tip_height - MIN_BLOCKS_TO_KEEP)}; int count = 0; for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) { if (m_blockfile_info[fileNumber].nSize == 0 || m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) { continue; } PruneOneBlockFile(fileNumber); setFilesToPrune.insert(fileNumber); count++; } LogPrintf("Prune (Manual): prune_height=%d removed %d blk/rev pairs\n", nLastBlockWeCanPrune, count); } void BlockManager::FindFilesToPrune(std::set &setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd) { LOCK2(cs_main, cs_LastBlockFile); if (chain_tip_height < 0 || GetPruneTarget() == 0) { return; } if (uint64_t(chain_tip_height) <= nPruneAfterHeight) { return; } unsigned int nLastBlockWeCanPrune = std::min( prune_height, chain_tip_height - static_cast(MIN_BLOCKS_TO_KEEP)); uint64_t nCurrentUsage = CalculateCurrentUsage(); // We don't check to prune until after we've allocated new space for files, // so we should leave a buffer under our target to account for another // allocation before the next pruning. uint64_t nBuffer = BLOCKFILE_CHUNK_SIZE + UNDOFILE_CHUNK_SIZE; uint64_t nBytesToPrune; int count = 0; if (nCurrentUsage + nBuffer >= GetPruneTarget()) { // On a prune event, the chainstate DB is flushed. // To avoid excessive prune events negating the benefit of high dbcache // values, we should not prune too rapidly. // So when pruning in IBD, increase the buffer a bit to avoid a re-prune // too soon. if (is_ibd) { // Since this is only relevant during IBD, we use a fixed 10% nBuffer += GetPruneTarget() / 10; } for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) { nBytesToPrune = m_blockfile_info[fileNumber].nSize + m_blockfile_info[fileNumber].nUndoSize; if (m_blockfile_info[fileNumber].nSize == 0) { continue; } // are we below our target? if (nCurrentUsage + nBuffer < GetPruneTarget()) { break; } // don't prune files that could have a block within // MIN_BLOCKS_TO_KEEP of the main chain's tip but keep scanning if (m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) { continue; } PruneOneBlockFile(fileNumber); // Queue up the files for removal setFilesToPrune.insert(fileNumber); nCurrentUsage -= nBytesToPrune; count++; } } LogPrint(BCLog::PRUNE, "Prune: target=%dMiB actual=%dMiB diff=%dMiB " "max_prune_height=%d removed %d blk/rev pairs\n", GetPruneTarget() / 1024 / 1024, nCurrentUsage / 1024 / 1024, (int64_t(GetPruneTarget()) - int64_t(nCurrentUsage)) / 1024 / 1024, nLastBlockWeCanPrune, count); } CBlockIndex *BlockManager::InsertBlockIndex(const BlockHash &hash) { AssertLockHeld(cs_main); if (hash.IsNull()) { return nullptr; } const auto [mi, inserted] = m_block_index.try_emplace(hash); CBlockIndex *pindex = &(*mi).second; if (inserted) { pindex->phashBlock = &((*mi).first); } return pindex; } bool BlockManager::LoadBlockIndex(const Consensus::Params ¶ms) { AssertLockHeld(cs_main); if (!m_block_tree_db->LoadBlockIndexGuts( params, [this](const BlockHash &hash) EXCLUSIVE_LOCKS_REQUIRED( cs_main) { return this->InsertBlockIndex(hash); })) { return false; } // Calculate nChainWork std::vector vSortedByHeight{GetAllBlockIndices()}; std::sort(vSortedByHeight.begin(), vSortedByHeight.end(), CBlockIndexHeightOnlyComparator()); for (CBlockIndex *pindex : vSortedByHeight) { if (ShutdownRequested()) { return false; } pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex); pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime); // We can link the chain of blocks for which we've received // transactions at some point, or blocks that are assumed-valid on the // basis of snapshot load (see PopulateAndValidateSnapshot()). // Pruned nodes may have deleted the block. if (pindex->nTx > 0) { if (!pindex->UpdateChainStats() && pindex->pprev) { m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex)); } } if (!pindex->nStatus.hasFailed() && pindex->pprev && pindex->pprev->nStatus.hasFailed()) { pindex->nStatus = pindex->nStatus.withFailedParent(); m_dirty_blockindex.insert(pindex); } if (pindex->pprev) { pindex->BuildSkip(); } } return true; } bool BlockManager::WriteBlockIndexDB() { std::vector> vFiles; vFiles.reserve(m_dirty_fileinfo.size()); for (int i : m_dirty_fileinfo) { vFiles.push_back(std::make_pair(i, &m_blockfile_info[i])); } m_dirty_fileinfo.clear(); std::vector vBlocks; vBlocks.reserve(m_dirty_blockindex.size()); for (const CBlockIndex *cbi : m_dirty_blockindex) { vBlocks.push_back(cbi); } m_dirty_blockindex.clear(); if (!m_block_tree_db->WriteBatchSync(vFiles, m_last_blockfile, vBlocks)) { return false; } return true; } bool BlockManager::LoadBlockIndexDB(const Consensus::Params &consensus_params) { if (!LoadBlockIndex(consensus_params)) { return false; } // Load block file info m_block_tree_db->ReadLastBlockFile(m_last_blockfile); m_blockfile_info.resize(m_last_blockfile + 1); LogPrintf("%s: last block file = %i\n", __func__, m_last_blockfile); for (int nFile = 0; nFile <= m_last_blockfile; nFile++) { m_block_tree_db->ReadBlockFileInfo(nFile, m_blockfile_info[nFile]); } LogPrintf("%s: last block file info: %s\n", __func__, m_blockfile_info[m_last_blockfile].ToString()); for (int nFile = m_last_blockfile + 1; true; nFile++) { CBlockFileInfo info; if (m_block_tree_db->ReadBlockFileInfo(nFile, info)) { m_blockfile_info.push_back(info); } else { break; } } // Check presence of blk files LogPrintf("Checking all blk files are present...\n"); std::set setBlkDataFiles; for (const auto &[_, block_index] : m_block_index) { if (block_index.nStatus.hasData()) { setBlkDataFiles.insert(block_index.nFile); } } for (const int i : setBlkDataFiles) { FlatFilePos pos(i, 0); if (CAutoFile(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION) .IsNull()) { return false; } } // Check whether we have ever pruned block & undo files m_block_tree_db->ReadFlag("prunedblockfiles", m_have_pruned); if (m_have_pruned) { LogPrintf( "LoadBlockIndexDB(): Block files have previously been pruned\n"); } // Check whether we need to continue reindexing if (m_block_tree_db->IsReindexing()) { fReindex = true; } return true; } const CBlockIndex * BlockManager::GetLastCheckpoint(const CCheckpointData &data) { const MapCheckpoints &checkpoints = data.mapCheckpoints; for (const MapCheckpoints::value_type &i : reverse_iterate(checkpoints)) { const BlockHash &hash = i.second; const CBlockIndex *pindex = LookupBlockIndex(hash); if (pindex) { return pindex; } } return nullptr; } bool BlockManager::IsBlockPruned(const CBlockIndex *pblockindex) { AssertLockHeld(::cs_main); return (m_have_pruned && !pblockindex->nStatus.hasData() && pblockindex->nTx > 0); } const CBlockIndex *GetFirstStoredBlock(const CBlockIndex *start_block) { AssertLockHeld(::cs_main); assert(start_block); const CBlockIndex *last_block = start_block; while (last_block->pprev && (last_block->pprev->nStatus.hasData())) { 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 rev files since they'll be // rewritten by the reindex anyway. This ensures that m_blockfile_info is in // sync with what's actually on disk by the time we start downloading, so that // pruning works correctly. void CleanupBlockRevFiles() { std::map mapBlockFiles; // Glob all blk?????.dat and rev?????.dat files from the blocks directory. // 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"); for (const auto &file : fs::directory_iterator{gArgs.GetBlocksDirPath()}) { const std::string path = fs::PathToString(file.path().filename()); if (fs::is_regular_file(file) && path.length() == 12 && path.substr(8, 4) == ".dat") { if (path.substr(0, 3) == "blk") { mapBlockFiles[path.substr(3, 5)] = file.path(); } else if (path.substr(0, 3) == "rev") { remove(file.path()); } } } // Remove all block files that aren't part of a contiguous set starting at // zero by walking the ordered map (keys are block file indices) by keeping // a separate counter. Once we hit a gap (or if 0 doesn't exist) start // removing block files. int contiguousCounter = 0; for (const auto &item : mapBlockFiles) { if (atoi(item.first) == contiguousCounter) { contiguousCounter++; continue; } remove(item.second); } } CBlockFileInfo *BlockManager::GetBlockFileInfo(size_t n) { LOCK(cs_LastBlockFile); return &m_blockfile_info.at(n); } static bool UndoWriteToDisk(const CBlockUndo &blockundo, FlatFilePos &pos, const BlockHash &hashBlock, const CMessageHeader::MessageMagic &messageStart) { // Open history file to append CAutoFile fileout(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION); if (fileout.IsNull()) { return error("%s: OpenUndoFile failed", __func__); } // Write index header unsigned int nSize = GetSerializeSize(blockundo, fileout.GetVersion()); fileout << messageStart << nSize; // Write undo data long fileOutPos = ftell(fileout.Get()); if (fileOutPos < 0) { return error("%s: ftell failed", __func__); } pos.nPos = (unsigned int)fileOutPos; fileout << blockundo; // calculate & write checksum CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION); hasher << hashBlock; hasher << blockundo; fileout << hasher.GetHash(); return true; } bool UndoReadFromDisk(CBlockUndo &blockundo, const CBlockIndex *pindex) { const FlatFilePos pos{WITH_LOCK(::cs_main, return pindex->GetUndoPos())}; if (pos.IsNull()) { return error("%s: no undo data available", __func__); } // Open history file to read CAutoFile filein(OpenUndoFile(pos, true), SER_DISK, CLIENT_VERSION); if (filein.IsNull()) { return error("%s: OpenUndoFile failed", __func__); } // Read block uint256 hashChecksum; // We need a CHashVerifier as reserializing may lose data CHashVerifier verifier(&filein); try { verifier << pindex->pprev->GetBlockHash(); verifier >> blockundo; filein >> hashChecksum; } catch (const std::exception &e) { return error("%s: Deserialize or I/O error - %s", __func__, e.what()); } // Verify checksum if (hashChecksum != verifier.GetHash()) { return error("%s: Checksum mismatch", __func__); } return true; } void 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)) { AbortNode("Flushing undo file to disk failed. This is likely the " "result of an I/O error."); } } void BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo) { LOCK(cs_LastBlockFile); if (m_blockfile_info.empty()) { // 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(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."); } // we do not always flush the undo file, as the chain tip may be lagging // behind the incoming blocks, // e.g. during IBD or a sync after a node going offline if (!fFinalize || finalize_undo) { FlushUndoFile(m_last_blockfile, finalize_undo); } } uint64_t BlockManager::CalculateCurrentUsage() { LOCK(cs_LastBlockFile); uint64_t retval = 0; for (const CBlockFileInfo &file : m_blockfile_info) { retval += file.nSize + file.nUndoSize; } return retval; } void UnlinkPrunedFiles(const std::set &setFilesToPrune) { for (const int i : setFilesToPrune) { FlatFilePos pos(i, 0); fs::remove(BlockFileSeq().FileName(pos)); fs::remove(UndoFileSeq().FileName(pos)); LogPrint(BCLog::BLOCKSTORE, "Prune: %s deleted blk/rev (%05u)\n", __func__, i); } } static FlatFileSeq BlockFileSeq() { return FlatFileSeq(gArgs.GetBlocksDirPath(), "blk", gArgs.GetBoolArg("-fastprune", false) ? 0x4000 /* 16kb */ : BLOCKFILE_CHUNK_SIZE); } static FlatFileSeq UndoFileSeq() { return FlatFileSeq(gArgs.GetBlocksDirPath(), "rev", UNDOFILE_CHUNK_SIZE); } FILE *OpenBlockFile(const FlatFilePos &pos, bool fReadOnly) { return BlockFileSeq().Open(pos, fReadOnly); } /** Open an undo file (rev?????.dat) */ static FILE *OpenUndoFile(const FlatFilePos &pos, bool fReadOnly) { return UndoFileSeq().Open(pos, fReadOnly); } fs::path GetBlockPosFilename(const FlatFilePos &pos) { return BlockFileSeq().FileName(pos); } bool BlockManager::FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int nHeight, CChain &active_chain, uint64_t nTime, bool fKnown) { LOCK(cs_LastBlockFile); unsigned int nFile = fKnown ? pos.nFile : m_last_blockfile; if (m_blockfile_info.size() <= nFile) { m_blockfile_info.resize(nFile + 1); } bool finalize_undo = false; if (!fKnown) { while (m_blockfile_info[nFile].nSize + nAddSize >= (gArgs.GetBoolArg("-fastprune", false) ? 0x10000 /* 64kb */ : MAX_BLOCKFILE_SIZE)) { // when the undo file is keeping up with the block file, we want to // flush it explicitly when it is lagging behind (more blocks arrive // than are being connected), we let the undo block write case // handle it finalize_undo = (m_blockfile_info[nFile].nHeightLast == (unsigned int)active_chain.Tip()->nHeight); nFile++; if (m_blockfile_info.size() <= nFile) { m_blockfile_info.resize(nFile + 1); } } pos.nFile = nFile; pos.nPos = m_blockfile_info[nFile].nSize; } if ((int)nFile != m_last_blockfile) { if (!fKnown) { LogPrint(BCLog::BLOCKSTORE, "Leaving block file %i: %s\n", m_last_blockfile, m_blockfile_info[m_last_blockfile].ToString()); } FlushBlockFile(!fKnown, finalize_undo); m_last_blockfile = nFile; } m_blockfile_info[nFile].AddBlock(nHeight, nTime); if (fKnown) { m_blockfile_info[nFile].nSize = std::max(pos.nPos + nAddSize, m_blockfile_info[nFile].nSize); } else { m_blockfile_info[nFile].nSize += nAddSize; } if (!fKnown) { bool out_of_space; size_t bytes_allocated = BlockFileSeq().Allocate(pos, nAddSize, out_of_space); if (out_of_space) { return AbortNode("Disk space is too low!", _("Disk space is too low!")); } if (bytes_allocated != 0 && IsPruneMode()) { m_check_for_pruning = true; } } m_dirty_fileinfo.insert(nFile); return true; } bool BlockManager::FindUndoPos(BlockValidationState &state, int nFile, FlatFilePos &pos, unsigned int nAddSize) { pos.nFile = nFile; LOCK(cs_LastBlockFile); pos.nPos = m_blockfile_info[nFile].nUndoSize; m_blockfile_info[nFile].nUndoSize += nAddSize; m_dirty_fileinfo.insert(nFile); bool out_of_space; size_t bytes_allocated = UndoFileSeq().Allocate(pos, nAddSize, out_of_space); if (out_of_space) { return AbortNode(state, "Disk space is too low!", _("Disk space is too low!")); } if (bytes_allocated != 0 && IsPruneMode()) { m_check_for_pruning = true; } return true; } static bool WriteBlockToDisk(const CBlock &block, FlatFilePos &pos, const CMessageHeader::MessageMagic &messageStart) { // Open history file to append CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION); if (fileout.IsNull()) { return error("WriteBlockToDisk: OpenBlockFile failed"); } // Write index header unsigned int nSize = GetSerializeSize(block, fileout.GetVersion()); fileout << messageStart << nSize; // Write block long fileOutPos = ftell(fileout.Get()); if (fileOutPos < 0) { return error("WriteBlockToDisk: ftell failed"); } pos.nPos = (unsigned int)fileOutPos; fileout << block; return true; } bool BlockManager::WriteUndoDataForBlock(const CBlockUndo &blockundo, BlockValidationState &state, CBlockIndex *pindex, const CChainParams &chainparams) { AssertLockHeld(::cs_main); // Write undo information to disk if (pindex->GetUndoPos().IsNull()) { FlatFilePos _pos; if (!FindUndoPos(state, pindex->nFile, _pos, ::GetSerializeSize(blockundo, CLIENT_VERSION) + 40)) { return error("ConnectBlock(): FindUndoPos failed"); } if (!UndoWriteToDisk(blockundo, _pos, pindex->pprev->GetBlockHash(), chainparams.DiskMagic())) { return AbortNode(state, "Failed to write undo data"); } // rev files are written in block height order, whereas blk files are // written as blocks come in (often out of order) we want to flush the // rev (undo) file once we've written the last block, which is indicated // by the last height in the block file info as below; note that this // does not catch the case where the undo writes are keeping up with the // block writes (usually when a synced up node is getting newly mined // blocks) -- this case is caught in the FindBlockPos function if (_pos.nFile < m_last_blockfile && static_cast(pindex->nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) { FlushUndoFile(_pos.nFile, true); } // update nUndoPos in block index pindex->nUndoPos = _pos.nPos; pindex->nStatus = pindex->nStatus.withUndo(); m_dirty_blockindex.insert(pindex); } return true; } bool ReadBlockFromDisk(CBlock &block, const FlatFilePos &pos, const Consensus::Params ¶ms) { block.SetNull(); // Open history file to read CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION); if (filein.IsNull()) { return error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString()); } // Read block try { filein >> block; } catch (const std::exception &e) { return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString()); } // Check the header if (!CheckProofOfWork(block.GetHash(), block.nBits, params)) { return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); } return true; } bool ReadBlockFromDisk(CBlock &block, const CBlockIndex *pindex, const Consensus::Params ¶ms) { const FlatFilePos block_pos{ WITH_LOCK(cs_main, return pindex->GetBlockPos())}; if (!ReadBlockFromDisk(block, block_pos, params)) { return false; } if (block.GetHash() != pindex->GetBlockHash()) { return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() " "doesn't match index for %s at %s", pindex->ToString(), block_pos.ToString()); } return true; } bool ReadTxFromDisk(CMutableTransaction &tx, const FlatFilePos &pos) { // Open history file to read CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION); if (filein.IsNull()) { return error("ReadTxFromDisk: OpenBlockFile failed for %s", pos.ToString()); } // Read tx try { filein >> tx; } catch (const std::exception &e) { return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString()); } return true; } bool ReadTxUndoFromDisk(CTxUndo &tx_undo, const FlatFilePos &pos) { // Open undo file to read CAutoFile filein(OpenUndoFile(pos, true), SER_DISK, CLIENT_VERSION); if (filein.IsNull()) { return error("ReadTxUndoFromDisk: OpenUndoFile failed for %s", pos.ToString()); } // Read undo data try { filein >> tx_undo; } catch (const std::exception &e) { return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString()); } return true; } 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; 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(BLOCK_SERIALIZATION_HEADER_SIZE); } if (!FindBlockPos(blockPos, nBlockSize, nHeight, active_chain, block.GetBlockTime(), position_known)) { error("%s: FindBlockPos failed", __func__); return FlatFilePos(); } if (!position_known) { if (!WriteBlockToDisk(block, blockPos, chainparams.DiskMagic())) { AbortNode("Failed to write block"); return FlatFilePos(); } } return blockPos; } -struct CImportingNow { - CImportingNow() { - assert(fImporting == false); - fImporting = true; - } +class ImportingNow { + std::atomic &m_importing; - ~CImportingNow() { - assert(fImporting == true); - fImporting = false; +public: + ImportingNow(std::atomic &importing) : m_importing{importing} { + assert(m_importing == false); + m_importing = true; + } + ~ImportingNow() { + assert(m_importing == true); + m_importing = false; } }; void ThreadImport(ChainstateManager &chainman, std::vector vImportFiles, const ArgsManager &args, const fs::path &mempool_path) { ScheduleBatchPriority(); { - CImportingNow imp; + ImportingNow imp{chainman.m_blockman.m_importing}; // -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 blocks_with_unknown_parent; while (true) { FlatFilePos pos(nFile, 0); if (!fs::exists(GetBlockPosFilename(pos))) { // No block files left to reindex break; } FILE *file = OpenBlockFile(pos, true); if (!file) { // This error is logged in OpenBlockFile break; } LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); chainman.ActiveChainstate().LoadExternalBlockFile( file, &pos, &blocks_with_unknown_parent); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exit %s\n", __func__); return; } nFile++; } WITH_LOCK( ::cs_main, chainman.m_blockman.m_block_tree_db->WriteReindexing(false)); fReindex = false; LogPrintf("Reindexing finished\n"); // To avoid ending up in a situation without genesis block, re-try // initializing (no-op if reindexing worked): chainman.ActiveChainstate().LoadGenesisBlock(); } // -loadblock= for (const fs::path &path : vImportFiles) { FILE *file = fsbridge::fopen(path, "rb"); if (file) { LogPrintf("Importing blocks file %s...\n", fs::PathToString(path)); chainman.ActiveChainstate().LoadExternalBlockFile(file); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exit %s\n", __func__); return; } } else { LogPrintf("Warning: Could not open blocks file %s\n", fs::PathToString(path)); } } // Reconsider blocks we know are valid. They may have been marked // invalid by, for instance, running an outdated version of the node // software. const MapCheckpoints &checkpoints = chainman.GetParams().Checkpoints().mapCheckpoints; for (const MapCheckpoints::value_type &i : checkpoints) { const BlockHash &hash = i.second; LOCK(cs_main); CBlockIndex *pblockindex = chainman.m_blockman.LookupBlockIndex(hash); if (pblockindex && !pblockindex->nStatus.isValid()) { LogPrintf("Reconsidering checkpointed block %s ...\n", hash.GetHex()); chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex); } if (pblockindex && pblockindex->nStatus.isOnParkedChain()) { LogPrintf("Unparking checkpointed block %s ...\n", hash.GetHex()); chainman.ActiveChainstate().UnparkBlockAndChildren(pblockindex); } } // scan for better chains in the block chain database, that are not yet // connected in the active best chain // 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 (Chainstate *chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { BlockValidationState state; if (!chainstate->ActivateBestChain(state, nullptr)) { LogPrintf("Failed to connect best block (%s)\n", state.ToString()); StartShutdown(); return; } } if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) { LogPrintf("Stopping after block import\n"); StartShutdown(); return; } - } // End scope of CImportingNow + } // End scope of ImportingNow chainman.ActiveChainstate().LoadMempool(mempool_path); } } // namespace node diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 67df50b4c..4aa66810a 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -1,256 +1,257 @@ // Copyright (c) 2011-2021 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_NODE_BLOCKSTORAGE_H #define BITCOIN_NODE_BLOCKSTORAGE_H #include #include #include #include #include #include #include // For CMessageHeader::MessageStartChars #include #include class ArgsManager; class BlockValidationState; class CBlock; class CBlockFileInfo; class CBlockHeader; class CBlockUndo; class CChain; class CChainParams; class CTxUndo; class Chainstate; class ChainstateManager; struct CCheckpointData; class Config; struct FlatFilePos; namespace Consensus { struct Params; } namespace node { static constexpr bool DEFAULT_STOPAFTERBLOCKIMPORT{false}; /** The pre-allocation chunk size for blk?????.dat files (since 0.8) */ static constexpr unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB /** The pre-allocation chunk size for rev?????.dat files (since 0.8) */ 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; // Because validation code takes pointers to the map's CBlockIndex objects, if // we ever switch to another associative container, we need to either use a // container that has stable addressing (true of all std associative // containers), or make the key a `std::unique_ptr` using BlockMap = std::unordered_map; /** * 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 `Chainstate` - information about, e.g., * candidate tips is not maintained here. */ class BlockManager { 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); bool FindUndoPos(BlockValidationState &state, int nFile, FlatFilePos &pos, unsigned int nAddSize); /** * Calculate the block/rev files to delete based on height specified * by user with RPC command pruneblockchain */ void FindFilesToPruneManual(std::set &setFilesToPrune, int nManualPruneHeight, int chain_tip_height); /** * Prune block and undo files (blk???.dat and undo???.dat) so that the disk * space used is less than a user-defined target. The user sets the target * (in MB) on the command line or in config file. This will be run on * startup and whenever new space is allocated in a block or undo file, * staying below the target. Changing back to unpruned requires a reindex * (which in this case means the blockchain must be re-downloaded.) * * Pruning functions are called from FlushStateToDisk when the * m_check_for_pruning flag has been set. Block and undo files are deleted * in lock-step (when blk00003.dat is deleted, so is rev00003.dat.) Pruning * cannot take place until the longest chain is at least a certain length * (CChainParams::nPruneAfterHeight). Pruning will never delete a block * within a defined distance (currently 288) from the active chain's tip. * The block index is updated by unsetting HAVE_DATA and HAVE_UNDO for any * blocks that were stored in the deleted files. A db flag records the fact * that at least some block files have been pruned. * * @param[out] setFilesToPrune The set of file indices that can be * unlinked will be returned */ void FindFilesToPrune(std::set &setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd); RecursiveMutex cs_LastBlockFile; std::vector m_blockfile_info; int m_last_blockfile = 0; /** * Global flag to indicate we should check to see if there are * block/undo files that should be deleted. Set on startup * or if we allocate more file space when we're in prune mode */ bool m_check_for_pruning = false; const bool m_prune_mode; /** Dirty block index entries. */ std::set m_dirty_blockindex; /** Dirty block file entries. */ std::set m_dirty_fileinfo; const kernel::BlockManagerOpts m_opts; public: using Options = kernel::BlockManagerOpts; explicit BlockManager(Options opts) : m_prune_mode{opts.prune_target > 0}, m_opts{std::move(opts)} {}; + std::atomic m_importing{false}; + BlockMap m_block_index GUARDED_BY(cs_main); std::vector GetAllBlockIndices() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** * All pairs A->B, where A (or one of its ancestors) misses transactions, * but B has transactions. Pruned nodes may have entries where B is missing * data. */ std::multimap m_blocks_unlinked; std::unique_ptr m_block_tree_db GUARDED_BY(::cs_main); bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool LoadBlockIndexDB(const Consensus::Params &consensus_params) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); CBlockIndex *AddToBlockIndex(const CBlockHeader &block, CBlockIndex *&best_header) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Create a new block index entry for a given block hash */ CBlockIndex *InsertBlockIndex(const BlockHash &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); //! Mark one block file as pruned (modify associated database entries) void PruneOneBlockFile(const int fileNumber) EXCLUSIVE_LOCKS_REQUIRED(cs_main); CBlockIndex *LookupBlockIndex(const BlockHash &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); const CBlockIndex *LookupBlockIndex(const BlockHash &hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Get block file info entry for one block file */ CBlockFileInfo *GetBlockFileInfo(size_t n); bool WriteUndoDataForBlock(const CBlockUndo &blockundo, BlockValidationState &state, CBlockIndex *pindex, const CChainParams &chainparams) 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); /** Whether running in -prune mode. */ [[nodiscard]] bool IsPruneMode() const { return m_prune_mode; } /** Attempt to stay below this number of bytes of block files. */ [[nodiscard]] uint64_t GetPruneTarget() const { return m_opts.prune_target; } - [[nodiscard]] bool LoadingBlocks() const { return fImporting || fReindex; } + [[nodiscard]] bool LoadingBlocks() const { return m_importing || fReindex; } /** * Calculate the amount of disk space the block & undo files currently use */ uint64_t CalculateCurrentUsage(); //! Returns last CBlockIndex* that is a checkpoint const CBlockIndex *GetLastCheckpoint(const CCheckpointData &data) 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); }; //! Find the first block that is not pruned const CBlockIndex *GetFirstStoredBlock(const CBlockIndex *start_block) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); void CleanupBlockRevFiles(); /** Open a block file (blk?????.dat) */ FILE *OpenBlockFile(const FlatFilePos &pos, bool fReadOnly = false); /** Translation to a filesystem path. */ fs::path GetBlockPosFilename(const FlatFilePos &pos); /** * Actually unlink the specified files */ void UnlinkPrunedFiles(const std::set &setFilesToPrune); /** Functions for disk access for blocks */ bool ReadBlockFromDisk(CBlock &block, const FlatFilePos &pos, const Consensus::Params &consensusParams); bool ReadBlockFromDisk(CBlock &block, const CBlockIndex *pindex, const Consensus::Params &consensusParams); bool UndoReadFromDisk(CBlockUndo &blockundo, const CBlockIndex *pindex); /** Functions for disk access for txs */ bool ReadTxFromDisk(CMutableTransaction &tx, const FlatFilePos &pos); bool ReadTxUndoFromDisk(CTxUndo &tx, const FlatFilePos &pos); void ThreadImport(ChainstateManager &chainman, std::vector vImportFiles, const ArgsManager &args, const fs::path &mempool_path); } // namespace node #endif // BITCOIN_NODE_BLOCKSTORAGE_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 81e16c177..4c658efca 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -1,793 +1,793 @@ // Copyright (c) 2018 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include class HTTPRPCRequestProcessor; using interfaces::BlockTip; using interfaces::Chain; using interfaces::FoundBlock; using interfaces::Handler; using interfaces::MakeHandler; using interfaces::Node; using interfaces::WalletClient; namespace node { namespace { 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)); } void initParameterInteraction() override { InitParameterInteraction(*Assert(m_context->args)); } bilingual_str getWarnings() override { return GetWarnings(true); } bool baseInitialize(Config &config) override { return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(config, gArgs) && AppInitSanityChecks() && AppInitLockDataDirectory() && AppInitInterfaces(*m_context); } bool appInitMain(Config &config, RPCServer &rpcServer, HTTPRPCRequestProcessor &httpRPCRequestProcessor, interfaces::BlockAndHeaderTipInfo *tip_info) override { return AppInitMain(config, rpcServer, httpRPCRequestProcessor, *m_context, tip_info); } void appShutdown() override { Interrupt(*m_context); Shutdown(*m_context); } void startShutdown() override { StartShutdown(); // Stop RPC for clean shutdown if any of waitfor* commands is // executed. if (gArgs.GetBoolArg("-server", false)) { InterruptRPC(); StopRPC(); } } bool shutdownRequested() override { return ShutdownRequested(); } bool isPersistentSettingIgnored(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, proxyType &proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; } bool getNodesStats(NodesStats &stats) override { stats.clear(); if (m_context->connman) { std::vector stats_temp; m_context->connman->GetNodeStats(stats_temp); stats.reserve(stats_temp.size()); for (auto &node_stats_temp : stats_temp) { stats.emplace_back(std::move(node_stats_temp), false, CNodeStateStats()); } // Try to retrieve the CNodeStateStats for each node. if (m_context->peerman) { TRY_LOCK(::cs_main, lockMain); if (lockMain) { for (auto &node_stats : stats) { std::get<1>(node_stats) = m_context->peerman->GetNodeStateStats( std::get<0>(node_stats).nodeid, std::get<2>(node_stats)); } } } return true; } return false; } bool getBanned(banmap_t &banmap) override { if (m_context->banman) { m_context->banman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr &net_addr, int64_t ban_time_offset) override { if (m_context->banman) { m_context->banman->Ban(net_addr, ban_time_offset); return true; } return false; } bool unban(const CSubNet &ip) override { if (m_context->banman) { m_context->banman->Unban(ip); return true; } return false; } bool disconnectByAddress(const CNetAddr &net_addr) override { if (m_context->connman) { return m_context->connman->DisconnectNode(net_addr); } return false; } bool disconnectById(NodeId id) override { if (m_context->connman) { return m_context->connman->DisconnectNode(id); } return false; } int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; } 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; } bool getHeaderTip(int &height, int64_t &block_time) override { LOCK(::cs_main); auto best_header = chainman().m_best_header; if (best_header) { height = best_header->nHeight; block_time = best_header->GetBlockTime(); return true; } return false; } int getNumBlocks() override { LOCK(::cs_main); return chainman().ActiveChain().Height(); } BlockHash getBestBlockHash() override { const CBlockIndex *tip = WITH_LOCK(::cs_main, return chainman().ActiveTip()); return tip ? tip->GetBlockHash() : chainman().GetParams().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { LOCK(::cs_main); if (chainman().ActiveChain().Tip()) { return chainman().ActiveChain().Tip()->GetBlockTime(); } // Genesis block's time of current network return chainman().GetParams().GenesisBlock().GetBlockTime(); } double getVerificationProgress() override { const CBlockIndex *tip; { LOCK(::cs_main); tip = chainman().ActiveChain().Tip(); } return GuessVerificationProgress(chainman().GetParams().TxData(), tip); } bool isInitialBlockDownload() override { return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool isLoadingBlocks() override { - return node::fReindex || node::fImporting; + return chainman().m_blockman.LoadingBlocks(); } void setNetworkActive(bool active) override { if (m_context->connman) { m_context->connman->SetNetworkActive(active); } } bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); } CFeeRate getDustRelayFee() override { if (!m_context->mempool) { return CFeeRate{DUST_RELAY_TX_FEE}; } return m_context->mempool->m_dust_relay_feerate; } UniValue executeRpc(const Config &config, const std::string &command, const UniValue ¶ms, const std::string &uri) override { JSONRPCRequest req; req.context = m_context; req.params = params; req.strMethod = command; req.URI = uri; return ::tableRPC.execute(config, req); } std::vector listRpcCommands() override { return ::tableRPC.listCommands(); } void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface) override { RPCSetTimerInterfaceIfUnset(iface); } void rpcUnsetTimerInterface(RPCTimerInterface *iface) override { RPCUnsetTimerInterface(iface); } bool getUnspentOutput(const COutPoint &output, Coin &coin) override { LOCK(::cs_main); return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin); } WalletClient &walletClient() override { return *Assert(m_context->wallet_client); } std::unique_ptr handleInitMessage(InitMessageFn fn) override { return MakeHandler(::uiInterface.InitMessage_connect(fn)); } std::unique_ptr handleMessageBox(MessageBoxFn fn) override { return MakeHandler(::uiInterface.ThreadSafeMessageBox_connect(fn)); } std::unique_ptr handleQuestion(QuestionFn fn) override { return MakeHandler(::uiInterface.ThreadSafeQuestion_connect(fn)); } std::unique_ptr handleShowProgress(ShowProgressFn fn) override { return MakeHandler(::uiInterface.ShowProgress_connect(fn)); } std::unique_ptr handleNotifyNumConnectionsChanged( NotifyNumConnectionsChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNumConnectionsChanged_connect(fn)); } std::unique_ptr handleNotifyNetworkActiveChanged( NotifyNetworkActiveChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNetworkActiveChanged_connect(fn)); } std::unique_ptr handleNotifyAlertChanged(NotifyAlertChangedFn fn) override { return MakeHandler(::uiInterface.NotifyAlertChanged_connect(fn)); } std::unique_ptr handleBannedListChanged(BannedListChangedFn fn) override { return MakeHandler(::uiInterface.BannedListChanged_connect(fn)); } std::unique_ptr handleNotifyBlockTip(NotifyBlockTipFn fn) override { return MakeHandler(::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 handleNotifyHeaderTip(NotifyHeaderTipFn fn) override { /* verification progress is unused when a header was received */ return MakeHandler(::uiInterface.NotifyHeaderTip_connect( [fn](SynchronizationState sync_state, int64_t height, int64_t timestamp, bool presync) { fn(sync_state, BlockTip{int(height), timestamp, BlockHash{}}, presync); })); } NodeContext *context() override { return m_context; } void setContext(NodeContext *context) override { m_context = context; } NodeContext *m_context{nullptr}; }; bool FillBlock(const CBlockIndex *index, const FoundBlock &block, UniqueLock &lock, const CChain &active) { if (!index) { return false; } if (block.m_hash) { *block.m_hash = index->GetBlockHash(); } if (block.m_height) { *block.m_height = index->nHeight; } if (block.m_time) { *block.m_time = index->GetBlockTime(); } 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; } // TODO backport core#25494 with change from core#25717 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); if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) { block.m_data->SetNull(); } } return true; } class NotificationsProxy : public CValidationInterface { public: explicit NotificationsProxy( std::shared_ptr notifications) : m_notifications(std::move(notifications)) {} virtual ~NotificationsProxy() = default; void TransactionAddedToMempool(const CTransactionRef &tx, std::shared_ptr>, uint64_t mempool_sequence) override { m_notifications->transactionAddedToMempool(tx, mempool_sequence); } void TransactionRemovedFromMempool(const CTransactionRef &tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override { m_notifications->transactionRemovedFromMempool(tx, reason, mempool_sequence); } void BlockConnected(const std::shared_ptr &block, const CBlockIndex *index) override { m_notifications->blockConnected(*block, index->nHeight); } void BlockDisconnected(const std::shared_ptr &block, const CBlockIndex *index) override { m_notifications->blockDisconnected(*block, index->nHeight); } void UpdatedBlockTip(const CBlockIndex *index, const CBlockIndex *fork_index, bool is_ibd) override { m_notifications->updatedBlockTip(); } void ChainStateFlushed(const CBlockLocator &locator) override { m_notifications->chainStateFlushed(locator); } std::shared_ptr m_notifications; }; class NotificationsHandlerImpl : public Handler { public: explicit NotificationsHandlerImpl( std::shared_ptr notifications) : m_proxy(std::make_shared( std::move(notifications))) { RegisterSharedValidationInterface(m_proxy); } ~NotificationsHandlerImpl() override { disconnect(); } void disconnect() override { if (m_proxy) { UnregisterSharedValidationInterface(m_proxy); m_proxy.reset(); } } std::shared_ptr m_proxy; }; class RpcHandlerImpl : public Handler { public: explicit RpcHandlerImpl(const CRPCCommand &command) : m_command(command), m_wrapped_command(&command) { m_command.actor = [this](const Config &config, const JSONRPCRequest &request, UniValue &result, bool last_handler) { if (!m_wrapped_command) { return false; } try { return m_wrapped_command->actor(config, request, result, last_handler); } catch (const UniValue &e) { // If this is not the last handler and a wallet not found // exception was thrown, return false so the next handler // can 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) { return false; } } throw; } }; ::tableRPC.appendCommand(m_command.name, &m_command); } void disconnect() final { if (m_wrapped_command) { m_wrapped_command = nullptr; ::tableRPC.removeCommand(m_command.name, &m_command); } } ~RpcHandlerImpl() override { disconnect(); } CRPCCommand m_command; const CRPCCommand *m_wrapped_command; }; class ChainImpl : public Chain { private: ChainstateManager &chainman() { return *Assert(m_node.chainman); } public: explicit ChainImpl(NodeContext &node, const CChainParams ¶ms) : m_node(node), m_params(params) {} std::optional 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; } BlockHash getBlockHash(int height) override { LOCK(::cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); CBlockIndex *block = active[height]; assert(block); return block->GetBlockHash(); } bool haveBlockOnDisk(int height) override { LOCK(cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); CBlockIndex *block = active[height]; return block && (block->nStatus.hasData() != 0) && block->nTx > 0; } CBlockLocator getTipLocator() override { LOCK(cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); return active.GetLocator(); } // TODO: backport core#25036 with changes from core#25717 std::optional findLocatorFork(const CBlockLocator &locator) override { LOCK(cs_main); const Chainstate &active = Assert(m_node.chainman)->ActiveChainstate(); if (const CBlockIndex *fork = active.FindForkInGlobalIndex(locator)) { return fork->nHeight; } return std::nullopt; } bool findBlock(const BlockHash &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); } 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(); return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active); } bool findAncestorByHeight(const BlockHash &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)) { if (const CBlockIndex *ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock, active); } } return FillBlock(nullptr, ancestor_out, lock, active); } bool findAncestorByHash(const BlockHash &block_hash, const BlockHash &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); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) { ancestor = nullptr; } return FillBlock(ancestor, ancestor_out, lock, active); } bool findCommonAncestor(const BlockHash &block_hash1, const BlockHash &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 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 compiler warnings. return int{FillBlock(ancestor, ancestor_out, lock, active)} & int{FillBlock(block1, block1_out, lock, active)} & int{FillBlock(block2, block2_out, lock, active)}; } void findCoins(std::map &coins) override { return FindCoins(m_node, coins); } double guessVerificationProgress(const BlockHash &block_hash) override { LOCK(cs_main); return GuessVerificationProgress( chainman().GetParams().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); } bool hasBlocks(const BlockHash &block_hash, int min_height, std::optional max_height) override { // hasBlocks returns true if all ancestors of block_hash in // specified range have block data (are not pruned), false if any // ancestors in specified range are missing data. // // For simplicity and robustness, min_height and max_height are only // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); if (const CBlockIndex *block = chainman().m_blockman.LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) { block = block->GetAncestor(*max_height); } for (; block->nStatus.hasData(); block = block->pprev) { // Check pprev to not segfault if min_height is too low if (block->nHeight <= min_height || !block->pprev) { return true; } } } return false; } bool broadcastTransaction(const Config &config, const CTransactionRef &tx, const Amount &max_tx_fee, bool relay, std::string &err_string) override { const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback=*/false); // Chain clients only care about failures to accept the tx to the // mempool. Disregard non-mempool related failures. Note: this will // need to be updated if BroadcastTransactions() is updated to // return other non-mempool failures that Chain clients do not need // to know about. return err == TransactionError::OK; } CFeeRate estimateFee() const override { if (!m_node.mempool) { return {}; } return m_node.mempool->estimateFee(); } CFeeRate relayMinFee() override { if (!m_node.mempool) { return CFeeRate{DEFAULT_MIN_RELAY_TX_FEE_PER_KB}; } return m_node.mempool->m_min_relay_feerate; } CFeeRate relayDustFee() override { if (!m_node.mempool) { return CFeeRate{DUST_RELAY_TX_FEE}; } return m_node.mempool->m_dust_relay_feerate; } bool havePruned() override { LOCK(cs_main); return m_node.chainman->m_blockman.m_have_pruned; } bool isReadyToBroadcast() override { return !chainman().m_blockman.LoadingBlocks() && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } void initMessage(const std::string &message) override { ::uiInterface.InitMessage(message); } void initWarning(const bilingual_str &message) override { InitWarning(message); } void initError(const bilingual_str &message) override { InitError(message); } void showProgress(const std::string &title, int progress, bool resume_possible) override { ::uiInterface.ShowProgress(title, progress, resume_possible); } std::unique_ptr handleNotifications( std::shared_ptr notifications) override { return std::make_unique( std::move(notifications)); } void waitForNotificationsIfTipChanged(const BlockHash &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; } } SyncWithValidationInterfaceQueue(); } std::unique_ptr handleRpc(const CRPCCommand &command) override { return std::make_unique(command); } bool rpcEnableDeprecated(const std::string &method) override { return IsDeprecatedRPCEnabled(gArgs, method); } void rpcRunLater(const std::string &name, std::function fn, int64_t seconds) override { RPCRunLater(name, std::move(fn), seconds); } int rpcSerializationFlags() override { return RPCSerializationFlags(); } util::SettingsValue getSetting(const std::string &name) override { return gArgs.GetSetting(name); } std::vector getSettingsList(const std::string &name) override { return gArgs.GetSettingsList(name); } util::SettingsValue getRwSetting(const std::string &name) override { util::SettingsValue result; gArgs.LockSettings([&](const util::Settings &settings) { if (const util::SettingsValue *value = util::FindKey(settings.rw_settings, name)) { result = *value; } }); return result; } bool updateRwSetting(const std::string &name, const util::SettingsValue &value, bool write) override { gArgs.LockSettings([&](util::Settings &settings) { if (value.isNull()) { settings.rw_settings.erase(name); } else { settings.rw_settings[name] = value; } }); return !write || gArgs.WriteSettingsFile(); } void requestMempoolTransactions(Notifications ¬ifications) override { if (!m_node.mempool) { return; } LOCK2(::cs_main, m_node.mempool->cs); for (const CTxMemPoolEntryRef &entry : m_node.mempool->mapTx) { notifications.transactionAddedToMempool(entry->GetSharedTx(), /*mempool_sequence=*/0); } } const CChainParams ¶ms() const override { return m_params; } NodeContext &m_node; const CChainParams &m_params; }; } // namespace } // namespace node namespace interfaces { std::unique_ptr MakeNode(node::NodeContext *context) { return std::make_unique(context); } std::unique_ptr MakeChain(node::NodeContext &node, const CChainParams ¶ms) { return std::make_unique(node, params); } } // namespace interfaces