Line data Source code
1 : // Copyright (c) 2025 The Dash Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <instantsend/net_instantsend.h>
6 :
7 : #include <bls/bls_batchverifier.h>
8 : #include <chainlock/chainlock.h>
9 : #include <consensus/params.h>
10 : #include <cxxtimer.hpp>
11 : #include <instantsend/instantsend.h>
12 : #include <instantsend/signing.h>
13 : #include <llmq/commitment.h>
14 : #include <llmq/quorumsman.h>
15 : #include <llmq/signhash.h>
16 : #include <llmq/signing.h>
17 : #include <masternode/sync.h>
18 : #include <node/interface_ui.h>
19 : #include <util/thread.h>
20 : #include <validation.h>
21 :
22 : #include <chrono>
23 : #include <set>
24 :
25 : // Forward declaration to break dependency over node/transaction.h
26 : namespace node {
27 : CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool,
28 : const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
29 : } // namespace node
30 :
31 : using node::GetTransaction;
32 : namespace {
33 : constexpr int BATCH_VERIFIER_SOURCE_THRESHOLD{8};
34 : constexpr int INVALID_ISLOCK_MISBEHAVIOR_SCORE{100};
35 : constexpr int UNKNOWN_CYCLE_HASH_MISBEHAVIOR_SCORE{1};
36 : constexpr int OLD_ACTIVE_SET_FAILURE_MISBEHAVIOR_SCORE{20};
37 : constexpr auto WORK_THREAD_SLEEP_INTERVAL{std::chrono::milliseconds{100}};
38 : } // namespace
39 :
40 1448 : static std::optional<int> GetBlockHeight(llmq::CInstantSendManager& is_manager, const CChainState& chainstate,
41 : const uint256& hash)
42 : {
43 1448 : if (hash.IsNull()) {
44 545 : return std::nullopt;
45 : }
46 903 : auto ret = is_manager.GetCachedHeight(hash);
47 903 : if (ret) return ret;
48 :
49 54 : const CBlockIndex* pindex = WITH_LOCK(::cs_main, return chainstate.m_blockman.LookupBlockIndex(hash));
50 27 : if (pindex == nullptr) {
51 0 : return std::nullopt;
52 : }
53 27 : is_manager.CacheBlockHeight(pindex);
54 27 : return pindex->nHeight;
55 1448 : }
56 :
57 1610 : struct NetInstantSend::BatchVerificationData {
58 805 : CBLSBatchVerifier<NodeId, uint256> batchVerifier{false, true, BATCH_VERIFIER_SOURCE_THRESHOLD};
59 : Uint256HashMap<llmq::CRecoveredSig> recSigs;
60 805 : size_t verifyCount{0};
61 805 : size_t alreadyVerified{0};
62 : };
63 :
64 300 : bool NetInstantSend::ValidateIncomingISLock(const instantsend::InstantSendLock& islock, NodeId node_id)
65 : {
66 300 : if (!islock.TriviallyValid()) {
67 0 : m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
68 0 : return false;
69 : }
70 :
71 300 : return true;
72 300 : }
73 :
74 300 : std::optional<int> NetInstantSend::ResolveCycleHeight(const uint256& cycle_hash)
75 : {
76 300 : auto cycle_height = GetBlockHeight(m_is_manager, m_chainstate, cycle_hash);
77 300 : if (cycle_height) {
78 300 : return cycle_height;
79 : }
80 :
81 0 : const auto block_index = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(cycle_hash));
82 0 : if (block_index == nullptr) {
83 0 : return std::nullopt;
84 : }
85 :
86 0 : m_is_manager.CacheBlockHeight(block_index);
87 0 : return block_index->nHeight;
88 300 : }
89 :
90 300 : bool NetInstantSend::ValidateDeterministicCycleHeight(
91 : int cycle_height,
92 : const Consensus::LLMQParams& llmq_params,
93 : NodeId node_id)
94 : {
95 : // Deterministic islocks MUST use rotation based llmq
96 300 : if (cycle_height % llmq_params.dkgInterval == 0) {
97 300 : return true;
98 : }
99 :
100 0 : m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
101 0 : return false;
102 300 : }
103 :
104 805 : std::unique_ptr<NetInstantSend::BatchVerificationData> NetInstantSend::BuildVerificationBatch(
105 : const Consensus::LLMQParams& llmq_params,
106 : int signOffset,
107 : const std::vector<instantsend::PendingISLockEntry>& pend)
108 : {
109 805 : auto data = std::make_unique<BatchVerificationData>();
110 :
111 1670 : for (const auto& pending : pend) {
112 865 : const auto& hash = pending.islock_hash;
113 865 : auto nodeId = pending.node_id;
114 865 : const auto& islock = pending.islock;
115 :
116 865 : if (data->batchVerifier.badSources.count(nodeId)) {
117 0 : continue;
118 : }
119 :
120 865 : CBLSSignature sig = islock->sig.Get();
121 865 : if (!sig.IsValid()) {
122 0 : data->batchVerifier.badSources.emplace(nodeId);
123 0 : continue;
124 : }
125 :
126 865 : auto id = islock->GetRequestId();
127 :
128 : // no need to verify an ISLOCK if we already have verified the recovered sig that belongs to it
129 865 : if (m_sigman.HasRecoveredSig(llmq_params.type, id, islock->txid)) {
130 582 : data->alreadyVerified++;
131 582 : continue;
132 : }
133 :
134 283 : auto cycleHeightOpt = GetBlockHeight(m_is_manager, m_chainstate, islock->cycleHash);
135 283 : if (!cycleHeightOpt) {
136 0 : data->batchVerifier.badSources.emplace(nodeId);
137 0 : continue;
138 : }
139 :
140 283 : int nSignHeight{-1};
141 283 : const auto dkgInterval = llmq_params.dkgInterval;
142 283 : const int tipHeight = m_is_manager.GetTipHeight();
143 283 : const int cycleHeight = *cycleHeightOpt;
144 283 : if (cycleHeight + dkgInterval < tipHeight) {
145 143 : nSignHeight = cycleHeight + dkgInterval - 1;
146 143 : }
147 : // For RegTest non-rotating quorum cycleHash has directly quorum hash
148 295 : auto quorum = llmq_params.useRotation ? llmq::SelectQuorumForSigning(llmq_params, m_chainstate.m_chain, m_qman,
149 271 : id, nSignHeight, signOffset)
150 12 : : m_qman.GetQuorum(llmq_params.type, islock->cycleHash);
151 :
152 283 : if (!quorum) {
153 : // should not happen, but if one fails to select, all others will also fail to select
154 0 : return nullptr;
155 : }
156 283 : uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get();
157 283 : data->batchVerifier.PushMessage(nodeId, hash, signHash, sig, quorum->qc->quorumPublicKey);
158 283 : data->verifyCount++;
159 :
160 : // We can reconstruct the CRecoveredSig objects from the islock and pass it to the signing manager, which
161 : // avoids unnecessary double-verification of the signature. We however only do this when verification here
162 : // turns out to be good (which is checked further down)
163 283 : if (!m_sigman.HasRecoveredSigForId(llmq_params.type, id)) {
164 566 : data->recSigs.try_emplace(hash, llmq::CRecoveredSig(llmq_params.type, quorum->qc->quorumHash, id, islock->txid,
165 283 : islock->sig));
166 283 : }
167 283 : }
168 :
169 805 : return data;
170 805 : }
171 :
172 805 : Uint256HashSet NetInstantSend::ApplyVerificationResults(
173 : const Consensus::LLMQParams& llmq_params,
174 : bool ban,
175 : BatchVerificationData& data,
176 : const std::vector<instantsend::PendingISLockEntry>& pend)
177 : {
178 805 : Uint256HashSet badISLocks;
179 805 : std::set<NodeId> penalized;
180 :
181 1670 : for (const auto& pending : pend) {
182 865 : const auto& hash = pending.islock_hash;
183 865 : auto nodeId = pending.node_id;
184 865 : const auto& islock = pending.islock;
185 :
186 865 : const bool source_bad = data.batchVerifier.badSources.count(nodeId);
187 865 : const bool message_bad = data.batchVerifier.badMessages.count(hash);
188 :
189 865 : if (source_bad || message_bad) {
190 0 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: verification failed, peer=%d\n",
191 : __func__, islock->txid.ToString(), hash.ToString(), nodeId);
192 0 : if (ban && source_bad && penalized.emplace(nodeId).second) {
193 : // Let's not be too harsh, as the peer might simply be unlucky and might have sent us
194 : // an old lock which does not validate anymore due to changed quorums
195 0 : m_peer_manager->PeerMisbehaving(nodeId, OLD_ACTIVE_SET_FAILURE_MISBEHAVIOR_SCORE);
196 0 : }
197 0 : if (message_bad) {
198 0 : badISLocks.emplace(hash);
199 0 : }
200 0 : continue;
201 : }
202 :
203 865 : ProcessInstantSendLock(nodeId, hash, islock);
204 :
205 : // Pass a reconstructed recovered sig to the signing manager to avoid double-verification of the sig.
206 865 : auto it = data.recSigs.find(hash);
207 865 : if (it != data.recSigs.end()) {
208 283 : auto recSig = std::make_shared<llmq::CRecoveredSig>(std::move(it->second));
209 283 : if (!m_sigman.HasRecoveredSigForId(llmq_params.type, recSig->getId())) {
210 281 : LogPrint(BCLog::INSTANTSEND, /* Continued */
211 : "NetInstantSend::%s -- txid=%s, islock=%s: "
212 : "passing reconstructed recSig to signing mgr, peer=%d\n",
213 : __func__, islock->txid.ToString(), hash.ToString(), nodeId);
214 281 : m_sigman.PushReconstructedRecoveredSig(recSig);
215 281 : }
216 283 : }
217 : }
218 :
219 805 : return badISLocks;
220 805 : }
221 :
222 : namespace {
223 : template <typename T>
224 : requires std::same_as<T, CTxIn> || std::same_as<T, COutPoint>
225 1467 : Uint256HashSet GetIdsFromLockable(const std::vector<T>& vec)
226 : {
227 1467 : Uint256HashSet ret{};
228 1467 : if (vec.empty()) return ret;
229 1467 : ret.reserve(vec.size());
230 5890 : for (const auto& in : vec) {
231 : if constexpr (std::is_same_v<T, COutPoint>) {
232 4415 : ret.emplace(instantsend::GenInputLockRequestId(in));
233 : } else if constexpr (std::is_same_v<T, CTxIn>) {
234 8 : ret.emplace(instantsend::GenInputLockRequestId(in.prevout));
235 : } else {
236 : assert(false);
237 : }
238 : }
239 1467 : return ret;
240 1467 : }
241 : } // anonymous namespace
242 :
243 96785 : void NetInstantSend::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv)
244 : {
245 96785 : if (msg_type != NetMsgType::ISDLOCK) {
246 96485 : return;
247 : }
248 :
249 300 : if (!m_is_manager.IsInstantSendEnabled()) return;
250 :
251 300 : auto islock = std::make_shared<instantsend::InstantSendLock>();
252 300 : vRecv >> *islock;
253 :
254 300 : const NodeId from = pfrom.GetId();
255 300 : uint256 hash = ::SerializeHash(*islock);
256 :
257 600 : WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(from, CInv{MSG_ISDLOCK, hash}));
258 :
259 300 : if (!ValidateIncomingISLock(*islock, from)) {
260 0 : return;
261 : }
262 :
263 300 : auto cycle_height = ResolveCycleHeight(islock->cycleHash);
264 300 : if (!cycle_height) {
265 : // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash
266 0 : m_peer_manager->PeerMisbehaving(from, UNKNOWN_CYCLE_HASH_MISBEHAVIOR_SCORE);
267 0 : return;
268 : }
269 :
270 300 : auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
271 300 : const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
272 300 : assert(llmq_params_opt);
273 300 : if (!ValidateDeterministicCycleHeight(*cycle_height, *llmq_params_opt, from)) {
274 0 : return;
275 : }
276 :
277 300 : if (!m_is_manager.AlreadyHave(CInv{MSG_ISDLOCK, hash})) {
278 300 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend -- ISDLOCK txid=%s, islock=%s: received islock, peer=%d\n",
279 : islock->txid.ToString(), hash.ToString(), from);
280 :
281 300 : m_is_manager.EnqueueInstantSendLock(from, hash, std::move(islock));
282 300 : }
283 96785 : }
284 :
285 2831 : void NetInstantSend::Start()
286 : {
287 : // can't start new thread if we have one running already
288 2831 : if (workThread.joinable()) {
289 0 : assert(false);
290 : }
291 :
292 5662 : workThread = std::thread(&util::TraceThread, "isman", [this] { WorkThreadMain(); });
293 2831 : }
294 :
295 5714 : void NetInstantSend::Stop()
296 : {
297 : // make sure to call Interrupt() first
298 5714 : if (!workInterrupt) {
299 0 : assert(false);
300 : }
301 :
302 5714 : if (workThread.joinable()) {
303 2831 : workThread.join();
304 2831 : }
305 5714 : }
306 :
307 805 : Uint256HashSet NetInstantSend::ProcessPendingInstantSendLocks(
308 : const Consensus::LLMQParams& llmq_params, int signOffset, bool ban,
309 : const std::vector<instantsend::PendingISLockEntry>& pend)
310 : {
311 805 : auto batch = BuildVerificationBatch(llmq_params, signOffset, pend);
312 805 : if (!batch) return {};
313 :
314 805 : cxxtimer::Timer verifyTimer(true);
315 805 : batch->batchVerifier.Verify();
316 805 : verifyTimer.stop();
317 :
318 805 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- verified locks. count=%d, alreadyVerified=%d, vt=%d, nodes=%d\n",
319 : __func__, batch->verifyCount, batch->alreadyVerified,
320 : verifyTimer.count(), batch->batchVerifier.GetUniqueSourceCount());
321 :
322 805 : return ApplyVerificationResults(llmq_params, ban, *batch, pend);
323 805 : }
324 :
325 805 : void NetInstantSend::ProcessPendingISLocks(std::vector<instantsend::PendingISLockEntry>&& locks_to_process)
326 : {
327 : // TODO Investigate if leaving this is ok
328 805 : auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
329 805 : const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
330 805 : assert(llmq_params_opt);
331 805 : const auto& llmq_params = llmq_params_opt.value();
332 805 : auto dkgInterval = llmq_params.dkgInterval;
333 :
334 : // First check against the current active set and don't ban
335 805 : auto bad_is_locks = ProcessPendingInstantSendLocks(llmq_params, /*signOffset=*/0, /*ban=*/false, locks_to_process);
336 805 : if (!bad_is_locks.empty()) {
337 0 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- doing verification on old active set\n", __func__);
338 :
339 : // filter out valid IS locks from "pend" - keep only bad ones
340 0 : std::vector<instantsend::PendingISLockEntry> still_pending;
341 0 : still_pending.reserve(bad_is_locks.size());
342 0 : for (auto& pending : locks_to_process) {
343 0 : if (bad_is_locks.contains(pending.islock_hash)) {
344 0 : still_pending.emplace_back(std::move(pending));
345 0 : }
346 : }
347 : // Now check against the previous active set and perform banning if this fails
348 0 : ProcessPendingInstantSendLocks(llmq_params, dkgInterval, /*ban=*/true, still_pending);
349 0 : }
350 805 : uiInterface.NotifyInstantSendChanged();
351 805 : }
352 :
353 865 : void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock)
354 : {
355 865 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__,
356 : islock->txid.ToString(), hash.ToString(), from);
357 :
358 865 : if (m_signer) {
359 601 : m_signer->ClearLockFromQueue(islock);
360 601 : }
361 865 : if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return;
362 :
363 865 : uint256 hashBlock{};
364 865 : auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock);
365 865 : const bool found_transaction{tx != nullptr};
366 : // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally
367 865 : const auto minedHeight = GetBlockHeight(m_is_manager, m_chainstate, hashBlock);
368 865 : if (found_transaction) {
369 : // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes,
370 : // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain
371 834 : if (minedHeight.has_value() && m_chainlocks.HasChainLock(*minedHeight, hashBlock)) {
372 14 : LogPrint(BCLog::INSTANTSEND, /* Continued */
373 : "NetInstantSend::%s -- txlock=%s, islock=%s: dropping islock as it already got a "
374 : "ChainLock in block %s, peer=%d\n",
375 : __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from);
376 14 : return;
377 : }
378 820 : m_is_manager.WriteNewISLock(hash, islock, minedHeight);
379 820 : } else {
380 31 : m_is_manager.AddPendingISLock(hash, islock, from);
381 : }
382 :
383 : // This will also add children TXs to pendingRetryTxs
384 851 : m_is_manager.RemoveNonLockedTx(islock->txid, true);
385 : // We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs.
386 : // We only need the ISLOCK from now on to detect conflicts
387 851 : TruncateRecoveredSigsForInputs(*islock);
388 851 : ResolveBlockConflicts(hash, *islock);
389 :
390 851 : if (found_transaction) {
391 820 : RemoveMempoolConflictsForLock(hash, *islock);
392 820 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- notify about lock %s for tx %s\n", __func__,
393 : hash.ToString(), tx->GetHash().ToString());
394 820 : GetMainSignals().NotifyTransactionLock(tx, islock);
395 : // bump m_mempool counter to make sure newly locked txes are picked up by getblocktemplate
396 820 : m_mempool.AddTransactionsUpdated(1);
397 820 : }
398 :
399 851 : CInv inv(MSG_ISDLOCK, hash);
400 851 : if (found_transaction) {
401 820 : m_peer_manager->PeerRelayInvFiltered(inv, *tx);
402 820 : } else {
403 31 : m_peer_manager->PeerRelayInvFiltered(inv, islock->txid);
404 31 : m_peer_manager->PeerAskPeersForTransaction(islock->txid);
405 : }
406 865 : }
407 :
408 2831 : void NetInstantSend::WorkThreadMain()
409 : {
410 378587 : while (!workInterrupt) {
411 757170 : bool fMoreWork = [&]() -> bool {
412 378585 : if (!m_is_manager.IsInstantSendEnabled()) return false;
413 :
414 378106 : auto [more_work, locks] = m_is_manager.FetchPendingLocks();
415 189053 : if (!locks.empty()) {
416 805 : ProcessPendingISLocks(std::move(locks));
417 805 : }
418 189053 : if (m_signer) {
419 154880 : m_signer->ProcessPendingRetryLockTxs(m_is_manager.PrepareTxToRetry());
420 154880 : }
421 189053 : return more_work;
422 378585 : }();
423 :
424 378585 : if (!fMoreWork && !workInterrupt.sleep_for(WORK_THREAD_SLEEP_INTERVAL)) {
425 2829 : return;
426 : }
427 : }
428 2831 : }
429 :
430 36798 : void NetInstantSend::TransactionAddedToMempool(const CTransactionRef& tx, int64_t, uint64_t mempool_sequence)
431 : {
432 36798 : if (!m_is_manager.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced() || tx->vin.empty()) {
433 35389 : return;
434 : }
435 :
436 1409 : instantsend::InstantSendLockPtr islock = m_is_manager.AttachISLockToTx(tx);
437 1409 : if (islock == nullptr) {
438 1385 : if (m_signer) {
439 934 : m_signer->ProcessTx(*tx, false, Params().GetConsensus());
440 934 : }
441 : // TX is not locked, so make sure it is tracked
442 1385 : m_is_manager.AddNonLockedTx(tx, nullptr);
443 1385 : } else {
444 24 : RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock);
445 : }
446 36798 : }
447 :
448 854 : void NetInstantSend::ClearConflicting(const Uint256HashMap<CTransactionRef>& to_delete)
449 : {
450 864 : for (const auto& [_, tx] : to_delete) {
451 20 : m_is_manager.RemoveNonLockedTx(tx->GetHash(), false);
452 10 : if (m_signer) {
453 16 : m_signer->ClearInputsFromQueue(GetIdsFromLockable(tx->vin));
454 8 : }
455 : }
456 854 : }
457 :
458 844 : void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock)
459 : {
460 844 : Uint256HashMap<CTransactionRef> toDelete;
461 :
462 : {
463 844 : LOCK(m_mempool.cs);
464 :
465 3156 : for (const auto& in : islock.inputs) {
466 2312 : auto it = m_mempool.mapNextTx.find(in);
467 2312 : if (it == m_mempool.mapNextTx.end()) {
468 960 : continue;
469 : }
470 1352 : if (it->second->GetHash() != islock.txid) {
471 0 : toDelete.emplace(it->second->GetHash(), m_mempool.get(it->second->GetHash()));
472 :
473 0 : LogPrintf("%s -- txid=%s, mempool TX %s with input %s conflicts with islock=%s\n", __func__,
474 : islock.txid.ToString(), it->second->GetHash().ToString(), in.ToStringShort(), hash.ToString());
475 0 : }
476 : }
477 :
478 844 : for (const auto& p : toDelete) {
479 0 : m_mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT);
480 : }
481 844 : }
482 844 : ClearConflicting(toDelete);
483 844 : }
484 :
485 220712 : void NetInstantSend::SynchronousUpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork,
486 : bool fInitialDownload)
487 : {
488 220712 : m_is_manager.CacheTipHeight(pindexNew);
489 220712 : }
490 :
491 220685 : void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
492 : {
493 220685 : bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height;
494 :
495 220685 : if (m_chainlocks.IsEnabled() && fDIP0008Active) {
496 : // Nothing to do here. We should keep all islocks and let chainlocks handle them.
497 71254 : return;
498 : }
499 :
500 149431 : int nConfirmedHeight = pindexNew->nHeight - Params().GetConsensus().nInstantSendKeepLock;
501 149431 : const CBlockIndex* pindex = pindexNew->GetAncestor(nConfirmedHeight);
502 :
503 149431 : if (pindex) {
504 144995 : HandleFullyConfirmedBlock(pindex);
505 144995 : }
506 220685 : }
507 :
508 317 : void NetInstantSend::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason,
509 : uint64_t mempool_sequence)
510 : {
511 317 : m_is_manager.TransactionIsRemoved(tx);
512 317 : }
513 :
514 227998 : void NetInstantSend::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
515 : {
516 227998 : if (!m_is_manager.IsInstantSendEnabled()) {
517 161139 : return;
518 : }
519 :
520 66859 : m_is_manager.CacheTipHeight(pindex);
521 :
522 66859 : if (m_mn_sync.IsBlockchainSynced()) {
523 65834 : const bool has_chainlock = m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash());
524 211318 : for (const auto& tx : pblock->vtx) {
525 145484 : if (tx->IsCoinBase() || tx->vin.empty()) {
526 : // coinbase and TXs with no inputs can't be locked
527 143572 : continue;
528 : }
529 :
530 1912 : if (!m_is_manager.IsLocked(tx->GetHash()) && !has_chainlock) {
531 1456 : if (m_signer) {
532 1242 : m_signer->ProcessTx(*tx, true, Params().GetConsensus());
533 1242 : }
534 : // TX is not locked, so make sure it is tracked
535 1456 : m_is_manager.AddNonLockedTx(tx, pindex);
536 1456 : } else {
537 : // TX is locked, so make sure we don't track it anymore
538 456 : m_is_manager.RemoveNonLockedTx(tx->GetHash(), true);
539 : }
540 : }
541 65834 : }
542 66859 : m_is_manager.WriteBlockISLocks(pblock, pindex);
543 227998 : }
544 :
545 14229 : void NetInstantSend::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected)
546 : {
547 14229 : m_is_manager.CacheDisconnectBlock(pindexDisconnected);
548 14229 : m_is_manager.CacheTipHeight(pindexDisconnected->pprev);
549 14229 : m_is_manager.RemoveBlockISLocks(pblock, pindexDisconnected);
550 14229 : }
551 :
552 13548 : void NetInstantSend::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr<const chainlock::ChainLockSig>& clsig)
553 : {
554 13548 : HandleFullyConfirmedBlock(pindex);
555 13548 : }
556 :
557 851 : void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock)
558 : {
559 851 : auto conflicts = m_is_manager.RetrieveISConflicts(islockHash, islock);
560 :
561 : // Lets see if any of the conflicts was already mined into a ChainLocked block
562 851 : bool hasChainLockedConflict = false;
563 861 : for (const auto& p : conflicts) {
564 10 : const auto* pindex = p.first;
565 10 : if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) {
566 0 : hasChainLockedConflict = true;
567 0 : break;
568 : }
569 : }
570 :
571 : // If a conflict was mined into a ChainLocked block, then we have no other choice and must prune the ISLOCK and all
572 : // chained ISLOCKs that build on top of this one. The probability of this is practically zero and can only happen
573 : // when large parts of the masternode network are controlled by an attacker. In this case we must still find
574 : // consensus and its better to sacrifice individual ISLOCKs then to sacrifice whole ChainLocks.
575 851 : if (hasChainLockedConflict) {
576 0 : LogPrintf("NetInstantSend::%s -- txid=%s, islock=%s: at least one conflicted TX already got a ChainLock\n",
577 : __func__, islock.txid.ToString(), islockHash.ToString());
578 0 : m_is_manager.RemoveConflictingLock(islockHash, islock);
579 0 : return;
580 : }
581 :
582 851 : bool hasTxForLock = m_is_manager.HasTxForLock(islockHash);
583 :
584 851 : bool activateBestChain = false;
585 861 : for (const auto& p : conflicts) {
586 10 : const auto* pindex = p.first;
587 10 : ClearConflicting(p.second);
588 :
589 10 : LogPrintf("NetInstantSend::%s -- invalidating block %s\n", __func__, pindex->GetBlockHash().ToString());
590 :
591 10 : BlockValidationState state;
592 : // need non-const pointer
593 20 : auto pindex2 = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(pindex->GetBlockHash()));
594 10 : if (!m_chainstate.InvalidateBlock(state, pindex2)) {
595 0 : LogPrintf("NetInstantSend::%s -- InvalidateBlock failed: %s\n", __func__, state.ToString());
596 : // This should not have happened and we are in a state were it's not safe to continue anymore
597 0 : assert(false);
598 : }
599 10 : if (hasTxForLock) {
600 0 : activateBestChain = true;
601 0 : } else {
602 10 : LogPrintf("NetInstantSend::%s -- resetting block %s\n", __func__, pindex2->GetBlockHash().ToString());
603 10 : LOCK(::cs_main);
604 10 : m_chainstate.ResetBlockFailureFlags(pindex2);
605 10 : }
606 10 : }
607 :
608 851 : if (activateBestChain) {
609 0 : BlockValidationState state;
610 0 : if (!m_chainstate.ActivateBestChain(state)) {
611 0 : LogPrintf("NetInstantSend::%s -- ActivateBestChain failed: %s\n", __func__, state.ToString());
612 : // This should not have happened and we are in a state were it's not safe to continue anymore
613 0 : assert(false);
614 : }
615 0 : }
616 851 : }
617 :
618 1459 : void NetInstantSend::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock)
619 : {
620 1459 : auto ids = GetIdsFromLockable(islock.inputs);
621 1459 : if (m_signer) {
622 1014 : m_signer->ClearInputsFromQueue(ids);
623 1014 : }
624 5874 : for (const auto& id : ids) {
625 4415 : m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id);
626 : }
627 1459 : }
628 :
629 158543 : void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex)
630 : {
631 158543 : if (!m_is_manager.IsInstantSendEnabled()) {
632 142962 : return;
633 : }
634 :
635 15581 : auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex);
636 17405 : for (const auto& [islockHash, islock] : removeISLocks) {
637 608 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n",
638 : __func__, islock->txid.ToString(), islockHash.ToString());
639 :
640 : // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered
641 : // fully confirmed now
642 608 : m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, islock->GetRequestId());
643 :
644 : // No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts
645 : // from now on. All inputs are spent now and can't be spend in any other TX.
646 608 : TruncateRecoveredSigsForInputs(*islock);
647 : }
648 158543 : }
|