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