Line data Source code
1 : // Copyright (c) 2019-2025 The Dash Core developers
2 : // Distributed under the MIT/X11 software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <chainlock/handler.h>
6 :
7 : #include <chain.h>
8 : #include <chainlock/chainlock.h>
9 : #include <chainlock/clsig.h>
10 : #include <chainparams.h>
11 : #include <consensus/validation.h>
12 : #include <instantsend/instantsend.h>
13 : #include <llmq/quorumsman.h>
14 : #include <masternode/sync.h>
15 : #include <msg_result.h>
16 : #include <node/interface_ui.h>
17 : #include <scheduler.h>
18 : #include <stats/client.h>
19 : #include <txmempool.h>
20 : #include <util/std23.h>
21 : #include <util/thread.h>
22 : #include <util/time.h>
23 : #include <validation.h>
24 : #include <validationinterface.h>
25 :
26 : // Forward declaration to break dependency over node/transaction.h
27 : namespace node {
28 : CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool,
29 : const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
30 : } // namespace node
31 :
32 : using node::GetTransaction;
33 :
34 : namespace {
35 : static constexpr auto CLEANUP_SEEN_TIMEOUT{24h};
36 : //! How long to wait for islocks until we consider a block with non-islocked TXs to be safe to sign
37 : static constexpr auto WAIT_FOR_ISLOCK_TIMEOUT{10min};
38 : } // anonymous namespace
39 :
40 : namespace chainlock {
41 9123 : ChainlockHandler::ChainlockHandler(chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, CTxMemPool& _mempool,
42 : const CMasternodeSync& mn_sync) :
43 3041 : m_chainlocks{chainlocks},
44 3041 : m_chainman{chainman},
45 3041 : mempool{_mempool},
46 3041 : m_mn_sync{mn_sync},
47 3041 : scheduler{std::make_unique<CScheduler>()},
48 6082 : scheduler_thread{
49 6082 : std::make_unique<std::thread>(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))}
50 6082 : {
51 3041 : }
52 :
53 6082 : ChainlockHandler::~ChainlockHandler()
54 3041 : {
55 3041 : scheduler->stop();
56 3041 : scheduler_thread->join();
57 6082 : }
58 :
59 2831 : void ChainlockHandler::Start()
60 : {
61 5662 : scheduler->scheduleEvery(
62 15932 : [&]() {
63 13101 : CheckActiveState();
64 13101 : EnforceBestChainLock();
65 13101 : Cleanup();
66 13101 : },
67 2831 : std::chrono::seconds{5});
68 2831 : }
69 :
70 3037 : void ChainlockHandler::Stop() { scheduler->stop(); }
71 :
72 58807 : bool ChainlockHandler::AlreadyHave(const CInv& inv) const
73 : {
74 58807 : LOCK(cs);
75 58807 : return seenChainLocks.count(inv.hash) != 0;
76 58807 : }
77 :
78 21321 : void ChainlockHandler::UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time)
79 : {
80 21321 : AssertLockNotHeld(cs);
81 21321 : LOCK(cs);
82 23499 : for (const auto& txid : tx) {
83 2178 : txFirstSeenTime.emplace(txid, time);
84 : }
85 21321 : }
86 :
87 14028 : MessageProcessingResult ChainlockHandler::ProcessNewChainLock(const NodeId from, const chainlock::ChainLockSig& clsig,
88 : const llmq::CQuorumManager& qman, const uint256& hash)
89 : {
90 14028 : CheckActiveState();
91 :
92 : {
93 14028 : LOCK(cs);
94 14028 : if (!seenChainLocks.emplace(hash, GetTime<std::chrono::seconds>()).second) {
95 459 : return {};
96 : }
97 :
98 : // height is expect to check twice: preliminary (for optimization) and inside UpdateBestsChainlock (as mutex is not kept during validation)
99 13569 : if (clsig.getHeight() <= m_chainlocks.GetBestChainLockHeight()) {
100 : // no need to process older/same CLSIGs
101 18 : return {};
102 : }
103 14028 : }
104 :
105 27102 : if (const auto ret = chainlock::VerifyChainLock(Params().GetConsensus(), m_chainman.ActiveChain(), qman, clsig);
106 13551 : ret != llmq::VerifyRecSigStatus::Valid) {
107 1 : LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- invalid CLSIG (%s), status=%d peer=%d\n", __func__,
108 : clsig.ToString(), std23::to_underlying(ret), from);
109 1 : if (from != -1) {
110 1 : return MisbehavingError{10};
111 : }
112 0 : return {};
113 : }
114 :
115 27100 : const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()));
116 :
117 13550 : if (!m_chainlocks.UpdateBestChainlock(hash, clsig, pindex)) return {};
118 :
119 13549 : if (pindex) {
120 27072 : scheduler->scheduleFromNow(
121 27072 : [&]() {
122 13536 : CheckActiveState();
123 13536 : EnforceBestChainLock();
124 13536 : },
125 13536 : std::chrono::seconds{0});
126 :
127 13536 : LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__,
128 : clsig.ToString(), from);
129 13536 : }
130 13549 : const CInv clsig_inv(MSG_CLSIG, hash);
131 13549 : return clsig_inv;
132 14028 : }
133 :
134 220688 : void ChainlockHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
135 : {
136 220688 : if (pindexNew == pindexFork) // blocks were disconnected without any new ones
137 0 : return;
138 220688 : if (fInitialDownload) return;
139 :
140 : // TODO: reconsider removing scheduler from here, because UpdatedBlockTip is already async call from notification
141 : // scheduler don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that
142 : // cs_main is never locked and TrySignChainTip is not called twice in parallel. Also avoids recursive calls due to
143 : // EnforceBestChainLock switching chains.
144 : // atomic[If tryLockChainTipScheduled is false, do (set it to true] and schedule signing).
145 :
146 204231 : if (bool expected = false; tryLockChainTipScheduled.compare_exchange_strong(expected, true)) {
147 398542 : scheduler->scheduleFromNow(
148 398539 : [&]() {
149 199268 : CheckActiveState();
150 199268 : EnforceBestChainLock();
151 199268 : Cleanup();
152 199268 : tryLockChainTipScheduled = false;
153 199268 : },
154 199271 : std::chrono::seconds{0});
155 199271 : }
156 220688 : }
157 :
158 239933 : void ChainlockHandler::CheckActiveState()
159 : {
160 239933 : bool oldIsEnabled = isEnabled;
161 239933 : isEnabled = m_chainlocks.IsEnabled();
162 :
163 239933 : if (!oldIsEnabled && isEnabled) {
164 : // ChainLocks got activated just recently, but it's possible that it was already running before, leaving
165 : // us with some stale values which we should not try to enforce anymore (there probably was a good reason
166 : // to disable spork19)
167 688 : LOCK(cs);
168 688 : m_chainlocks.ResetChainlock();
169 688 : lastNotifyChainLockBlockIndex = nullptr;
170 688 : }
171 239933 : }
172 :
173 36800 : void ChainlockHandler::TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime, uint64_t)
174 : {
175 36800 : if (tx->IsCoinBase() || tx->vin.empty()) {
176 572 : return;
177 : }
178 :
179 36228 : LOCK(cs);
180 36228 : txFirstSeenTime.emplace(tx->GetHash(), nAcceptTime);
181 36800 : }
182 :
183 244470 : void ChainlockHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
184 : {
185 244470 : m_chainlocks.AcceptedBlockHeader(pindexNew);
186 244470 : }
187 :
188 228002 : void ChainlockHandler::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
189 : {
190 228002 : if (!m_mn_sync.IsBlockchainSynced()) {
191 124351 : return;
192 : }
193 :
194 : // We listen for BlockConnected so that we can collect all TX ids of all included TXs of newly received blocks
195 103651 : int64_t curTime = GetTime<std::chrono::seconds>().count();
196 : {
197 103651 : LOCK(cs);
198 337310 : for (const auto& tx : pblock->vtx) {
199 233659 : if (!tx->IsCoinBase() && !tx->vin.empty()) {
200 11589 : txFirstSeenTime.emplace(tx->GetHash(), curTime);
201 11589 : }
202 : }
203 103651 : }
204 228002 : }
205 :
206 6109 : bool ChainlockHandler::IsTxSafeForMining(const uint256& txid) const
207 : {
208 6109 : auto tx_age{0s};
209 : {
210 6109 : LOCK(cs);
211 6109 : auto it = txFirstSeenTime.find(txid);
212 6109 : if (it != txFirstSeenTime.end()) {
213 5990 : tx_age = GetTime<std::chrono::seconds>() - it->second;
214 5990 : }
215 6109 : }
216 :
217 6109 : return tx_age >= WAIT_FOR_ISLOCK_TIMEOUT;
218 0 : }
219 :
220 : // WARNING: cs_main and cs should not be held!
221 : // This should also not be called from validation signals, as this might result in recursive calls
222 225905 : void ChainlockHandler::EnforceBestChainLock()
223 : {
224 225905 : if (!isEnabled) {
225 141085 : return;
226 : }
227 :
228 84820 : AssertLockNotHeld(cs);
229 84820 : AssertLockNotHeld(cs_main);
230 :
231 :
232 448020 : auto [clsig, currentBestChainLockBlockIndex] = m_chainlocks.GetBestChainlockWithPindex();
233 84820 : if (currentBestChainLockBlockIndex == nullptr) {
234 : // we don't have the header/block, so we can't do anything right now
235 25735 : return;
236 : }
237 :
238 59085 : BlockValidationState dummy_state;
239 :
240 : // Go backwards through the chain referenced by clsig until we find a block that is part of the main chain.
241 : // For each of these blocks, check if there are children that are NOT part of the chain referenced by clsig
242 : // and mark all of them as conflicting.
243 59085 : LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- enforcing block %s via CLSIG (%s)\n", __func__,
244 : currentBestChainLockBlockIndex->GetBlockHash().ToString(), clsig.ToString());
245 59085 : m_chainman.ActiveChainstate().EnforceBlock(dummy_state, currentBestChainLockBlockIndex);
246 :
247 :
248 118170 : if (/*activateNeeded =*/WITH_LOCK(::cs_main, return m_chainman.ActiveTip()->GetAncestor(
249 59085 : currentBestChainLockBlockIndex->nHeight)) !=
250 59085 : currentBestChainLockBlockIndex) {
251 53 : if (!m_chainman.ActiveChainstate().ActivateBestChain(dummy_state)) {
252 0 : LogPrintf("ChainlockHandler::%s -- ActivateBestChain failed: %s\n", __func__, dummy_state.ToString());
253 0 : return;
254 : }
255 53 : LOCK(::cs_main);
256 53 : if (m_chainman.ActiveTip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex) {
257 23 : return;
258 : }
259 53 : }
260 :
261 : {
262 59062 : LOCK(cs);
263 118124 : if (lastNotifyChainLockBlockIndex == currentBestChainLockBlockIndex) return;
264 13549 : lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex;
265 59062 : }
266 :
267 13549 : GetMainSignals().NotifyChainLock(currentBestChainLockBlockIndex, std::make_shared<chainlock::ChainLockSig>(clsig),
268 13549 : clsig.ToString());
269 13549 : uiInterface.NotifyChainLock(clsig.getBlockHash().ToString(), clsig.getHeight());
270 13549 : ::g_stats_client->gauge("chainlocks.blockHeight", clsig.getHeight(), 1.0f);
271 225905 : }
272 :
273 2099 : void ChainlockHandler::CleanupFromSigner(const std::vector<std::shared_ptr<Uint256HashSet>>& cleanup_txes)
274 : {
275 2099 : LOCK(cs);
276 46219 : for (const auto& tx : cleanup_txes) {
277 45589 : for (const uint256& txid : *tx) {
278 1469 : txFirstSeenTime.erase(txid);
279 : }
280 : }
281 2099 : }
282 :
283 212369 : void ChainlockHandler::Cleanup()
284 : {
285 212369 : constexpr auto CLEANUP_INTERVAL{30s};
286 212369 : if (!m_mn_sync.IsBlockchainSynced()) {
287 106101 : return;
288 : }
289 :
290 106268 : if (!cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) {
291 100539 : return;
292 : }
293 :
294 : {
295 5729 : LOCK(cs);
296 38579 : for (auto it = seenChainLocks.begin(); it != seenChainLocks.end();) {
297 32850 : if (GetTime<std::chrono::seconds>() - it->second >= CLEANUP_SEEN_TIMEOUT) {
298 426 : it = seenChainLocks.erase(it);
299 426 : } else {
300 32424 : ++it;
301 : }
302 : }
303 5729 : }
304 :
305 5729 : LOCK(::cs_main);
306 5729 : LOCK2(mempool.cs, cs); // need mempool.cs due to GetTransaction calls
307 22566 : for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end();) {
308 16837 : uint256 hashBlock;
309 33674 : if (auto tx = GetTransaction(nullptr, &mempool, it->first, Params().GetConsensus(), hashBlock); !tx) {
310 : // tx has vanished, probably due to conflicts
311 64 : it = txFirstSeenTime.erase(it);
312 16837 : } else if (!hashBlock.IsNull()) {
313 16007 : const auto* pindex = m_chainman.m_blockman.LookupBlockIndex(hashBlock);
314 16007 : assert(pindex); // GetTransaction gave us that hashBlock, it should resolve to a valid block index
315 31326 : if (m_chainman.ActiveTip()->GetAncestor(pindex->nHeight) == pindex &&
316 15319 : m_chainman.ActiveChain().Height() - pindex->nHeight > chainlock::TX_CONFIRM_THRESHOLD) {
317 : // tx is sufficiently deep, we can stop tracking it
318 6400 : it = txFirstSeenTime.erase(it);
319 6400 : } else {
320 9607 : ++it;
321 : }
322 16007 : } else {
323 766 : ++it;
324 : }
325 : }
326 212369 : }
327 :
328 : } // namespace chainlock
|