Line data Source code
1 : // Copyright (c) 2018-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 <evo/specialtxman.h>
6 :
7 : #include <chainlock/chainlock.h>
8 : #include <chainlock/clsig.h>
9 : #include <chainlock/handler.h>
10 : #include <evo/assetlocktx.h>
11 : #include <evo/cbtx.h>
12 : #include <evo/creditpool.h>
13 : #include <evo/deterministicmns.h>
14 : #include <evo/mnhftx.h>
15 : #include <evo/netinfo.h>
16 : #include <evo/simplifiedmns.h>
17 : #include <llmq/blockprocessor.h>
18 : #include <llmq/commitment.h>
19 : #include <llmq/quorumsman.h>
20 : #include <llmq/utils.h>
21 : #include <messagesigner.h>
22 : #include <util/helpers.h>
23 :
24 : #include <chainparams.h>
25 : #include <consensus/amount.h>
26 : #include <consensus/validation.h>
27 : #include <deploymentstatus.h>
28 : #include <hash.h>
29 : #include <primitives/block.h>
30 : #include <util/system.h>
31 : #include <validation.h>
32 :
33 18236 : static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const Consensus::Params& consensus_params,
34 : const CChain& chain, const llmq::CQuorumManager& qman,
35 : const chainlock::Chainlocks& chainlocks, BlockValidationState& state)
36 : {
37 18236 : if (cbTx.nVersion < CCbTx::Version::CLSIG_AND_BALANCE) {
38 3032 : return true;
39 : }
40 :
41 15204 : static Mutex cached_mutex;
42 : static const CBlockIndex* cached_pindex GUARDED_BY(cached_mutex){nullptr};
43 15204 : static std::optional<std::pair<CBLSSignature, uint32_t>> cached_chainlock GUARDED_BY(cached_mutex){std::nullopt};
44 :
45 15204 : auto best_clsig = chainlocks.GetBestChainLock();
46 15204 : if (best_clsig.getHeight() == pindex->nHeight - 1 && cbTx.bestCLHeightDiff == 0 &&
47 0 : cbTx.bestCLSignature == best_clsig.getSig()) {
48 : // matches our best clsig which still hold values for the previous block
49 0 : LOCK(cached_mutex);
50 0 : cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
51 0 : cached_pindex = pindex;
52 0 : return true;
53 0 : }
54 :
55 15204 : std::optional<std::pair<CBLSSignature, uint32_t>> prevBlockCoinbaseChainlock{std::nullopt};
56 15204 : if (LOCK(cached_mutex); cached_pindex == pindex->pprev) {
57 0 : prevBlockCoinbaseChainlock = cached_chainlock;
58 0 : }
59 15204 : if (!prevBlockCoinbaseChainlock.has_value()) {
60 15204 : prevBlockCoinbaseChainlock = GetNonNullCoinbaseChainlock(pindex->pprev);
61 15204 : }
62 : // If std::optional prevBlockCoinbaseChainlock is empty, then up to the previous block, coinbase Chainlock is null.
63 15204 : if (prevBlockCoinbaseChainlock.has_value()) {
64 : // Previous block Coinbase has a non-null Chainlock: current block's Chainlock must be non-null and at least as new as the previous one
65 0 : if (!cbTx.bestCLSignature.IsValid()) {
66 : // IsNull() doesn't exist for CBLSSignature: we assume that a non valid BLS sig is null
67 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-null-clsig");
68 : }
69 0 : if (cbTx.bestCLHeightDiff > prevBlockCoinbaseChainlock.value().second + 1) {
70 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-older-clsig");
71 : }
72 0 : }
73 :
74 : // IsNull() doesn't exist for CBLSSignature: we assume that a valid BLS sig is non-null
75 15204 : if (cbTx.bestCLSignature.IsValid()) {
76 0 : int curBlockCoinbaseCLHeight = pindex->nHeight - static_cast<int>(cbTx.bestCLHeightDiff) - 1;
77 0 : if (best_clsig.getHeight() == curBlockCoinbaseCLHeight && best_clsig.getSig() == cbTx.bestCLSignature) {
78 : // matches our best (but outdated) clsig, no need to verify it again
79 0 : LOCK(cached_mutex);
80 0 : cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
81 0 : cached_pindex = pindex;
82 0 : return true;
83 0 : }
84 0 : uint256 curBlockCoinbaseCLBlockHash = pindex->GetAncestor(curBlockCoinbaseCLHeight)->GetBlockHash();
85 0 : chainlock::ChainLockSig clsig{curBlockCoinbaseCLHeight, curBlockCoinbaseCLBlockHash, cbTx.bestCLSignature};
86 0 : llmq::VerifyRecSigStatus ret = chainlock::VerifyChainLock(consensus_params, chain, qman, clsig);
87 0 : if (ret != llmq::VerifyRecSigStatus::Valid) {
88 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid-clsig");
89 : }
90 0 : LOCK(cached_mutex);
91 0 : cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
92 0 : cached_pindex = pindex;
93 15204 : } else if (cbTx.bestCLHeightDiff != 0) {
94 : // Null bestCLSignature is allowed only with bestCLHeightDiff = 0
95 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-cldiff");
96 : }
97 :
98 15204 : return true;
99 18236 : }
100 :
101 61022 : static bool CheckSpecialTxInner(CDeterministicMNManager& dmnman, llmq::CQuorumSnapshotManager& qsnapman,
102 : const ChainstateManager& chainman, const llmq::CQuorumManager& qman,
103 : const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view,
104 : const std::optional<CRangesSet>& indexes, bool check_sigs, TxValidationState& state)
105 : EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
106 : {
107 61022 : AssertLockHeld(::cs_main);
108 :
109 61022 : if (!tx.HasExtraPayloadField())
110 31925 : return true;
111 :
112 29097 : if (!DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
113 1 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-dip3-inactive");
114 : }
115 :
116 : try {
117 29096 : switch (tx.nType) {
118 : case TRANSACTION_PROVIDER_REGISTER:
119 35 : return CheckProRegTx(tx, pindexPrev, dmnman, view, chainman, state, check_sigs);
120 : case TRANSACTION_PROVIDER_UPDATE_SERVICE:
121 4 : return CheckProUpServTx(tx, pindexPrev, dmnman, chainman, state, check_sigs);
122 : case TRANSACTION_PROVIDER_UPDATE_REGISTRAR:
123 3 : return CheckProUpRegTx(tx, pindexPrev, dmnman, view, chainman, state, check_sigs);
124 : case TRANSACTION_PROVIDER_UPDATE_REVOKE:
125 4 : return CheckProUpRevTx(tx, pindexPrev, dmnman, chainman, state, check_sigs);
126 : case TRANSACTION_COINBASE: {
127 0 : if (!tx.IsCoinBase()) {
128 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-invalid");
129 : }
130 0 : if (const auto opt_cbTx = GetTxPayload<CCbTx>(tx)) {
131 0 : return CheckCbTx(*opt_cbTx, pindexPrev, state);
132 : } else {
133 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-payload");
134 : }
135 : }
136 : case TRANSACTION_QUORUM_COMMITMENT:
137 29050 : return llmq::CheckLLMQCommitment({dmnman, qsnapman, chainman, pindexPrev}, tx, state);
138 : case TRANSACTION_MNHF_SIGNAL:
139 0 : return CheckMNHFTx(chainman, qman, tx, pindexPrev, state);
140 : case TRANSACTION_ASSET_LOCK:
141 0 : return CheckAssetLockTx(tx, state);
142 : case TRANSACTION_ASSET_UNLOCK:
143 0 : return CheckAssetUnlockTx(chainman.m_blockman, qman, tx, pindexPrev, indexes, state);
144 : }
145 0 : } catch (const std::exception& e) {
146 0 : LogPrintf("%s -- failed: %s\n", __func__, e.what());
147 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "failed-check-special-tx");
148 0 : }
149 :
150 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-check");
151 61022 : }
152 :
153 84 : bool CSpecialTxProcessor::CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state)
154 : {
155 84 : AssertLockHeld(::cs_main);
156 168 : return CheckSpecialTxInner(m_dmnman, m_qsnapman, m_chainman, m_qman, tx, pindexPrev, view, std::nullopt, check_sigs,
157 84 : state);
158 0 : }
159 :
160 0 : static void HandleQuorumCommitment(const llmq::CFinalCommitment& qc, const std::vector<CDeterministicMNCPtr>& members,
161 : bool debugLogs, CDeterministicMNList& mnList)
162 : {
163 0 : for (size_t i = 0; i < members.size(); i++) {
164 0 : if (!mnList.HasMN(members[i]->proTxHash)) {
165 0 : continue;
166 : }
167 0 : if (!qc.validMembers[i]) {
168 : // punish MN for failed DKG participation
169 : // The idea is to immediately ban a MN when it fails 2 DKG sessions with only a few blocks in-between
170 : // If there were enough blocks between failures, the MN has a chance to recover as he reduces his penalty by 1 for every block
171 : // If it however fails 3 times in the timespan of a single payment cycle, it should definitely get banned
172 0 : mnList.PoSePunish(members[i]->proTxHash, mnList.CalcPenalty(66), debugLogs);
173 0 : }
174 0 : }
175 0 : }
176 :
177 36470 : bool CSpecialTxProcessor::BuildNewListFromBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindexPrev,
178 : const CCoinsViewCache& view, bool debugLogs,
179 : BlockValidationState& state, CDeterministicMNList& mnListRet)
180 : {
181 36470 : AssertLockHeld(cs_main);
182 36470 : CDeterministicMNList oldList = m_dmnman.GetListForBlock(pindexPrev);
183 36470 : return RebuildListFromBlock(block, pindexPrev, oldList, view, debugLogs, state, mnListRet);
184 36470 : }
185 :
186 36470 : bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindexPrev,
187 : const CDeterministicMNList& prevList, const CCoinsViewCache& view,
188 : bool debugLogs, BlockValidationState& state,
189 : CDeterministicMNList& mnListRet)
190 : {
191 : // Verify that prevList either represents an empty/initial state (default-constructed),
192 : // or it matches the previous block's hash.
193 36470 : assert(prevList == CDeterministicMNList() || prevList.GetBlockHash() == pindexPrev->GetBlockHash());
194 :
195 36470 : int nHeight = pindexPrev->nHeight + 1;
196 :
197 36470 : CDeterministicMNList newList = prevList;
198 36470 : newList.SetBlockHash(uint256()); // we can't know the final block hash, so better not return a (invalid) block hash
199 36470 : newList.SetHeight(nHeight);
200 :
201 36470 : auto payee = prevList.GetMNPayee(pindexPrev);
202 :
203 : // we iterate the prevList here and update the newList
204 : // this is only valid as long these have not diverged at this point, which is the case as long as we don't add
205 : // code above this loop that modifies newList
206 50691 : prevList.ForEachMN(/*onlyValid=*/false, [&pindexPrev, &newList, this](const auto& dmn) {
207 14221 : if (!dmn.pdmnState->confirmedHash.IsNull()) {
208 : // already confirmed
209 13958 : return;
210 : }
211 : // this works on the previous block, so confirmation will happen one block after nMasternodeMinimumConfirmations
212 : // has been reached, but the block hash will then point to the block at nMasternodeMinimumConfirmations
213 263 : int nConfirmations = pindexPrev->nHeight - dmn.pdmnState->nRegisteredHeight;
214 263 : if (nConfirmations >= this->m_consensus_params.nMasternodeMinimumConfirmations) {
215 130 : auto newState = std::make_shared<CDeterministicMNState>(*dmn.pdmnState);
216 130 : newState->UpdateConfirmedHash(dmn.proTxHash, pindexPrev->GetBlockHash());
217 130 : newList.UpdateMN(dmn.proTxHash, newState);
218 130 : }
219 14221 : });
220 :
221 36470 : newList.DecreaseScores();
222 :
223 36470 : const bool isMNRewardReallocation{
224 36470 : DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_MN_RR)};
225 36470 : const bool is_v24_deployed{DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)};
226 :
227 : // we skip the coinbase
228 94668 : for (int i = 1; i < (int)block.vtx.size(); i++) {
229 58198 : const CTransaction& tx = *block.vtx[i];
230 :
231 58198 : if (!tx.IsSpecialTxVersion()) {
232 : // only interested in special TXs
233 8 : continue;
234 : }
235 :
236 58190 : if (tx.nType == TRANSACTION_PROVIDER_REGISTER) {
237 69 : const auto opt_proTx = GetTxPayload<CProRegTx>(tx);
238 69 : if (!opt_proTx) {
239 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
240 : }
241 69 : auto& proTx = *opt_proTx;
242 :
243 69 : auto dmn = std::make_shared<CDeterministicMN>(newList.GetTotalRegisteredCount(), proTx.nType);
244 69 : dmn->proTxHash = tx.GetHash();
245 :
246 : // collateralOutpoint is either pointing to an external collateral or to the ProRegTx itself
247 69 : if (proTx.collateralOutpoint.hash.IsNull()) {
248 66 : dmn->collateralOutpoint = COutPoint(tx.GetHash(), proTx.collateralOutpoint.n);
249 66 : } else {
250 3 : dmn->collateralOutpoint = proTx.collateralOutpoint;
251 : }
252 :
253 : // Complain about spent collaterals only when we process the tip.
254 : // This is safe because blocks below the tip were verified
255 : // when they were connected initially.
256 69 : if (!view.GetBestBlock().IsNull()) {
257 69 : Coin coin;
258 69 : CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount;
259 72 : if (!proTx.collateralOutpoint.hash.IsNull() && (!view.GetCoin(dmn->collateralOutpoint, coin) ||
260 3 : coin.IsSpent() || coin.out.nValue != expectedCollateral)) {
261 : // should actually never get to this point as CheckProRegTx should have handled this case.
262 : // We do this additional check nevertheless to be 100% sure
263 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-collateral");
264 : }
265 69 : }
266 :
267 69 : auto replacedDmn = newList.GetMNByCollateral(dmn->collateralOutpoint);
268 69 : if (replacedDmn != nullptr) {
269 : // This might only happen with a ProRegTx that refers an external collateral
270 : // In that case the new ProRegTx will replace the old one. This means the old one is removed
271 : // and the new one is added like a completely fresh one, which is also at the bottom of the payment list
272 0 : newList.RemoveMN(replacedDmn->proTxHash);
273 0 : if (debugLogs) {
274 0 : LogPrintf("%s -- MN %s removed from list because collateral was used for " /* Continued */
275 : "a new ProRegTx. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n",
276 : __func__, replacedDmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(),
277 : nHeight, newList.GetCounts().total());
278 0 : }
279 0 : }
280 :
281 138 : for (const auto& entry : proTx.netInfo->GetEntries()) {
282 138 : if (const auto service_opt{entry.GetAddrPort()}) {
283 69 : if (newList.HasUniqueProperty(*service_opt)) {
284 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
285 : }
286 69 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
287 0 : if (newList.HasUniqueProperty(*domain_opt)) {
288 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
289 : }
290 0 : } else {
291 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-netinfo-entry");
292 : }
293 : }
294 69 : if (newList.HasUniqueProperty(proTx.keyIDOwner) || newList.HasUniqueProperty(proTx.pubKeyOperator)) {
295 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-key");
296 : }
297 :
298 69 : dmn->nOperatorReward = proTx.nOperatorReward;
299 :
300 69 : auto dmnState = std::make_shared<CDeterministicMNState>(proTx);
301 69 : dmnState->nRegisteredHeight = nHeight;
302 69 : if (proTx.netInfo->IsEmpty()) {
303 : // start in banned pdmnState as we need to wait for a ProUpServTx
304 0 : dmnState->BanIfNotBanned(nHeight);
305 0 : }
306 69 : dmn->pdmnState = dmnState;
307 :
308 69 : newList.AddMN(dmn);
309 :
310 69 : if (debugLogs) {
311 69 : LogPrintf("%s -- MN %s added at height %d: %s\n", __func__, tx.GetHash().ToString(), nHeight,
312 : proTx.ToString());
313 69 : }
314 58190 : } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
315 8 : const auto opt_proTx = GetTxPayload<CProUpServTx>(tx);
316 8 : if (!opt_proTx) {
317 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
318 : }
319 :
320 16 : for (const auto& entry : opt_proTx->netInfo->GetEntries()) {
321 16 : if (const auto service_opt{entry.GetAddrPort()}) {
322 8 : if (newList.HasUniqueProperty(*service_opt) &&
323 0 : newList.GetUniquePropertyMN(*service_opt)->proTxHash != opt_proTx->proTxHash) {
324 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
325 : }
326 8 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
327 0 : if (newList.HasUniqueProperty(*domain_opt) &&
328 0 : newList.GetUniquePropertyMN(*domain_opt)->proTxHash != opt_proTx->proTxHash) {
329 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
330 : }
331 0 : } else {
332 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-netinfo-entry");
333 : }
334 : }
335 :
336 8 : auto dmn = newList.GetMN(opt_proTx->proTxHash);
337 8 : if (!dmn) {
338 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-hash");
339 : }
340 8 : if (opt_proTx->nType != dmn->nType) {
341 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-type-mismatch");
342 : }
343 8 : if (!IsValidMnType(opt_proTx->nType)) {
344 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-type");
345 : }
346 :
347 8 : auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
348 8 : if (is_v24_deployed) {
349 : // Extended addresses support in v24 means that the version can be updated
350 0 : newState->nVersion = opt_proTx->nVersion;
351 0 : }
352 8 : newState->netInfo = opt_proTx->netInfo;
353 8 : newState->scriptOperatorPayout = opt_proTx->scriptOperatorPayout;
354 8 : if (opt_proTx->nType == MnType::Evo) {
355 0 : newState->platformNodeID = opt_proTx->platformNodeID;
356 0 : if (opt_proTx->nVersion < ProTxVersion::ExtAddr) {
357 0 : newState->platformP2PPort = opt_proTx->platformP2PPort;
358 0 : newState->platformHTTPPort = opt_proTx->platformHTTPPort;
359 0 : } else {
360 : // From ExtAddr onwards the Platform ports are stored in netInfo. Clear the
361 : // legacy scalar fields (which a legacy registration may have left set) so the
362 : // in-memory state matches its serialized form, which omits them for ExtAddr
363 : // (see CDeterministicMNState serialization). Otherwise a stale value would
364 : // survive in diff-reconstructed lists but vanish through a snapshot round-trip.
365 0 : newState->platformP2PPort = 0;
366 0 : newState->platformHTTPPort = 0;
367 : }
368 0 : }
369 8 : if (newState->IsBanned()) {
370 : // only revive when all keys are set
371 8 : if (newState->pubKeyOperator != CBLSLazyPublicKey() && !newState->keyIDVoting.IsNull() &&
372 4 : !newState->keyIDOwner.IsNull()) {
373 4 : newState->Revive(nHeight);
374 4 : if (debugLogs) {
375 4 : LogPrintf("%s -- MN %s revived at height %d\n", __func__, opt_proTx->proTxHash.ToString(), nHeight);
376 4 : }
377 4 : }
378 4 : }
379 :
380 8 : newList.UpdateMN(opt_proTx->proTxHash, newState);
381 8 : if (debugLogs) {
382 8 : LogPrintf("%s -- MN %s updated at height %d: %s\n", __func__, opt_proTx->proTxHash.ToString(), nHeight,
383 : opt_proTx->ToString());
384 8 : }
385 58121 : } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR) {
386 6 : const auto opt_proTx = GetTxPayload<CProUpRegTx>(tx);
387 6 : if (!opt_proTx) {
388 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
389 : }
390 :
391 6 : auto dmn = newList.GetMN(opt_proTx->proTxHash);
392 6 : if (!dmn) {
393 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-hash");
394 : }
395 6 : auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
396 6 : if (newState->pubKeyOperator != opt_proTx->pubKeyOperator) {
397 : // reset all operator related fields and put MN into PoSe-banned state in case the operator key changes
398 6 : newState->ResetOperatorFields();
399 6 : newState->BanIfNotBanned(nHeight);
400 : // we update pubKeyOperator here, make sure state version matches
401 : // Make sure we don't accidentally downgrade the state version if using version after basic BLS
402 6 : newState->nVersion = newState->nVersion > ProTxVersion::BasicBLS ? newState->nVersion : opt_proTx->nVersion;
403 6 : newState->netInfo = NetInfoInterface::MakeNetInfo(newState->nVersion);
404 6 : newState->pubKeyOperator = opt_proTx->pubKeyOperator;
405 6 : }
406 6 : newState->keyIDVoting = opt_proTx->keyIDVoting;
407 6 : newState->scriptPayout = opt_proTx->scriptPayout;
408 :
409 6 : newList.UpdateMN(opt_proTx->proTxHash, newState);
410 :
411 6 : if (debugLogs) {
412 6 : LogPrintf("%s -- MN %s updated at height %d: %s\n", __func__, opt_proTx->proTxHash.ToString(), nHeight,
413 : opt_proTx->ToString());
414 6 : }
415 58113 : } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REVOKE) {
416 7 : const auto opt_proTx = GetTxPayload<CProUpRevTx>(tx);
417 7 : if (!opt_proTx) {
418 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
419 : }
420 :
421 7 : auto dmn = newList.GetMN(opt_proTx->proTxHash);
422 7 : if (!dmn) {
423 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-hash");
424 : }
425 7 : auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
426 7 : newState->ResetOperatorFields();
427 7 : newState->BanIfNotBanned(nHeight);
428 7 : newState->nRevocationReason = opt_proTx->nReason;
429 :
430 7 : newList.UpdateMN(opt_proTx->proTxHash, newState);
431 :
432 7 : if (debugLogs) {
433 7 : LogPrintf("%s -- MN %s revoked operator key at height %d: %s\n", __func__,
434 : opt_proTx->proTxHash.ToString(), nHeight, opt_proTx->ToString());
435 7 : }
436 58107 : } else if (tx.nType == TRANSACTION_QUORUM_COMMITMENT) {
437 58100 : const auto opt_qc = GetTxPayload<llmq::CFinalCommitmentTxPayload>(tx);
438 58100 : if (!opt_qc) {
439 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-payload");
440 : }
441 58100 : if (!opt_qc->commitment.IsNull()) {
442 0 : const auto& llmq_params_opt = Params().GetLLMQ(opt_qc->commitment.llmqType);
443 0 : if (!llmq_params_opt.has_value()) {
444 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-commitment-type");
445 : }
446 0 : int qcnHeight = int(opt_qc->nHeight);
447 0 : int quorumHeight = qcnHeight - (qcnHeight % llmq_params_opt->dkgInterval) +
448 0 : int(opt_qc->commitment.quorumIndex);
449 0 : auto pQuorumBaseBlockIndex = pindexPrev->GetAncestor(quorumHeight);
450 0 : if (!pQuorumBaseBlockIndex || pQuorumBaseBlockIndex->GetBlockHash() != opt_qc->commitment.quorumHash) {
451 : // we should actually never get into this case as validation should have caught it...but let's be sure
452 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-quorum-hash");
453 : }
454 :
455 : // The commitment has already been validated at this point, so it's safe to use members of it
456 :
457 0 : const auto members = llmq::utils::GetAllQuorumMembers(opt_qc->commitment.llmqType,
458 0 : {m_dmnman, m_qsnapman, m_chainman,
459 0 : pQuorumBaseBlockIndex});
460 0 : HandleQuorumCommitment(opt_qc->commitment, members, debugLogs, newList);
461 0 : }
462 58100 : }
463 58190 : }
464 :
465 : // we skip the coinbase
466 94668 : for (int i = 1; i < (int)block.vtx.size(); i++) {
467 58198 : const CTransaction& tx = *block.vtx[i];
468 :
469 : // check if any existing MN collateral is spent by this transaction
470 58420 : for (const auto& in : tx.vin) {
471 222 : auto dmn = newList.GetMNByCollateral(in.prevout);
472 222 : if (dmn && dmn->collateralOutpoint == in.prevout) {
473 5 : newList.RemoveMN(dmn->proTxHash);
474 :
475 5 : if (debugLogs) {
476 5 : LogPrintf("%s -- MN %s removed from list because collateral was spent. " /* Continued */
477 : "collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n",
478 : __func__, dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight,
479 : newList.GetCounts().total());
480 5 : }
481 5 : }
482 222 : }
483 58198 : }
484 :
485 : // The payee for the current block was determined by the previous block's list, but it might have disappeared in the
486 : // current block. We still pay that MN one last time, however.
487 44644 : if (auto dmn = payee ? newList.GetMN(payee->proTxHash) : nullptr) {
488 8174 : auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
489 8174 : newState->nLastPaidHeight = nHeight;
490 : // Starting from v19 and until MNRewardReallocation, EvoNodes will be paid 4 blocks in a row
491 : // No need to check if v19 is active, since EvoNode ProRegTxes are allowed only after v19 activation
492 : // Note: If the payee wasn't found in the current block that's fine
493 8174 : if (dmn->nType == MnType::Evo && !isMNRewardReallocation) {
494 0 : ++newState->nConsecutivePayments;
495 0 : if (debugLogs) {
496 0 : LogPrint(BCLog::MNPAYMENTS, "%s -- MN %s is an EvoNode, bumping nConsecutivePayments to %d\n", __func__,
497 : dmn->proTxHash.ToString(), newState->nConsecutivePayments);
498 0 : }
499 0 : }
500 8174 : newList.UpdateMN(payee->proTxHash, newState);
501 8174 : }
502 :
503 : // reset nConsecutivePayments on non-paid EvoNodes
504 36470 : auto newList2 = newList;
505 50755 : newList2.ForEachMN(/*onlyValid=*/false, [&](const auto& dmn) {
506 14285 : if (dmn.nType != MnType::Evo) return;
507 0 : if (payee != nullptr && dmn.proTxHash == payee->proTxHash && !isMNRewardReallocation) return;
508 0 : if (dmn.pdmnState->nConsecutivePayments == 0) return;
509 0 : if (debugLogs) {
510 0 : LogPrint(BCLog::MNPAYMENTS, "%s -- MN %s, reset nConsecutivePayments %d->0\n", __func__,
511 : dmn.proTxHash.ToString(), dmn.pdmnState->nConsecutivePayments);
512 0 : }
513 0 : auto newState = std::make_shared<CDeterministicMNState>(*dmn.pdmnState);
514 0 : newState->nConsecutivePayments = 0;
515 0 : newList.UpdateMN(dmn.proTxHash, newState);
516 14285 : });
517 :
518 36470 : mnListRet = newList;
519 :
520 36470 : return true;
521 36470 : }
522 :
523 49015 : bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, bool fJustCheck,
524 : bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional<MNListUpdates>& updatesRet)
525 : {
526 49015 : AssertLockHeld(::cs_main);
527 :
528 : try {
529 : static int64_t nTimeLoop = 0;
530 : static int64_t nTimeQuorum = 0;
531 : static int64_t nTimeDMN = 0;
532 : static int64_t nTimeMerkleMNL = 0;
533 : static int64_t nTimeMerkleQuorums = 0;
534 : static int64_t nTimeCbTxCL = 0;
535 : static int64_t nTimeMnehf = 0;
536 : static int64_t nTimePayload = 0;
537 : static int64_t nTimeCreditPool = 0;
538 :
539 49015 : int64_t nTime1 = GetTimeMicros();
540 :
541 49015 : std::optional<CCbTx> opt_cbTx{std::nullopt};
542 49015 : if (fCheckCbTxMerkleRoots && block.vtx.size() > 0 && block.vtx[0]->nType == TRANSACTION_COINBASE) {
543 18236 : const auto& tx = block.vtx[0];
544 18236 : if (!tx->IsCoinBase()) {
545 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid");
546 : }
547 18236 : if (opt_cbTx = GetTxPayload<CCbTx>(*tx); opt_cbTx) {
548 18236 : TxValidationState tx_state;
549 18236 : if (!CheckCbTx(*opt_cbTx, pindex->pprev, tx_state)) {
550 0 : assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS ||
551 : tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL);
552 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
553 0 : strprintf("Special Transaction check failed (tx hash %s) %s",
554 0 : tx->GetHash().ToString(), tx_state.GetDebugMessage()));
555 : }
556 18236 : } else {
557 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-payload");
558 : }
559 18236 : }
560 49015 : if (fCheckCbTxMerkleRoots) {
561 : // To ensure that opt_cbTx is not missing when it's supposed to be
562 49015 : if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003) && !opt_cbTx.has_value()) {
563 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-version");
564 : }
565 49015 : }
566 :
567 49015 : int64_t nTime2 = GetTimeMicros();
568 49015 : nTimePayload += nTime2 - nTime1;
569 49015 : LogPrint(BCLog::BENCHMARK, " - GetTxPayload: %.2fms [%.2fs]\n", 0.001 * (nTime2 - nTime1),
570 : nTimePayload * 0.000001);
571 :
572 49015 : CRangesSet indexes;
573 49015 : if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V20)) {
574 15204 : CCreditPool creditPool{m_cpoolman.GetCreditPool(pindex->pprev)};
575 15204 : LogPrint(BCLog::CREDITPOOL, "CSpecialTxProcessor::%s -- CCreditPool is %s\n", __func__, creditPool.ToString());
576 15204 : indexes = std::move(creditPool.indexes);
577 15204 : }
578 :
579 128188 : for (size_t i = 0; i < block.vtx.size(); ++i) {
580 : // we validated CCbTx above, starts from the 2nd transaction
581 79174 : if (i == 0 && block.vtx[i]->nType == TRANSACTION_COINBASE) continue;
582 :
583 60938 : const auto ptr_tx = block.vtx[i];
584 60938 : TxValidationState tx_state;
585 : // At this moment CheckSpecialTx() may fail by 2 possible ways:
586 : // consensus failures and "TX_BAD_SPECIAL"
587 60938 : if (!CheckSpecialTxInner(m_dmnman, m_qsnapman, m_chainman, m_qman, *ptr_tx, pindex->pprev, view, indexes,
588 60938 : fCheckCbTxMerkleRoots, tx_state)) {
589 1 : assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS || tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL);
590 2 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
591 1 : strprintf("Special Transaction check failed (tx hash %s) %s", ptr_tx->GetHash().ToString(), tx_state.GetDebugMessage()));
592 : }
593 60938 : }
594 :
595 49014 : int64_t nTime3 = GetTimeMicros();
596 49014 : nTimeLoop += nTime3 - nTime2;
597 49014 : LogPrint(BCLog::BENCHMARK, " - Loop: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeLoop * 0.000001);
598 :
599 49014 : if (opt_cbTx.has_value()) {
600 18236 : if (!CheckCreditPoolDiffForBlock(block, pindex, *opt_cbTx, state)) {
601 0 : return error("CSpecialTxProcessor: CheckCreditPoolDiffForBlock for block %s failed with %s",
602 0 : pindex->GetBlockHash().ToString(), state.ToString());
603 : }
604 18236 : }
605 :
606 49014 : int64_t nTime4 = GetTimeMicros();
607 49014 : nTimeCreditPool += nTime4 - nTime3;
608 49014 : LogPrint(BCLog::BENCHMARK, " - CheckCreditPoolDiffForBlock: %.2fms [%.2fs]\n", 0.001 * (nTime4 - nTime3),
609 : nTimeCreditPool * 0.000001);
610 :
611 49014 : if (!m_qblockman.ProcessBlock(block, pindex, state, fJustCheck, fCheckCbTxMerkleRoots)) {
612 : // pass the state returned by the function above
613 0 : return false;
614 : }
615 :
616 49014 : int64_t nTime5 = GetTimeMicros();
617 49014 : nTimeQuorum += nTime5 - nTime4;
618 49014 : LogPrint(BCLog::BENCHMARK, " - m_qblockman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime5 - nTime4),
619 : nTimeQuorum * 0.000001);
620 :
621 49014 : CDeterministicMNList mn_list;
622 49014 : if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003)) {
623 18236 : if (!BuildNewListFromBlock(block, pindex->pprev, view, true, state, mn_list)) {
624 : // pass the state returned by the function above
625 0 : return false;
626 : }
627 18236 : mn_list.SetBlockHash(pindex->GetBlockHash());
628 :
629 18236 : if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, mn_list, updatesRet)) {
630 : // pass the state returned by the function above
631 0 : return false;
632 : }
633 18236 : }
634 :
635 49014 : int64_t nTime6 = GetTimeMicros();
636 49014 : nTimeDMN += nTime6 - nTime5;
637 49014 : LogPrint(BCLog::BENCHMARK, " - m_dmnman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5),
638 : nTimeDMN * 0.000001);
639 :
640 49014 : if (opt_cbTx.has_value()) {
641 18236 : uint256 calculatedMerkleRootMNL;
642 18236 : if (!CalcCbTxMerkleRootMNList(calculatedMerkleRootMNL, mn_list.to_sml(), state)) {
643 : // pass the state returned by the function above
644 0 : return false;
645 : }
646 18236 : if (calculatedMerkleRootMNL != opt_cbTx->merkleRootMNList) {
647 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-mnmerkleroot");
648 : }
649 :
650 18236 : int64_t nTime6_1 = GetTimeMicros();
651 18236 : nTimeMerkleMNL += nTime6_1 - nTime6;
652 18236 : LogPrint(BCLog::BENCHMARK, " - CalcCbTxMerkleRootMNList: %.2fms [%.2fs]\n",
653 : 0.001 * (nTime6_1 - nTime6), nTimeMerkleMNL * 0.000001);
654 :
655 18236 : if (opt_cbTx->nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) {
656 18236 : uint256 calculatedMerkleRootQuorums;
657 18236 : if (!CalcCbTxMerkleRootQuorums(block, pindex->pprev, m_qblockman, calculatedMerkleRootQuorums, state)) {
658 : // pass the state returned by the function above
659 0 : return false;
660 : }
661 18236 : if (calculatedMerkleRootQuorums != opt_cbTx->merkleRootQuorums) {
662 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-quorummerkleroot");
663 : }
664 18236 : }
665 :
666 18236 : int64_t nTime6_2 = GetTimeMicros();
667 18236 : nTimeMerkleQuorums += nTime6_2 - nTime6_1;
668 :
669 18236 : LogPrint(BCLog::BENCHMARK, " - CalcCbTxMerkleRootQuorums: %.2fms [%.2fs]\n",
670 : 0.001 * (nTime6_2 - nTime6_1), nTimeMerkleQuorums * 0.000001);
671 :
672 18236 : if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_consensus_params, m_chainman.ActiveChain(), m_qman,
673 18236 : m_chainlocks, state)) {
674 : // pass the state returned by the function above
675 0 : return false;
676 : }
677 :
678 18236 : int64_t nTime6_3 = GetTimeMicros();
679 18236 : nTimeCbTxCL += nTime6_3 - nTime6_2;
680 18236 : LogPrint(BCLog::BENCHMARK, " - CheckCbTxBestChainlock: %.2fms [%.2fs]\n",
681 : 0.001 * (nTime6_3 - nTime6_2), nTimeCbTxCL * 0.000001);
682 18236 : }
683 :
684 49014 : int64_t nTime7 = GetTimeMicros();
685 :
686 49014 : if (!m_mnhfman.ProcessBlock(block, pindex, fJustCheck, state)) {
687 : // pass the state returned by the function above
688 0 : return false;
689 : }
690 :
691 49014 : int64_t nTime8 = GetTimeMicros();
692 49014 : nTimeMnehf += nTime8 - nTime7;
693 49014 : LogPrint(BCLog::BENCHMARK, " - m_mnhfman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime8 - nTime7),
694 : nTimeMnehf * 0.000001);
695 :
696 49014 : if (DeploymentActiveAfter(pindex, m_consensus_params, Consensus::DEPLOYMENT_V19) && bls::bls_legacy_scheme.load()) {
697 : // NOTE: The block next to the activation is the one that is using new rules.
698 : // V19 activated just activated, so we must switch to the new rules here.
699 204 : bls::bls_legacy_scheme.store(false);
700 204 : LogPrintf("CSpecialTxProcessor::%s -- bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
701 204 : }
702 49015 : } catch (const std::exception& e) {
703 0 : LogPrintf("CSpecialTxProcessor::%s -- FAILURE! %s\n", __func__, e.what());
704 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-procspectxsinblock");
705 0 : }
706 :
707 49014 : return true;
708 49015 : }
709 :
710 389 : bool CSpecialTxProcessor::UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, std::optional<MNListUpdates>& updatesRet)
711 : {
712 389 : AssertLockHeld(::cs_main);
713 :
714 389 : auto bls_legacy_scheme = bls::bls_legacy_scheme.load();
715 :
716 : try {
717 389 : if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V19) && !bls_legacy_scheme) {
718 : // NOTE: The block next to the activation is the one that is using new rules.
719 : // Removing the activation block here, so we must switch back to the old rules.
720 0 : bls::bls_legacy_scheme.store(true);
721 0 : LogPrintf("CSpecialTxProcessor::%s -- bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
722 0 : }
723 :
724 389 : if (!m_mnhfman.UndoBlock(block, pindex)) {
725 0 : return false;
726 : }
727 :
728 389 : if (!m_dmnman.UndoBlock(pindex, updatesRet)) {
729 0 : return false;
730 : }
731 :
732 389 : if (!m_qblockman.UndoBlock(block, pindex)) {
733 0 : return false;
734 : }
735 389 : } catch (const std::exception& e) {
736 0 : bls::bls_legacy_scheme.store(bls_legacy_scheme);
737 0 : LogPrintf("CSpecialTxProcessor::%s -- bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
738 0 : return error(strprintf("CSpecialTxProcessor::%s -- FAILURE! %s\n", __func__, e.what()).c_str());
739 0 : }
740 :
741 389 : return true;
742 389 : }
743 :
744 18236 : bool CSpecialTxProcessor::CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx,
745 : BlockValidationState& state)
746 : {
747 18236 : AssertLockHeld(::cs_main);
748 :
749 18236 : if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0008)) return true;
750 18236 : if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V20)) return true;
751 :
752 : try {
753 15204 : const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_consensus_params);
754 30408 : const auto creditPoolDiff = GetCreditPoolDiffForBlock(m_cpoolman, block,
755 15204 : pindex->pprev, m_consensus_params, blockSubsidy, state);
756 15204 : if (!creditPoolDiff.has_value()) return false;
757 :
758 15204 : const CAmount target_balance{cbTx.creditPoolBalance};
759 : // But it maybe not included yet in previous block yet; in this case value must be 0
760 15204 : const CAmount locked_calculated{creditPoolDiff->GetTotalLocked()};
761 15204 : if (target_balance != locked_calculated) {
762 0 : LogPrintf("CSpecialTxProcessor::%s -- mismatched locked amount in CbTx: %lld against re-calculated: %lld\n", __func__, target_balance, locked_calculated);
763 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-assetlocked-amount");
764 : }
765 :
766 15204 : } catch (const std::exception& e) {
767 0 : LogPrintf("CSpecialTxProcessor::%s -- FAILURE! %s\n", __func__, e.what());
768 0 : return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-checkcreditpooldiff");
769 0 : }
770 :
771 15204 : return true;
772 18236 : }
773 :
774 : template <typename ProTx>
775 63 : static bool CheckService(const ProTx& proTx, TxValidationState& state)
776 : {
777 63 : switch (proTx.netInfo->Validate()) {
778 : case NetInfoStatus::BadAddress:
779 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr");
780 : case NetInfoStatus::BadPort:
781 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-port");
782 : case NetInfoStatus::BadType:
783 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-type");
784 : case NetInfoStatus::NotRoutable:
785 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-unroutable");
786 : case NetInfoStatus::Malformed:
787 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-bad");
788 : case NetInfoStatus::Success:
789 63 : return true;
790 : // Shouldn't be possible during self-checks
791 : case NetInfoStatus::BadInput:
792 : case NetInfoStatus::Duplicate:
793 : case NetInfoStatus::MaxLimit:
794 0 : assert(false);
795 : } // no default case, so the compiler can warn about missing cases
796 0 : assert(false);
797 63 : }
798 :
799 : template <typename ProTx>
800 0 : static bool CheckPlatformFields(const ProTx& proTx, bool is_extended_addr, TxValidationState& state)
801 : {
802 0 : if (proTx.platformNodeID.IsNull()) {
803 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-nodeid");
804 : }
805 :
806 0 : if (is_extended_addr) {
807 : // platformHTTPPort and platformP2PPort have been subsumed by netInfo. They should always be zero.
808 0 : if (proTx.platformP2PPort != 0) {
809 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
810 : }
811 0 : if (proTx.platformHTTPPort != 0) {
812 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
813 : }
814 0 : return true;
815 : }
816 :
817 0 : if (::IsNodeOnMainnet()) {
818 0 : if (proTx.platformP2PPort != ::MainParams().GetDefaultPlatformP2PPort()) {
819 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
820 : }
821 0 : if (proTx.platformHTTPPort != ::MainParams().GetDefaultPlatformHTTPPort()) {
822 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
823 : }
824 0 : }
825 0 : if (proTx.platformP2PPort == ::MainParams().GetDefaultPort()) {
826 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
827 : }
828 0 : if (proTx.platformHTTPPort == ::MainParams().GetDefaultPort()) {
829 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
830 : }
831 :
832 0 : const uint16_t core_port{proTx.netInfo->GetPrimary().GetPort()};
833 0 : if (proTx.platformP2PPort == proTx.platformHTTPPort || proTx.platformP2PPort == core_port ||
834 0 : proTx.platformHTTPPort == core_port) {
835 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-dup-ports");
836 : }
837 :
838 0 : return true;
839 0 : }
840 :
841 : template <typename ProTx>
842 7 : static bool CheckHashSig(const ProTx& proTx, const PKHash& pkhash, TxValidationState& state)
843 : {
844 9 : if (std::string strError; !CHashSigner::VerifyHash(::SerializeHash(proTx), ToKeyID(pkhash), proTx.vchSig, strError)) {
845 2 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
846 : }
847 5 : return true;
848 7 : }
849 :
850 : template <typename ProTx>
851 2 : static bool CheckStringSig(const ProTx& proTx, const PKHash& pkhash, TxValidationState& state)
852 : {
853 4 : if (std::string strError;
854 2 : !CMessageSigner::VerifyMessage(ToKeyID(pkhash), proTx.vchSig, proTx.MakeSignString(), strError)) {
855 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
856 : }
857 2 : return true;
858 2 : }
859 :
860 : template <typename ProTx>
861 8 : static bool CheckHashSig(const ProTx& proTx, const CBLSPublicKey& pubKey, TxValidationState& state)
862 : {
863 8 : if (!proTx.sig.VerifyInsecure(pubKey, ::SerializeHash(proTx))) {
864 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
865 : }
866 8 : return true;
867 8 : }
868 :
869 : template <typename ProTx>
870 74 : static std::optional<ProTx> GetValidatedPayload(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
871 : const ChainstateManager& chainman, TxValidationState& state)
872 : {
873 74 : if (tx.nType != ProTx::SPECIALTX_TYPE) {
874 0 : state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type");
875 0 : return std::nullopt;
876 : }
877 :
878 74 : auto opt_ptx = GetTxPayload<ProTx>(tx);
879 74 : if (!opt_ptx) {
880 0 : state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-payload");
881 0 : return std::nullopt;
882 : }
883 74 : if (!opt_ptx->IsTriviallyValid(pindexPrev, chainman, state)) {
884 : // pass the state returned by the function above
885 0 : return std::nullopt;
886 : }
887 74 : return opt_ptx;
888 74 : }
889 :
890 : /**
891 : * Validates potential changes to masternode state version by ProTx transaction version
892 : * @param[in] pindexPrev Previous block index to validate DEPLOYMENT_V24 activation
893 : * @param[in] tx_type Special transaction type
894 : * @param[in] state_version Current masternode state version
895 : * @param[in] tx_version Proposed transaction version
896 : * @param[out] state This may be set to an Error state if any error occurred processing them
897 : * @returns true if version change is valid or DEPLOYMENT_V24 is not active
898 : */
899 15 : static bool IsVersionChangeValid(gsl::not_null<const CBlockIndex*> pindexPrev, const uint16_t tx_type,
900 : const uint16_t state_version, const uint16_t tx_version,
901 : const ChainstateManager& chainman, TxValidationState& state)
902 : {
903 15 : if (!DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)) {
904 : // New restrictions only apply after v24 deployment
905 15 : return true;
906 : }
907 :
908 0 : if (state_version >= ProTxVersion::BasicBLS && tx_version == ProTxVersion::LegacyBLS) {
909 : // Don't allow legacy scheme versioned transactions after upgrading to basic scheme
910 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-downgrade");
911 : }
912 :
913 0 : if (state_version == ProTxVersion::LegacyBLS && tx_version > ProTxVersion::BasicBLS) {
914 : // Nodes using the legacy scheme must first upgrade to the basic scheme before upgrading further
915 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-upgrade");
916 : }
917 :
918 0 : if (tx_type != TRANSACTION_PROVIDER_UPDATE_SERVICE && tx_version == ProTxVersion::ExtAddr) {
919 : // Only new entries (ProRegTx) and service updates (ProUpServTx) can use ExtAddr versioning
920 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-tx-type");
921 : }
922 :
923 0 : return true;
924 15 : }
925 :
926 59 : bool CheckProRegTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
927 : CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman,
928 : TxValidationState& state, bool check_sigs)
929 : {
930 59 : const auto opt_ptx = GetValidatedPayload<CProRegTx>(tx, pindexPrev, chainman, state);
931 59 : if (!opt_ptx) {
932 : // pass the state returned by the function above
933 0 : return false;
934 : }
935 :
936 59 : const bool is_v24_active{DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)};
937 :
938 : // No longer allow legacy scheme masternode registration
939 59 : if (is_v24_active && opt_ptx->nVersion < ProTxVersion::BasicBLS) {
940 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-disallowed");
941 : }
942 :
943 : // It's allowed to set addr to 0, which will put the MN into PoSe-banned state and require a ProUpServTx to be
944 : // issues later. If any of both is set, it must be valid however
945 59 : if (!opt_ptx->netInfo->IsEmpty() && !CheckService(*opt_ptx, state)) {
946 : // pass the state returned by the function above
947 0 : return false;
948 : }
949 :
950 59 : if (opt_ptx->nType == MnType::Evo) {
951 0 : if (!CheckPlatformFields(*opt_ptx, opt_ptx->nVersion >= ProTxVersion::ExtAddr, state)) {
952 0 : return false;
953 : }
954 0 : }
955 :
956 59 : CTxDestination collateralTxDest;
957 59 : const PKHash* keyForPayloadSig = nullptr;
958 59 : COutPoint collateralOutpoint;
959 :
960 59 : CAmount expectedCollateral = GetMnType(opt_ptx->nType).collat_amount;
961 :
962 59 : if (!opt_ptx->collateralOutpoint.hash.IsNull()) {
963 2 : Coin coin;
964 2 : if (!view.GetCoin(opt_ptx->collateralOutpoint, coin) || coin.IsSpent() || coin.out.nValue != expectedCollateral) {
965 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral");
966 : }
967 :
968 2 : if (!ExtractDestination(coin.out.scriptPubKey, collateralTxDest)) {
969 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-dest");
970 : }
971 :
972 : // Extract key from collateral. This only works for P2PK and P2PKH collaterals and will fail for P2SH.
973 : // Issuer of this ProRegTx must prove ownership with this key by signing the ProRegTx
974 2 : keyForPayloadSig = std::get_if<PKHash>(&collateralTxDest);
975 2 : if (!keyForPayloadSig) {
976 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-pkh");
977 : }
978 :
979 2 : collateralOutpoint = opt_ptx->collateralOutpoint;
980 2 : } else {
981 57 : if (opt_ptx->collateralOutpoint.n >= tx.vout.size()) {
982 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-index");
983 : }
984 57 : if (tx.vout[opt_ptx->collateralOutpoint.n].nValue != expectedCollateral) {
985 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral");
986 : }
987 :
988 57 : if (!ExtractDestination(tx.vout[opt_ptx->collateralOutpoint.n].scriptPubKey, collateralTxDest)) {
989 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-dest");
990 : }
991 :
992 57 : collateralOutpoint = COutPoint(tx.GetHash(), opt_ptx->collateralOutpoint.n);
993 : }
994 :
995 : // don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server)
996 : // this check applies to internal and external collateral, but internal collaterals are not necessarily a P2PKH
997 118 : if (collateralTxDest == CTxDestination(PKHash(opt_ptx->keyIDOwner)) ||
998 59 : collateralTxDest == CTxDestination(PKHash(opt_ptx->keyIDVoting))) {
999 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-reuse");
1000 : }
1001 :
1002 59 : if (pindexPrev) {
1003 59 : auto mnList = dmnman.GetListForBlock(pindexPrev);
1004 :
1005 : // only allow reusing of addresses when it's for the same collateral (which replaces the old MN)
1006 118 : for (const auto& entry : opt_ptx->netInfo->GetEntries()) {
1007 118 : if (const auto service_opt{entry.GetAddrPort()}) {
1008 59 : if (mnList.HasUniqueProperty(*service_opt) &&
1009 0 : mnList.GetUniquePropertyMN(*service_opt)->collateralOutpoint != collateralOutpoint) {
1010 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
1011 : }
1012 59 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
1013 0 : if (mnList.HasUniqueProperty(*domain_opt) &&
1014 0 : mnList.GetUniquePropertyMN(*domain_opt)->collateralOutpoint != collateralOutpoint) {
1015 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
1016 : }
1017 0 : } else {
1018 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-entry");
1019 : }
1020 : }
1021 :
1022 : // never allow duplicate keys, even if this ProTx would replace an existing MN
1023 59 : if (mnList.HasUniqueProperty(opt_ptx->keyIDOwner) || mnList.HasUniqueProperty(opt_ptx->pubKeyOperator)) {
1024 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-key");
1025 : }
1026 :
1027 : // never allow duplicate platformNodeIds for EvoNodes
1028 59 : if (opt_ptx->nType == MnType::Evo) {
1029 0 : if (mnList.HasUniqueProperty(opt_ptx->platformNodeID)) {
1030 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-platformnodeid");
1031 : }
1032 0 : }
1033 :
1034 59 : if (!DeploymentDIP0003Enforced(pindexPrev->nHeight, Params().GetConsensus())) {
1035 25 : if (opt_ptx->keyIDOwner != opt_ptx->keyIDVoting) {
1036 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-key-not-same");
1037 : }
1038 25 : }
1039 59 : }
1040 :
1041 59 : if (!CheckInputsHash(tx, *opt_ptx, state)) {
1042 : // pass the state returned by the function above
1043 0 : return false;
1044 : }
1045 :
1046 59 : if (keyForPayloadSig) {
1047 : // collateral is not part of this ProRegTx, so we must verify ownership of the collateral
1048 2 : if (check_sigs && !CheckStringSig(*opt_ptx, *keyForPayloadSig, state)) {
1049 : // pass the state returned by the function above
1050 0 : return false;
1051 : }
1052 2 : } else {
1053 : // collateral is part of this ProRegTx, so we know the collateral is owned by the issuer
1054 57 : if (!opt_ptx->vchSig.empty()) {
1055 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
1056 : }
1057 : }
1058 :
1059 59 : return true;
1060 59 : }
1061 :
1062 4 : bool CheckProUpServTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, CDeterministicMNManager& dmnman,
1063 : const ChainstateManager& chainman, TxValidationState& state, bool check_sigs)
1064 : {
1065 4 : const auto opt_ptx = GetValidatedPayload<CProUpServTx>(tx, pindexPrev, chainman, state);
1066 4 : if (!opt_ptx) {
1067 : // pass the state returned by the function above
1068 0 : return false;
1069 : }
1070 :
1071 4 : if (!CheckService(*opt_ptx, state)) {
1072 : // pass the state returned by the function above
1073 0 : return false;
1074 : }
1075 :
1076 4 : if (opt_ptx->nType == MnType::Evo) {
1077 0 : if (!CheckPlatformFields(*opt_ptx, opt_ptx->nVersion >= ProTxVersion::ExtAddr, state)) {
1078 0 : return false;
1079 : }
1080 0 : }
1081 :
1082 4 : auto mnList = dmnman.GetListForBlock(pindexPrev);
1083 4 : auto dmn = mnList.GetMN(opt_ptx->proTxHash);
1084 4 : if (!dmn) {
1085 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash");
1086 : }
1087 :
1088 4 : if (!IsVersionChangeValid(pindexPrev, tx.nType, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) {
1089 : // pass the state returned by the function above
1090 0 : return false;
1091 : }
1092 :
1093 : // don't allow updating to addresses already used by other MNs
1094 8 : for (const auto& entry : opt_ptx->netInfo->GetEntries()) {
1095 8 : if (const auto service_opt{entry.GetAddrPort()}) {
1096 4 : if (mnList.HasUniqueProperty(*service_opt) &&
1097 0 : mnList.GetUniquePropertyMN(*service_opt)->proTxHash != opt_ptx->proTxHash) {
1098 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
1099 : }
1100 4 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
1101 0 : if (mnList.HasUniqueProperty(*domain_opt) &&
1102 0 : mnList.GetUniquePropertyMN(*domain_opt)->proTxHash != opt_ptx->proTxHash) {
1103 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
1104 : }
1105 0 : } else {
1106 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-entry");
1107 : }
1108 : }
1109 :
1110 : // don't allow updating to platformNodeIds already used by other EvoNodes
1111 4 : if (opt_ptx->nType == MnType::Evo) {
1112 0 : if (mnList.HasUniqueProperty(opt_ptx->platformNodeID) &&
1113 0 : mnList.GetUniquePropertyMN(opt_ptx->platformNodeID)->proTxHash != opt_ptx->proTxHash) {
1114 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-platformnodeid");
1115 : }
1116 0 : }
1117 :
1118 4 : if (opt_ptx->scriptOperatorPayout != CScript()) {
1119 0 : if (dmn->nOperatorReward == 0) {
1120 : // don't allow setting operator reward payee in case no operatorReward was set
1121 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-payee");
1122 : }
1123 0 : if (!opt_ptx->scriptOperatorPayout.IsPayToPublicKeyHash() && !opt_ptx->scriptOperatorPayout.IsPayToScriptHash()) {
1124 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-payee");
1125 : }
1126 0 : }
1127 :
1128 : // we can only check the signature if pindexPrev != nullptr and the MN is known
1129 4 : if (!CheckInputsHash(tx, *opt_ptx, state)) {
1130 : // pass the state returned by the function above
1131 0 : return false;
1132 : }
1133 4 : if (check_sigs && !CheckHashSig(*opt_ptx, dmn->pdmnState->pubKeyOperator.Get(), state)) {
1134 : // pass the state returned by the function above
1135 0 : return false;
1136 : }
1137 :
1138 4 : return true;
1139 4 : }
1140 :
1141 7 : bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
1142 : CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman,
1143 : TxValidationState& state, bool check_sigs)
1144 : {
1145 7 : const auto opt_ptx = GetValidatedPayload<CProUpRegTx>(tx, pindexPrev, chainman, state);
1146 7 : if (!opt_ptx) {
1147 : // pass the state returned by the function above
1148 0 : return false;
1149 : }
1150 :
1151 7 : CTxDestination payoutDest;
1152 7 : if (!ExtractDestination(opt_ptx->scriptPayout, payoutDest)) {
1153 : // should not happen as we checked script types before
1154 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest");
1155 : }
1156 :
1157 7 : auto mnList = dmnman.GetListForBlock(pindexPrev);
1158 7 : auto dmn = mnList.GetMN(opt_ptx->proTxHash);
1159 7 : if (!dmn) {
1160 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash");
1161 : }
1162 :
1163 7 : if (!IsVersionChangeValid(pindexPrev, tx.nType, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) {
1164 : // pass the state returned by the function above
1165 0 : return false;
1166 : }
1167 :
1168 : // don't allow reuse of payee key for other keys (don't allow people to put the payee key onto an online server)
1169 14 : if (payoutDest == CTxDestination(PKHash(dmn->pdmnState->keyIDOwner)) ||
1170 7 : payoutDest == CTxDestination(PKHash(opt_ptx->keyIDVoting))) {
1171 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-reuse");
1172 : }
1173 :
1174 7 : Coin coin;
1175 7 : if (!view.GetCoin(dmn->collateralOutpoint, coin) || coin.IsSpent()) {
1176 : // this should never happen (there would be no dmn otherwise)
1177 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-collateral");
1178 : }
1179 :
1180 : // don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server)
1181 7 : CTxDestination collateralTxDest;
1182 7 : if (!ExtractDestination(coin.out.scriptPubKey, collateralTxDest)) {
1183 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-collateral-dest");
1184 : }
1185 14 : if (collateralTxDest == CTxDestination(PKHash(dmn->pdmnState->keyIDOwner)) ||
1186 7 : collateralTxDest == CTxDestination(PKHash(opt_ptx->keyIDVoting))) {
1187 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-reuse");
1188 : }
1189 :
1190 7 : if (mnList.HasUniqueProperty(opt_ptx->pubKeyOperator)) {
1191 0 : auto otherDmn = mnList.GetUniquePropertyMN(opt_ptx->pubKeyOperator);
1192 0 : if (opt_ptx->proTxHash != otherDmn->proTxHash) {
1193 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-key");
1194 : }
1195 0 : }
1196 :
1197 7 : if (!DeploymentDIP0003Enforced(pindexPrev->nHeight, Params().GetConsensus())) {
1198 1 : if (dmn->pdmnState->keyIDOwner != opt_ptx->keyIDVoting) {
1199 0 : return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-key-not-same");
1200 : }
1201 1 : }
1202 :
1203 7 : if (!CheckInputsHash(tx, *opt_ptx, state)) {
1204 : // pass the state returned by the function above
1205 0 : return false;
1206 : }
1207 7 : if (check_sigs && !CheckHashSig(*opt_ptx, PKHash(dmn->pdmnState->keyIDOwner), state)) {
1208 : // pass the state returned by the function above
1209 2 : return false;
1210 : }
1211 :
1212 5 : return true;
1213 7 : }
1214 :
1215 4 : bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, CDeterministicMNManager& dmnman,
1216 : const ChainstateManager& chainman, TxValidationState& state, bool check_sigs)
1217 : {
1218 4 : const auto opt_ptx = GetValidatedPayload<CProUpRevTx>(tx, pindexPrev, chainman, state);
1219 4 : if (!opt_ptx) {
1220 : // pass the state returned by the function above
1221 0 : return false;
1222 : }
1223 :
1224 4 : auto mnList = dmnman.GetListForBlock(pindexPrev);
1225 4 : auto dmn = mnList.GetMN(opt_ptx->proTxHash);
1226 4 : if (!dmn) {
1227 0 : return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash");
1228 : }
1229 :
1230 4 : if (!IsVersionChangeValid(pindexPrev, tx.nType, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) {
1231 : // pass the state returned by the function above
1232 0 : return false;
1233 : }
1234 :
1235 4 : if (!CheckInputsHash(tx, *opt_ptx, state)) {
1236 : // pass the state returned by the function above
1237 0 : return false;
1238 : }
1239 4 : if (check_sigs && !CheckHashSig(*opt_ptx, dmn->pdmnState->pubKeyOperator.Get(), state)) {
1240 : // pass the state returned by the function above
1241 0 : return false;
1242 : }
1243 :
1244 4 : return true;
1245 4 : }
1246 :
1247 95 : bool IsStandardSpecialTx(const CTransaction& tx, std::string& reason)
1248 : {
1249 95 : if (!tx.IsSpecialTxVersion()) return true;
1250 :
1251 0 : if (tx.nType != TRANSACTION_ASSET_LOCK) return true;
1252 :
1253 : // Each input is referenced by Platform's funding state transition; beyond this
1254 : // many inputs that state transition exceeds Platform's ~20 kB size limit.
1255 : static constexpr size_t MAX_STANDARD_ASSET_LOCK_INPUTS{100};
1256 0 : if (tx.vin.size() > MAX_STANDARD_ASSET_LOCK_INPUTS) {
1257 0 : reason = "assetlocktx-too-many-inputs";
1258 0 : return false;
1259 : }
1260 :
1261 0 : constexpr int max_tx_size_for_platform = 20480;
1262 0 : if (tx.GetTotalSize() > max_tx_size_for_platform) {
1263 0 : reason = "assetlocktx-too-big";
1264 0 : return false;
1265 : }
1266 :
1267 0 : if (const auto opt_assetLockTx = GetTxPayload<CAssetLockPayload>(tx);
1268 0 : opt_assetLockTx.has_value() && opt_assetLockTx->getVersion() >= 2) {
1269 0 : reason = "assetlocktx-version-2";
1270 0 : return false;
1271 : }
1272 :
1273 0 : return true;
1274 95 : }
|