Line data Source code
1 : // Copyright (c) 2021-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 <consensus/validation.h>
6 : #include <deploymentstatus.h>
7 : #include <evo/evodb.h>
8 : #include <evo/mnhftx.h>
9 : #include <evo/specialtx.h>
10 : #include <llmq/commitment.h>
11 : #include <llmq/quorumsman.h>
12 : #include <llmq/signhash.h>
13 : #include <node/blockstorage.h>
14 : #include <util/std23.h>
15 :
16 : #include <chain.h>
17 : #include <chainparams.h>
18 : #include <validation.h>
19 : #include <versionbits.h>
20 :
21 : #include <algorithm>
22 : #include <stack>
23 : #include <string>
24 : #include <vector>
25 :
26 : using node::ReadBlockFromDisk;
27 :
28 : static const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
29 : static const std::string DB_SIGNALS = "mnhf_s";
30 : static const std::string DB_SIGNALS_v2 = "mnhf_s2";
31 :
32 25652 : uint256 MNHFTxPayload::GetRequestId() const
33 : {
34 25652 : return ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{signal.versionBit}));
35 0 : }
36 :
37 3785 : CMutableTransaction MNHFTxPayload::PrepareTx() const
38 : {
39 3785 : CMutableTransaction tx;
40 3785 : tx.nVersion = 3;
41 3785 : tx.nType = SPECIALTX_TYPE;
42 3785 : SetTxPayload(tx, *this);
43 :
44 3785 : return tx;
45 3785 : }
46 :
47 9189 : CMNHFManager::CMNHFManager(CEvoDB& evoDb, const ChainstateManager& chainman, const llmq::CQuorumManager& qman) :
48 3063 : m_evoDb(evoDb),
49 3063 : m_chainman{chainman},
50 3063 : m_qman{qman}
51 6126 : {
52 : assert(globalInstance == nullptr);
53 : globalInstance = this;
54 3063 : }
55 :
56 9189 : CMNHFManager::~CMNHFManager()
57 9189 : {
58 3063 : assert(globalInstance != nullptr);
59 3063 : globalInstance = nullptr;
60 9189 : }
61 :
62 1032735 : CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev)
63 : {
64 1032735 : if (!DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) return {};
65 :
66 525006 : Signals signals_tmp = GetForBlock(pindexPrev);
67 :
68 525006 : if (pindexPrev == nullptr) return {};
69 525006 : const int height = pindexPrev->nHeight + 1;
70 :
71 525006 : Signals signals_ret;
72 :
73 740818 : for (auto signal : signals_tmp) {
74 215812 : bool expired{false};
75 215812 : const auto signal_pindex = pindexPrev->GetAncestor(signal.second);
76 215812 : assert(signal_pindex != nullptr);
77 215812 : const int64_t signal_time = signal_pindex->GetMedianTimePast();
78 647436 : for (int index = 0; index < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++index) {
79 431624 : const auto& deployment = Params().GetConsensus().vDeployments[index];
80 431624 : if (deployment.bit != signal.first) continue;
81 207877 : if (signal_time < deployment.nStartTime) {
82 : // new deployment is using the same bit as the old one
83 240 : LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is expired at height=%d\n",
84 : signal.first, signal.second, height);
85 240 : expired = true;
86 240 : }
87 207877 : }
88 215812 : if (!expired) {
89 215572 : signals_ret.insert(signal);
90 215572 : }
91 : }
92 525006 : return signals_ret;
93 1557741 : }
94 :
95 1171 : bool MNHFTx::Verify(const llmq::CQuorumManager& qman, const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const
96 : {
97 1171 : if (versionBit >= VERSIONBITS_NUM_BITS) {
98 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds");
99 : }
100 :
101 1171 : const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf;
102 1171 : const auto quorum = qman.GetQuorum(llmqType, quorumHash);
103 :
104 1171 : if (!quorum) {
105 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-missing-quorum");
106 : }
107 :
108 1171 : const llmq::SignHash signHash{llmqType, quorum->qc->quorumHash, requestId, msgHash};
109 1171 : if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) {
110 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid");
111 : }
112 :
113 1171 : return true;
114 1171 : }
115 :
116 1173 : bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state)
117 : {
118 1173 : if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) {
119 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-type");
120 : }
121 :
122 1173 : const auto opt_mnhfTx = GetTxPayload<MNHFTxPayload>(tx);
123 1173 : if (!opt_mnhfTx) {
124 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-payload");
125 : }
126 1173 : auto& mnhfTx = *opt_mnhfTx;
127 1173 : if (mnhfTx.nVersion == 0 || mnhfTx.nVersion > MNHFTxPayload::CURRENT_VERSION) {
128 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-version");
129 : }
130 :
131 1173 : if (!Params().IsValidMNActivation(mnhfTx.signal.versionBit, pindexPrev->GetMedianTimePast())) {
132 1 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-non-ehf");
133 : }
134 :
135 2344 : const CBlockIndex* pindexQuorum = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(mnhfTx.signal.quorumHash));
136 1172 : if (!pindexQuorum) {
137 1 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-hash");
138 : }
139 :
140 1171 : if (pindexQuorum != pindexPrev->GetAncestor(pindexQuorum->nHeight)) {
141 : // not part of active chain
142 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-hash");
143 : }
144 :
145 : // Copy transaction except `quorumSig` field to calculate hash
146 1171 : CMutableTransaction tx_copy(tx);
147 1171 : auto payload_copy = mnhfTx;
148 1171 : payload_copy.signal.sig = CBLSSignature();
149 1171 : SetTxPayload(tx_copy, payload_copy);
150 1171 : uint256 msgHash = tx_copy.GetHash();
151 :
152 :
153 1171 : if (!mnhfTx.signal.Verify(qman, mnhfTx.signal.quorumHash, mnhfTx.GetRequestId(), msgHash, state)) {
154 : // set up inside Verify
155 0 : return false;
156 : }
157 :
158 1171 : return true;
159 1173 : }
160 :
161 39323 : std::optional<uint8_t> extractEHFSignal(const CTransaction& tx)
162 : {
163 39323 : if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) {
164 : // only interested in special TXs 'TRANSACTION_MNHF_SIGNAL'
165 39267 : return std::nullopt;
166 : }
167 :
168 56 : const auto opt_mnhfTx = GetTxPayload<MNHFTxPayload>(tx);
169 56 : if (!opt_mnhfTx) {
170 0 : return std::nullopt;
171 : }
172 56 : return opt_mnhfTx->signal.versionBit;
173 39323 : }
174 :
175 367781 : static bool extractSignals(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CBlock& block, const CBlockIndex* const pindex, std::vector<uint8_t>& new_signals, BlockValidationState& state)
176 : {
177 : // we skip the coinbase
178 770854 : for (size_t i = 1; i < block.vtx.size(); ++i) {
179 403073 : const CTransaction& tx = *block.vtx[i];
180 :
181 403073 : if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) {
182 : // only interested in special TXs 'TRANSACTION_MNHF_SIGNAL'
183 402635 : continue;
184 : }
185 :
186 438 : TxValidationState tx_state;
187 438 : if (!CheckMNHFTx(chainman, qman, tx, pindex, tx_state)) {
188 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage());
189 : }
190 :
191 438 : const auto opt_mnhfTx = GetTxPayload<MNHFTxPayload>(tx);
192 438 : if (!opt_mnhfTx) {
193 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-tx-payload");
194 : }
195 438 : const uint8_t bit = opt_mnhfTx->signal.versionBit;
196 438 : if (std23::ranges::contains(new_signals, bit)) {
197 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicates-in-block");
198 : }
199 438 : new_signals.push_back(bit);
200 438 : }
201 :
202 367781 : return true;
203 367781 : }
204 :
205 341215 : std::optional<CMNHFManager::Signals> CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pindex, bool fJustCheck, BlockValidationState& state)
206 : {
207 : try {
208 341215 : std::vector<uint8_t> new_signals;
209 341215 : if (!extractSignals(m_chainman, m_qman, block, pindex, new_signals, state)) {
210 : // state is set inside extractSignals
211 0 : return std::nullopt;
212 : }
213 341215 : Signals signals = GetSignalsStage(pindex->pprev);
214 341215 : if (new_signals.empty()) {
215 340862 : if (!fJustCheck) {
216 253291 : AddToCache(signals, pindex);
217 253291 : }
218 340862 : LogPrint(BCLog::EHF, "CMNHFManager::ProcessBlock: no new signals; number of known signals: %d\n", signals.size());
219 340862 : return signals;
220 : }
221 :
222 353 : const int mined_height = pindex->nHeight;
223 :
224 : // Extra validation of signals to be sure that it can succeed
225 742 : for (const auto& versionBit : new_signals) {
226 389 : LogPrintf("CMNHFManager::ProcessBlock: add mnhf bit=%d block:%s number of known signals:%lld\n", versionBit, pindex->GetBlockHash().ToString(), signals.size());
227 389 : if (signals.find(versionBit) != signals.end()) {
228 0 : state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicate");
229 0 : return std::nullopt;
230 : }
231 :
232 389 : if (!Params().IsValidMNActivation(versionBit, pindex->GetMedianTimePast())) {
233 0 : state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-non-mn-fork");
234 0 : return std::nullopt;
235 : }
236 : }
237 353 : if (fJustCheck) {
238 : // We are done, no need actually update any params
239 48 : return signals;
240 : }
241 642 : for (const auto& versionBit : new_signals) {
242 337 : signals.insert({versionBit, mined_height});
243 : }
244 :
245 305 : AddToCache(signals, pindex);
246 305 : return signals;
247 341215 : } catch (const std::exception& e) {
248 0 : LogPrintf("CMNHFManager::ProcessBlock -- failed: %s\n", e.what());
249 0 : state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-proc-mnhf-inblock");
250 0 : return std::nullopt;
251 0 : }
252 341215 : }
253 :
254 26566 : bool CMNHFManager::UndoBlock(const CBlock& block, const CBlockIndex* const pindex)
255 : {
256 26566 : std::vector<uint8_t> excluded_signals;
257 26566 : BlockValidationState state;
258 26566 : if (!extractSignals(m_chainman, m_qman, block, pindex, excluded_signals, state)) {
259 0 : LogPrintf("CMNHFManager::%s: failed to extract signals\n", __func__);
260 0 : return false;
261 : }
262 26566 : if (excluded_signals.empty()) {
263 26533 : return true;
264 : }
265 :
266 33 : const Signals signals = GetForBlock(pindex);
267 82 : for (const auto& versionBit : excluded_signals) {
268 49 : LogPrintf("%s: exclude mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals.size());
269 49 : assert(signals.find(versionBit) != signals.end());
270 49 : assert(Params().IsValidMNActivation(versionBit, pindex->GetMedianTimePast()));
271 : }
272 :
273 33 : return true;
274 26566 : }
275 :
276 525039 : CMNHFManager::Signals CMNHFManager::GetForBlock(const CBlockIndex* pindex)
277 : {
278 525039 : if (pindex == nullptr) return {};
279 :
280 525039 : std::stack<const CBlockIndex *> to_calculate;
281 :
282 525039 : std::optional<CMNHFManager::Signals> signalsTmp;
283 525039 : while (!(signalsTmp = GetFromCache(pindex)).has_value()) {
284 0 : to_calculate.push(pindex);
285 0 : pindex = pindex->pprev;
286 : }
287 :
288 525039 : const Consensus::Params& consensusParams{Params().GetConsensus()};
289 525039 : while (!to_calculate.empty()) {
290 0 : const CBlockIndex* pindex_top{to_calculate.top()};
291 0 : if (pindex_top->nHeight % 1000 == 0) {
292 0 : LogPrintf("re-index EHF signals at block %d\n", pindex_top->nHeight);
293 0 : }
294 0 : CBlock block;
295 0 : if (!ReadBlockFromDisk(block, pindex_top, consensusParams)) {
296 0 : throw std::runtime_error("failed-getehfforblock-read");
297 : }
298 0 : BlockValidationState state;
299 0 : signalsTmp = ProcessBlock(block, pindex_top, false, state);
300 0 : if (!signalsTmp.has_value()) {
301 0 : LogPrintf("%s: process block failed due to %s\n", __func__, state.ToString());
302 0 : throw std::runtime_error("failed-getehfforblock-construct");
303 : }
304 :
305 0 : to_calculate.pop();
306 0 : }
307 525039 : return *signalsTmp;
308 525039 : }
309 :
310 525039 : std::optional<CMNHFManager::Signals> CMNHFManager::GetFromCache(const CBlockIndex* const pindex)
311 : {
312 525039 : Signals signals{};
313 525039 : if (pindex == nullptr) return signals;
314 :
315 : // TODO: remove this check of phashBlock to nullptr
316 : // This check is needed only because unit test 'versionbits_tests.cpp'
317 : // lets `phashBlock` to be nullptr
318 525039 : if (pindex->phashBlock == nullptr) return signals;
319 :
320 :
321 525039 : const uint256& blockHash = pindex->GetBlockHash();
322 : {
323 525039 : LOCK(cs_cache);
324 525039 : if (mnhfCache.get(blockHash, signals)) {
325 479724 : return signals;
326 : }
327 525039 : }
328 : {
329 45315 : LOCK(cs_cache);
330 45315 : if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) {
331 532 : mnhfCache.insert(blockHash, signals);
332 532 : return signals;
333 : }
334 45315 : }
335 44783 : if (m_evoDb.Read(std::make_pair(DB_SIGNALS_v2, blockHash), signals)) {
336 44783 : LOCK(cs_cache);
337 44783 : mnhfCache.insert(blockHash, signals);
338 44783 : return signals;
339 44783 : }
340 0 : if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_MN_RR)) {
341 : // before mn_rr activation we are safe
342 0 : if (m_evoDb.Read(std::make_pair(DB_SIGNALS, blockHash), signals)) {
343 0 : LOCK(cs_cache);
344 0 : mnhfCache.insert(blockHash, signals);
345 0 : return signals;
346 0 : }
347 0 : }
348 0 : return std::nullopt;
349 525039 : }
350 :
351 253596 : void CMNHFManager::AddToCache(const Signals& signals, const CBlockIndex* const pindex)
352 : {
353 253596 : assert(pindex != nullptr);
354 253596 : const uint256& blockHash = pindex->GetBlockHash();
355 : {
356 253596 : LOCK(cs_cache);
357 253596 : mnhfCache.insert(blockHash, signals);
358 253596 : }
359 253596 : if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) return;
360 :
361 87600 : m_evoDb.Write(std::make_pair(DB_SIGNALS_v2, blockHash), signals);
362 253596 : }
363 :
364 0 : void CMNHFManager::AddSignal(const CBlockIndex* const pindex, int bit)
365 : {
366 0 : auto signals = GetForBlock(pindex->pprev);
367 0 : signals.emplace(bit, pindex->nHeight);
368 0 : AddToCache(signals, pindex);
369 0 : }
370 :
371 3049 : bool CMNHFManager::ForceSignalDBUpdate()
372 : {
373 : // force ehf signals db update
374 3049 : auto dbTx = m_evoDb.BeginTransaction();
375 :
376 3049 : const bool last_legacy = bls::bls_legacy_scheme.load();
377 3049 : bls::bls_legacy_scheme.store(false);
378 3049 : GetSignalsStage(m_chainman.ActiveTip());
379 3049 : bls::bls_legacy_scheme.store(last_legacy);
380 :
381 3049 : dbTx->Commit();
382 : // flush it to disk
383 3049 : if (!m_evoDb.CommitRootTransaction()) {
384 0 : LogPrintf("CMNHFManager::%s -- failed to commit to evoDB\n", __func__);
385 0 : return false;
386 : }
387 3049 : return true;
388 3049 : }
389 :
390 0 : std::string MNHFTx::ToString() const
391 : {
392 0 : return strprintf("MNHFTx(versionBit=%d, quorumHash=%s, sig=%s)",
393 0 : versionBit, quorumHash.ToString(), sig.ToString());
394 0 : }
395 0 : std::string MNHFTxPayload::ToString() const
396 : {
397 0 : return strprintf("MNHFTxPayload(nVersion=%d, signal=%s)",
398 0 : nVersion, signal.ToString());
399 0 : }
|