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 <chainlock/signing.h>
6 :
7 : #include <chainlock/clsig.h>
8 : #include <chainlock/handler.h>
9 : #include <instantsend/instantsend.h>
10 : #include <llmq/signing_shares.h>
11 : #include <masternode/sync.h>
12 : #include <msg_result.h>
13 : #include <node/blockstorage.h>
14 : #include <scheduler.h>
15 : #include <util/thread.h>
16 : #include <validation.h>
17 :
18 : #include <thread>
19 :
20 : using node::ReadBlockFromDisk;
21 :
22 : namespace chainlock {
23 0 : ChainLockSigner::ChainLockSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks,
24 : ChainlockHandler& clhandler, const llmq::CInstantSendManager& isman,
25 : const llmq::CQuorumManager& qman, llmq::CSigningManager& sigman,
26 : llmq::CSigSharesManager& shareman, const CMasternodeSync& mn_sync) :
27 0 : m_chainstate{chainstate},
28 0 : m_chainlocks{chainlocks},
29 0 : m_clhandler{clhandler},
30 0 : m_isman{isman},
31 0 : m_qman{qman},
32 0 : m_sigman{sigman},
33 0 : m_shareman{shareman},
34 0 : m_mn_sync{mn_sync},
35 0 : m_scheduler{std::make_unique<CScheduler>()},
36 0 : m_scheduler_thread{
37 0 : std::make_unique<std::thread>(std::thread(util::TraceThread, "cls-schdlr", [&] { m_scheduler->serviceQueue(); }))}
38 0 : {
39 0 : }
40 :
41 0 : ChainLockSigner::~ChainLockSigner() { Stop(); }
42 :
43 0 : void ChainLockSigner::Start()
44 : {
45 0 : m_scheduler->scheduleEvery(
46 0 : [&]() {
47 0 : if (!m_chainlocks.IsSigningEnabled()) return;
48 : // regularly retry signing the current chaintip as it might have failed before due to missing islocks
49 0 : TrySignChainTip();
50 0 : Cleanup();
51 0 : },
52 0 : std::chrono::seconds{5});
53 0 : }
54 :
55 0 : void ChainLockSigner::Stop()
56 : {
57 0 : m_scheduler->stop();
58 0 : if (m_scheduler_thread->joinable()) m_scheduler_thread->join();
59 0 : }
60 :
61 0 : void ChainLockSigner::RegisterRecoveryInterface()
62 : {
63 0 : m_sigman.RegisterRecoveredSigsListener(this);
64 0 : }
65 :
66 0 : void ChainLockSigner::UnregisterRecoveryInterface()
67 : {
68 0 : m_sigman.UnregisterRecoveredSigsListener(this);
69 0 : }
70 :
71 0 : void ChainLockSigner::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
72 : {
73 0 : TrySignChainTip();
74 0 : }
75 :
76 0 : void ChainLockSigner::TrySignChainTip()
77 : {
78 0 : TRY_LOCK(cs_try_sign, locked);
79 0 : if (!locked) {
80 0 : return;
81 : }
82 :
83 0 : if (!m_mn_sync.IsBlockchainSynced()) {
84 0 : return;
85 : }
86 :
87 0 : if (!m_chainlocks.IsEnabled() || !m_chainlocks.IsSigningEnabled()) {
88 0 : return;
89 : }
90 :
91 0 : const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip());
92 :
93 0 : if (!pindex || !pindex->pprev) {
94 0 : return;
95 : }
96 :
97 : // DIP8 defines a process called "Signing attempts" which should run before the CLSIG is finalized
98 : // To simplify the initial implementation, we skip this process and directly try to create a CLSIG
99 : // This will fail when multiple blocks compete, but we accept this for the initial implementation.
100 : // Later, we'll add the multiple attempts process.
101 :
102 : {
103 0 : LOCK(cs_signer);
104 :
105 0 : if (pindex->nHeight == lastSignedHeight) {
106 : // already signed this one
107 0 : return;
108 : }
109 0 : }
110 :
111 0 : if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) {
112 : // already got the same CLSIG or a better one
113 0 : return;
114 : }
115 :
116 0 : if (m_chainlocks.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
117 : // don't sign if another conflicting CLSIG is already present. EnforceBestChainLock will later enforce
118 : // the correct chain.
119 0 : return;
120 : }
121 :
122 0 : LogPrint(BCLog::CHAINLOCKS, "%s -- trying to sign %s, height=%d\n", __func__, pindex->GetBlockHash().ToString(),
123 : pindex->nHeight);
124 :
125 : // When the new IX system is activated, we only try to ChainLock blocks which include safe transactions. A TX is
126 : // considered safe when it is islocked or at least known since 10 minutes (from mempool or block). These checks are
127 : // performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the
128 : // way down, we consider all TXs to be safe.
129 0 : if (m_isman.IsInstantSendEnabled()) {
130 0 : const auto* pindexWalk = pindex;
131 0 : while (pindexWalk != nullptr) {
132 0 : if (pindex->nHeight - pindexWalk->nHeight > TX_CONFIRM_THRESHOLD) {
133 : // no need to check further down, safe to assume that TXs below this height won't be
134 : // islocked anymore if they aren't already
135 0 : LogPrint(BCLog::CHAINLOCKS, "%s -- tip and previous %d blocks all safe\n", __func__, TX_CONFIRM_THRESHOLD);
136 0 : break;
137 : }
138 0 : if (m_chainlocks.HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) {
139 : // we don't care about islocks for TXs that are ChainLocked already
140 0 : LogPrint(BCLog::CHAINLOCKS, "%s -- chainlock at height %d\n", __func__, pindexWalk->nHeight);
141 0 : break;
142 : }
143 :
144 0 : auto txids = GetBlockTxs(pindexWalk->GetBlockHash());
145 0 : if (!txids) {
146 0 : pindexWalk = pindexWalk->pprev;
147 0 : continue;
148 : }
149 :
150 0 : for (const auto& txid : *txids) {
151 0 : if (!m_clhandler.IsTxSafeForMining(txid) && !m_isman.IsLocked(txid)) {
152 0 : LogPrint(BCLog::CHAINLOCKS, /* Continued */
153 : "%s -- not signing block %s due to TX %s not being islocked and not old enough.\n",
154 : __func__, pindexWalk->GetBlockHash().ToString(), txid.ToString());
155 0 : return;
156 : }
157 : }
158 :
159 0 : pindexWalk = pindexWalk->pprev;
160 0 : }
161 0 : }
162 :
163 0 : uint256 requestId = GenSigRequestId(pindex->nHeight);
164 0 : uint256 msgHash = pindex->GetBlockHash();
165 :
166 0 : if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) {
167 : // might have happened while we didn't hold cs
168 0 : return;
169 : }
170 : {
171 0 : LOCK(cs_signer);
172 0 : lastSignedHeight = pindex->nHeight;
173 0 : lastSignedRequestId = requestId;
174 0 : lastSignedMsgHash = msgHash;
175 0 : }
176 :
177 0 : m_shareman.AsyncSignIfMember(Params().GetConsensus().llmqTypeChainLocks, m_sigman, requestId, msgHash);
178 0 : }
179 :
180 0 : void ChainLockSigner::BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
181 : {
182 0 : AssertLockNotHeld(cs_signer);
183 0 : LOCK(cs_signer);
184 0 : blockTxs.erase(pindex->GetBlockHash());
185 0 : }
186 :
187 :
188 0 : void ChainLockSigner::BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
189 : {
190 0 : if (!m_mn_sync.IsBlockchainSynced()) {
191 0 : return;
192 : }
193 :
194 : // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are safe.
195 0 : const uint256& hash = pindex->GetBlockHash();
196 :
197 0 : AssertLockNotHeld(cs_signer);
198 0 : LOCK(cs_signer);
199 0 : auto it = blockTxs.find(hash);
200 0 : if (it == blockTxs.end()) {
201 : // We must create this entry even if there are no lockable transactions in the block, so that TrySignChainTip
202 : // later knows about this block
203 0 : it = blockTxs.emplace(hash, std::make_shared<Uint256HashSet>()).first;
204 0 : }
205 0 : auto& txids = *it->second;
206 0 : for (const auto& tx : block->vtx) {
207 0 : if (!tx->IsCoinBase() && !tx->vin.empty()) {
208 0 : txids.emplace(tx->GetHash());
209 0 : }
210 : }
211 0 : }
212 0 : ChainLockSigner::BlockTxs::mapped_type ChainLockSigner::GetBlockTxs(const uint256& blockHash)
213 : {
214 0 : AssertLockNotHeld(cs_signer);
215 0 : AssertLockNotHeld(::cs_main);
216 :
217 0 : ChainLockSigner::BlockTxs::mapped_type ret;
218 :
219 : {
220 0 : LOCK(cs_signer);
221 0 : auto it = blockTxs.find(blockHash);
222 0 : if (it != blockTxs.end()) {
223 0 : ret = it->second;
224 0 : }
225 0 : }
226 0 : if (!ret) {
227 : // This should only happen when freshly started.
228 : // If running for some time, SyncTransaction should have been called before which fills blockTxs.
229 0 : LogPrint(BCLog::CHAINLOCKS, "%s -- blockTxs for %s not found. Trying ReadBlockFromDisk\n", __func__,
230 : blockHash.ToString());
231 :
232 : uint32_t blockTime;
233 : {
234 0 : LOCK(::cs_main);
235 0 : const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(blockHash);
236 0 : if (!pindex) {
237 0 : return nullptr;
238 : }
239 0 : CBlock block;
240 0 : if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
241 0 : return nullptr;
242 : }
243 :
244 0 : ret = std::make_shared<Uint256HashSet>();
245 0 : for (const auto& tx : block.vtx) {
246 0 : if (tx->IsCoinBase() || tx->vin.empty()) {
247 0 : continue;
248 : }
249 0 : ret->emplace(tx->GetHash());
250 : }
251 :
252 0 : blockTime = block.nTime;
253 0 : }
254 : {
255 0 : LOCK(cs_signer);
256 0 : blockTxs.emplace(blockHash, ret);
257 0 : }
258 0 : m_clhandler.UpdateTxFirstSeenMap(*ret, blockTime);
259 0 : }
260 0 : return ret;
261 0 : }
262 :
263 0 : llmq::RecoveredSigResult ChainLockSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig)
264 : {
265 0 : if (!m_chainlocks.IsEnabled()) {
266 0 : return std::monostate{};
267 : }
268 :
269 0 : ChainLockSig clsig;
270 : {
271 0 : LOCK(cs_signer);
272 :
273 0 : if (recoveredSig.getId() != lastSignedRequestId || recoveredSig.getMsgHash() != lastSignedMsgHash) {
274 : // this is not what we signed, so lets not create a CLSIG for it
275 0 : return std::monostate{};
276 : }
277 0 : if (m_chainlocks.GetBestChainLockHeight() >= lastSignedHeight) {
278 : // already got the same or a better CLSIG through the CLSIG message
279 0 : return std::monostate{};
280 : }
281 :
282 0 : clsig = ChainLockSig(lastSignedHeight, lastSignedMsgHash, recoveredSig.sig.Get());
283 0 : }
284 : // TODO: split ProcessNewChainLock into network and non-network variants; when no peer
285 : // is specified (node == -1), only m_inventory is ever populated
286 0 : auto clresult = m_clhandler.ProcessNewChainLock(-1, clsig, m_qman, ::SerializeHash(clsig));
287 0 : if (!clresult.m_inventory.empty()) {
288 0 : return clresult.m_inventory.front();
289 : }
290 0 : return std::monostate{};
291 0 : }
292 :
293 0 : void ChainLockSigner::Cleanup()
294 : {
295 0 : constexpr auto CLEANUP_INTERVAL{30s};
296 0 : if (!m_mn_sync.IsBlockchainSynced()) {
297 0 : return;
298 : }
299 :
300 0 : if (!m_cleanup_throttler.TryCleanup(CLEANUP_INTERVAL)) {
301 0 : return;
302 : }
303 :
304 0 : AssertLockNotHeld(cs_signer);
305 0 : std::vector<std::shared_ptr<Uint256HashSet>> removed;
306 0 : LOCK2(::cs_main, cs_signer);
307 0 : for (auto it = blockTxs.begin(); it != blockTxs.end();) {
308 0 : const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(it->first);
309 0 : if (!pindex) {
310 0 : it = blockTxs.erase(it);
311 0 : } else if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) {
312 0 : removed.push_back(it->second);
313 0 : it = blockTxs.erase(it);
314 0 : } else if (m_chainlocks.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) {
315 0 : it = blockTxs.erase(it);
316 0 : } else {
317 0 : ++it;
318 : }
319 : }
320 0 : m_clhandler.CleanupFromSigner(removed);
321 0 : }
322 : } // namespace chainlock
|