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 0 : static std::optional<int> GetBlockHeight(llmq::CInstantSendManager& is_manager, const CChainState& chainstate,
41 : const uint256& hash)
42 : {
43 0 : if (hash.IsNull()) {
44 0 : return std::nullopt;
45 : }
46 0 : auto ret = is_manager.GetCachedHeight(hash);
47 0 : if (ret) return ret;
48 :
49 0 : const CBlockIndex* pindex = WITH_LOCK(::cs_main, return chainstate.m_blockman.LookupBlockIndex(hash));
50 0 : if (pindex == nullptr) {
51 0 : return std::nullopt;
52 : }
53 0 : is_manager.CacheBlockHeight(pindex);
54 0 : return pindex->nHeight;
55 0 : }
56 :
57 0 : struct NetInstantSend::BatchVerificationData {
58 0 : CBLSBatchVerifier<NodeId, uint256> batchVerifier{false, true, BATCH_VERIFIER_SOURCE_THRESHOLD};
59 : Uint256HashMap<llmq::CRecoveredSig> recSigs;
60 0 : size_t verifyCount{0};
61 0 : size_t alreadyVerified{0};
62 : };
63 :
64 0 : bool NetInstantSend::ValidateIncomingISLock(const instantsend::InstantSendLock& islock, NodeId node_id)
65 : {
66 0 : if (!islock.TriviallyValid()) {
67 0 : m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
68 0 : return false;
69 : }
70 :
71 0 : return true;
72 0 : }
73 :
74 0 : std::optional<int> NetInstantSend::ResolveCycleHeight(const uint256& cycle_hash)
75 : {
76 0 : auto cycle_height = GetBlockHeight(m_is_manager, m_chainstate, cycle_hash);
77 0 : if (cycle_height) {
78 0 : 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 0 : }
89 :
90 0 : 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 0 : if (cycle_height % llmq_params.dkgInterval == 0) {
97 0 : return true;
98 : }
99 :
100 0 : m_peer_manager->PeerMisbehaving(node_id, INVALID_ISLOCK_MISBEHAVIOR_SCORE);
101 0 : return false;
102 0 : }
103 :
104 0 : 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 0 : auto data = std::make_unique<BatchVerificationData>();
110 :
111 0 : for (const auto& pending : pend) {
112 0 : const auto& hash = pending.islock_hash;
113 0 : auto nodeId = pending.node_id;
114 0 : const auto& islock = pending.islock;
115 :
116 0 : if (data->batchVerifier.badSources.count(nodeId)) {
117 0 : continue;
118 : }
119 :
120 0 : CBLSSignature sig = islock->sig.Get();
121 0 : if (!sig.IsValid()) {
122 0 : data->batchVerifier.badSources.emplace(nodeId);
123 0 : continue;
124 : }
125 :
126 0 : 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 0 : if (m_sigman.HasRecoveredSig(llmq_params.type, id, islock->txid)) {
130 0 : data->alreadyVerified++;
131 0 : continue;
132 : }
133 :
134 0 : auto cycleHeightOpt = GetBlockHeight(m_is_manager, m_chainstate, islock->cycleHash);
135 0 : if (!cycleHeightOpt) {
136 0 : data->batchVerifier.badSources.emplace(nodeId);
137 0 : continue;
138 : }
139 :
140 0 : int nSignHeight{-1};
141 0 : const auto dkgInterval = llmq_params.dkgInterval;
142 0 : const int tipHeight = m_is_manager.GetTipHeight();
143 0 : const int cycleHeight = *cycleHeightOpt;
144 0 : if (cycleHeight + dkgInterval < tipHeight) {
145 0 : nSignHeight = cycleHeight + dkgInterval - 1;
146 0 : }
147 : // For RegTest non-rotating quorum cycleHash has directly quorum hash
148 0 : auto quorum = llmq_params.useRotation ? llmq::SelectQuorumForSigning(llmq_params, m_chainstate.m_chain, m_qman,
149 0 : id, nSignHeight, signOffset)
150 0 : : m_qman.GetQuorum(llmq_params.type, islock->cycleHash);
151 :
152 0 : 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 0 : uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get();
157 0 : data->batchVerifier.PushMessage(nodeId, hash, signHash, sig, quorum->qc->quorumPublicKey);
158 0 : 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 0 : if (!m_sigman.HasRecoveredSigForId(llmq_params.type, id)) {
164 0 : data->recSigs.try_emplace(hash, llmq::CRecoveredSig(llmq_params.type, quorum->qc->quorumHash, id, islock->txid,
165 0 : islock->sig));
166 0 : }
167 0 : }
168 :
169 0 : return data;
170 0 : }
171 :
172 0 : Uint256HashSet NetInstantSend::ApplyVerificationResults(
173 : const Consensus::LLMQParams& llmq_params,
174 : bool ban,
175 : BatchVerificationData& data,
176 : const std::vector<instantsend::PendingISLockEntry>& pend)
177 : {
178 0 : Uint256HashSet badISLocks;
179 0 : std::set<NodeId> penalized;
180 :
181 0 : for (const auto& pending : pend) {
182 0 : const auto& hash = pending.islock_hash;
183 0 : auto nodeId = pending.node_id;
184 0 : const auto& islock = pending.islock;
185 :
186 0 : const bool source_bad = data.batchVerifier.badSources.count(nodeId);
187 0 : const bool message_bad = data.batchVerifier.badMessages.count(hash);
188 :
189 0 : 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 0 : ProcessInstantSendLock(nodeId, hash, islock);
204 :
205 : // Pass a reconstructed recovered sig to the signing manager to avoid double-verification of the sig.
206 0 : auto it = data.recSigs.find(hash);
207 0 : if (it != data.recSigs.end()) {
208 0 : auto recSig = std::make_shared<llmq::CRecoveredSig>(std::move(it->second));
209 0 : if (!m_sigman.HasRecoveredSigForId(llmq_params.type, recSig->getId())) {
210 0 : 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 0 : m_sigman.PushReconstructedRecoveredSig(recSig);
215 0 : }
216 0 : }
217 : }
218 :
219 0 : return badISLocks;
220 0 : }
221 :
222 : namespace {
223 : template <typename T>
224 : requires std::same_as<T, CTxIn> || std::same_as<T, COutPoint>
225 0 : Uint256HashSet GetIdsFromLockable(const std::vector<T>& vec)
226 : {
227 0 : Uint256HashSet ret{};
228 0 : if (vec.empty()) return ret;
229 0 : ret.reserve(vec.size());
230 0 : for (const auto& in : vec) {
231 : if constexpr (std::is_same_v<T, COutPoint>) {
232 0 : ret.emplace(instantsend::GenInputLockRequestId(in));
233 : } else if constexpr (std::is_same_v<T, CTxIn>) {
234 0 : ret.emplace(instantsend::GenInputLockRequestId(in.prevout));
235 : } else {
236 : assert(false);
237 : }
238 : }
239 0 : return ret;
240 0 : }
241 : } // anonymous namespace
242 :
243 0 : void NetInstantSend::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv)
244 : {
245 0 : if (msg_type != NetMsgType::ISDLOCK) {
246 0 : return;
247 : }
248 :
249 0 : if (!m_is_manager.IsInstantSendEnabled()) return;
250 :
251 0 : auto islock = std::make_shared<instantsend::InstantSendLock>();
252 0 : vRecv >> *islock;
253 :
254 0 : const NodeId from = pfrom.GetId();
255 0 : uint256 hash = ::SerializeHash(*islock);
256 :
257 0 : WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(from, CInv{MSG_ISDLOCK, hash}));
258 :
259 0 : if (!ValidateIncomingISLock(*islock, from)) {
260 0 : return;
261 : }
262 :
263 0 : auto cycle_height = ResolveCycleHeight(islock->cycleHash);
264 0 : 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 0 : auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
271 0 : const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
272 0 : assert(llmq_params_opt);
273 0 : if (!ValidateDeterministicCycleHeight(*cycle_height, *llmq_params_opt, from)) {
274 0 : return;
275 : }
276 :
277 0 : if (!m_is_manager.AlreadyHave(CInv{MSG_ISDLOCK, hash})) {
278 0 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend -- ISDLOCK txid=%s, islock=%s: received islock, peer=%d\n",
279 : islock->txid.ToString(), hash.ToString(), from);
280 :
281 0 : m_is_manager.EnqueueInstantSendLock(from, hash, std::move(islock));
282 0 : }
283 0 : }
284 :
285 0 : void NetInstantSend::Start()
286 : {
287 : // can't start new thread if we have one running already
288 0 : if (workThread.joinable()) {
289 0 : assert(false);
290 : }
291 :
292 0 : workThread = std::thread(&util::TraceThread, "isman", [this] { WorkThreadMain(); });
293 0 : }
294 :
295 0 : void NetInstantSend::Stop()
296 : {
297 : // make sure to call Interrupt() first
298 0 : if (!workInterrupt) {
299 0 : assert(false);
300 : }
301 :
302 0 : if (workThread.joinable()) {
303 0 : workThread.join();
304 0 : }
305 0 : }
306 :
307 0 : Uint256HashSet NetInstantSend::ProcessPendingInstantSendLocks(
308 : const Consensus::LLMQParams& llmq_params, int signOffset, bool ban,
309 : const std::vector<instantsend::PendingISLockEntry>& pend)
310 : {
311 0 : auto batch = BuildVerificationBatch(llmq_params, signOffset, pend);
312 0 : if (!batch) return {};
313 :
314 0 : cxxtimer::Timer verifyTimer(true);
315 0 : batch->batchVerifier.Verify();
316 0 : verifyTimer.stop();
317 :
318 0 : 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 0 : return ApplyVerificationResults(llmq_params, ban, *batch, pend);
323 0 : }
324 :
325 0 : void NetInstantSend::ProcessPendingISLocks(std::vector<instantsend::PendingISLockEntry>&& locks_to_process)
326 : {
327 : // TODO Investigate if leaving this is ok
328 0 : auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
329 0 : const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
330 0 : assert(llmq_params_opt);
331 0 : const auto& llmq_params = llmq_params_opt.value();
332 0 : auto dkgInterval = llmq_params.dkgInterval;
333 :
334 : // First check against the current active set and don't ban
335 0 : auto bad_is_locks = ProcessPendingInstantSendLocks(llmq_params, /*signOffset=*/0, /*ban=*/false, locks_to_process);
336 0 : 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 0 : uiInterface.NotifyInstantSendChanged();
351 0 : }
352 :
353 0 : void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock)
354 : {
355 0 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__,
356 : islock->txid.ToString(), hash.ToString(), from);
357 :
358 0 : if (m_signer) {
359 0 : m_signer->ClearLockFromQueue(islock);
360 0 : }
361 0 : if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return;
362 :
363 0 : uint256 hashBlock{};
364 0 : auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock);
365 0 : 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 0 : const auto minedHeight = GetBlockHeight(m_is_manager, m_chainstate, hashBlock);
368 0 : 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 0 : if (minedHeight.has_value() && m_chainlocks.HasChainLock(*minedHeight, hashBlock)) {
372 0 : 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 0 : return;
377 : }
378 0 : m_is_manager.WriteNewISLock(hash, islock, minedHeight);
379 0 : } else {
380 0 : m_is_manager.AddPendingISLock(hash, islock, from);
381 : }
382 :
383 : // This will also add children TXs to pendingRetryTxs
384 0 : 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 0 : TruncateRecoveredSigsForInputs(*islock);
388 0 : ResolveBlockConflicts(hash, *islock);
389 :
390 0 : if (found_transaction) {
391 0 : RemoveMempoolConflictsForLock(hash, *islock);
392 0 : LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- notify about lock %s for tx %s\n", __func__,
393 : hash.ToString(), tx->GetHash().ToString());
394 0 : GetMainSignals().NotifyTransactionLock(tx, islock);
395 : // bump m_mempool counter to make sure newly locked txes are picked up by getblocktemplate
396 0 : m_mempool.AddTransactionsUpdated(1);
397 0 : }
398 :
399 0 : CInv inv(MSG_ISDLOCK, hash);
400 0 : if (found_transaction) {
401 0 : m_peer_manager->PeerRelayInvFiltered(inv, *tx);
402 0 : } else {
403 0 : m_peer_manager->PeerRelayInvFiltered(inv, islock->txid);
404 0 : m_peer_manager->PeerAskPeersForTransaction(islock->txid);
405 : }
406 0 : }
407 :
408 0 : void NetInstantSend::WorkThreadMain()
409 : {
410 0 : while (!workInterrupt) {
411 0 : bool fMoreWork = [&]() -> bool {
412 0 : if (!m_is_manager.IsInstantSendEnabled()) return false;
413 :
414 0 : auto [more_work, locks] = m_is_manager.FetchPendingLocks();
415 0 : if (!locks.empty()) {
416 0 : ProcessPendingISLocks(std::move(locks));
417 0 : }
418 0 : if (m_signer) {
419 0 : m_signer->ProcessPendingRetryLockTxs(m_is_manager.PrepareTxToRetry());
420 0 : }
421 0 : return more_work;
422 0 : }();
423 :
424 0 : if (!fMoreWork && !workInterrupt.sleep_for(WORK_THREAD_SLEEP_INTERVAL)) {
425 0 : return;
426 : }
427 : }
428 0 : }
429 :
430 0 : void NetInstantSend::TransactionAddedToMempool(const CTransactionRef& tx, int64_t, uint64_t mempool_sequence)
431 : {
432 0 : if (!m_is_manager.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced() || tx->vin.empty()) {
433 0 : return;
434 : }
435 :
436 0 : instantsend::InstantSendLockPtr islock = m_is_manager.AttachISLockToTx(tx);
437 0 : if (islock == nullptr) {
438 0 : if (m_signer) {
439 0 : m_signer->ProcessTx(*tx, false, Params().GetConsensus());
440 0 : }
441 : // TX is not locked, so make sure it is tracked
442 0 : m_is_manager.AddNonLockedTx(tx, nullptr);
443 0 : } else {
444 0 : RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock);
445 : }
446 0 : }
447 :
448 0 : void NetInstantSend::ClearConflicting(const Uint256HashMap<CTransactionRef>& to_delete)
449 : {
450 0 : for (const auto& [_, tx] : to_delete) {
451 0 : m_is_manager.RemoveNonLockedTx(tx->GetHash(), false);
452 0 : if (m_signer) {
453 0 : m_signer->ClearInputsFromQueue(GetIdsFromLockable(tx->vin));
454 0 : }
455 : }
456 0 : }
457 :
458 0 : void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock)
459 : {
460 0 : Uint256HashMap<CTransactionRef> toDelete;
461 :
462 : {
463 0 : LOCK(m_mempool.cs);
464 :
465 0 : for (const auto& in : islock.inputs) {
466 0 : auto it = m_mempool.mapNextTx.find(in);
467 0 : if (it == m_mempool.mapNextTx.end()) {
468 0 : continue;
469 : }
470 0 : 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 0 : for (const auto& p : toDelete) {
479 0 : m_mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT);
480 : }
481 0 : }
482 0 : ClearConflicting(toDelete);
483 0 : }
484 :
485 0 : void NetInstantSend::SynchronousUpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork,
486 : bool fInitialDownload)
487 : {
488 0 : m_is_manager.CacheTipHeight(pindexNew);
489 0 : }
490 :
491 0 : void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
492 : {
493 0 : bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height;
494 :
495 0 : if (m_chainlocks.IsEnabled() && fDIP0008Active) {
496 : // Nothing to do here. We should keep all islocks and let chainlocks handle them.
497 0 : return;
498 : }
499 :
500 0 : int nConfirmedHeight = pindexNew->nHeight - Params().GetConsensus().nInstantSendKeepLock;
501 0 : const CBlockIndex* pindex = pindexNew->GetAncestor(nConfirmedHeight);
502 :
503 0 : if (pindex) {
504 0 : HandleFullyConfirmedBlock(pindex);
505 0 : }
506 0 : }
507 :
508 0 : void NetInstantSend::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason,
509 : uint64_t mempool_sequence)
510 : {
511 0 : m_is_manager.TransactionIsRemoved(tx);
512 0 : }
513 :
514 0 : void NetInstantSend::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
515 : {
516 0 : if (!m_is_manager.IsInstantSendEnabled()) {
517 0 : return;
518 : }
519 :
520 0 : m_is_manager.CacheTipHeight(pindex);
521 :
522 0 : if (m_mn_sync.IsBlockchainSynced()) {
523 0 : const bool has_chainlock = m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash());
524 0 : for (const auto& tx : pblock->vtx) {
525 0 : if (tx->IsCoinBase() || tx->vin.empty()) {
526 : // coinbase and TXs with no inputs can't be locked
527 0 : continue;
528 : }
529 :
530 0 : if (!m_is_manager.IsLocked(tx->GetHash()) && !has_chainlock) {
531 0 : if (m_signer) {
532 0 : m_signer->ProcessTx(*tx, true, Params().GetConsensus());
533 0 : }
534 : // TX is not locked, so make sure it is tracked
535 0 : m_is_manager.AddNonLockedTx(tx, pindex);
536 0 : } else {
537 : // TX is locked, so make sure we don't track it anymore
538 0 : m_is_manager.RemoveNonLockedTx(tx->GetHash(), true);
539 : }
540 : }
541 0 : }
542 0 : m_is_manager.WriteBlockISLocks(pblock, pindex);
543 0 : }
544 :
545 0 : void NetInstantSend::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected)
546 : {
547 0 : m_is_manager.CacheDisconnectBlock(pindexDisconnected);
548 0 : m_is_manager.CacheTipHeight(pindexDisconnected->pprev);
549 0 : m_is_manager.RemoveBlockISLocks(pblock, pindexDisconnected);
550 0 : }
551 :
552 0 : void NetInstantSend::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr<const chainlock::ChainLockSig>& clsig)
553 : {
554 0 : HandleFullyConfirmedBlock(pindex);
555 0 : }
556 :
557 0 : void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock)
558 : {
559 0 : 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 0 : bool hasChainLockedConflict = false;
563 0 : for (const auto& p : conflicts) {
564 0 : const auto* pindex = p.first;
565 0 : 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 0 : 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 0 : bool hasTxForLock = m_is_manager.HasTxForLock(islockHash);
583 :
584 0 : bool activateBestChain = false;
585 0 : for (const auto& p : conflicts) {
586 0 : const auto* pindex = p.first;
587 0 : ClearConflicting(p.second);
588 :
589 0 : LogPrintf("NetInstantSend::%s -- invalidating block %s\n", __func__, pindex->GetBlockHash().ToString());
590 :
591 0 : BlockValidationState state;
592 : // need non-const pointer
593 0 : auto pindex2 = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(pindex->GetBlockHash()));
594 0 : 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 0 : if (hasTxForLock) {
600 0 : activateBestChain = true;
601 0 : } else {
602 0 : LogPrintf("NetInstantSend::%s -- resetting block %s\n", __func__, pindex2->GetBlockHash().ToString());
603 0 : LOCK(::cs_main);
604 0 : m_chainstate.ResetBlockFailureFlags(pindex2);
605 0 : }
606 0 : }
607 :
608 0 : 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 0 : }
617 :
618 0 : void NetInstantSend::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock)
619 : {
620 0 : auto ids = GetIdsFromLockable(islock.inputs);
621 0 : if (m_signer) {
622 0 : m_signer->ClearInputsFromQueue(ids);
623 0 : }
624 0 : for (const auto& id : ids) {
625 0 : m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id);
626 : }
627 0 : }
628 :
629 0 : void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex)
630 : {
631 0 : if (!m_is_manager.IsInstantSendEnabled()) {
632 0 : return;
633 : }
634 :
635 0 : auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex);
636 0 : for (const auto& [islockHash, islock] : removeISLocks) {
637 0 : 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 0 : 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 0 : TruncateRecoveredSigsForInputs(*islock);
647 : }
648 0 : }
|