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 552 : ChainlockHandler::ChainlockHandler(chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, CTxMemPool& _mempool,
42 : const CMasternodeSync& mn_sync) :
43 184 : m_chainlocks{chainlocks},
44 184 : m_chainman{chainman},
45 184 : mempool{_mempool},
46 184 : m_mn_sync{mn_sync},
47 184 : scheduler{std::make_unique<CScheduler>()},
48 368 : scheduler_thread{
49 368 : std::make_unique<std::thread>(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))}
50 368 : {
51 184 : }
52 :
53 368 : ChainlockHandler::~ChainlockHandler()
54 184 : {
55 184 : scheduler->stop();
56 184 : scheduler_thread->join();
57 368 : }
58 :
59 0 : void ChainlockHandler::Start()
60 : {
61 0 : scheduler->scheduleEvery(
62 0 : [&]() {
63 0 : CheckActiveState();
64 0 : EnforceBestChainLock();
65 0 : Cleanup();
66 0 : },
67 0 : std::chrono::seconds{5});
68 0 : }
69 :
70 180 : void ChainlockHandler::Stop() { scheduler->stop(); }
71 :
72 0 : bool ChainlockHandler::AlreadyHave(const CInv& inv) const
73 : {
74 0 : LOCK(cs);
75 0 : return seenChainLocks.count(inv.hash) != 0;
76 0 : }
77 :
78 2018 : void ChainlockHandler::UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time)
79 : {
80 2018 : AssertLockNotHeld(cs);
81 2018 : LOCK(cs);
82 4044 : for (const auto& txid : tx) {
83 2026 : txFirstSeenTime.emplace(txid, time);
84 : }
85 2018 : }
86 :
87 0 : MessageProcessingResult ChainlockHandler::ProcessNewChainLock(const NodeId from, const chainlock::ChainLockSig& clsig,
88 : const llmq::CQuorumManager& qman, const uint256& hash)
89 : {
90 0 : CheckActiveState();
91 :
92 : {
93 0 : LOCK(cs);
94 0 : if (!seenChainLocks.emplace(hash, GetTime<std::chrono::seconds>()).second) {
95 0 : return {};
96 : }
97 :
98 : // height is expect to check twice: preliminary (for optimization) and inside UpdateBestsChainlock (as mutex is not kept during validation)
99 0 : if (clsig.getHeight() <= m_chainlocks.GetBestChainLockHeight()) {
100 : // no need to process older/same CLSIGs
101 0 : return {};
102 : }
103 0 : }
104 :
105 0 : if (const auto ret = chainlock::VerifyChainLock(Params().GetConsensus(), m_chainman.ActiveChain(), qman, clsig);
106 0 : ret != llmq::VerifyRecSigStatus::Valid) {
107 0 : LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- invalid CLSIG (%s), status=%d peer=%d\n", __func__,
108 : clsig.ToString(), std23::to_underlying(ret), from);
109 0 : if (from != -1) {
110 0 : return MisbehavingError{10};
111 : }
112 0 : return {};
113 : }
114 :
115 0 : const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash()));
116 :
117 0 : if (!m_chainlocks.UpdateBestChainlock(hash, clsig, pindex)) return {};
118 :
119 0 : if (pindex) {
120 0 : scheduler->scheduleFromNow(
121 0 : [&]() {
122 0 : CheckActiveState();
123 0 : EnforceBestChainLock();
124 0 : },
125 0 : std::chrono::seconds{0});
126 :
127 0 : LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__,
128 : clsig.ToString(), from);
129 0 : }
130 0 : const CInv clsig_inv(MSG_CLSIG, hash);
131 0 : return clsig_inv;
132 0 : }
133 :
134 0 : void ChainlockHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
135 : {
136 0 : if (pindexNew == pindexFork) // blocks were disconnected without any new ones
137 0 : return;
138 0 : 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 0 : if (bool expected = false; tryLockChainTipScheduled.compare_exchange_strong(expected, true)) {
147 0 : scheduler->scheduleFromNow(
148 0 : [&]() {
149 0 : CheckActiveState();
150 0 : EnforceBestChainLock();
151 0 : Cleanup();
152 0 : tryLockChainTipScheduled = false;
153 0 : },
154 0 : std::chrono::seconds{0});
155 0 : }
156 0 : }
157 :
158 0 : void ChainlockHandler::CheckActiveState()
159 : {
160 0 : bool oldIsEnabled = isEnabled;
161 0 : isEnabled = m_chainlocks.IsEnabled();
162 :
163 0 : 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 0 : LOCK(cs);
168 0 : m_chainlocks.ResetChainlock();
169 0 : lastNotifyChainLockBlockIndex = nullptr;
170 0 : }
171 0 : }
172 :
173 0 : void ChainlockHandler::TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime, uint64_t)
174 : {
175 0 : if (tx->IsCoinBase() || tx->vin.empty()) {
176 0 : return;
177 : }
178 :
179 0 : LOCK(cs);
180 0 : txFirstSeenTime.emplace(tx->GetHash(), nAcceptTime);
181 0 : }
182 :
183 0 : void ChainlockHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew)
184 : {
185 0 : m_chainlocks.AcceptedBlockHeader(pindexNew);
186 0 : }
187 :
188 0 : void ChainlockHandler::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
189 : {
190 0 : if (!m_mn_sync.IsBlockchainSynced()) {
191 0 : 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 0 : int64_t curTime = GetTime<std::chrono::seconds>().count();
196 : {
197 0 : LOCK(cs);
198 0 : for (const auto& tx : pblock->vtx) {
199 0 : if (!tx->IsCoinBase() && !tx->vin.empty()) {
200 0 : txFirstSeenTime.emplace(tx->GetHash(), curTime);
201 0 : }
202 : }
203 0 : }
204 0 : }
205 :
206 2143 : bool ChainlockHandler::IsTxSafeForMining(const uint256& txid) const
207 : {
208 2143 : auto tx_age{0s};
209 : {
210 2143 : LOCK(cs);
211 2143 : auto it = txFirstSeenTime.find(txid);
212 2143 : if (it != txFirstSeenTime.end()) {
213 2035 : tx_age = GetTime<std::chrono::seconds>() - it->second;
214 2035 : }
215 2143 : }
216 :
217 2143 : 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 0 : void ChainlockHandler::EnforceBestChainLock()
223 : {
224 0 : if (!isEnabled) {
225 0 : return;
226 : }
227 :
228 0 : AssertLockNotHeld(cs);
229 0 : AssertLockNotHeld(cs_main);
230 :
231 :
232 0 : auto [clsig, currentBestChainLockBlockIndex] = m_chainlocks.GetBestChainlockWithPindex();
233 0 : if (currentBestChainLockBlockIndex == nullptr) {
234 : // we don't have the header/block, so we can't do anything right now
235 0 : return;
236 : }
237 :
238 0 : 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 0 : LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- enforcing block %s via CLSIG (%s)\n", __func__,
244 : currentBestChainLockBlockIndex->GetBlockHash().ToString(), clsig.ToString());
245 0 : m_chainman.ActiveChainstate().EnforceBlock(dummy_state, currentBestChainLockBlockIndex);
246 :
247 :
248 0 : if (/*activateNeeded =*/WITH_LOCK(::cs_main, return m_chainman.ActiveTip()->GetAncestor(
249 0 : currentBestChainLockBlockIndex->nHeight)) !=
250 0 : currentBestChainLockBlockIndex) {
251 0 : 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 0 : LOCK(::cs_main);
256 0 : if (m_chainman.ActiveTip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex) {
257 0 : return;
258 : }
259 0 : }
260 :
261 : {
262 0 : LOCK(cs);
263 0 : if (lastNotifyChainLockBlockIndex == currentBestChainLockBlockIndex) return;
264 0 : lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex;
265 0 : }
266 :
267 0 : GetMainSignals().NotifyChainLock(currentBestChainLockBlockIndex, std::make_shared<chainlock::ChainLockSig>(clsig),
268 0 : clsig.ToString());
269 0 : uiInterface.NotifyChainLock(clsig.getBlockHash().ToString(), clsig.getHeight());
270 0 : ::g_stats_client->gauge("chainlocks.blockHeight", clsig.getHeight(), 1.0f);
271 0 : }
272 :
273 0 : void ChainlockHandler::CleanupFromSigner(const std::vector<std::shared_ptr<Uint256HashSet>>& cleanup_txes)
274 : {
275 0 : LOCK(cs);
276 0 : for (const auto& tx : cleanup_txes) {
277 0 : for (const uint256& txid : *tx) {
278 0 : txFirstSeenTime.erase(txid);
279 : }
280 : }
281 0 : }
282 :
283 0 : void ChainlockHandler::Cleanup()
284 : {
285 0 : constexpr auto CLEANUP_INTERVAL{30s};
286 0 : if (!m_mn_sync.IsBlockchainSynced()) {
287 0 : return;
288 : }
289 :
290 0 : if (!cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) {
291 0 : return;
292 : }
293 :
294 : {
295 0 : LOCK(cs);
296 0 : for (auto it = seenChainLocks.begin(); it != seenChainLocks.end();) {
297 0 : if (GetTime<std::chrono::seconds>() - it->second >= CLEANUP_SEEN_TIMEOUT) {
298 0 : it = seenChainLocks.erase(it);
299 0 : } else {
300 0 : ++it;
301 : }
302 : }
303 0 : }
304 :
305 0 : LOCK(::cs_main);
306 0 : LOCK2(mempool.cs, cs); // need mempool.cs due to GetTransaction calls
307 0 : for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end();) {
308 0 : uint256 hashBlock;
309 0 : if (auto tx = GetTransaction(nullptr, &mempool, it->first, Params().GetConsensus(), hashBlock); !tx) {
310 : // tx has vanished, probably due to conflicts
311 0 : it = txFirstSeenTime.erase(it);
312 0 : } else if (!hashBlock.IsNull()) {
313 0 : const auto* pindex = m_chainman.m_blockman.LookupBlockIndex(hashBlock);
314 0 : assert(pindex); // GetTransaction gave us that hashBlock, it should resolve to a valid block index
315 0 : if (m_chainman.ActiveTip()->GetAncestor(pindex->nHeight) == pindex &&
316 0 : m_chainman.ActiveChain().Height() - pindex->nHeight > chainlock::TX_CONFIRM_THRESHOLD) {
317 : // tx is sufficiently deep, we can stop tracking it
318 0 : it = txFirstSeenTime.erase(it);
319 0 : } else {
320 0 : ++it;
321 : }
322 0 : } else {
323 0 : ++it;
324 : }
325 : }
326 0 : }
327 :
328 : } // namespace chainlock
|