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/signing.h>
6 :
7 : #include <chain.h>
8 : #include <chainlock/chainlock.h>
9 : #include <chainparams.h>
10 : #include <index/txindex.h>
11 : #include <instantsend/instantsend.h>
12 : #include <llmq/quorumsman.h>
13 : #include <llmq/signing_shares.h>
14 : #include <logging.h>
15 : #include <masternode/sync.h>
16 : #include <spork.h>
17 : #include <util/helpers.h>
18 : #include <validation.h>
19 :
20 : #include <ranges>
21 :
22 : // Forward declaration to break dependency over node/transaction.h
23 : namespace node {
24 : CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool,
25 : const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
26 : } // namespace node
27 :
28 : using node::fImporting;
29 : using node::fReindex;
30 : using node::GetTransaction;
31 :
32 : namespace instantsend {
33 0 : InstantSendSigner::InstantSendSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks,
34 : llmq::CInstantSendManager& isman, llmq::CSigningManager& sigman,
35 : llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman,
36 : CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync) :
37 0 : m_chainstate{chainstate},
38 0 : m_chainlocks{chainlocks},
39 0 : m_isman{isman},
40 0 : m_sigman{sigman},
41 0 : m_shareman{shareman},
42 0 : m_qman{qman},
43 0 : m_sporkman{sporkman},
44 0 : m_mempool{mempool},
45 0 : m_mn_sync{mn_sync}
46 0 : {
47 0 : }
48 :
49 0 : InstantSendSigner::~InstantSendSigner() = default;
50 :
51 0 : void InstantSendSigner::RegisterRecoveryInterface()
52 : {
53 0 : m_sigman.RegisterRecoveredSigsListener(this);
54 0 : }
55 :
56 0 : void InstantSendSigner::UnregisterRecoveryInterface()
57 : {
58 0 : m_sigman.UnregisterRecoveredSigsListener(this);
59 0 : }
60 :
61 0 : void InstantSendSigner::ClearInputsFromQueue(const Uint256HashSet& ids)
62 : {
63 0 : LOCK(cs_input_requests);
64 0 : for (const auto& id : ids) {
65 0 : inputRequestIds.erase(id);
66 : }
67 0 : }
68 :
69 0 : void InstantSendSigner::ClearLockFromQueue(const InstantSendLockPtr& islock)
70 : {
71 0 : LOCK(cs_creating);
72 0 : creatingInstantSendLocks.erase(islock->GetRequestId());
73 0 : txToCreatingInstantSendLocks.erase(islock->txid);
74 0 : }
75 :
76 0 : llmq::RecoveredSigResult InstantSendSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
77 : {
78 0 : if (!m_isman.IsInstantSendEnabled()) {
79 0 : return std::monostate{};
80 : }
81 :
82 0 : if (Params().GetConsensus().llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) {
83 0 : return std::monostate{};
84 : }
85 :
86 0 : uint256 txid;
87 0 : if (LOCK(cs_input_requests); inputRequestIds.count(recoveredSig.getId())) {
88 0 : txid = recoveredSig.getMsgHash();
89 0 : }
90 0 : if (!txid.IsNull()) {
91 0 : HandleNewInputLockRecoveredSig(recoveredSig, txid);
92 0 : } else if (/*isInstantSendLock=*/WITH_LOCK(cs_creating, return creatingInstantSendLocks.count(recoveredSig.getId()))) {
93 0 : HandleNewInstantSendLockRecoveredSig(recoveredSig);
94 0 : }
95 0 : return std::monostate{};
96 0 : }
97 :
98 0 : bool InstantSendSigner::IsInstantSendMempoolSigningEnabled() const
99 : {
100 0 : return !fReindex && !fImporting && m_sporkman.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED) == 0;
101 : }
102 :
103 0 : void InstantSendSigner::HandleNewInputLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig, const uint256& txid)
104 : {
105 0 : if (g_txindex) {
106 0 : g_txindex->BlockUntilSyncedToCurrentChain();
107 0 : }
108 :
109 0 : uint256 _hashBlock{};
110 0 : const auto tx = GetTransaction(nullptr, &m_mempool, txid, Params().GetConsensus(), _hashBlock);
111 0 : if (!tx) {
112 0 : return;
113 : }
114 :
115 0 : if (LogAcceptDebug(BCLog::INSTANTSEND)) {
116 0 : for (const auto& in : tx->vin) {
117 0 : if (GenInputLockRequestId(in.prevout) == recoveredSig.getId()) {
118 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got recovered sig for input %s\n", __func__,
119 : txid.ToString(), in.prevout.ToStringShort());
120 0 : break;
121 : }
122 : }
123 0 : }
124 :
125 0 : TrySignInstantSendLock(*tx);
126 0 : }
127 :
128 0 : void InstantSendSigner::ProcessPendingRetryLockTxs(const std::vector<CTransactionRef>& retryTxs)
129 : {
130 0 : if (!m_isman.IsInstantSendEnabled()) {
131 0 : return;
132 : }
133 :
134 0 : int retryCount = 0;
135 0 : for (const auto& tx : retryTxs) {
136 : {
137 0 : if (LOCK(cs_creating); txToCreatingInstantSendLocks.count(tx->GetHash())) {
138 : // we're already in the middle of locking this one
139 0 : continue;
140 : }
141 0 : if (m_isman.IsLocked(tx->GetHash())) {
142 0 : continue;
143 : }
144 0 : if (m_isman.GetConflictingLock(*tx) != nullptr) {
145 : // should not really happen as we have already filtered these out
146 0 : continue;
147 : }
148 : }
149 :
150 : // CheckCanLock is already called by ProcessTx, so we should avoid calling it twice. But we also shouldn't spam
151 : // the logs when retrying TXs that are not ready yet.
152 0 : if (LogAcceptDebug(BCLog::INSTANTSEND)) {
153 0 : if (!CheckCanLock(*tx, false, Params().GetConsensus())) {
154 0 : continue;
155 : }
156 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: retrying to lock\n", __func__, tx->GetHash().ToString());
157 0 : }
158 :
159 0 : ProcessTx(*tx, false, Params().GetConsensus());
160 0 : retryCount++;
161 : }
162 :
163 0 : if (retryCount != 0) {
164 0 : LogPrint(BCLog::INSTANTSEND, "%s -- retried %d TXs.\n", __func__, retryCount);
165 0 : }
166 0 : }
167 :
168 0 : bool InstantSendSigner::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) const
169 : {
170 0 : if (tx.vin.empty()) {
171 : // can't lock TXs without inputs (e.g. quorum commitments)
172 0 : return false;
173 : }
174 :
175 0 : return std::ranges::all_of(tx.vin, [&](const auto& in) {
176 0 : return CheckCanLock(in.prevout, printDebug, tx.GetHash(), params);
177 : });
178 0 : }
179 :
180 0 : bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash,
181 : const Consensus::Params& params) const
182 : {
183 0 : int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired;
184 :
185 0 : if (m_isman.IsLocked(outpoint.hash)) {
186 : // if prevout was ix locked, allow locking of descendants (no matter if prevout is in mempool or already mined)
187 0 : return true;
188 : }
189 :
190 0 : auto mempoolTx = m_mempool.get(outpoint.hash);
191 0 : if (mempoolTx) {
192 0 : if (printDebug) {
193 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: parent mempool TX %s is not locked\n", __func__,
194 : txHash.ToString(), outpoint.hash.ToString());
195 0 : }
196 0 : return false;
197 : }
198 :
199 0 : uint256 hashBlock{};
200 0 : const auto tx = GetTransaction(nullptr, &m_mempool, outpoint.hash, params, hashBlock);
201 : // this relies on enabled txindex and won't work if we ever try to remove the requirement for txindex for masternodes
202 0 : if (!tx || hashBlock.IsNull()) {
203 0 : if (printDebug) {
204 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to find parent TX %s in mined block\n", __func__, txHash.ToString(),
205 : outpoint.hash.ToString());
206 0 : }
207 0 : return false;
208 : }
209 :
210 0 : int blockHeight{0};
211 0 : if (auto ret = m_isman.GetCachedHeight(hashBlock)) {
212 0 : blockHeight = *ret;
213 0 : } else {
214 0 : const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock));
215 0 : if (pindex == nullptr) {
216 0 : if (printDebug) {
217 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to determine mined height for parent TX %s\n",
218 : __func__, txHash.ToString(), outpoint.hash.ToString());
219 0 : }
220 0 : return false;
221 : }
222 0 : m_isman.CacheBlockHeight(pindex);
223 0 : blockHeight = pindex->nHeight;
224 : }
225 :
226 0 : const int tipHeight = m_isman.GetTipHeight();
227 :
228 0 : if (tipHeight < blockHeight) {
229 0 : if (printDebug) {
230 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: cached tip height %d is below block height %d for parent TX %s\n",
231 : __func__, txHash.ToString(), tipHeight, blockHeight, outpoint.hash.ToString());
232 0 : }
233 0 : return false;
234 : }
235 :
236 0 : const int nTxAge = tipHeight - blockHeight + 1;
237 :
238 0 : if (nTxAge < nInstantSendConfirmationsRequired && !m_chainlocks.HasChainLock(blockHeight, hashBlock)) {
239 0 : if (printDebug) {
240 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nInstantSendConfirmationsRequired=%d\n", __func__,
241 : txHash.ToString(), outpoint.ToStringShort(), nTxAge, nInstantSendConfirmationsRequired);
242 0 : }
243 0 : return false;
244 : }
245 :
246 0 : return true;
247 0 : }
248 :
249 0 : void InstantSendSigner::HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
250 : {
251 0 : InstantSendLockPtr islock;
252 :
253 : {
254 0 : LOCK(cs_creating);
255 0 : auto it = creatingInstantSendLocks.find(recoveredSig.getId());
256 0 : if (it == creatingInstantSendLocks.end()) {
257 0 : return;
258 : }
259 :
260 0 : islock = std::make_shared<InstantSendLock>(std::move(it->second));
261 0 : creatingInstantSendLocks.erase(it);
262 0 : txToCreatingInstantSendLocks.erase(islock->txid);
263 0 : }
264 :
265 0 : if (islock->txid != recoveredSig.getMsgHash()) {
266 0 : LogPrintf("%s -- txid=%s: islock conflicts with %s, dropping own version\n", __func__, islock->txid.ToString(),
267 : recoveredSig.getMsgHash().ToString());
268 0 : return;
269 : }
270 :
271 0 : islock->sig = recoveredSig.sig;
272 0 : m_isman.TryEmplacePendingLock(/*hash=*/::SerializeHash(*islock), /*id=*/-1, islock);
273 0 : }
274 :
275 0 : void InstantSendSigner::ProcessTx(const CTransaction& tx, bool fRetroactive, const Consensus::Params& params)
276 : {
277 0 : if (!m_isman.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced()) {
278 0 : return;
279 : }
280 :
281 0 : if (params.llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) {
282 0 : return;
283 : }
284 :
285 0 : if (!CheckCanLock(tx, true, params)) {
286 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: CheckCanLock returned false\n", __func__, tx.GetHash().ToString());
287 0 : return;
288 : }
289 :
290 0 : auto conflictingLock = m_isman.GetConflictingLock(tx);
291 0 : if (conflictingLock != nullptr) {
292 0 : auto conflictingLockHash = ::SerializeHash(*conflictingLock);
293 0 : LogPrintf("%s -- txid=%s: conflicts with islock %s, txid=%s\n", __func__, tx.GetHash().ToString(),
294 : conflictingLockHash.ToString(), conflictingLock->txid.ToString());
295 0 : return;
296 : }
297 :
298 : // Only sign for inlocks or islocks if mempool IS signing is enabled.
299 : // However, if we are processing a tx because it was included in a block we should
300 : // sign even if mempool IS signing is disabled. This allows a ChainLock to happen on this
301 : // block after we retroactively locked all transactions.
302 0 : if (!IsInstantSendMempoolSigningEnabled() && !fRetroactive) return;
303 :
304 0 : if (!TrySignInputLocks(tx, fRetroactive, params.llmqTypeDIP0024InstantSend, params)) {
305 0 : return;
306 : }
307 :
308 : // We might have received all input locks before we got the corresponding TX. In this case, we have to sign the
309 : // islock now instead of waiting for the input locks.
310 0 : TrySignInstantSendLock(tx);
311 0 : }
312 :
313 0 : bool InstantSendSigner::TrySignInputLocks(const CTransaction& tx, bool fRetroactive, Consensus::LLMQType llmqType,
314 : const Consensus::Params& params)
315 : {
316 0 : std::vector<uint256> ids;
317 0 : ids.reserve(tx.vin.size());
318 :
319 0 : size_t alreadyVotedCount = 0;
320 0 : for (const auto& in : tx.vin) {
321 0 : auto id = GenInputLockRequestId(in.prevout);
322 0 : ids.emplace_back(id);
323 :
324 0 : uint256 otherTxHash;
325 0 : if (m_sigman.GetVoteForId(params.llmqTypeDIP0024InstantSend, id, otherTxHash)) {
326 0 : if (otherTxHash != tx.GetHash()) {
327 0 : LogPrintf("%s -- txid=%s: input %s is conflicting with previous vote for tx %s\n", __func__,
328 : tx.GetHash().ToString(), in.prevout.ToStringShort(), otherTxHash.ToString());
329 0 : return false;
330 : }
331 0 : alreadyVotedCount++;
332 0 : }
333 :
334 : // don't even try the actual signing if any input is conflicting
335 0 : if (m_sigman.IsConflicting(params.llmqTypeDIP0024InstantSend, id, tx.GetHash())) {
336 0 : LogPrintf("%s -- txid=%s: m_sigman.IsConflicting returned true. id=%s\n", __func__, tx.GetHash().ToString(),
337 : id.ToString());
338 0 : return false;
339 : }
340 : }
341 0 : if (!fRetroactive && alreadyVotedCount == ids.size()) {
342 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: already voted on all inputs, bailing out\n", __func__,
343 : tx.GetHash().ToString());
344 0 : return true;
345 : }
346 :
347 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: trying to vote on %d inputs\n", __func__, tx.GetHash().ToString(),
348 : tx.vin.size());
349 :
350 0 : for (const auto i : util::irange(tx.vin.size())) {
351 0 : const auto& in = tx.vin[i];
352 0 : auto& id = ids[i];
353 0 : WITH_LOCK(cs_input_requests, inputRequestIds.emplace(id));
354 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: trying to vote on input %s with id %s. fRetroactive=%d\n",
355 : __func__, tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString(), fRetroactive);
356 0 : if (m_shareman.AsyncSignIfMember(llmqType, m_sigman, id, tx.GetHash(), {}, fRetroactive)) {
357 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: voted on input %s with id %s\n", __func__,
358 : tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString());
359 0 : }
360 : }
361 :
362 0 : return true;
363 0 : }
364 :
365 0 : void InstantSendSigner::TrySignInstantSendLock(const CTransaction& tx)
366 : {
367 0 : const auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
368 :
369 0 : for (const auto& in : tx.vin) {
370 0 : auto id = GenInputLockRequestId(in.prevout);
371 0 : if (!m_sigman.HasRecoveredSig(llmqType, id, tx.GetHash())) {
372 0 : return;
373 : }
374 : }
375 :
376 0 : LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got all recovered sigs, creating InstantSendLock\n", __func__,
377 : tx.GetHash().ToString());
378 :
379 0 : InstantSendLock islock;
380 0 : islock.txid = tx.GetHash();
381 0 : for (const auto& in : tx.vin) {
382 0 : islock.inputs.emplace_back(in.prevout);
383 : }
384 :
385 0 : auto id = islock.GetRequestId();
386 :
387 0 : if (m_sigman.HasRecoveredSigForId(llmqType, id)) {
388 0 : return;
389 : }
390 :
391 0 : const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
392 0 : assert(llmq_params_opt);
393 0 : const auto quorum = llmq::SelectQuorumForSigning(llmq_params_opt.value(), m_chainstate.m_chain, m_qman, id);
394 :
395 0 : if (!quorum) {
396 0 : LogPrint(BCLog::INSTANTSEND, "%s -- failed to select quorum. islock id=%s, txid=%s\n", __func__, id.ToString(),
397 : tx.GetHash().ToString());
398 0 : return;
399 : }
400 :
401 0 : const int cycle_height = quorum->m_quorum_base_block_index->nHeight -
402 0 : quorum->m_quorum_base_block_index->nHeight % llmq_params_opt->dkgInterval;
403 0 : islock.cycleHash = quorum->m_quorum_base_block_index->GetAncestor(cycle_height)->GetBlockHash();
404 :
405 : {
406 0 : LOCK(cs_creating);
407 0 : auto e = creatingInstantSendLocks.emplace(id, std::move(islock));
408 0 : if (!e.second) {
409 0 : return;
410 : }
411 0 : txToCreatingInstantSendLocks.emplace(tx.GetHash(), &e.first->second);
412 0 : }
413 :
414 0 : m_shareman.AsyncSignIfMember(llmqType, m_sigman, id, tx.GetHash(), quorum->m_quorum_base_block_index->GetBlockHash());
415 0 : }
416 : } // namespace instantsend
|