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 540 : CInstantSendManager::CInstantSendManager(CSporkManager& sporkman, const util::DbWrapperParams& db_params) :
18 180 : db{db_params},
19 180 : spork_manager{sporkman}
20 180 : {
21 180 : }
22 :
23 360 : CInstantSendManager::~CInstantSendManager() = default;
24 :
25 0 : bool ShouldReportISLockTiming() { return g_stats_client->active() || LogAcceptDebug(BCLog::INSTANTSEND); }
26 :
27 0 : void CInstantSendManager::EnqueueInstantSendLock(NodeId from, const uint256& hash,
28 : std::shared_ptr<instantsend::InstantSendLock> islock)
29 : {
30 0 : if (ShouldReportISLockTiming()) {
31 0 : auto time_diff = [&]() -> int64_t {
32 0 : LOCK(cs_timingsTxSeen);
33 0 : 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 0 : auto diff = Ticks<std::chrono::milliseconds>(SteadyClock::now() - it->second);
36 0 : timingsTxSeen.erase(it);
37 0 : 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 0 : return 0;
41 0 : }();
42 0 : ::g_stats_client->timing("islock_ms", time_diff);
43 0 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock took %dms\n", __func__,
44 : islock->txid.ToString(), time_diff);
45 0 : }
46 :
47 0 : LOCK(cs_pendingLocks);
48 0 : pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{from, std::move(islock)});
49 0 : }
50 :
51 0 : instantsend::PendingState CInstantSendManager::FetchPendingLocks()
52 : {
53 0 : instantsend::PendingState ret;
54 :
55 0 : 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 0 : const size_t maxCount = 32;
59 : // The keys of the removed values are temporaily stored here to avoid invalidating an iterator
60 0 : std::vector<uint256> removed;
61 0 : removed.reserve(std::min(maxCount, pendingInstantSendLocks.size()));
62 :
63 0 : for (auto& [islockHash, pending] : pendingInstantSendLocks) {
64 : // Check if we've reached max count
65 0 : if (ret.m_pending_is.size() >= maxCount) {
66 0 : ret.m_pending_work = true;
67 0 : break;
68 : }
69 0 : ret.m_pending_is.push_back(instantsend::PendingISLockEntry{std::move(pending), islockHash});
70 0 : removed.emplace_back(islockHash);
71 : }
72 :
73 0 : for (const auto& islockHash : removed) {
74 0 : pendingInstantSendLocks.erase(islockHash);
75 : }
76 :
77 0 : return ret;
78 0 : }
79 :
80 0 : bool CInstantSendManager::PreVerifyIsLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) const
81 : {
82 0 : if (db.KnownInstantSendLock(hash)) {
83 0 : return false;
84 : }
85 :
86 0 : if (const auto sameTxIsLock = db.GetInstantSendLockByTxid(islock->txid)) {
87 : // can happen, nothing to do
88 0 : return false;
89 : }
90 0 : for (const auto& in : islock->inputs) {
91 0 : const auto sameOutpointIsLock = db.GetInstantSendLockByInput(in);
92 0 : 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 0 : }
97 0 : return true;
98 0 : }
99 :
100 0 : void CInstantSendManager::WriteNewISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock,
101 : std::optional<int> minedHeight)
102 : {
103 0 : db.WriteNewInstantSendLock(hash, islock);
104 0 : if (minedHeight.has_value()) {
105 0 : db.WriteInstantSendLockMined(hash, *minedHeight);
106 0 : }
107 0 : }
108 :
109 0 : 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 0 : LOCK(cs_pendingLocks);
113 0 : pendingNoTxInstantSendLocks.try_emplace(hash, instantsend::PendingISLockFromPeer{from, islock});
114 0 : }
115 :
116 0 : void CInstantSendManager::TransactionIsRemoved(const CTransactionRef& tx)
117 : {
118 0 : if (tx->vin.empty()) {
119 0 : return;
120 : }
121 :
122 0 : instantsend::InstantSendLockPtr islock = GetInstantSendLockByTxid(tx->GetHash());
123 :
124 0 : if (islock == nullptr) {
125 0 : 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 0 : }
132 :
133 0 : void CInstantSendManager::WriteBlockISLocks(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
134 : {
135 0 : db.WriteBlockInstantSendLocks(pblock, pindex);
136 0 : }
137 :
138 0 : void CInstantSendManager::RemoveBlockISLocks(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
139 : {
140 0 : db.RemoveBlockInstantSendLocks(pblock, pindex);
141 0 : }
142 :
143 0 : instantsend::InstantSendLockPtr CInstantSendManager::AttachISLockToTx(const CTransactionRef& tx)
144 : {
145 0 : LOCK(cs_pendingLocks);
146 0 : for (auto it = pendingNoTxInstantSendLocks.begin(); it != pendingNoTxInstantSendLocks.end(); ++it) {
147 0 : 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 0 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__,
151 : tx->GetHash().ToString(), it->first.ToString());
152 0 : auto islock = it->second.islock;
153 0 : pendingInstantSendLocks.try_emplace(it->first, it->second);
154 0 : pendingNoTxInstantSendLocks.erase(it);
155 0 : return islock;
156 0 : }
157 0 : return nullptr;
158 0 : }
159 :
160 0 : void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined)
161 : {
162 : {
163 0 : LOCK(cs_nonLocked);
164 0 : auto [it, did_insert] = nonLockedTxs.emplace(tx->GetHash(), NonLockedTxInfo());
165 0 : auto& nonLockedTxInfo = it->second;
166 0 : nonLockedTxInfo.pindexMined = pindexMined;
167 :
168 0 : if (did_insert) {
169 0 : nonLockedTxInfo.tx = tx;
170 0 : for (const auto& in : tx->vin) {
171 0 : nonLockedTxs[in.prevout.hash].children.emplace(tx->GetHash());
172 0 : nonLockedTxsByOutpoints.emplace(in.prevout, tx->GetHash());
173 : }
174 0 : }
175 0 : }
176 0 : AttachISLockToTx(tx);
177 0 : if (ShouldReportISLockTiming()) {
178 0 : LOCK(cs_timingsTxSeen);
179 : // Only insert the time the first time we see the tx, as we sometimes try to resign
180 0 : timingsTxSeen.try_emplace(tx->GetHash(), SteadyClock::now());
181 0 : }
182 :
183 0 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, pindexMined=%s\n", __func__,
184 : tx->GetHash().ToString(), pindexMined ? pindexMined->GetBlockHash().ToString() : "");
185 0 : }
186 :
187 0 : void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChildren)
188 : {
189 0 : LOCK(cs_nonLocked);
190 :
191 0 : auto it = nonLockedTxs.find(txid);
192 0 : if (it == nonLockedTxs.end()) {
193 0 : return;
194 : }
195 0 : const auto& info = it->second;
196 :
197 0 : size_t retryChildrenCount = 0;
198 0 : if (retryChildren) {
199 : // TX got locked, so we can retry locking children
200 0 : LOCK(cs_pendingRetry);
201 0 : for (const auto& childTxid : info.children) {
202 0 : pendingRetryTxs.emplace(childTxid);
203 0 : retryChildrenCount++;
204 : }
205 0 : }
206 : // don't try to lock it anymore
207 0 : WITH_LOCK(cs_pendingRetry, pendingRetryTxs.erase(txid));
208 :
209 0 : if (info.tx) {
210 0 : for (const auto& in : info.tx->vin) {
211 0 : if (auto jt = nonLockedTxs.find(in.prevout.hash); jt != nonLockedTxs.end()) {
212 0 : jt->second.children.erase(txid);
213 0 : if (!jt->second.tx && jt->second.children.empty()) {
214 0 : nonLockedTxs.erase(jt);
215 0 : }
216 0 : }
217 0 : nonLockedTxsByOutpoints.erase(in.prevout);
218 : }
219 0 : }
220 :
221 0 : nonLockedTxs.erase(it);
222 :
223 0 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, retryChildren=%d, retryChildrenCount=%d\n",
224 : __func__, txid.ToString(), retryChildren, retryChildrenCount);
225 0 : }
226 :
227 0 : std::vector<CTransactionRef> CInstantSendManager::PrepareTxToRetry()
228 : {
229 0 : std::vector<CTransactionRef> txns{};
230 :
231 0 : LOCK2(cs_nonLocked, cs_pendingRetry);
232 0 : if (pendingRetryTxs.empty()) return txns;
233 0 : txns.reserve(pendingRetryTxs.size());
234 0 : for (const auto& txid : pendingRetryTxs) {
235 0 : if (auto it = nonLockedTxs.find(txid); it != nonLockedTxs.end()) {
236 0 : const auto& [_, tx_info] = *it;
237 0 : if (tx_info.tx) {
238 0 : txns.push_back(tx_info.tx);
239 0 : }
240 0 : }
241 : }
242 0 : return txns;
243 0 : }
244 :
245 0 : void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeId id,
246 : const instantsend::InstantSendLockPtr& islock)
247 : {
248 0 : if (db.KnownInstantSendLock(hash)) return;
249 0 : LOCK(cs_pendingLocks);
250 0 : if (!pendingInstantSendLocks.count(hash)) {
251 0 : pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{id, islock});
252 0 : }
253 0 : }
254 :
255 0 : 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 0 : std::unordered_map<const CBlockIndex*, Uint256HashMap<CTransactionRef>> conflicts;
260 : {
261 0 : LOCK(cs_nonLocked);
262 0 : for (const auto& in : islock.inputs) {
263 0 : auto it = nonLockedTxsByOutpoints.find(in);
264 0 : if (it != nonLockedTxsByOutpoints.end()) {
265 0 : auto& conflictTxid = it->second;
266 0 : if (conflictTxid == islock.txid) {
267 0 : continue;
268 : }
269 0 : auto jt = nonLockedTxs.find(conflictTxid);
270 0 : if (jt == nonLockedTxs.end()) {
271 0 : continue;
272 : }
273 0 : auto& info = jt->second;
274 0 : if (!info.pindexMined || !info.tx) {
275 0 : continue;
276 : }
277 0 : 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 0 : conflicts[info.pindexMined].emplace(conflictTxid, info.tx);
280 0 : }
281 : }
282 0 : }
283 :
284 0 : return conflicts;
285 0 : }
286 :
287 0 : bool CInstantSendManager::HasTxForLock(const uint256& islockHash) const
288 : {
289 0 : LOCK(cs_pendingLocks);
290 0 : return pendingNoTxInstantSendLocks.find(islockHash) == pendingNoTxInstantSendLocks.end();
291 0 : }
292 :
293 0 : void CInstantSendManager::RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock)
294 : {
295 0 : LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: Removing ISLOCK and its chained children\n", __func__,
296 : islock.txid.ToString(), islockHash.ToString());
297 0 : const int tipHeight = GetTipHeight();
298 :
299 0 : auto removedIslocks = db.RemoveChainedInstantSendLocks(islockHash, islock.txid, tipHeight);
300 0 : for (const auto& h : removedIslocks) {
301 0 : LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: removed (child) ISLOCK %s\n", __func__,
302 : islock.txid.ToString(), islockHash.ToString(), h.ToString());
303 : }
304 0 : }
305 :
306 0 : bool CInstantSendManager::AlreadyHave(const CInv& inv) const
307 : {
308 0 : if (!IsInstantSendEnabled()) {
309 0 : return true;
310 : }
311 :
312 0 : return WITH_LOCK(cs_pendingLocks, return pendingInstantSendLocks.count(inv.hash) != 0 ||
313 0 : pendingNoTxInstantSendLocks.count(inv.hash) != 0) ||
314 0 : db.KnownInstantSendLock(inv.hash);
315 0 : }
316 :
317 0 : bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, instantsend::InstantSendLock& ret) const
318 : {
319 0 : if (!IsInstantSendEnabled()) {
320 0 : return false;
321 : }
322 :
323 0 : auto islock = db.GetInstantSendLockByHash(hash);
324 0 : if (!islock) {
325 0 : LOCK(cs_pendingLocks);
326 0 : auto it = pendingInstantSendLocks.find(hash);
327 0 : if (it != pendingInstantSendLocks.end()) {
328 0 : islock = it->second.islock;
329 0 : } else {
330 0 : auto itNoTx = pendingNoTxInstantSendLocks.find(hash);
331 0 : if (itNoTx != pendingNoTxInstantSendLocks.end()) {
332 0 : islock = itNoTx->second.islock;
333 0 : } else {
334 0 : return false;
335 : }
336 : }
337 0 : }
338 0 : ret = *islock;
339 0 : return true;
340 0 : }
341 :
342 0 : instantsend::InstantSendLockPtr CInstantSendManager::GetInstantSendLockByTxid(const uint256& txid) const
343 : {
344 0 : if (!IsInstantSendEnabled()) {
345 0 : return nullptr;
346 : }
347 :
348 0 : return db.GetInstantSendLockByTxid(txid);
349 0 : }
350 :
351 600672 : bool CInstantSendManager::IsLocked(const uint256& txHash) const
352 : {
353 600672 : if (!IsInstantSendEnabled()) {
354 2638 : return false;
355 : }
356 :
357 598034 : return db.KnownInstantSendLock(db.GetInstantSendLockHashByTxid(txHash));
358 600672 : }
359 :
360 95 : bool CInstantSendManager::IsWaitingForTx(const uint256& txHash) const
361 : {
362 95 : if (!IsInstantSendEnabled()) {
363 95 : return false;
364 : }
365 :
366 0 : LOCK(cs_pendingLocks);
367 0 : auto it = pendingNoTxInstantSendLocks.begin();
368 0 : while (it != pendingNoTxInstantSendLocks.end()) {
369 0 : if (it->second.islock->txid == txHash) {
370 0 : LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, txHash.ToString(),
371 : it->first.ToString());
372 0 : return true;
373 : }
374 0 : ++it;
375 : }
376 0 : return false;
377 95 : }
378 :
379 47324 : instantsend::InstantSendLockPtr CInstantSendManager::GetConflictingLock(const CTransaction& tx) const
380 : {
381 47324 : if (!IsInstantSendEnabled()) {
382 47324 : return nullptr;
383 : }
384 :
385 0 : for (const auto& in : tx.vin) {
386 0 : auto otherIsLock = db.GetInstantSendLockByInput(in.prevout);
387 0 : if (!otherIsLock) {
388 0 : continue;
389 : }
390 :
391 0 : if (otherIsLock->txid != tx.GetHash()) {
392 0 : return otherIsLock;
393 : }
394 0 : }
395 0 : return nullptr;
396 47324 : }
397 :
398 0 : size_t CInstantSendManager::GetInstantSendLockCount() const
399 : {
400 0 : 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 0 : void CInstantSendManager::CacheBlockHeight(const CBlockIndex* const block_index) const
420 : {
421 0 : LOCK(cs_height_cache);
422 0 : m_cached_block_heights.insert(block_index->GetBlockHash(), block_index->nHeight);
423 0 : }
424 :
425 0 : void CInstantSendManager::CacheDisconnectBlock(const CBlockIndex* pindexDisconnected)
426 : {
427 0 : LOCK(cs_height_cache);
428 0 : m_cached_block_heights.erase(pindexDisconnected->GetBlockHash());
429 0 : }
430 :
431 0 : std::optional<int> CInstantSendManager::GetCachedHeight(const uint256& hash) const
432 : {
433 0 : LOCK(cs_height_cache);
434 :
435 0 : int cached_height{0};
436 0 : if (m_cached_block_heights.get(hash, cached_height)) return cached_height;
437 :
438 0 : return std::nullopt;
439 0 : }
440 :
441 0 : void CInstantSendManager::CacheTipHeight(const CBlockIndex* const tip) const
442 : {
443 0 : LOCK(cs_height_cache);
444 0 : if (tip) {
445 0 : m_cached_block_heights.insert(tip->GetBlockHash(), tip->nHeight);
446 0 : m_cached_tip_height = tip->nHeight;
447 0 : } else {
448 0 : m_cached_tip_height = -1;
449 : }
450 0 : }
451 :
452 0 : 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 0 : LOCK(cs_height_cache);
458 0 : if (m_cached_tip_height >= 0) {
459 0 : return m_cached_tip_height;
460 : }
461 0 : return 0;
462 0 : }
463 :
464 650234 : bool CInstantSendManager::IsInstantSendEnabled() const
465 : {
466 650234 : return !fReindex && !fImporting && spork_manager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED);
467 : }
468 :
469 0 : Uint256HashMap<instantsend::InstantSendLockPtr> CInstantSendManager::RemoveConfirmedInstantSendLocks(const CBlockIndex* pindex)
470 : {
471 0 : int nUntilHeight = pindex->nHeight;
472 0 : auto removeISLocks = db.RemoveConfirmedInstantSendLocks(nUntilHeight);
473 :
474 0 : 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 0 : std::vector<uint256> toRemove;
479 : {
480 0 : LOCK(cs_nonLocked);
481 0 : for (const auto& p : nonLockedTxs) {
482 0 : const auto* pindexMined = p.second.pindexMined;
483 :
484 0 : if (pindexMined && pindex->GetAncestor(pindexMined->nHeight) == pindexMined) {
485 0 : toRemove.emplace_back(p.first);
486 0 : }
487 : }
488 0 : }
489 0 : for (const auto& txid : toRemove) {
490 : // This will also add children to pendingRetryTxs
491 0 : RemoveNonLockedTx(txid, true);
492 : }
493 :
494 0 : return removeISLocks;
495 0 : }
496 : } // namespace llmq
|