|
| 1 | +// Copyright (c) 2025 The Dash Core developers |
| 2 | +// Distributed under the MIT software license, see the accompanying |
| 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | + |
| 5 | +#include <llmq/net_signing.h> |
| 6 | + |
| 7 | +#include <llmq/commitment.h> |
| 8 | +#include <llmq/quorums.h> |
| 9 | +#include <llmq/signhash.h> |
| 10 | +#include <llmq/signing.h> |
| 11 | + |
| 12 | +#include <bls/bls_batchverifier.h> |
| 13 | +#include <cxxtimer.hpp> |
| 14 | +#include <logging.h> |
| 15 | +#include <streams.h> |
| 16 | +#include <util/thread.h> |
| 17 | +#include <validationinterface.h> |
| 18 | + |
| 19 | +#include <unordered_map> |
| 20 | + |
| 21 | +static bool PreVerifyRecoveredSig(Consensus::LLMQType& llmqType, const llmq::CQuorumManager& quorum_manager, |
| 22 | + const llmq::CRecoveredSig& recoveredSig) |
| 23 | +{ |
| 24 | + auto quorum = quorum_manager.GetQuorum(llmqType, recoveredSig.getQuorumHash()); |
| 25 | + |
| 26 | + if (!quorum) { |
| 27 | + LogPrint(BCLog::LLMQ, "NetSigning::%s -- quorum %s not found\n", __func__, recoveredSig.getQuorumHash().ToString()); |
| 28 | + return false; |
| 29 | + } |
| 30 | + if (!llmq::IsQuorumActive(llmqType, quorum_manager, quorum->qc->quorumHash)) { |
| 31 | + return false; |
| 32 | + } |
| 33 | + |
| 34 | + return true; |
| 35 | +} |
| 36 | + |
| 37 | +void NetSigning::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv) |
| 38 | +{ |
| 39 | + if (msg_type != NetMsgType::QSIGREC) return; |
| 40 | + |
| 41 | + auto recoveredSig = std::make_shared<llmq::CRecoveredSig>(); |
| 42 | + vRecv >> *recoveredSig; |
| 43 | + |
| 44 | + WITH_LOCK(cs_main, m_peer_manager->PeerEraseObjectRequest(pfrom.GetId(), |
| 45 | + CInv{MSG_QUORUM_RECOVERED_SIG, recoveredSig->GetHash()})); |
| 46 | + |
| 47 | + auto llmqType = recoveredSig->getLlmqType(); |
| 48 | + if (!Params().GetLLMQ(llmqType).has_value()) { |
| 49 | + m_peer_manager->PeerMisbehaving(pfrom.GetId(), 100); |
| 50 | + } |
| 51 | + if (!PreVerifyRecoveredSig(llmqType, m_sig_manager.Qman(), *recoveredSig)) { |
| 52 | + return; |
| 53 | + } |
| 54 | + |
| 55 | + m_sig_manager.ProcessRecoveredSig(pfrom.GetId(), std::move(recoveredSig)); |
| 56 | +} |
| 57 | + |
| 58 | +void NetSigning::Start() |
| 59 | +{ |
| 60 | + // can't start new thread if we have one running already |
| 61 | + if (workThread.joinable()) { |
| 62 | + assert(false); |
| 63 | + } |
| 64 | + |
| 65 | + workThread = std::thread(&util::TraceThread, "recsigs", [this] { WorkThreadMain(); }); |
| 66 | +} |
| 67 | + |
| 68 | +void NetSigning::Stop() |
| 69 | +{ |
| 70 | + // make sure to call InterruptWorkerThread() first |
| 71 | + if (!workInterrupt) { |
| 72 | + assert(false); |
| 73 | + } |
| 74 | + |
| 75 | + if (workThread.joinable()) { |
| 76 | + workThread.join(); |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +void NetSigning::ProcessRecoveredSig(std::shared_ptr<const llmq::CRecoveredSig> recoveredSig, bool consider_proactive_relay) |
| 81 | +{ |
| 82 | + if (!m_sig_manager.ProcessRecoveredSig(recoveredSig)) return; |
| 83 | + |
| 84 | + auto listeners = m_sig_manager.GetListeners(); |
| 85 | + for (auto& l : listeners) { |
| 86 | + m_peer_manager->PeerPostProcessMessage(l->HandleNewRecoveredSig(*recoveredSig)); |
| 87 | + } |
| 88 | + |
| 89 | + // TODO refactor to use a better abstraction analogous to IsAllMembersConnectedEnabled |
| 90 | + auto proactive_relay = consider_proactive_relay && recoveredSig->getLlmqType() != Consensus::LLMQType::LLMQ_100_67 && |
| 91 | + recoveredSig->getLlmqType() != Consensus::LLMQType::LLMQ_400_60 && |
| 92 | + recoveredSig->getLlmqType() != Consensus::LLMQType::LLMQ_400_85; |
| 93 | + GetMainSignals().NotifyRecoveredSig(recoveredSig, recoveredSig->GetHash().ToString(), proactive_relay); |
| 94 | +} |
| 95 | + |
| 96 | +bool NetSigning::ProcessPendingRecoveredSigs() |
| 97 | +{ |
| 98 | + Uint256HashMap<std::shared_ptr<const llmq::CRecoveredSig>> pending{m_sig_manager.FetchPendingReconstructed()}; |
| 99 | + |
| 100 | + for (const auto& p : pending) { |
| 101 | + ProcessRecoveredSig(p.second, true); |
| 102 | + } |
| 103 | + |
| 104 | + std::unordered_map<NodeId, std::list<std::shared_ptr<const llmq::CRecoveredSig>>> recSigsByNode; |
| 105 | + std::unordered_map<std::pair<Consensus::LLMQType, uint256>, llmq::CQuorumCPtr, StaticSaltedHasher> quorums; |
| 106 | + |
| 107 | + const size_t nMaxBatchSize{32}; |
| 108 | + bool more_work = m_sig_manager.CollectPendingRecoveredSigsToVerify(nMaxBatchSize, recSigsByNode, quorums); |
| 109 | + if (recSigsByNode.empty()) { |
| 110 | + return false; |
| 111 | + } |
| 112 | + |
| 113 | + // It's ok to perform insecure batched verification here as we verify against the quorum public keys, which are not |
| 114 | + // craftable by individual entities, making the rogue public key attack impossible |
| 115 | + CBLSBatchVerifier<NodeId, uint256> batchVerifier(false, false); |
| 116 | + |
| 117 | + size_t verifyCount = 0; |
| 118 | + for (const auto& p : recSigsByNode) { |
| 119 | + NodeId nodeId = p.first; |
| 120 | + const auto& v = p.second; |
| 121 | + |
| 122 | + for (const auto& recSig : v) { |
| 123 | + // we didn't verify the lazy signature until now |
| 124 | + if (!recSig->sig.Get().IsValid()) { |
| 125 | + batchVerifier.badSources.emplace(nodeId); |
| 126 | + break; |
| 127 | + } |
| 128 | + |
| 129 | + const auto& quorum = quorums.at(std::make_pair(recSig->getLlmqType(), recSig->getQuorumHash())); |
| 130 | + batchVerifier.PushMessage(nodeId, recSig->GetHash(), recSig->buildSignHash().Get(), recSig->sig.Get(), |
| 131 | + quorum->qc->quorumPublicKey); |
| 132 | + verifyCount++; |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + cxxtimer::Timer verifyTimer(true); |
| 137 | + batchVerifier.Verify(); |
| 138 | + verifyTimer.stop(); |
| 139 | + |
| 140 | + LogPrint(BCLog::LLMQ, "CSigningManager::%s -- verified recovered sig(s). count=%d, vt=%d, nodes=%d\n", __func__, |
| 141 | + verifyCount, verifyTimer.count(), recSigsByNode.size()); |
| 142 | + |
| 143 | + Uint256HashSet processed; |
| 144 | + for (const auto& p : recSigsByNode) { |
| 145 | + NodeId nodeId = p.first; |
| 146 | + const auto& v = p.second; |
| 147 | + |
| 148 | + if (batchVerifier.badSources.count(nodeId)) { |
| 149 | + LogPrint(BCLog::LLMQ, "CSigningManager::%s -- invalid recSig from other node, banning peer=%d\n", __func__, |
| 150 | + nodeId); |
| 151 | + m_peer_manager->PeerMisbehaving(nodeId, 100); |
| 152 | + continue; |
| 153 | + } |
| 154 | + |
| 155 | + for (const auto& recSig : v) { |
| 156 | + if (!processed.emplace(recSig->GetHash()).second) { |
| 157 | + continue; |
| 158 | + } |
| 159 | + |
| 160 | + ProcessRecoveredSig(recSig, nodeId == -1); |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + return more_work; |
| 165 | +} |
| 166 | + |
| 167 | +void NetSigning::WorkThreadMain() |
| 168 | +{ |
| 169 | + while (!workInterrupt) { |
| 170 | + bool fMoreWork = ProcessPendingRecoveredSigs(); |
| 171 | + |
| 172 | + m_sig_manager.Cleanup(); |
| 173 | + |
| 174 | + // TODO Wakeup when pending signing is needed? |
| 175 | + if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(100))) { |
| 176 | + return; |
| 177 | + } |
| 178 | + } |
| 179 | +} |
0 commit comments