Line data Source code
1 : // Copyright (c) 2014-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 <spork.h>
6 :
7 : #include <chainparams.h>
8 : #include <flat-database.h>
9 : #include <key_io.h>
10 : #include <logging.h>
11 : #include <messagesigner.h>
12 : #include <protocol.h>
13 : #include <script/standard.h>
14 : #include <timedata.h>
15 : #include <util/helpers.h>
16 : #include <util/message.h> // for MESSAGE_MAGIC
17 : #include <util/string.h>
18 :
19 : #include <ranges>
20 : #include <string>
21 :
22 146 : const std::string SporkStore::SERIALIZATION_VERSION_STRING = "CSporkManager-Version-2";
23 :
24 195005 : std::optional<SporkValue> CSporkManager::SporkValueIfActive(SporkId nSporkID) const
25 : {
26 195005 : AssertLockHeld(cs);
27 :
28 195005 : if (!mapSporksActive.count(nSporkID)) return std::nullopt;
29 :
30 : {
31 0 : LOCK(cs_cache);
32 0 : if (auto it = mapSporksCachedValues.find(nSporkID); it != mapSporksCachedValues.end()) {
33 0 : return {it->second};
34 : }
35 0 : }
36 :
37 : // calc how many values we have and how many signers vote for every value
38 0 : std::unordered_map<SporkValue, int> mapValueCounts;
39 0 : for (const auto& [_, spork] : mapSporksActive.at(nSporkID)) {
40 0 : mapValueCounts[spork.nValue]++;
41 0 : if (mapValueCounts.at(spork.nValue) >= nMinSporkKeys) {
42 : // nMinSporkKeys is always more than the half of the max spork keys number,
43 : // so there is only one such value and we can stop here
44 : {
45 0 : LOCK(cs_cache);
46 0 : mapSporksCachedValues[nSporkID] = spork.nValue;
47 0 : }
48 0 : return {spork.nValue};
49 : }
50 : }
51 :
52 0 : return std::nullopt;
53 195005 : }
54 :
55 0 : void SporkStore::Clear()
56 : {
57 0 : LOCK(cs);
58 0 : mapSporksActive.clear();
59 0 : mapSporksByHash.clear();
60 : // sporkPubKeyID and sporkPrivKey should be set in init.cpp,
61 : // we should not alter them here.
62 0 : }
63 :
64 1254 : CSporkManager::CSporkManager() :
65 627 : m_db{std::make_unique<db_type>("sporks.dat", "magicSporkCache")}
66 627 : {
67 627 : }
68 :
69 1252 : CSporkManager::~CSporkManager()
70 626 : {
71 626 : if (!is_valid) return;
72 0 : m_db->Store(*this);
73 1252 : }
74 :
75 0 : bool CSporkManager::LoadCache()
76 : {
77 0 : assert(m_db != nullptr);
78 0 : is_valid = m_db->Load(*this);
79 0 : if (is_valid) {
80 0 : CheckAndRemove();
81 0 : }
82 0 : return is_valid;
83 : }
84 :
85 0 : void CSporkManager::CheckAndRemove()
86 : {
87 0 : LOCK(cs);
88 :
89 0 : if (setSporkPubKeyIDs.empty()) return;
90 :
91 0 : for (auto itActive = mapSporksActive.begin(); itActive != mapSporksActive.end();) {
92 0 : auto itSignerPair = itActive->second.begin();
93 0 : while (itSignerPair != itActive->second.end()) {
94 0 : bool fHasValidSig = setSporkPubKeyIDs.find(itSignerPair->first) != setSporkPubKeyIDs.end() &&
95 0 : itSignerPair->second.CheckSignature(itSignerPair->first);
96 0 : if (!fHasValidSig) {
97 0 : mapSporksByHash.erase(itSignerPair->second.GetHash());
98 0 : itActive->second.erase(itSignerPair++);
99 0 : continue;
100 : }
101 0 : ++itSignerPair;
102 : }
103 0 : if (itActive->second.empty()) {
104 0 : mapSporksActive.erase(itActive++);
105 0 : continue;
106 : }
107 0 : ++itActive;
108 : }
109 :
110 0 : for (auto itByHash = mapSporksByHash.begin(); itByHash != mapSporksByHash.end();) {
111 0 : bool found = false;
112 0 : for (const auto& signer : setSporkPubKeyIDs) {
113 0 : if (itByHash->second.CheckSignature(signer)) {
114 0 : found = true;
115 0 : break;
116 : }
117 : }
118 0 : if (!found) {
119 0 : mapSporksByHash.erase(itByHash++);
120 0 : continue;
121 : }
122 0 : ++itByHash;
123 : }
124 0 : }
125 :
126 0 : std::optional<CKeyID> CSporkManager::GetValidSporkSigner(const CSporkMessage& spork) const
127 : {
128 0 : if (spork.nTimeSigned > GetAdjustedTime() + 2 * 60 * 60) {
129 0 : LogPrint(BCLog::SPORK, "CSporkManager::%s -- ERROR: too far into the future\n", __func__);
130 0 : return std::nullopt;
131 : }
132 :
133 0 : auto opt_keyIDSigner = spork.GetSignerKeyID();
134 :
135 0 : if (opt_keyIDSigner == std::nullopt || WITH_LOCK(cs, return !setSporkPubKeyIDs.count(*opt_keyIDSigner))) {
136 0 : LogPrint(BCLog::SPORK, "CSporkManager::%s -- ERROR: invalid signature\n", __func__);
137 0 : return std::nullopt;
138 : }
139 0 : return opt_keyIDSigner;
140 0 : }
141 :
142 0 : bool CSporkManager::ProcessSpork(const CSporkMessage& spork, const CKeyID& keyIDSigner, std::string_view peer_log_suffix)
143 : {
144 0 : uint256 hash = spork.GetHash();
145 0 : std::string strLogMsg{strprintf("SPORK -- hash: %s id: %d value: %10d%s", hash.ToString(), spork.nSporkID,
146 0 : spork.nValue, peer_log_suffix)};
147 :
148 : {
149 0 : LOCK(cs); // make sure to not lock this together with cs_main
150 0 : if (mapSporksActive.count(spork.nSporkID)) {
151 0 : if (mapSporksActive[spork.nSporkID].count(keyIDSigner)) {
152 0 : if (mapSporksActive[spork.nSporkID][keyIDSigner].nTimeSigned >= spork.nTimeSigned) {
153 0 : LogPrint(BCLog::SPORK, "%s seen\n", strLogMsg);
154 0 : return false;
155 : } else {
156 0 : LogPrintf("%s updated\n", strLogMsg);
157 : }
158 0 : } else {
159 0 : LogPrintf("%s new signer\n", strLogMsg);
160 : }
161 0 : } else {
162 0 : LogPrintf("%s new\n", strLogMsg);
163 : }
164 :
165 0 : mapSporksByHash[hash] = spork;
166 0 : mapSporksActive[spork.nSporkID][keyIDSigner] = spork;
167 : // Clear cached values on new spork being processed
168 : {
169 0 : LOCK(cs_cache);
170 0 : mapSporksCachedActive.erase(spork.nSporkID);
171 0 : mapSporksCachedValues.erase(spork.nSporkID);
172 0 : }
173 0 : }
174 :
175 0 : return true;
176 0 : }
177 :
178 0 : std::unordered_map<SporkId, std::map<CKeyID, CSporkMessage>> CSporkManager::ActiveSporks() const
179 : {
180 0 : LOCK(cs); // make sure to not lock this together with cs_main
181 0 : return mapSporksActive;
182 0 : }
183 :
184 0 : std::optional<CInv> CSporkManager::UpdateSpork(SporkId nSporkID, SporkValue nValue)
185 : {
186 0 : CSporkMessage spork(nSporkID, nValue, GetAdjustedTime());
187 :
188 0 : LOCK(cs);
189 :
190 0 : if (!spork.Sign(sporkPrivKey)) {
191 0 : LogPrintf("CSporkManager::%s -- ERROR: signing failed for spork %d\n", __func__, nSporkID);
192 0 : return std::nullopt;
193 : }
194 :
195 0 : auto opt_keyIDSigner = spork.GetSignerKeyID();
196 0 : if (opt_keyIDSigner == std::nullopt || !setSporkPubKeyIDs.count(*opt_keyIDSigner)) {
197 0 : LogPrintf("CSporkManager::UpdateSpork: failed to find keyid for private key\n");
198 0 : return std::nullopt;
199 : }
200 :
201 0 : LogPrintf("CSporkManager::%s -- signed %d %s\n", __func__, nSporkID, spork.GetHash().ToString());
202 :
203 0 : mapSporksByHash[spork.GetHash()] = spork;
204 0 : mapSporksActive[nSporkID][*opt_keyIDSigner] = spork;
205 : // Clear cached values on new spork being processed
206 :
207 0 : LOCK(cs_cache);
208 0 : mapSporksCachedActive.erase(spork.nSporkID);
209 0 : mapSporksCachedValues.erase(spork.nSporkID);
210 :
211 :
212 0 : CInv inv(MSG_SPORK, spork.GetHash());
213 0 : return inv;
214 0 : }
215 :
216 795457 : bool CSporkManager::IsSporkActive(SporkId nSporkID) const
217 : {
218 : // If nSporkID is cached, and the cached value is true, then return early true
219 : {
220 795457 : LOCK(cs_cache);
221 795457 : if (auto it = mapSporksCachedActive.find(nSporkID); it != mapSporksCachedActive.end() && it->second) {
222 600443 : return true;
223 : }
224 795457 : }
225 :
226 195014 : SporkValue nSporkValue = GetSporkValue(nSporkID);
227 : // Get time is somewhat costly it looks like
228 195014 : bool ret = nSporkValue < GetAdjustedTime();
229 : // Only cache true values
230 195014 : if (ret) {
231 9 : LOCK(cs_cache);
232 9 : mapSporksCachedActive[nSporkID] = ret;
233 9 : }
234 195014 : return ret;
235 795457 : }
236 :
237 195014 : SporkValue CSporkManager::GetSporkValue(SporkId nSporkID) const
238 : {
239 : // Harden all sporks on Mainnet
240 195014 : if (!Params().IsTestChain()) {
241 9 : switch (nSporkID) {
242 : case SPORK_21_QUORUM_ALL_CONNECTED:
243 0 : return 1;
244 : default:
245 9 : return 0;
246 : }
247 : }
248 :
249 195005 : LOCK(cs);
250 :
251 195005 : if (auto opt_sporkValue = SporkValueIfActive(nSporkID)) {
252 0 : return *opt_sporkValue;
253 : }
254 :
255 :
256 195005 : if (auto optSpork = util::find_if_opt(sporkDefs,
257 679906 : [&nSporkID](const auto& sporkDef) { return sporkDef.sporkId == nSporkID; })) {
258 195005 : return optSpork->defaultValue;
259 : } else {
260 0 : LogPrint(BCLog::SPORK, "CSporkManager::GetSporkValue -- Unknown Spork ID %d\n", nSporkID);
261 0 : return -1;
262 : }
263 195014 : }
264 :
265 0 : SporkId CSporkManager::GetSporkIDByName(std::string_view strName)
266 : {
267 0 : if (auto optSpork = util::find_if_opt(sporkDefs,
268 0 : [&strName](const auto& sporkDef) { return sporkDef.name == strName; })) {
269 0 : return optSpork->sporkId;
270 : }
271 :
272 0 : LogPrint(BCLog::SPORK, "CSporkManager::GetSporkIDByName -- Unknown Spork name '%s'\n", strName);
273 0 : return SPORK_INVALID;
274 0 : }
275 :
276 0 : std::optional<CSporkMessage> CSporkManager::GetSporkByHash(const uint256& hash) const
277 : {
278 0 : LOCK(cs);
279 :
280 0 : if (const auto it = mapSporksByHash.find(hash); it != mapSporksByHash.end())
281 0 : return {it->second};
282 :
283 0 : return std::nullopt;
284 0 : }
285 :
286 0 : bool CSporkManager::SetSporkAddress(const std::string& strAddress)
287 : {
288 0 : LOCK(cs);
289 0 : CTxDestination dest = DecodeDestination(strAddress);
290 0 : const PKHash* pkhash = std::get_if<PKHash>(&dest);
291 0 : if (!pkhash) {
292 0 : LogPrintf("CSporkManager::SetSporkAddress -- Failed to parse spork address\n");
293 0 : return false;
294 : }
295 0 : setSporkPubKeyIDs.insert(ToKeyID(*pkhash));
296 0 : return true;
297 0 : }
298 :
299 0 : bool CSporkManager::SetMinSporkKeys(int minSporkKeys)
300 : {
301 0 : LOCK(cs);
302 0 : if (int maxKeysNumber = setSporkPubKeyIDs.size(); (minSporkKeys <= maxKeysNumber / 2) || (minSporkKeys > maxKeysNumber)) {
303 0 : LogPrintf("CSporkManager::SetMinSporkKeys -- Invalid min spork signers number: %d\n", minSporkKeys);
304 0 : return false;
305 : }
306 0 : nMinSporkKeys = minSporkKeys;
307 0 : return true;
308 0 : }
309 :
310 0 : bool CSporkManager::SetPrivKey(const std::string& strPrivKey)
311 : {
312 0 : CKey key;
313 0 : CPubKey pubKey;
314 0 : if (!CMessageSigner::GetKeysFromSecret(strPrivKey, key, pubKey)) {
315 0 : LogPrintf("CSporkManager::SetPrivKey -- Failed to parse private key\n");
316 0 : return false;
317 : }
318 :
319 0 : LOCK(cs);
320 0 : if (setSporkPubKeyIDs.find(pubKey.GetID()) == setSporkPubKeyIDs.end()) {
321 0 : LogPrintf("CSporkManager::SetPrivKey -- New private key does not belong to spork addresses\n");
322 0 : return false;
323 : }
324 :
325 0 : if (!CSporkMessage().Sign(key)) {
326 0 : LogPrintf("CSporkManager::SetPrivKey -- Test signing failed\n");
327 0 : return false;
328 : }
329 :
330 : // Test signing successful, proceed
331 0 : LogPrintf("CSporkManager::SetPrivKey -- Successfully initialized as spork signer\n");
332 0 : sporkPrivKey = key;
333 0 : return true;
334 0 : }
335 :
336 0 : std::string SporkStore::ToString() const
337 : {
338 0 : LOCK(cs);
339 0 : return strprintf("Sporks: %llu", mapSporksActive.size());
340 0 : }
341 :
342 0 : uint256 CSporkMessage::GetHash() const
343 : {
344 0 : return SerializeHash(*this);
345 : }
346 :
347 0 : uint256 CSporkMessage::GetSignatureHash() const
348 : {
349 0 : CHashWriter s(SER_GETHASH, 0);
350 0 : s << nSporkID;
351 0 : s << nValue;
352 0 : s << nTimeSigned;
353 0 : return s.GetHash();
354 : }
355 :
356 0 : bool CSporkMessage::Sign(const CKey& key)
357 : {
358 0 : if (!key.IsValid()) {
359 0 : LogPrintf("CSporkMessage::Sign -- signing key is not valid\n");
360 0 : return false;
361 : }
362 :
363 0 : CKeyID pubKeyId = key.GetPubKey().GetID();
364 :
365 : // Harden Spork6 so that it is active on testnet and no other networks
366 0 : if (std::string strError; Params().NetworkIDString() == CBaseChainParams::TESTNET) {
367 0 : uint256 hash = GetSignatureHash();
368 :
369 0 : if (!CHashSigner::SignHash(hash, key, vchSig)) {
370 0 : LogPrintf("CSporkMessage::Sign -- SignHash() failed\n");
371 0 : return false;
372 : }
373 :
374 0 : if (!CHashSigner::VerifyHash(hash, pubKeyId, vchSig, strError)) {
375 0 : LogPrintf("CSporkMessage::Sign -- VerifyHash() failed, error: %s\n", strError);
376 0 : return false;
377 : }
378 0 : } else {
379 0 : std::string strMessage = ToString(nSporkID) + ToString(nValue) + ToString(nTimeSigned);
380 :
381 0 : if (!CMessageSigner::SignMessage(strMessage, vchSig, key)) {
382 0 : LogPrintf("CSporkMessage::Sign -- SignMessage() failed\n");
383 0 : return false;
384 : }
385 :
386 0 : if (!CMessageSigner::VerifyMessage(pubKeyId, vchSig, strMessage, strError)) {
387 0 : LogPrintf("CSporkMessage::Sign -- VerifyMessage() failed, error: %s\n", strError);
388 0 : return false;
389 : }
390 0 : }
391 :
392 0 : return true;
393 0 : }
394 :
395 0 : bool CSporkMessage::CheckSignature(const CKeyID& pubKeyId) const
396 : {
397 : // Harden Spork6 so that it is active on testnet and no other networks
398 0 : if (std::string strError; Params().NetworkIDString() == CBaseChainParams::TESTNET) {
399 0 : uint256 hash = GetSignatureHash();
400 :
401 0 : if (!CHashSigner::VerifyHash(hash, pubKeyId, vchSig, strError)) {
402 0 : LogPrint(BCLog::SPORK, "CSporkMessage::CheckSignature -- VerifyHash() failed, error: %s\n", strError);
403 0 : return false;
404 : }
405 0 : } else {
406 0 : std::string strMessage = ToString(nSporkID) + ToString(nValue) + ToString(nTimeSigned);
407 :
408 0 : if (!CMessageSigner::VerifyMessage(pubKeyId, vchSig, strMessage, strError)) {
409 0 : LogPrint(BCLog::SPORK, "CSporkMessage::CheckSignature -- VerifyMessage() failed, error: %s\n", strError);
410 0 : return false;
411 : }
412 0 : }
413 :
414 0 : return true;
415 0 : }
416 :
417 0 : std::optional<CKeyID> CSporkMessage::GetSignerKeyID() const
418 : {
419 0 : CPubKey pubkeyFromSig;
420 : // Harden Spork6 so that it is active on testnet and no other networks
421 0 : if (Params().NetworkIDString() == CBaseChainParams::TESTNET) {
422 0 : if (!pubkeyFromSig.RecoverCompact(GetSignatureHash(), vchSig)) {
423 0 : return std::nullopt;
424 : }
425 0 : } else {
426 0 : std::string strMessage = ToString(nSporkID) + ToString(nValue) + ToString(nTimeSigned);
427 0 : CHashWriter ss(SER_GETHASH, 0);
428 0 : ss << MESSAGE_MAGIC;
429 0 : ss << strMessage;
430 0 : if (!pubkeyFromSig.RecoverCompact(ss.GetHash(), vchSig)) {
431 0 : return std::nullopt;
432 : }
433 0 : }
434 :
435 0 : return {pubkeyFromSig.GetID()};
436 0 : }
|