Line data Source code
1 : // Copyright (c) 2019-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/instantsend.h>
6 :
7 : #include <chainparams.h>
8 : #include <consensus/validation.h>
9 : #include <node/blockstorage.h>
10 : #include <spork.h>
11 : #include <stats/client.h>
12 :
13 : using node::fImporting;
14 : using node::fReindex;
15 :
16 : namespace llmq {
17 9189 : CInstantSendManager::CInstantSendManager(CSporkManager& sporkman, const util::DbWrapperParams& db_params) :
18 3063 : db{db_params},
19 3063 : spork_manager{sporkman}
20 3063 : {
21 3063 : }
22 :
23 6126 : CInstantSendManager::~CInstantSendManager() = default;
24 :
25 3141 : bool ShouldReportISLockTiming() { return g_stats_client->active() || LogAcceptDebug(BCLog::INSTANTSEND); }
26 :
27 300 : void CInstantSendManager::EnqueueInstantSendLock(NodeId from, const uint256& hash,
28 : std::shared_ptr<instantsend::InstantSendLock> islock)
29 : {
30 300 : if (ShouldReportISLockTiming()) {
31 600 : auto time_diff = [&]() -> int64_t {
32 300 : LOCK(cs_timingsTxSeen);
33 300 : if (auto it = timingsTxSeen.find(islock->txid); it != timingsTxSeen.end()) {
34 : // This is the normal case where we received the TX before the islock
35 267 : auto diff = Ticks<std::chrono::milliseconds>(SteadyClock::now() - it->second);
36 267 : timingsTxSeen.erase(it);
37 267 : return diff;
38 : }
39 : // But if we received the islock and don't know when we got the tx, then say 0, to indicate we received the islock first.
40 33 : return 0;
41 300 : }();
42 300 : ::g_stats_client->timing("islock_ms", time_diff);
43 300 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock took %dms\n", __func__,
44 : islock->txid.ToString(), time_diff);
45 300 : }
46 :
47 300 : LOCK(cs_pendingLocks);
48 300 : pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{from, std::move(islock)});
49 300 : }
50 :
51 189053 : instantsend::PendingState CInstantSendManager::FetchPendingLocks()
52 : {
53 189053 : instantsend::PendingState ret;
54 :
55 189053 : LOCK(cs_pendingLocks);
56 : // only process a max 32 locks at a time to avoid duplicate verification of recovered signatures which have been
57 : // verified by CSigningManager in parallel
58 189053 : const size_t maxCount = 32;
59 : // The keys of the removed values are temporaily stored here to avoid invalidating an iterator
60 189053 : std::vector<uint256> removed;
61 189053 : removed.reserve(std::min(maxCount, pendingInstantSendLocks.size()));
62 :
63 189918 : for (auto& [islockHash, pending] : pendingInstantSendLocks) {
64 : // Check if we've reached max count
65 865 : if (ret.m_pending_is.size() >= maxCount) {
66 0 : ret.m_pending_work = true;
67 0 : break;
68 : }
69 2595 : ret.m_pending_is.push_back(instantsend::PendingISLockEntry{std::move(pending), islockHash});
70 865 : removed.emplace_back(islockHash);
71 : }
72 :
73 189918 : for (const auto& islockHash : removed) {
74 865 : pendingInstantSendLocks.erase(islockHash);
75 : }
76 :
77 189053 : return ret;
78 189053 : }
79 :
80 865 : bool CInstantSendManager::PreVerifyIsLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) const
81 : {
82 865 : if (db.KnownInstantSendLock(hash)) {
83 0 : return false;
84 : }
85 :
86 865 : if (const auto sameTxIsLock = db.GetInstantSendLockByTxid(islock->txid)) {
87 : // can happen, nothing to do
88 0 : return false;
89 : }
90 3250 : for (const auto& in : islock->inputs) {
91 2385 : const auto sameOutpointIsLock = db.GetInstantSendLockByInput(in);
92 2385 : if (sameOutpointIsLock != nullptr) {
93 0 : LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: conflicting outpoint in islock. input=%s, other islock=%s, peer=%d\n", __func__,
94 : islock->txid.ToString(), hash.ToString(), in.ToStringShort(), ::SerializeHash(*sameOutpointIsLock).ToString(), from);
95 0 : }
96 2385 : }
97 865 : return true;
98 865 : }
99 :
100 820 : void CInstantSendManager::WriteNewISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock,
101 : std::optional<int> minedHeight)
102 : {
103 820 : db.WriteNewInstantSendLock(hash, islock);
104 820 : if (minedHeight.has_value()) {
105 306 : db.WriteInstantSendLockMined(hash, *minedHeight);
106 306 : }
107 820 : }
108 :
109 31 : void CInstantSendManager::AddPendingISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from)
110 : {
111 : // put it in a separate pending map and try again later
112 31 : LOCK(cs_pendingLocks);
113 31 : pendingNoTxInstantSendLocks.try_emplace(hash, instantsend::PendingISLockFromPeer{from, islock});
114 31 : }
115 :
116 2311 : void CInstantSendManager::TransactionIsRemoved(const CTransactionRef& tx)
117 : {
118 2311 : if (tx->vin.empty()) {
119 52 : return;
120 : }
121 :
122 2259 : instantsend::InstantSendLockPtr islock = GetInstantSendLockByTxid(tx->GetHash());
123 :
124 2259 : if (islock == nullptr) {
125 2259 : return;
126 : }
127 :
128 0 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- transaction %s was removed from mempool\n", __func__,
129 : tx->GetHash().ToString());
130 0 : RemoveConflictingLock(::SerializeHash(*islock), *islock);
131 2311 : }
132 :
133 66859 : void CInstantSendManager::WriteBlockISLocks(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
134 : {
135 66859 : db.WriteBlockInstantSendLocks(pblock, pindex);
136 66859 : }
137 :
138 14229 : void CInstantSendManager::RemoveBlockISLocks(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
139 : {
140 14229 : db.RemoveBlockInstantSendLocks(pblock, pindex);
141 14229 : }
142 :
143 4250 : instantsend::InstantSendLockPtr CInstantSendManager::AttachISLockToTx(const CTransactionRef& tx)
144 : {
145 4250 : LOCK(cs_pendingLocks);
146 4333 : for (auto it = pendingNoTxInstantSendLocks.begin(); it != pendingNoTxInstantSendLocks.end(); ++it) {
147 114 : if (it->second.islock->txid != tx->GetHash()) continue;
148 :
149 : // we received an islock earlier, let's put it back into pending and verify/lock
150 31 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__,
151 : tx->GetHash().ToString(), it->first.ToString());
152 31 : auto islock = it->second.islock;
153 31 : pendingInstantSendLocks.try_emplace(it->first, it->second);
154 31 : pendingNoTxInstantSendLocks.erase(it);
155 31 : return islock;
156 31 : }
157 4219 : return nullptr;
158 4250 : }
159 :
160 2841 : void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined)
161 : {
162 : {
163 2841 : LOCK(cs_nonLocked);
164 2841 : auto [it, did_insert] = nonLockedTxs.emplace(tx->GetHash(), NonLockedTxInfo());
165 2841 : auto& nonLockedTxInfo = it->second;
166 2841 : nonLockedTxInfo.pindexMined = pindexMined;
167 :
168 2841 : if (did_insert) {
169 2129 : nonLockedTxInfo.tx = tx;
170 8208 : for (const auto& in : tx->vin) {
171 6079 : nonLockedTxs[in.prevout.hash].children.emplace(tx->GetHash());
172 6079 : nonLockedTxsByOutpoints.emplace(in.prevout, tx->GetHash());
173 : }
174 2129 : }
175 2841 : }
176 2841 : AttachISLockToTx(tx);
177 2841 : if (ShouldReportISLockTiming()) {
178 2841 : LOCK(cs_timingsTxSeen);
179 : // Only insert the time the first time we see the tx, as we sometimes try to resign
180 2841 : timingsTxSeen.try_emplace(tx->GetHash(), SteadyClock::now());
181 2841 : }
182 :
183 2841 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, pindexMined=%s\n", __func__,
184 : tx->GetHash().ToString(), pindexMined ? pindexMined->GetBlockHash().ToString() : "");
185 2841 : }
186 :
187 2423 : void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChildren)
188 : {
189 2423 : LOCK(cs_nonLocked);
190 :
191 2423 : auto it = nonLockedTxs.find(txid);
192 2423 : if (it == nonLockedTxs.end()) {
193 421 : return;
194 : }
195 2002 : const auto& info = it->second;
196 :
197 2002 : size_t retryChildrenCount = 0;
198 2002 : if (retryChildren) {
199 : // TX got locked, so we can retry locking children
200 1992 : LOCK(cs_pendingRetry);
201 2363 : for (const auto& childTxid : info.children) {
202 371 : pendingRetryTxs.emplace(childTxid);
203 371 : retryChildrenCount++;
204 : }
205 1992 : }
206 : // don't try to lock it anymore
207 4004 : WITH_LOCK(cs_pendingRetry, pendingRetryTxs.erase(txid));
208 :
209 2002 : if (info.tx) {
210 7932 : for (const auto& in : info.tx->vin) {
211 5930 : if (auto jt = nonLockedTxs.find(in.prevout.hash); jt != nonLockedTxs.end()) {
212 5495 : jt->second.children.erase(txid);
213 5495 : if (!jt->second.tx && jt->second.children.empty()) {
214 4532 : nonLockedTxs.erase(jt);
215 4532 : }
216 5495 : }
217 5930 : nonLockedTxsByOutpoints.erase(in.prevout);
218 : }
219 2002 : }
220 :
221 2002 : nonLockedTxs.erase(it);
222 :
223 2002 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, retryChildren=%d, retryChildrenCount=%d\n",
224 : __func__, txid.ToString(), retryChildren, retryChildrenCount);
225 2423 : }
226 :
227 154880 : std::vector<CTransactionRef> CInstantSendManager::PrepareTxToRetry()
228 : {
229 154880 : std::vector<CTransactionRef> txns{};
230 :
231 154880 : LOCK2(cs_nonLocked, cs_pendingRetry);
232 154880 : if (pendingRetryTxs.empty()) return txns;
233 239 : txns.reserve(pendingRetryTxs.size());
234 478 : for (const auto& txid : pendingRetryTxs) {
235 239 : if (auto it = nonLockedTxs.find(txid); it != nonLockedTxs.end()) {
236 239 : const auto& [_, tx_info] = *it;
237 239 : if (tx_info.tx) {
238 239 : txns.push_back(tx_info.tx);
239 239 : }
240 239 : }
241 : }
242 239 : return txns;
243 154880 : }
244 :
245 538 : void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeId id,
246 : const instantsend::InstantSendLockPtr& islock)
247 : {
248 538 : if (db.KnownInstantSendLock(hash)) return;
249 538 : LOCK(cs_pendingLocks);
250 538 : if (!pendingInstantSendLocks.count(hash)) {
251 534 : pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{id, islock});
252 534 : }
253 538 : }
254 :
255 851 : std::unordered_map<const CBlockIndex*, Uint256HashMap<CTransactionRef>> CInstantSendManager::RetrieveISConflicts(
256 : const uint256& islockHash, const instantsend::InstantSendLock& islock)
257 : {
258 : // Lets first collect all non-locked TXs which conflict with the given ISLOCK
259 851 : std::unordered_map<const CBlockIndex*, Uint256HashMap<CTransactionRef>> conflicts;
260 : {
261 851 : LOCK(cs_nonLocked);
262 3208 : for (const auto& in : islock.inputs) {
263 2357 : auto it = nonLockedTxsByOutpoints.find(in);
264 2357 : if (it != nonLockedTxsByOutpoints.end()) {
265 26 : auto& conflictTxid = it->second;
266 26 : if (conflictTxid == islock.txid) {
267 0 : continue;
268 : }
269 26 : auto jt = nonLockedTxs.find(conflictTxid);
270 26 : if (jt == nonLockedTxs.end()) {
271 0 : continue;
272 : }
273 26 : auto& info = jt->second;
274 26 : if (!info.pindexMined || !info.tx) {
275 16 : continue;
276 : }
277 10 : LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: mined TX %s with input %s and mined in block %s conflicts with islock\n", __func__,
278 : islock.txid.ToString(), islockHash.ToString(), conflictTxid.ToString(), in.ToStringShort(), info.pindexMined->GetBlockHash().ToString());
279 10 : conflicts[info.pindexMined].emplace(conflictTxid, info.tx);
280 10 : }
281 : }
282 851 : }
283 :
284 851 : return conflicts;
285 851 : }
286 :
287 851 : bool CInstantSendManager::HasTxForLock(const uint256& islockHash) const
288 : {
289 851 : LOCK(cs_pendingLocks);
290 851 : return pendingNoTxInstantSendLocks.find(islockHash) == pendingNoTxInstantSendLocks.end();
291 851 : }
292 :
293 40 : void CInstantSendManager::RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock)
294 : {
295 40 : LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: Removing ISLOCK and its chained children\n", __func__,
296 : islock.txid.ToString(), islockHash.ToString());
297 40 : const int tipHeight = GetTipHeight();
298 :
299 40 : auto removedIslocks = db.RemoveChainedInstantSendLocks(islockHash, islock.txid, tipHeight);
300 110 : for (const auto& h : removedIslocks) {
301 70 : LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: removed (child) ISLOCK %s\n", __func__,
302 : islock.txid.ToString(), islockHash.ToString(), h.ToString());
303 : }
304 40 : }
305 :
306 1350 : bool CInstantSendManager::AlreadyHave(const CInv& inv) const
307 : {
308 1350 : if (!IsInstantSendEnabled()) {
309 0 : return true;
310 : }
311 :
312 3929 : return WITH_LOCK(cs_pendingLocks, return pendingInstantSendLocks.count(inv.hash) != 0 ||
313 1350 : pendingNoTxInstantSendLocks.count(inv.hash) != 0) ||
314 1229 : db.KnownInstantSendLock(inv.hash);
315 1350 : }
316 :
317 333 : bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, instantsend::InstantSendLock& ret) const
318 : {
319 333 : if (!IsInstantSendEnabled()) {
320 0 : return false;
321 : }
322 :
323 333 : auto islock = db.GetInstantSendLockByHash(hash);
324 333 : if (!islock) {
325 8 : LOCK(cs_pendingLocks);
326 8 : auto it = pendingInstantSendLocks.find(hash);
327 8 : if (it != pendingInstantSendLocks.end()) {
328 0 : islock = it->second.islock;
329 0 : } else {
330 8 : auto itNoTx = pendingNoTxInstantSendLocks.find(hash);
331 8 : if (itNoTx != pendingNoTxInstantSendLocks.end()) {
332 8 : islock = itNoTx->second.islock;
333 8 : } else {
334 0 : return false;
335 : }
336 : }
337 8 : }
338 333 : ret = *islock;
339 333 : return true;
340 333 : }
341 :
342 2277 : instantsend::InstantSendLockPtr CInstantSendManager::GetInstantSendLockByTxid(const uint256& txid) const
343 : {
344 2277 : if (!IsInstantSendEnabled()) {
345 2157 : return nullptr;
346 : }
347 :
348 120 : return db.GetInstantSendLockByTxid(txid);
349 2277 : }
350 :
351 2404202 : bool CInstantSendManager::IsLocked(const uint256& txHash) const
352 : {
353 2404202 : if (!IsInstantSendEnabled()) {
354 1722213 : return false;
355 : }
356 :
357 681989 : return db.KnownInstantSendLock(db.GetInstantSendLockHashByTxid(txHash));
358 2404202 : }
359 :
360 131790 : bool CInstantSendManager::IsWaitingForTx(const uint256& txHash) const
361 : {
362 131790 : if (!IsInstantSendEnabled()) {
363 124660 : return false;
364 : }
365 :
366 7130 : LOCK(cs_pendingLocks);
367 7130 : auto it = pendingNoTxInstantSendLocks.begin();
368 7173 : while (it != pendingNoTxInstantSendLocks.end()) {
369 142 : if (it->second.islock->txid == txHash) {
370 99 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, txHash.ToString(),
371 : it->first.ToString());
372 99 : return true;
373 : }
374 43 : ++it;
375 : }
376 7031 : return false;
377 131790 : }
378 :
379 502713 : instantsend::InstantSendLockPtr CInstantSendManager::GetConflictingLock(const CTransaction& tx) const
380 : {
381 502713 : if (!IsInstantSendEnabled()) {
382 418719 : return nullptr;
383 : }
384 :
385 178601 : for (const auto& in : tx.vin) {
386 94740 : auto otherIsLock = db.GetInstantSendLockByInput(in.prevout);
387 94740 : if (!otherIsLock) {
388 92982 : continue;
389 : }
390 :
391 1758 : if (otherIsLock->txid != tx.GetHash()) {
392 133 : return otherIsLock;
393 : }
394 94740 : }
395 83861 : return nullptr;
396 502713 : }
397 :
398 4238 : size_t CInstantSendManager::GetInstantSendLockCount() const
399 : {
400 4238 : return db.GetInstantSendLockCount();
401 : }
402 :
403 0 : CInstantSendManager::Counts CInstantSendManager::GetCounts() const
404 : {
405 0 : Counts ret;
406 0 : ret.m_verified = db.GetInstantSendLockCount();
407 : {
408 0 : LOCK(cs_pendingLocks);
409 0 : ret.m_unverified = pendingInstantSendLocks.size();
410 0 : ret.m_awaiting_tx = pendingNoTxInstantSendLocks.size();
411 0 : }
412 : {
413 0 : LOCK(cs_nonLocked);
414 0 : ret.m_unprotected_tx = nonLockedTxs.size();
415 0 : }
416 0 : return ret;
417 : }
418 :
419 1534 : void CInstantSendManager::CacheBlockHeight(const CBlockIndex* const block_index) const
420 : {
421 1534 : LOCK(cs_height_cache);
422 1534 : m_cached_block_heights.insert(block_index->GetBlockHash(), block_index->nHeight);
423 1534 : }
424 :
425 14229 : void CInstantSendManager::CacheDisconnectBlock(const CBlockIndex* pindexDisconnected)
426 : {
427 14229 : LOCK(cs_height_cache);
428 14229 : m_cached_block_heights.erase(pindexDisconnected->GetBlockHash());
429 14229 : }
430 :
431 6501 : std::optional<int> CInstantSendManager::GetCachedHeight(const uint256& hash) const
432 : {
433 6501 : LOCK(cs_height_cache);
434 :
435 6501 : int cached_height{0};
436 6501 : if (m_cached_block_heights.get(hash, cached_height)) return cached_height;
437 :
438 1534 : return std::nullopt;
439 6501 : }
440 :
441 304631 : void CInstantSendManager::CacheTipHeight(const CBlockIndex* const tip) const
442 : {
443 304631 : LOCK(cs_height_cache);
444 304631 : if (tip) {
445 304631 : m_cached_block_heights.insert(tip->GetBlockHash(), tip->nHeight);
446 304631 : m_cached_tip_height = tip->nHeight;
447 304631 : } else {
448 0 : m_cached_tip_height = -1;
449 : }
450 304631 : }
451 :
452 5921 : int CInstantSendManager::GetTipHeight() const
453 : {
454 : // It returns the cached tip height which is updated through notification mechanism
455 : // If cached tip is not set by any reason, it's okay to return 0 because
456 : // chainstate is not fully loaded yet and tip is not set
457 5921 : LOCK(cs_height_cache);
458 5921 : if (m_cached_tip_height >= 0) {
459 5921 : return m_cached_tip_height;
460 : }
461 0 : return 0;
462 5921 : }
463 :
464 4088840 : bool CInstantSendManager::IsInstantSendEnabled() const
465 : {
466 4088840 : return !fReindex && !fImporting && spork_manager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED);
467 : }
468 :
469 15581 : Uint256HashMap<instantsend::InstantSendLockPtr> CInstantSendManager::RemoveConfirmedInstantSendLocks(const CBlockIndex* pindex)
470 : {
471 15581 : int nUntilHeight = pindex->nHeight;
472 15581 : auto removeISLocks = db.RemoveConfirmedInstantSendLocks(nUntilHeight);
473 :
474 15581 : db.RemoveArchivedInstantSendLocks(nUntilHeight - 100);
475 :
476 : // Find all previously unlocked TXs that got locked by this fully confirmed (ChainLock) block and remove them
477 : // from the nonLockedTxs map. Also collect all children of these TXs and mark them for retrying of IS locking.
478 15581 : std::vector<uint256> toRemove;
479 : {
480 15581 : LOCK(cs_nonLocked);
481 21224 : for (const auto& p : nonLockedTxs) {
482 5643 : const auto* pindexMined = p.second.pindexMined;
483 :
484 5643 : if (pindexMined && pindex->GetAncestor(pindexMined->nHeight) == pindexMined) {
485 1106 : toRemove.emplace_back(p.first);
486 1106 : }
487 : }
488 15581 : }
489 16687 : for (const auto& txid : toRemove) {
490 : // This will also add children to pendingRetryTxs
491 1106 : RemoveNonLockedTx(txid, true);
492 : }
493 :
494 15581 : return removeISLocks;
495 15581 : }
496 : } // namespace llmq
|