diff --git a/src/active/dkgsession.cpp b/src/active/dkgsession.cpp index b50bd966f194..7ab5d2756b90 100644 --- a/src/active/dkgsession.cpp +++ b/src/active/dkgsession.cpp @@ -92,6 +92,14 @@ std::optional ActiveDKGSession::SendContributions() skContrib.MakeNewKey(); } + if (i != myIdx && ShouldSimulateError(DKGError::type::CONTRIBUTION_INVALID)) { + // Deliver a share that decrypts to an all-zero (structurally invalid) + // CBLSSecretKey on the recipient. Used to exercise the DKG intake's + // handling of a peer-supplied invalid contribution share. + logger.Batch("sending invalid (zero) share to %s", m->dmn->proTxHash.ToString()); + skContrib = CBLSSecretKey{}; + } + if (!qc.contributions->Encrypt(i, m->dmn->pdmnState->pubKeyOperator.Get(), skContrib, PROTOCOL_VERSION)) { logger.Batch("failed to encrypt contribution for %s", m->dmn->proTxHash.ToString()); return std::nullopt; diff --git a/src/bls/bls_worker.cpp b/src/bls/bls_worker.cpp index 554f060a0767..aeae68b1ed3b 100644 --- a/src/bls/bls_worker.cpp +++ b/src/bls/bls_worker.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -69,11 +70,6 @@ void CBLSWorker::Stop() workerPool.stop(true); } -void CBLSWorker::PushJob(std::function job) -{ - workerPool.push([job = std::move(job)](int) { job(); }); -} - #ifndef BUILD_BITCOIN_INTERNAL bool CBLSWorker::GenerateContributions(int quorumThreshold, Span ids, BLSVerificationVectorPtr& vvecRet, std::vector& skSharesRet) { @@ -304,8 +300,14 @@ struct Aggregator : public std::enable_shared_from_this> { template static T SyncAggregate(Span vec, size_t start, size_t count) { + // Defence in depth: AggregateInsecure() asserts that every operand is valid, so + // peer-influenced data (e.g. a decrypted contribution share) must never reach it + // while invalid. Short-circuit to an invalid aggregate instead; callers already + // treat an invalid result as an aggregation failure rather than aborting. T result = *vec[start]; + if (!result.IsValid()) return {}; for (size_t j = 1; j < count; j++) { + if (!vec[start + j]->IsValid()) return {}; result.AggregateInsecure(*vec[start + j]); } return result; @@ -686,6 +688,13 @@ CBLSPublicKey CBLSWorker::BuildPubKeyShare(const BLSVerificationVectorPtr& vvec, void CBLSWorker::AsyncVerifyContributionShares(const CBLSId& forId, Span vvecs, Span skShares, bool parallel, bool aggregated, std::function&)> doneCallback) { + if (!forId.IsValid()) { + std::vector result; + result.assign(vvecs.size(), false); + doneCallback(result); + return; + } + // Empty input never schedules any work, so doneCallback would never fire and the // future-based wrappers would throw broken_promise. Mirror the empty-input // short-circuit in AsyncBuildQuorumVerificationVector / AsyncAggregateHelper. @@ -693,9 +702,31 @@ void CBLSWorker::AsyncVerifyContributionShares(const CBLSId& forId, Span{}); return; } - if (!forId.IsValid() || !VerifyVerificationVectors(vvecs)) { + + if (!VerifyVerificationVectors(vvecs)) { + // A batch-level verification-vector problem must not fail the whole batch. The + // only thing the batched VerifyVerificationVectors adds over the per-member + // VerifyVerificationVector check already run in PreVerifyMessage is detecting a + // coefficient point shared between two members' vvecs. Failing the whole batch on + // that lets one member, by placing a coefficient that collides with another + // member's, force every other honest member in the same verification batch to be + // marked invalid and complained about - a denial of service against their + // participation in the DKG. Instead, verify each contribution on its own: a member + // fails only if its own share is inconsistent with its own vvec, so a member that + // placed a colliding coefficient is caught on its own while the honest members it + // collided with verify normally. + LogPrint(BCLog::LLMQ_DKG, "CBLSWorker::%s -- batch verification-vector check failed; verifying %d contributions individually\n", + __func__, vvecs.size()); + std::vector> futures; + futures.reserve(vvecs.size()); + for (size_t i = 0; i < vvecs.size(); i++) { + futures.emplace_back(AsyncVerifyContributionShare(forId, vvecs[i], skShares[i])); + } std::vector result; - result.assign(vvecs.size(), false); + result.reserve(vvecs.size()); + for (auto& f : futures) { + result.push_back(f.get()); + } doneCallback(result); return; } diff --git a/src/bls/bls_worker.h b/src/bls/bls_worker.h index 7e57df6e964c..369058a9591a 100644 --- a/src/bls/bls_worker.h +++ b/src/bls/bls_worker.h @@ -56,7 +56,6 @@ class CBLSWorker void Start(int16_t worker_count); void Stop(); - void PushJob(std::function job); #ifndef BUILD_BITCOIN_INTERNAL bool GenerateContributions(int threshold, Span ids, BLSVerificationVectorPtr& vvecRet, std::vector& skSharesRet); diff --git a/src/chain.h b/src/chain.h index d3ea67d24dfa..a67a88641e9f 100644 --- a/src/chain.h +++ b/src/chain.h @@ -41,8 +41,8 @@ class CBlockFileInfo { public: unsigned int nBlocks{}; //!< number of blocks stored in file - unsigned int nSize{}; //!< number of used bytes of block file - unsigned int nUndoSize{}; //!< number of used bytes in the undo file + uint64_t nSize{}; //!< number of used bytes of block file + uint64_t nUndoSize{}; //!< number of used bytes in the undo file unsigned int nHeightFirst{}; //!< lowest height of block in file unsigned int nHeightLast{}; //!< highest height of block in file uint64_t nTimeFirst{}; //!< earliest time of block in file @@ -158,10 +158,10 @@ class CBlockIndex int nFile GUARDED_BY(::cs_main){0}; //! Byte offset within blk?????.dat where this block's data is stored - unsigned int nDataPos GUARDED_BY(::cs_main){0}; + uint64_t nDataPos GUARDED_BY(::cs_main){0}; //! Byte offset within rev?????.dat where this block's undo data is stored - unsigned int nUndoPos GUARDED_BY(::cs_main){0}; + uint64_t nUndoPos GUARDED_BY(::cs_main){0}; //! (memory only) Total amount of work (expected number of hashes) in the chain up to and including this block arith_uint256 nChainWork{}; diff --git a/src/flatfile.cpp b/src/flatfile.cpp index ef8df497afaf..bee9e6d9b51c 100644 --- a/src/flatfile.cpp +++ b/src/flatfile.cpp @@ -45,7 +45,7 @@ FILE* FlatFileSeq::Open(const FlatFilePos& pos, bool read_only) LogPrintf("Unable to open file %s\n", fs::PathToString(path)); return nullptr; } - if (pos.nPos && fseek(file, pos.nPos, SEEK_SET)) { + if (pos.nPos && FileSeek64(file, pos.nPos, SEEK_SET)) { LogPrintf("Unable to seek to position %u of %s\n", pos.nPos, fs::PathToString(path)); fclose(file); return nullptr; @@ -57,11 +57,11 @@ size_t FlatFileSeq::Allocate(const FlatFilePos& pos, size_t add_size, bool& out_ { out_of_space = false; - unsigned int n_old_chunks = (pos.nPos + m_chunk_size - 1) / m_chunk_size; - unsigned int n_new_chunks = (pos.nPos + add_size + m_chunk_size - 1) / m_chunk_size; + uint64_t n_old_chunks = (pos.nPos + m_chunk_size - 1) / m_chunk_size; + uint64_t n_new_chunks = (pos.nPos + add_size + m_chunk_size - 1) / m_chunk_size; if (n_new_chunks > n_old_chunks) { - size_t old_size = pos.nPos; - size_t new_size = n_new_chunks * m_chunk_size; + uint64_t old_size = pos.nPos; + uint64_t new_size = n_new_chunks * m_chunk_size; size_t inc_size = new_size - old_size; if (CheckDiskSpace(m_dir, inc_size)) { diff --git a/src/flatfile.h b/src/flatfile.h index 44b7c5ef9f51..d35793a26e93 100644 --- a/src/flatfile.h +++ b/src/flatfile.h @@ -14,7 +14,7 @@ struct FlatFilePos { int nFile{-1}; - unsigned int nPos{0}; + uint64_t nPos{0}; SERIALIZE_METHODS(FlatFilePos, obj) { @@ -23,7 +23,7 @@ struct FlatFilePos FlatFilePos() {} - FlatFilePos(int nFileIn, unsigned int nPosIn) : + FlatFilePos(int nFileIn, uint64_t nPosIn) : nFile(nFileIn), nPos(nPosIn) {} diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index c64fb419227a..138cd5ac8f7f 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -388,6 +389,14 @@ void CGovernanceManager::AddGovernanceObjectForTesting(const CGovernanceObject& mapObjects.emplace(govobj.GetHash(), std::make_shared(govobj)); } +void CGovernanceManager::AddOrphanVoteForTesting(const CGovernanceVote& vote) +{ + AssertLockNotHeld(cs_store); + LOCK(cs_store); + cmmapOrphanVotes.Insert(vote.GetParentHash(), + governance::OrphanVote{vote, Now() + GOVERNANCE_ORPHAN_EXPIRATION_TIME}); +} + void CGovernanceManager::CheckAndRemove() { AssertLockNotHeld(cs_store); @@ -1159,6 +1168,12 @@ std::vector CGovernanceManager::GetOrphanVoteObjectHashes() } } + // A random subset, so that parent hashes ground to sort first cannot pin which ones are requested + if (vecHashesFiltered.size() > MAX_ORPHAN_OBJECT_REQUESTS) { + Shuffle(vecHashesFiltered.begin(), vecHashesFiltered.end(), FastRandomContext()); + vecHashesFiltered.resize(MAX_ORPHAN_OBJECT_REQUESTS); + } + return vecHashesFiltered; } diff --git a/src/governance/governance.h b/src/governance/governance.h index 04536cb5ef04..481fc8875ebb 100644 --- a/src/governance/governance.h +++ b/src/governance/governance.h @@ -332,6 +332,8 @@ class CGovernanceManager : public GovernanceStore * running collateral or chain validation. */ void AddGovernanceObjectForTesting(const CGovernanceObject& govobj) EXCLUSIVE_LOCKS_REQUIRED(!cs_store); + /** Test-only helper: caches a vote as an orphan of its parent without validating it. */ + void AddOrphanVoteForTesting(const CGovernanceVote& vote) EXCLUSIVE_LOCKS_REQUIRED(!cs_store); // Thread-safe accessors bool HaveObjectForHash(const uint256& nHash) const @@ -358,7 +360,12 @@ class CGovernanceManager : public GovernanceStore // Used by NetGovernance std::vector FetchRelayInventory() EXCLUSIVE_LOCKS_REQUIRED(!cs_relay); void CheckAndRemove() EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - /** Get hashes of governance objects for which we have orphan votes. Also cleans up expired orphans. */ + /** Upper bound on the orphan parent hashes GetOrphanVoteObjectHashes() returns per call. Each one is + * re-requested from every peer, and one masternode key can sign votes for any number of invented + * parent hashes. */ + static constexpr size_t MAX_ORPHAN_OBJECT_REQUESTS{100}; + /** Get hashes of governance objects for which we have orphan votes, at most + * MAX_ORPHAN_OBJECT_REQUESTS of them chosen at random. Also cleans up expired orphans. */ [[nodiscard]] std::vector GetOrphanVoteObjectHashes() EXCLUSIVE_LOCKS_REQUIRED(!cs_store); std::pair, std::vector> FetchGovernanceObjectVotes( size_t peers_per_hash_max, int64_t now, std::map>& map_asked_recently) const diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index a2229c6667a6..3116594eb9cc 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -492,7 +492,7 @@ bool BlockFilterIndex::LookupFilterRange(int start_height, const CBlockIndex* st if (!file || entry.pos.nFile != file_num) { file = std::make_unique(m_filter_fileseq->Open(entry.pos, true)); file_num = entry.pos.nFile; - } else if (fseek(file->Get(), entry.pos.nPos, SEEK_SET) != 0) { + } else if (FileSeek64(file->Get(), entry.pos.nPos, SEEK_SET) != 0) { LogError("%s: unable to seek to position %u in filter file %d\n", __func__, entry.pos.nPos, entry.pos.nFile); return false; diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index 7cb5069f43e1..000b8ebe2cc7 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -87,7 +88,7 @@ bool TxIndex::FindTx(const uint256& tx_hash, uint256& block_hash, CTransactionRe CBlockHeader header; try { file >> header; - if (fseek(file.Get(), postx.nTxOffset, SEEK_CUR)) { + if (FileSeek64(file.Get(), postx.nTxOffset, SEEK_CUR)) { LogError("%s: fseek(...) failed\n", __func__); return false; } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index fa035a627ddf..b9280d26d0de 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -94,7 +94,7 @@ struct BlockInfo { const uint256* prev_hash = nullptr; int height = -1; int file_number = -1; - unsigned data_pos = 0; + uint64_t data_pos = 0; const CBlock* data = nullptr; const CBlockUndo* undo_data = nullptr; diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index c94cf1cfa106..b961a0a7ec89 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -28,6 +28,5 @@ LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, Chains LLMQContext::~LLMQContext() { - qman->InterruptWarming(); bls_worker->Stop(); } diff --git a/src/llmq/dkgsession.cpp b/src/llmq/dkgsession.cpp index e7a1998f8a4c..0df95d9fbd49 100644 --- a/src/llmq/dkgsession.cpp +++ b/src/llmq/dkgsession.cpp @@ -238,6 +238,13 @@ std::optional CDKGSession::ReceiveMessage(const CDKGContribution& qc) if (!MaybeDecrypt(*qc.contributions, *myIdx, skContribution, PROTOCOL_VERSION)) { logger.Batch("contribution from %s could not be decrypted", member->dmn->proTxHash.ToString()); complain = true; + } else if (!skContribution.IsValid()) { + // A decryptable but structurally-invalid (e.g. all-zero) share must never be + // retained: it later reaches CBLSSecretKey::AggregateInsecure() in the batched + // contribution verification, whose assert(IsValid()) would abort an honest node. + // Treat it as a bad contribution and complain. + logger.Batch("contribution from %s decrypted to an invalid share", member->dmn->proTxHash.ToString()); + complain = true; } else if (member->idx != myIdx && ShouldSimulateError(DKGError::type::COMPLAIN_LIE)) { logger.Batch("lying/complaining for %s", member->dmn->proTxHash.ToString()); complain = true; diff --git a/src/llmq/dkgsession.h b/src/llmq/dkgsession.h index 91b77abf1cf2..6b29c3427b0e 100644 --- a/src/llmq/dkgsession.h +++ b/src/llmq/dkgsession.h @@ -69,6 +69,7 @@ class DKGError { CONTRIBUTION_LIE, JUSTIFY_OMIT, JUSTIFY_LIE, + CONTRIBUTION_INVALID, _COUNT }; static constexpr DKGError::type from_string(std::string_view in) { @@ -79,6 +80,7 @@ class DKGError { if (in == "contribution-lie") return CONTRIBUTION_LIE; if (in == "justify-lie") return JUSTIFY_LIE; if (in == "justify-omit") return JUSTIFY_OMIT; + if (in == "contribution-invalid") return CONTRIBUTION_INVALID; return _COUNT; } }; diff --git a/src/llmq/net_quorum.cpp b/src/llmq/net_quorum.cpp index 98bd4441def3..adbc2c488530 100644 --- a/src/llmq/net_quorum.cpp +++ b/src/llmq/net_quorum.cpp @@ -336,8 +336,11 @@ bool NetQuorum::ProcessContribQDATA(const CNode& pfrom, CDataStream& vRecv, std::vector vecSecretKeys; vecSecretKeys.resize(vecEncrypted.size()); for (const auto i : util::irange(vecEncrypted.size())) { - if (!m_nodeman->Decrypt(vecEncrypted[i], memberIdx, vecSecretKeys[i], PROTOCOL_VERSION)) { - m_peer_manager->PeerMisbehaving(pfrom.GetId(), 10, "failed to decrypt"); + if (!m_nodeman->Decrypt(vecEncrypted[i], memberIdx, vecSecretKeys[i], PROTOCOL_VERSION) || + !vecSecretKeys[i].IsValid()) { + // Reject an undecryptable or structurally-invalid (e.g. all-zero) share before + // it reaches AggregateSecretKeys()/AggregateInsecure(), which asserts validity. + m_peer_manager->PeerMisbehaving(pfrom.GetId(), 10, "invalid contribution share"); return false; } } diff --git a/src/llmq/net_signing.cpp b/src/llmq/net_signing.cpp index 2e70d79d520b..bce2095b1c7e 100644 --- a/src/llmq/net_signing.cpp +++ b/src/llmq/net_signing.cpp @@ -401,7 +401,7 @@ void NetSigning::ProcessPendingSigShares( { // It's ok to perform insecure batched verification here as we verify against the quorum public key shares, // which are not craftable by individual entities, making the rogue public key attack impossible - CBLSBatchVerifier batchVerifier(false, true); + SigShareBatchVerifier batchVerifier(false, true); cxxtimer::Timer prepareTimer(true); size_t verifyCount = 0; @@ -432,7 +432,7 @@ void NetSigning::ProcessPendingSigShares( assert(false); } - batchVerifier.PushMessage(nodeId, sigShare.GetKey(), sigShare.GetSignHash(), sig, pubKeyShare); + PushSigShareToBatchVerifier(batchVerifier, nodeId, sigShare, sig, pubKeyShare); verifyCount++; } } diff --git a/src/llmq/quorumsman.cpp b/src/llmq/quorumsman.cpp index 985aa583bbc8..92f02d9fd987 100644 --- a/src/llmq/quorumsman.cpp +++ b/src/llmq/quorumsman.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -41,9 +42,17 @@ CQuorumManager::CQuorumManager(CBLSWorker& _blsWorker, CDeterministicMNManager& AssertLockHeld(::cs_main); mapQuorumsCache.Init(m_chainman.GetConsensus(), /*limit_by_connections=*/false); MigrateOldQuorumDB(_evoDb); + m_cache_interrupt.reset(); + m_cache_thread = std::thread(&util::TraceThread, "q-cache", [this] { CacheWarmingThreadMain(); }); } -CQuorumManager::~CQuorumManager() = default; +CQuorumManager::~CQuorumManager() +{ + if (m_cache_thread.joinable()) { + m_cache_interrupt(); + m_cache_thread.join(); + } +} bool CQuorumManager::GetEncryptedContributions(Consensus::LLMQType llmq_type, const CBlockIndex* block_index, const std::vector& valid_members, const uint256& protx_hash, @@ -620,29 +629,50 @@ void CQuorumManager::WriteContributions(const CQuorumPtr& quorum) const quorum->WriteContributions(*db); } -void CQuorumManager::QueueQuorumForWarming(CQuorumCPtr pQuorum) const +void CQuorumManager::CacheWarmingThreadMain() const { - if (!pQuorum->HasVerificationVector()) { - return; - } - // The job may reference this manager: ~LLMQContext stops the worker pool before destroying it. - blsWorker.PushJob([this, pQuorum = std::move(pQuorum)]() { + while (!m_cache_interrupt) { + CQuorumCPtr pQuorum; + { + LOCK(m_cache_cs); + if (!m_cache_queue.empty()) { + pQuorum = std::move(m_cache_queue.front()); + m_cache_queue.pop_front(); + }; + } + + if (!pQuorum) { + m_cache_interrupt.sleep_for(std::chrono::milliseconds(100)); + continue; + } + cxxtimer::Timer t(true); - LogPrint(BCLog::LLMQ, "CQuorumManager::QueueQuorumForWarming -- type=%d height=%d hash=%s start\n", + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- type=%d height=%d hash=%s start\n", __func__, std23::to_underlying(pQuorum->params.type), pQuorum->m_quorum_base_block_index->nHeight, pQuorum->m_quorum_base_block_index->GetBlockHash().ToString()); + + // when then later some other thread tries to get keys, it will be much faster for (const auto i : util::irange(pQuorum->members.size())) { - if (m_warming_interrupted) { - return; + if (m_cache_interrupt) { + break; } if (pQuorum->qc->validMembers[i]) { pQuorum->GetPubKeyShare(i); } } - LogPrint(BCLog::LLMQ, "CQuorumManager::QueueQuorumForWarming -- type=%d height=%d hash=%s done. time=%d\n", + + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- type=%d height=%d hash=%s done. time=%d\n", __func__, std23::to_underlying(pQuorum->params.type), pQuorum->m_quorum_base_block_index->nHeight, pQuorum->m_quorum_base_block_index->GetBlockHash().ToString(), t.count()); - }); + } +} + +void CQuorumManager::QueueQuorumForWarming(CQuorumCPtr pQuorum) const +{ + if (pQuorum->HasVerificationVector()) { + LOCK(m_cache_cs); + m_cache_queue.push_back(std::move(pQuorum)); + } } // TODO: remove in v23 diff --git a/src/llmq/quorumsman.h b/src/llmq/quorumsman.h index 86d06ae275d9..2955796d41f9 100644 --- a/src/llmq/quorumsman.h +++ b/src/llmq/quorumsman.h @@ -14,12 +14,14 @@ #include #include #include +#include #include -#include +#include #include #include +#include class CBLSSignature; class CBLSWorker; @@ -94,7 +96,10 @@ class CQuorumManager final mutable Uint256LruHashMap quorumBaseBlockIndexCache GUARDED_BY(cs_quorumBaseBlockIndexCache); - std::atomic m_warming_interrupted{false}; + mutable Mutex m_cache_cs; + mutable std::deque m_cache_queue GUARDED_BY(m_cache_cs); + mutable CThreadInterrupt m_cache_interrupt; + mutable std::thread m_cache_thread; public: CQuorumManager() = delete; @@ -131,20 +136,20 @@ class CQuorumManager final // all these methods will lock cs_main for a short period of time CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps, !m_cache_cs); CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash, const CChain& chain) const - EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_db, !m_cs_maps, !m_cache_cs); std::vector ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps, !m_cache_cs); // this one is cs_main-free std::vector ScanQuorums(Consensus::LLMQType llmqType, gsl::not_null pindexStart, size_t nCountRequested) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps, !m_cache_cs); std::vector ScanQuorums(Consensus::LLMQType llmqType, gsl::not_null pindexStart, size_t nCountRequested, const CChain& chain) const - EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_db, !m_cs_maps, !m_cache_cs); std::vector ScanCommitments(Consensus::LLMQType llmqType, size_t nCountRequested) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps); @@ -177,30 +182,29 @@ class CQuorumManager final CQuorumPtr GetCachedMutableQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const EXCLUSIVE_LOCKS_REQUIRED(!m_cs_maps); void WriteContributions(const CQuorumPtr& quorum) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - void QueueQuorumForWarming(CQuorumCPtr pQuorum) const; - /** Make queued and running warming jobs return; call before stopping the BLS worker pool */ - void InterruptWarming() { m_warming_interrupted = true; } + void QueueQuorumForWarming(CQuorumCPtr pQuorum) const EXCLUSIVE_LOCKS_REQUIRED(!m_cache_cs); private: // all private methods here are cs_main-free std::vector ScanQuorums(Consensus::LLMQType llmqType, gsl::not_null pindexStart, size_t nCountRequested, const CChain* chain) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps, !m_cache_cs); bool BuildQuorumContributions(const CFinalCommitmentPtr& fqc, const std::shared_ptr& quorum) const; CQuorumPtr BuildQuorumFromCommitment(Consensus::LLMQType llmqType, gsl::not_null pQuorumBaseBlockIndex, bool populate_cache) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps, !m_cache_cs); CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, gsl::not_null pindex, bool populate_cache = true) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !m_cs_maps, !m_cache_cs); CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, gsl::not_null pindex, const CChain& chain, bool populate_cache = true) const - EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_db, !m_cs_maps); + EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_db, !m_cs_maps, !m_cache_cs); + void CacheWarmingThreadMain() const EXCLUSIVE_LOCKS_REQUIRED(!m_cache_cs); void MigrateOldQuorumDB(CEvoDB& evoDb) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_db); }; diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index 2d6bf9dac87a..cf9f62e02d1b 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -48,6 +48,12 @@ void CSigShare::UpdateKey() key.second = quorumMember; } +void PushSigShareToBatchVerifier(SigShareBatchVerifier& batchVerifier, NodeId from, const CSigShare& sigShare, + const CBLSSignature& sig, const CBLSPublicKey& pubKeyShare) +{ + batchVerifier.PushMessage(from, {from, sigShare.GetKey()}, sigShare.GetSignHash(), sig, pubKeyShare); +} + std::string CSigSesAnn::ToString() const { return strprintf("sessionId=%d, llmqType=%d, quorumHash=%s, id=%s, msgHash=%s", @@ -255,6 +261,10 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(const CNode& pfrom, const CSigSe LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- ann={%s}, node=%d\n", __func__, ann.ToString(), pfrom.GetId()); + if (!IsQuorumActive(llmqType, qman, ann.getQuorumHash())) { + return true; + } + auto quorum = qman.GetQuorum(llmqType, ann.getQuorumHash()); if (!quorum) { // TODO should we ban here? @@ -434,6 +444,10 @@ bool CSigSharesManager::ProcessMessageBatchedSigShares(const CNode& pfrom, const bool CSigSharesManager::ProcessMessageSigShare(NodeId fromId, const CSigShare& sigShare) { + if (!IsQuorumActive(sigShare.getLlmqType(), qman, sigShare.getQuorumHash())) { + return true; + } + auto quorum = qman.GetQuorum(sigShare.getLlmqType(), sigShare.getQuorumHash()); if (!quorum) { return true; diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 5e885dff3ee5..509bf0366632 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -6,6 +6,7 @@ #define BITCOIN_LLMQ_SIGNING_SHARES_H #include +#include #include #include #include @@ -88,6 +89,14 @@ class CSigShare : virtual public CSigBase } }; +// Batch entries are keyed by sender as well as SigShareKey. QSIGSHARE is accepted from any peer, so two peers can +// push the same key; with a sender-less key the second push would alias the first entry, letting one peer's share be +// judged in place of the other's (banning an honest sender, or never checking a forged share). +using SigShareBatchVerifier = CBLSBatchVerifier>; + +void PushSigShareToBatchVerifier(SigShareBatchVerifier& batchVerifier, NodeId from, const CSigShare& sigShare, + const CBLSSignature& sig, const CBLSPublicKey& pubKeyShare); + // Nodes will first announce a signing session with a sessionId to be used in all future P2P messages related to that // session. We locally keep track of the mapping for each node. We also assign new sessionIds for outgoing sessions // and send QSIGSESANN messages appropriately. All values except the max value for uint32_t are valid as sessionId diff --git a/src/masternode/meta.cpp b/src/masternode/meta.cpp index 2c7a67e0d221..d1a37f3d5f44 100644 --- a/src/masternode/meta.cpp +++ b/src/masternode/meta.cpp @@ -200,11 +200,10 @@ bool CMasternodeMetaMan::SetPlatformBan(const uint256& inv_hash, PlatformBanMess const uint256& protx_hash = ban_msg.m_protx_hash; - bool ret = GetMetaInfo(protx_hash).SetPlatformBan(true, ban_msg.m_requested_height); - if (ret) { - m_seen_platform_bans.emplace(inv_hash, std::move(ban_msg)); - } - return ret; + const bool changed = GetMetaInfo(protx_hash).SetPlatformBan(true, ban_msg.m_requested_height); + // Remember authenticated messages even when newer metadata supersedes them. + m_seen_platform_bans.emplace(inv_hash, std::move(ban_msg)); + return changed; } bool CMasternodeMetaMan::AlreadyHavePlatformBan(const uint256& inv_hash) const diff --git a/src/masternode/meta.h b/src/masternode/meta.h index 2e51680b740a..552ff2d14e62 100644 --- a/src/masternode/meta.h +++ b/src/masternode/meta.h @@ -75,7 +75,7 @@ class CMasternodeMetaInfo if (height < m_platform_ban_updated) { return false; } - if (height == m_platform_ban_updated && !is_banned) { + if (height == m_platform_ban_updated && (!is_banned || m_platform_ban)) { return false; } m_platform_ban = is_banned; diff --git a/src/net.cpp b/src/net.cpp index 4e9d3457cb7f..38ba21d4b13b 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1762,7 +1762,11 @@ std::pair CConnman::SocketSendData(CNode& node) const } } - node.fPauseSend = node.m_send_memusage + node.m_transport->GetSendMemoryUsage() > nSendBufferMaxSize; + const bool pause_send{node.m_send_memusage + node.m_transport->GetSendMemoryUsage() > nSendBufferMaxSize}; + if (pause_send && !node.fPauseSend) { + node.m_pause_send_since = GetTime(); + } + node.fPauseSend = pause_send; if (it == node.vSendMsg.end()) { assert(node.m_send_memusage == 0); @@ -2320,6 +2324,14 @@ bool CConnman::InactivityCheck(const CNode& node) const return true; } + if (node.fPauseSend) { + const auto last_pause{node.m_pause_send_since.load()}; + if (now > last_pause + TIMEOUT_INTERVAL) { + LogPrint(BCLog::NET, "send buffer full for %is peer=%d\n", count_seconds(now - last_pause), node.GetId()); + return true; + } + } + if (!node.fSuccessfullyConnected) { if (node.m_transport->GetInfo().transport_type == TransportProtocolType::DETECTING) { LogPrint(BCLog::NET, "V2 handshake timeout peer=%d\n", node.GetId()); @@ -2341,7 +2353,7 @@ Sock::EventsPerSock CConnman::GenerateWaitSockets(Span nodes) } for (CNode* pnode : nodes) { - bool select_recv = !pnode->fHasRecvData; + bool select_recv = !pnode->fHasRecvData && !pnode->fPauseRecv; bool select_send = !pnode->fCanSendData; if (!select_recv && !select_send) continue; @@ -4885,7 +4897,10 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) // Update memory usage of send buffer. pnode->m_send_memusage += msg.GetMemoryUsage(); - if (pnode->m_send_memusage + pnode->m_transport->GetSendMemoryUsage() > nSendBufferMaxSize) pnode->fPauseSend = true; + if (pnode->m_send_memusage + pnode->m_transport->GetSendMemoryUsage() > nSendBufferMaxSize && !pnode->fPauseSend) { + pnode->m_pause_send_since = GetTime(); + pnode->fPauseSend = true; + } // Move message to vSendMsg queue. pnode->vSendMsg.push_back(std::move(msg)); pnode->nSendMsgSize = pnode->vSendMsg.size(); diff --git a/src/net.h b/src/net.h index 3b1ded7de0fb..c23545887849 100644 --- a/src/net.h +++ b/src/net.h @@ -828,6 +828,8 @@ class CNode std::atomic_bool fPauseRecv{false}; std::atomic_bool fPauseSend{false}; + /** When was fPauseSend last set from false to true */ + std::atomic m_pause_send_since{0s}; std::atomic_bool fHasRecvData{false}; std::atomic_bool fCanSendData{false}; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index c41876bc606c..93754ee7d20b 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3850,14 +3850,18 @@ MessageProcessingResult PeerManagerImpl::ProcessPlatformBanMessage(NodeId node, const uint256 hash = ban_msg.GetHash(); - LogPrintf("PLATFORMBAN -- hash: %s protx_hash: %s height: %d peer=%d\n", hash.ToString(), ban_msg.m_protx_hash.ToString(), ban_msg.m_requested_height, node); - // NOTE: deliberately no solicitation gate here, unlike the other GETDATA-only object types. // PLATFORMBAN has no local ingress (no RPC, no internal producer): the originating Dash // Platform node injects the ban by pushing the message straight to a Dash Core peer, so the // first hop is always unsolicited by design. See p2p_platform_ban.py. MessageProcessingResult ret{}; ret.m_to_erase = CInv{MSG_PLATFORM_BAN, hash}; + if (m_mn_metaman.AlreadyHavePlatformBan(hash)) { + return ret; + } + + LogPrintf("PLATFORMBAN -- hash: %s protx_hash: %s height: %d peer=%d\n", hash.ToString(), + ban_msg.m_protx_hash.ToString(), ban_msg.m_requested_height, node); const auto list = m_dmnman.GetListAtChainTip(); auto dmn = list.GetMN(ban_msg.m_protx_hash); diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 1f2680fa0a6c..ff04bdb8ab2a 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -520,12 +521,12 @@ static bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const fileout << messageStart << nSize; // Write undo data - long fileOutPos = ftell(fileout.Get()); + const int64_t fileOutPos{FileTell64(fileout.Get())}; if (fileOutPos < 0) { LogError("%s: ftell failed\n", __func__); return false; } - pos.nPos = (unsigned int)fileOutPos; + pos.nPos = fileOutPos; fileout << blockundo; // calculate & write checksum @@ -759,12 +760,12 @@ static bool WriteBlockToDisk(const CBlock& block, FlatFilePos& pos, const CMessa fileout << messageStart << nSize; // Write block - long fileOutPos = ftell(fileout.Get()); + const int64_t fileOutPos{FileTell64(fileout.Get())}; if (fileOutPos < 0) { LogError("WriteBlockToDisk: ftell failed\n"); return false; } - pos.nPos = (unsigned int)fileOutPos; + pos.nPos = fileOutPos; fileout << block; return true; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 14ae6d160349..f289c75cefaf 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -232,6 +232,13 @@ inline CAmount CalculateOutputValue(const TxType& tx) } +/** nVersion and nType share one 32-bit wire field. nVersion fills the low half as raw bits: + * sign-extending a negative nVersion would overwrite nType. */ +constexpr int32_t PackVersionAndType(int16_t nVersion, uint16_t nType) +{ + return static_cast(static_cast(nVersion) | (static_cast(nType) << 16)); +} + /** The basic transaction that is broadcasted on the network and contained in * blocks. A transaction can contain multiple inputs and outputs. */ @@ -268,7 +275,7 @@ class CTransaction template inline void Serialize(Stream& s) const { - int32_t n32bitVersion = this->nVersion | (this->nType << 16); + int32_t n32bitVersion = PackVersionAndType(this->nVersion, this->nType); s << n32bitVersion; s << vin; s << vout; @@ -354,7 +361,7 @@ struct CMutableTransaction SERIALIZE_METHODS(CMutableTransaction, obj) { int32_t n32bitVersion; - SER_WRITE(obj, n32bitVersion = obj.nVersion | (obj.nType << 16)); + SER_WRITE(obj, n32bitVersion = PackVersionAndType(obj.nVersion, obj.nType)); READWRITE(n32bitVersion); SER_READ(obj, obj.nVersion = (int16_t) (n32bitVersion & 0xffff)); SER_READ(obj, obj.nType = (uint16_t) ((n32bitVersion >> 16) & 0xffff)); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 4c129a5c9b95..f997090edd62 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1063,11 +1063,7 @@ bool EvalScript(std::vector >& stack, const CScript& bool fSuccess = false; if (vchSig.size()) { - valtype vchHash(32); - CSHA256() - .Write(vchMessage.data(), vchMessage.size()) - .Finalize(vchHash.data()); - fSuccess = CPubKey(vchPubKey).Verify(uint256(vchHash), vchSig); + fSuccess = checker.CheckDataSig(vchSig, vchMessage, vchPubKey); } if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && vchSig.size()) { @@ -1420,7 +1416,7 @@ class CTransactionSignatureSerializer template void Serialize(S &s) const { // Serialize nVersion - int32_t n32bitVersion = txTo.nVersion | (txTo.nType << 16); + int32_t n32bitVersion = PackVersionAndType(txTo.nVersion, txTo.nType); ::Serialize(s, n32bitVersion); // Serialize vin unsigned int nInputs = fAnyoneCanPay ? 1 : txTo.vin.size(); @@ -1533,12 +1529,37 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn template uint256 SignatureHash(const CScript& scriptCode, const CMutableTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache); template uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache); +static uint256 DataSignatureHash(const std::vector& vchMessage) +{ + uint256 hash; + CSHA256().Write(vchMessage.data(), vchMessage.size()).Finalize(hash.begin()); + return hash; +} + +bool BaseSignatureChecker::CheckDataSig(const std::vector& vchSig, const std::vector& vchMessage, + const std::vector& vchPubKey) const +{ + return CPubKey(vchPubKey).Verify(DataSignatureHash(vchMessage), vchSig); +} + template bool GenericTransactionSignatureChecker::VerifySignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash) const { return pubkey.Verify(sighash, vchSig); } +template +bool GenericTransactionSignatureChecker::CheckDataSig(const std::vector& vchSig, + const std::vector& vchMessage, + const std::vector& vchPubKey) const +{ + // An invalid key serializes to no bytes in the signature cache entry, so it must not reach the cache + const CPubKey pubkey(vchPubKey); + if (!pubkey.IsValid()) return false; + // Through the virtual VerifySignature, so CachingTransactionSignatureChecker caches the result + return VerifySignature(vchSig, pubkey, DataSignatureHash(vchMessage)); +} + template bool GenericTransactionSignatureChecker::CheckSig(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { diff --git a/src/script/interpreter.h b/src/script/interpreter.h index df0b1548f414..e6ec7d3392aa 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -156,6 +156,10 @@ class BaseSignatureChecker return false; } + /** Verify vchSig as an ECDSA signature by vchPubKey over SHA256(vchMessage), as CHECKDATASIG does */ + virtual bool CheckDataSig(const std::vector& vchSig, const std::vector& vchMessage, + const std::vector& vchPubKey) const; + virtual ~BaseSignatureChecker() {} }; @@ -187,6 +191,8 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; + bool CheckDataSig(const std::vector& vchSig, const std::vector& vchMessage, + const std::vector& vchPubKey) const override; }; using TransactionSignatureChecker = GenericTransactionSignatureChecker; @@ -212,6 +218,11 @@ class DeferringSignatureChecker : public BaseSignatureChecker { return m_checker.CheckSequence(nSequence); } + bool CheckDataSig(const std::vector& vchSig, const std::vector& vchMessage, + const std::vector& vchPubKey) const override + { + return m_checker.CheckDataSig(vchSig, vchMessage, vchPubKey); + } }; bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr); diff --git a/src/streams.cpp b/src/streams.cpp index 6921dad67733..98e29cd4d0ed 100644 --- a/src/streams.cpp +++ b/src/streams.cpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -13,7 +14,7 @@ std::size_t AutoFile::detail_fread(Span dst) if (m_xor.empty()) { return std::fread(dst.data(), 1, dst.size(), m_file); } else { - const auto init_pos{std::ftell(m_file)}; + const auto init_pos{FileTell64(m_file)}; if (init_pos < 0) throw std::ios_base::failure("AutoFile::read: ftell failed"); std::size_t ret{std::fread(dst.data(), 1, dst.size(), m_file)}; util::Xor(dst.subspan(0, ret), m_xor, init_pos); @@ -49,7 +50,7 @@ void AutoFile::write(Span src) throw std::ios_base::failure("AutoFile::write: write failed"); } } else { - auto current_pos{std::ftell(m_file)}; + auto current_pos{FileTell64(m_file)}; if (current_pos < 0) throw std::ios_base::failure("AutoFile::write: ftell failed"); std::array buf; while (src.size() > 0) { diff --git a/src/test/bls_tests.cpp b/src/test/bls_tests.cpp index 5696a34273ff..3abcb14611f5 100644 --- a/src/test/bls_tests.cpp +++ b/src/test/bls_tests.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include