LCOV - code coverage report
Current view: top level - src - spork.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 36 264 13.6 %
Date: 2026-06-25 07:23:51 Functions: 9 29 31.0 %

          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 : }

Generated by: LCOV version 1.16