Line data Source code
1 : // Copyright (c) 2018-2021 The Dash Core developers
2 : // Copyright (c) 2021-2022 The PIVX Core developers
3 : // Distributed under the MIT/X11 software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "llmq/quorums_blockprocessor.h"
7 :
8 : #include "bls/key_io.h"
9 : #include "chain.h"
10 : #include "chainparams.h"
11 : #include "consensus/validation.h"
12 : #include "evo/evodb.h"
13 : #include "evo/specialtx_validation.h"
14 : #include "llmq/quorums_utils.h"
15 : #include "net.h"
16 : #include "primitives/block.h"
17 : #include "quorums_debug.h"
18 : #include "spork.h"
19 : #include "validation.h"
20 :
21 : namespace llmq
22 : {
23 : std::unique_ptr<CQuorumBlockProcessor> quorumBlockProcessor{nullptr};
24 :
25 : static const std::string DB_MINED_COMMITMENT = "q_mc";
26 : static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT = "q_mcih";
27 :
28 475 : CQuorumBlockProcessor::CQuorumBlockProcessor(CEvoDB &_evoDb) :
29 475 : evoDb(_evoDb)
30 : {
31 475 : utils::InitQuorumsCache(mapHasMinedCommitmentCache);
32 475 : }
33 :
34 : template<typename... Args>
35 2 : static int LogMisbehaving(CNode* pfrom, int nDoS, const char* fmt, const Args&... args)
36 : {
37 : try {
38 2 : LogPrint(BCLog::LLMQ, "Invalid QFCOMMITMENT message from peer=%d (reason: %s)\n",
39 : pfrom->GetId(), tfm::format(fmt, args...));
40 0 : } catch (tinyformat::format_error &e) {
41 0 : LogPrintf("Error (%s) while formatting message %s\n", std::string(e.what()), fmt);
42 : }
43 2 : return nDoS;
44 : }
45 :
46 102 : void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, CDataStream& vRecv, int& retMisbehavingScore)
47 : {
48 102 : AssertLockNotHeld(cs_main);
49 201 : CFinalCommitment qc;
50 102 : vRecv >> qc;
51 :
52 102 : uint256 qfc_hash{::SerializeHash(qc)};
53 102 : {
54 102 : LOCK(cs_main);
55 102 : g_connman->RemoveAskFor(qfc_hash, MSG_QUORUM_FINAL_COMMITMENT);
56 : }
57 :
58 102 : if (qc.IsNull()) {
59 0 : retMisbehavingScore = LogMisbehaving(pfrom, 100, "null commitment");
60 3 : return;
61 : }
62 :
63 : // Check if we already got a better one locally
64 : // We do this before verifying the commitment to avoid DoS
65 102 : if (HasBetterMinableCommitment(qc)) {
66 : return;
67 : }
68 :
69 200 : CValidationState state;
70 404 : if (!WITH_LOCK(cs_main, return VerifyLLMQCommitment(qc, chainActive.Tip(), state); )) {
71 : // Not punishable reject reasons
72 2 : static std::set<std::string> not_punishable_reasons = {
73 : // can't really punish the node for "bad-qc-quorum-hash" here, as we might simply be
74 : // the one that is on the wrong chain or not fully synced
75 : "bad-qc-quorum-hash-not-found", "bad-qc-quorum-hash-not-active-chain"
76 4 : };
77 :
78 6 : int dos = (not_punishable_reasons.count(state.GetRejectReason()) ? 0 : state.GetDoSScore());
79 4 : retMisbehavingScore = LogMisbehaving(pfrom, dos, "invalid commtiment for quorum %s: %s",
80 4 : qc.quorumHash.ToString(), state.GetRejectReason());
81 2 : return;
82 : }
83 :
84 198 : LogPrintf("%s :received commitment for quorum %s:%d, validMembers=%d, signers=%d, peer=%d\n", __func__,
85 99 : qc.quorumHash.ToString(), qc.llmqType, qc.CountValidMembers(), qc.CountSigners(), pfrom->GetId());
86 :
87 99 : AddAndRelayMinableCommitment(qc, &qfc_hash);
88 : }
89 :
90 56426 : bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& state, bool fJustCheck)
91 : {
92 112852 : LOCK(cs_main);
93 56426 : const auto& consensus = Params().GetConsensus();
94 :
95 56426 : bool fDIP3Active = consensus.NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_V6_0);
96 56426 : if (!fDIP3Active) {
97 : return true;
98 : }
99 :
100 66439 : std::map<Consensus::LLMQType, CFinalCommitment> qcs;
101 10013 : if (!GetCommitmentsFromBlock(block, pindex, qcs, state)) {
102 : return false;
103 : }
104 :
105 : // The following checks make sure that there is always a (possibly null) commitment while in the mining phase
106 : // until the first non-null commitment has been mined. After the non-null commitment, no other commitments are
107 : // allowed, including null commitments.
108 : // Note: must only check quorums that were enabled at the _previous_ block height to match mining logic
109 20021 : for (const auto& llmq : consensus.llmqs) {
110 : // skip these checks when replaying blocks after the crash
111 30036 : if (WITH_LOCK(cs_main, return chainActive.Tip(); ) == nullptr) {
112 : break;
113 : }
114 :
115 : // does the currently processed block contain a (possibly null) commitment for the current session?
116 10012 : Consensus::LLMQType type = llmq.first;
117 10012 : bool hasCommitmentInNewBlock = qcs.count(type) != 0;
118 10012 : bool isCommitmentRequired = IsCommitmentRequired(type, pindex->nHeight);
119 :
120 10012 : if (hasCommitmentInNewBlock && !isCommitmentRequired) {
121 : // If we're either not in the mining phase or a non-null commitment was mined already, reject the block
122 6 : return state.DoS(100, false, REJECT_INVALID, "bad-qc-not-allowed");
123 : }
124 :
125 10010 : if (!hasCommitmentInNewBlock && isCommitmentRequired) {
126 : // If no non-null commitment was mined for the mining phase yet and the new block does not include
127 : // a (possibly null) commitment, the block should be rejected.
128 2 : return state.DoS(100, false, REJECT_INVALID, "bad-qc-missing");
129 : }
130 : }
131 :
132 10009 : const uint256& blockHash = block.GetHash();
133 :
134 12538 : for (const auto& p : qcs) {
135 2529 : const auto& qc = p.second;
136 2529 : if (!ProcessCommitment(pindex->nHeight, blockHash, qc, state, fJustCheck)) {
137 0 : return false;
138 : }
139 : }
140 :
141 10009 : return true;
142 : }
143 :
144 : // We store a mapping from minedHeight->quorumHeight in the DB
145 : // minedHeight is inversed so that entries are traversable in reversed order
146 20979 : static std::tuple<std::string, uint8_t, uint32_t> BuildInversedHeightKey(Consensus::LLMQType llmqType, int nMinedHeight)
147 : {
148 : // nMinedHeight must be converted to big endian to make it comparable when serialized
149 20979 : return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT, static_cast<uint8_t>(llmqType), htobe32(std::numeric_limits<uint32_t>::max() - nMinedHeight));
150 : }
151 :
152 2529 : bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, CValidationState& state, bool fJustCheck)
153 : {
154 : // skip `bad-qc-block` checks below when replaying blocks after a crash
155 7587 : const uint256& quorumHash = WITH_LOCK(cs_main, return chainActive.Tip(); ) != nullptr
156 2529 : ? GetQuorumBlockHash((Consensus::LLMQType)qc.llmqType, nHeight)
157 2529 : : qc.quorumHash;
158 :
159 5058 : if (quorumHash.IsNull()) {
160 0 : return state.DoS(100, false, REJECT_INVALID, "bad-qc-null-quorumhash");
161 : }
162 2529 : if (quorumHash != qc.quorumHash) {
163 0 : return state.DoS(100, false, REJECT_INVALID, "bad-qc-block");
164 : }
165 :
166 : // index of quorumHash (and commitment signature) already checked
167 2529 : const CBlockIndex* quorumIndex = mapBlockIndex.at(quorumHash);
168 :
169 2529 : if (fJustCheck || qc.IsNull()) {
170 2380 : return true;
171 : }
172 :
173 : // Store commitment in DB
174 149 : auto cacheKey = std::make_pair(qc.llmqType, quorumHash);
175 298 : evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, cacheKey), std::make_pair(qc, blockHash));
176 149 : evoDb.Write(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, nHeight), quorumIndex->nHeight);
177 :
178 149 : {
179 149 : LOCK(minableCommitmentsCs);
180 149 : mapHasMinedCommitmentCache.at((Consensus::LLMQType)qc.llmqType).erase(qc.quorumHash);
181 149 : minableCommitmentsByQuorum.erase(cacheKey);
182 298 : minableCommitments.erase(::SerializeHash(qc));
183 : }
184 :
185 298 : LogPrintf("%s: processed commitment from block. type=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s\n", __func__,
186 298 : qc.llmqType, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), bls::EncodePublic(Params(), qc.quorumPublicKey));
187 :
188 149 : return true;
189 : }
190 :
191 1457 : bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pindex)
192 : {
193 2914 : std::map<Consensus::LLMQType, CFinalCommitment> qcs;
194 2914 : CValidationState dummy;
195 1457 : if (!GetCommitmentsFromBlock(block, pindex, qcs, dummy)) {
196 : return false;
197 : }
198 :
199 1467 : for (const auto& p : qcs) {
200 10 : const auto& qc = p.second;
201 10 : if (qc.IsNull()) {
202 10 : continue;
203 : }
204 :
205 0 : evoDb.Erase(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(static_cast<uint8_t>(qc.llmqType), qc.quorumHash)));
206 0 : evoDb.Erase(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, pindex->nHeight));
207 0 : {
208 0 : LOCK(minableCommitmentsCs);
209 0 : mapHasMinedCommitmentCache.at((Consensus::LLMQType)qc.llmqType).erase(qc.quorumHash);
210 : }
211 :
212 : // if a reorg happened, we should allow to mine this commitment later
213 0 : if (!HasBetterMinableCommitment(qc)) {
214 0 : AddAndRelayMinableCommitment(qc);
215 : }
216 : }
217 :
218 1457 : return true;
219 : }
220 :
221 11470 : bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindex, std::map<Consensus::LLMQType, CFinalCommitment>& ret, CValidationState& state)
222 : {
223 11470 : ret.clear();
224 :
225 141256 : for (const auto& tx : block.vtx) {
226 129787 : if (!tx->IsQuorumCommitmentTx()) {
227 127244 : continue;
228 : }
229 5085 : LLMQCommPL pl;
230 2543 : if (!GetTxPayload(*tx, pl)) {
231 : // should not happen as it was verified before processing the block
232 0 : return state.DoS(100, false, REJECT_INVALID, "bad-qc-payload");
233 : }
234 :
235 : // only allow one commitment per type and per block
236 2543 : if (ret.count((Consensus::LLMQType)pl.commitment.llmqType)) {
237 2 : return state.DoS(100, false, REJECT_INVALID, "bad-qc-dup");
238 : }
239 :
240 2542 : ret.emplace((Consensus::LLMQType)pl.commitment.llmqType, std::move(pl.commitment));
241 : }
242 :
243 11469 : return true;
244 : }
245 :
246 11125 : bool CQuorumBlockProcessor::IsMiningPhase(Consensus::LLMQType llmqType, int nHeight)
247 : {
248 11125 : const auto& params = Params().GetConsensus().llmqs.at(llmqType);
249 11125 : int phaseIndex = nHeight % params.dkgInterval;
250 11125 : return phaseIndex >= params.dkgMiningWindowStart && phaseIndex <= params.dkgMiningWindowEnd;
251 : }
252 :
253 11686 : bool CQuorumBlockProcessor::IsCommitmentRequired(Consensus::LLMQType llmqType, int nHeight)
254 : {
255 11686 : const uint256& quorumHash = GetQuorumBlockHash(llmqType, nHeight);
256 :
257 : // perform extra check for quorumHash.IsNull as the quorum hash is unknown for the first block of a session
258 : // this is because the currently processed block's hash will be the quorumHash of this session
259 40763 : bool isMiningPhase = !quorumHash.IsNull() && IsMiningPhase(llmqType, nHeight);
260 :
261 : // did we already mine a non-null commitment for this session?
262 23372 : bool hasMinedCommitment = !quorumHash.IsNull() && HasMinedCommitment(llmqType, quorumHash);
263 :
264 11686 : return isMiningPhase && !hasMinedCommitment;
265 : }
266 :
267 : // This method returns UINT256_ZERO on the first block of the DKG interval (because the block hash is not known yet)
268 14620 : uint256 CQuorumBlockProcessor::GetQuorumBlockHash(Consensus::LLMQType llmqType, int nHeight)
269 : {
270 14620 : auto& params = Params().GetConsensus().llmqs.at(llmqType);
271 14620 : int quorumStartHeight = nHeight - (nHeight % params.dkgInterval);
272 :
273 14620 : LOCK(cs_main);
274 14620 : if (quorumStartHeight > chainActive.Height()) {
275 561 : return UINT256_ZERO;
276 : }
277 28679 : return chainActive[quorumStartHeight]->GetBlockHash();
278 : }
279 :
280 28454 : bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash)
281 : {
282 28454 : bool fExists;
283 28454 : {
284 28454 : LOCK(minableCommitmentsCs);
285 28454 : if (mapHasMinedCommitmentCache.at((Consensus::LLMQType)llmqType).get(quorumHash, fExists)) {
286 55534 : return fExists;
287 : }
288 : }
289 :
290 687 : fExists = evoDb.Exists(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(static_cast<uint8_t>(llmqType), quorumHash)));
291 :
292 29141 : LOCK(minableCommitmentsCs);
293 687 : mapHasMinedCommitmentCache.at((Consensus::LLMQType)llmqType).insert(quorumHash, fExists);
294 :
295 687 : return fExists;
296 : }
297 :
298 164 : bool CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& retQc, uint256& retMinedBlockHash)
299 : {
300 328 : auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(static_cast<uint8_t>(llmqType), quorumHash));
301 328 : std::pair<CFinalCommitment, uint256> p;
302 164 : if (!evoDb.Read(key, p)) {
303 : return false;
304 : }
305 163 : retQc = std::move(p.first);
306 163 : retMinedBlockHash = p.second;
307 163 : return true;
308 : }
309 :
310 : // The returned quorums are in reversed order, so the most recent one is at index 0
311 10415 : std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount)
312 : {
313 10415 : LOCK(evoDb.cs);
314 :
315 20830 : auto dbIt = evoDb.GetCurTransaction().NewIteratorUniquePtr();
316 :
317 20830 : auto firstKey = BuildInversedHeightKey(llmqType, pindex->nHeight);
318 20830 : auto lastKey = BuildInversedHeightKey(llmqType, 0);
319 :
320 10415 : dbIt->Seek(firstKey);
321 :
322 10415 : std::vector<const CBlockIndex*> ret;
323 10415 : ret.reserve(maxCount);
324 :
325 23400 : while (dbIt->Valid() && ret.size() < maxCount) {
326 29581 : decltype(firstKey) curKey;
327 16596 : int quorumHeight;
328 16596 : if (!dbIt->GetKey(curKey) || curKey >= lastKey) {
329 : break;
330 : }
331 16596 : if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT || std::get<1>(curKey) != static_cast<uint8_t>(llmqType)) {
332 : break;
333 : }
334 :
335 12985 : uint32_t nMinedHeight = std::numeric_limits<uint32_t>::max() - be32toh(std::get<2>(curKey));
336 12985 : if (nMinedHeight > (uint32_t) pindex->nHeight) {
337 : break;
338 : }
339 :
340 12985 : if (!dbIt->GetValue(quorumHeight)) {
341 : break;
342 : }
343 :
344 12985 : auto quorumIndex = pindex->GetAncestor(quorumHeight);
345 12985 : assert(quorumIndex);
346 12985 : ret.emplace_back(quorumIndex);
347 :
348 12985 : dbIt->Next();
349 : }
350 :
351 20830 : return ret;
352 : }
353 :
354 : // The returned quorums are in reversed order, so the most recent one is at index 0
355 0 : std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> CQuorumBlockProcessor::GetMinedAndActiveCommitmentsUntilBlock(const CBlockIndex* pindex)
356 : {
357 0 : std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> ret;
358 :
359 0 : for (const auto& p : Params().GetConsensus().llmqs) {
360 0 : auto& v = ret[p.second.type];
361 0 : v.reserve(p.second.signingActiveQuorumCount);
362 0 : auto commitments = GetMinedCommitmentsUntilBlock(p.second.type, pindex, p.second.signingActiveQuorumCount);
363 0 : for (auto& c : commitments) {
364 0 : v.emplace_back(c);
365 : }
366 : }
367 :
368 0 : return ret;
369 : }
370 :
371 1691 : bool CQuorumBlockProcessor::HasMinableCommitment(const uint256& hash)
372 : {
373 1691 : LOCK(minableCommitmentsCs);
374 3382 : return minableCommitments.count(hash) != 0;
375 : }
376 :
377 102 : bool CQuorumBlockProcessor::HasBetterMinableCommitment(const CFinalCommitment& qc)
378 : {
379 102 : LOCK(minableCommitmentsCs);
380 102 : auto it = minableCommitmentsByQuorum.find(std::make_pair(qc.llmqType, qc.quorumHash));
381 102 : if (it != minableCommitmentsByQuorum.end()) {
382 2 : auto jt = minableCommitments.find(it->second);
383 2 : return jt != minableCommitments.end() && jt->second.CountSigners() >= qc.CountSigners();
384 : }
385 : return false;
386 : }
387 :
388 156 : void CQuorumBlockProcessor::AddAndRelayMinableCommitment(const CFinalCommitment& fqc, uint256* cached_fqc_hash)
389 : {
390 156 : const uint256& commitmentHash = cached_fqc_hash ? *cached_fqc_hash : ::SerializeHash(fqc);
391 156 : {
392 156 : LOCK(minableCommitmentsCs);
393 156 : auto k = std::make_pair(fqc.llmqType, fqc.quorumHash);
394 156 : auto ins = minableCommitmentsByQuorum.emplace(k, commitmentHash);
395 156 : if (!ins.second) {
396 : // remove old commitment
397 3 : minableCommitments.erase(ins.first->second);
398 : // update commitment hash to the new one
399 3 : ins.first->second = commitmentHash;
400 : }
401 : // add new commitment
402 156 : minableCommitments.emplace(commitmentHash, fqc);
403 312 : quorumDKGDebugManager->UpdateLocalSessionStatus((Consensus::LLMQType)fqc.llmqType, [&](CDKGDebugSessionStatus& status) {
404 59 : if (status.quorumHash != fqc.quorumHash || status.receivedFinalCommitment) {
405 : return false;
406 : }
407 57 : status.receivedFinalCommitment = true;
408 57 : return true;
409 : });
410 : }
411 : // relay commitment inv (if DKG is not in maintenance)
412 156 : if (!sporkManager.IsSporkActive(SPORK_22_LLMQ_DKG_MAINTENANCE)) {
413 153 : CInv inv(MSG_QUORUM_FINAL_COMMITMENT, commitmentHash);
414 153 : g_connman->RelayInv(inv);
415 : }
416 156 : }
417 :
418 96 : bool CQuorumBlockProcessor::GetMinableCommitmentByHash(const uint256& commitmentHash, llmq::CFinalCommitment& ret)
419 : {
420 192 : LOCK(minableCommitmentsCs);
421 96 : auto it = minableCommitments.find(commitmentHash);
422 96 : if (it == minableCommitments.end()) {
423 : return false;
424 : }
425 96 : ret = it->second;
426 : return true;
427 : }
428 :
429 : // Will return false if no commitment should be mined
430 : // Will return true and a null commitment if no minable commitment is known and none was mined yet
431 1674 : bool CQuorumBlockProcessor::GetMinableCommitment(Consensus::LLMQType llmqType, int nHeight, CFinalCommitment& ret)
432 : {
433 1674 : if (!IsCommitmentRequired(llmqType, nHeight)) {
434 : // no commitment required
435 : return false;
436 : }
437 :
438 405 : uint256 quorumHash = GetQuorumBlockHash(llmqType, nHeight);
439 810 : if (quorumHash.IsNull()) {
440 : return false;
441 : }
442 :
443 405 : if (sporkManager.IsSporkActive(SPORK_22_LLMQ_DKG_MAINTENANCE)) {
444 : // null commitment required
445 16 : ret = CFinalCommitment(Params().GetConsensus().llmqs.at(llmqType), quorumHash);
446 16 : return true;
447 : }
448 :
449 2063 : LOCK(minableCommitmentsCs);
450 :
451 389 : auto k = std::make_pair(static_cast<uint8_t>(llmqType), quorumHash);
452 389 : auto it = minableCommitmentsByQuorum.find(k);
453 389 : if (it == minableCommitmentsByQuorum.end()) {
454 : // null commitment required
455 318 : ret = CFinalCommitment(Params().GetConsensus().llmqs.at(llmqType), quorumHash);
456 318 : return true;
457 : }
458 :
459 71 : ret = minableCommitments.at(it->second);
460 :
461 : return true;
462 : }
463 :
464 1368 : bool CQuorumBlockProcessor::GetMinableCommitmentTx(Consensus::LLMQType llmqType, int nHeight, CTransactionRef& ret)
465 : {
466 2736 : LLMQCommPL pl;
467 1368 : if (!GetMinableCommitment(llmqType, nHeight, pl.commitment)) {
468 : return false;
469 : }
470 :
471 354 : pl.nHeight = nHeight;
472 :
473 354 : CMutableTransaction tx;
474 354 : tx.nVersion = CTransaction::TxVersion::SAPLING;
475 354 : tx.nType = CTransaction::TxType::LLMQCOMM;
476 354 : SetTxPayload(tx, pl);
477 :
478 354 : ret = MakeTransactionRef(tx);
479 :
480 354 : return true;
481 : }
482 :
483 : } // namespace llmq
|