LCOV - code coverage report
Current view: top level - src - spork.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 207 264 78.4 %
Date: 2026-06-25 07:23:43 Functions: 27 29 93.1 %

          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        3308 : const std::string SporkStore::SERIALIZATION_VERSION_STRING = "CSporkManager-Version-2";
      23             : 
      24     6262516 : std::optional<SporkValue> CSporkManager::SporkValueIfActive(SporkId nSporkID) const
      25             : {
      26     6262516 :     AssertLockHeld(cs);
      27             : 
      28     6262516 :     if (!mapSporksActive.count(nSporkID)) return std::nullopt;
      29             : 
      30             :     {
      31      607582 :         LOCK(cs_cache);
      32      607582 :         if (auto it = mapSporksCachedValues.find(nSporkID); it != mapSporksCachedValues.end()) {
      33      603248 :             return {it->second};
      34             :         }
      35      607582 :     }
      36             : 
      37             :     // calc how many values we have and how many signers vote for every value
      38        4334 :     std::unordered_map<SporkValue, int> mapValueCounts;
      39       10029 :     for (const auto& [_, spork] : mapSporksActive.at(nSporkID)) {
      40        5695 :         mapValueCounts[spork.nValue]++;
      41        5695 :         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        3192 :                 LOCK(cs_cache);
      46        3192 :                 mapSporksCachedValues[nSporkID] = spork.nValue;
      47        3192 :             }
      48        3192 :             return {spork.nValue};
      49             :         }
      50             :     }
      51             : 
      52        1142 :     return std::nullopt;
      53     6262516 : }
      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        7124 : CSporkManager::CSporkManager() :
      65        3562 :     m_db{std::make_unique<db_type>("sporks.dat", "magicSporkCache")}
      66        3562 : {
      67        3562 : }
      68             : 
      69        7122 : CSporkManager::~CSporkManager()
      70        3561 : {
      71        3561 :     if (!is_valid) return;
      72        2897 :     m_db->Store(*this);
      73        7122 : }
      74             : 
      75        2897 : bool CSporkManager::LoadCache()
      76             : {
      77        2897 :     assert(m_db != nullptr);
      78        2897 :     is_valid = m_db->Load(*this);
      79        2897 :     if (is_valid) {
      80        2897 :         CheckAndRemove();
      81        2897 :     }
      82        2897 :     return is_valid;
      83             : }
      84             : 
      85        2897 : void CSporkManager::CheckAndRemove()
      86             : {
      87        2897 :     LOCK(cs);
      88             : 
      89        2897 :     if (setSporkPubKeyIDs.empty()) return;
      90             : 
      91        3963 :     for (auto itActive = mapSporksActive.begin(); itActive != mapSporksActive.end();) {
      92        1066 :         auto itSignerPair = itActive->second.begin();
      93        2174 :         while (itSignerPair != itActive->second.end()) {
      94        2156 :             bool fHasValidSig = setSporkPubKeyIDs.find(itSignerPair->first) != setSporkPubKeyIDs.end() &&
      95        1048 :                                 itSignerPair->second.CheckSignature(itSignerPair->first);
      96        1108 :             if (!fHasValidSig) {
      97          60 :                 mapSporksByHash.erase(itSignerPair->second.GetHash());
      98          60 :                 itActive->second.erase(itSignerPair++);
      99          60 :                 continue;
     100             :             }
     101        1048 :             ++itSignerPair;
     102             :         }
     103        1066 :         if (itActive->second.empty()) {
     104          18 :             mapSporksActive.erase(itActive++);
     105          18 :             continue;
     106             :         }
     107        1048 :         ++itActive;
     108             :     }
     109             : 
     110        4132 :     for (auto itByHash = mapSporksByHash.begin(); itByHash != mapSporksByHash.end();) {
     111        1235 :         bool found = false;
     112        1238 :         for (const auto& signer : setSporkPubKeyIDs) {
     113        1235 :             if (itByHash->second.CheckSignature(signer)) {
     114        1232 :                 found = true;
     115        1232 :                 break;
     116             :             }
     117             :         }
     118        1235 :         if (!found) {
     119           3 :             mapSporksByHash.erase(itByHash++);
     120           3 :             continue;
     121             :         }
     122        1232 :         ++itByHash;
     123             :     }
     124        2897 : }
     125             : 
     126        2391 : std::optional<CKeyID> CSporkManager::GetValidSporkSigner(const CSporkMessage& spork) const
     127             : {
     128        2391 :     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        2391 :     auto opt_keyIDSigner = spork.GetSignerKeyID();
     134             : 
     135        4782 :     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        2391 :     return opt_keyIDSigner;
     140        2391 : }
     141             : 
     142        2391 : bool CSporkManager::ProcessSpork(const CSporkMessage& spork, const CKeyID& keyIDSigner, std::string_view peer_log_suffix)
     143             : {
     144        2391 :     uint256 hash = spork.GetHash();
     145        4782 :     std::string strLogMsg{strprintf("SPORK -- hash: %s id: %d value: %10d%s", hash.ToString(), spork.nSporkID,
     146        2391 :                                     spork.nValue, peer_log_suffix)};
     147             : 
     148             :     {
     149        2391 :         LOCK(cs); // make sure to not lock this together with cs_main
     150        2391 :         if (mapSporksActive.count(spork.nSporkID)) {
     151        1111 :             if (mapSporksActive[spork.nSporkID].count(keyIDSigner)) {
     152         967 :                 if (mapSporksActive[spork.nSporkID][keyIDSigner].nTimeSigned >= spork.nTimeSigned) {
     153         409 :                     LogPrint(BCLog::SPORK, "%s seen\n", strLogMsg);
     154         409 :                     return false;
     155             :                 } else {
     156         558 :                     LogPrintf("%s updated\n", strLogMsg);
     157             :                 }
     158         558 :             } else {
     159         144 :                 LogPrintf("%s new signer\n", strLogMsg);
     160             :             }
     161         702 :         } else {
     162        1280 :             LogPrintf("%s new\n", strLogMsg);
     163             :         }
     164             : 
     165        1982 :         mapSporksByHash[hash] = spork;
     166        1982 :         mapSporksActive[spork.nSporkID][keyIDSigner] = spork;
     167             :         // Clear cached values on new spork being processed
     168             :         {
     169        1982 :             LOCK(cs_cache);
     170        1982 :             mapSporksCachedActive.erase(spork.nSporkID);
     171        1982 :             mapSporksCachedValues.erase(spork.nSporkID);
     172        1982 :         }
     173        2391 :     }
     174             : 
     175        1982 :     return true;
     176        2391 : }
     177             : 
     178         941 : std::unordered_map<SporkId, std::map<CKeyID, CSporkMessage>> CSporkManager::ActiveSporks() const
     179             : {
     180         941 :     LOCK(cs); // make sure to not lock this together with cs_main
     181         941 :     return mapSporksActive;
     182         941 : }
     183             : 
     184         405 : std::optional<CInv> CSporkManager::UpdateSpork(SporkId nSporkID, SporkValue nValue)
     185             : {
     186         405 :     CSporkMessage spork(nSporkID, nValue, GetAdjustedTime());
     187             : 
     188         405 :     LOCK(cs);
     189             : 
     190         405 :     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         405 :     auto opt_keyIDSigner = spork.GetSignerKeyID();
     196         405 :     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         405 :     LogPrintf("CSporkManager::%s -- signed %d %s\n", __func__, nSporkID, spork.GetHash().ToString());
     202             : 
     203         405 :     mapSporksByHash[spork.GetHash()] = spork;
     204         405 :     mapSporksActive[nSporkID][*opt_keyIDSigner] = spork;
     205             :     // Clear cached values on new spork being processed
     206             : 
     207         405 :     LOCK(cs_cache);
     208         405 :     mapSporksCachedActive.erase(spork.nSporkID);
     209         405 :     mapSporksCachedValues.erase(spork.nSporkID);
     210             : 
     211             : 
     212         405 :     CInv inv(MSG_SPORK, spork.GetHash());
     213         405 :     return inv;
     214         405 : }
     215             : 
     216     6373034 : bool CSporkManager::IsSporkActive(SporkId nSporkID) const
     217             : {
     218             :     // If nSporkID is cached, and the cached value is true, then return early true
     219             :     {
     220     6373034 :         LOCK(cs_cache);
     221     6373034 :         if (auto it = mapSporksCachedActive.find(nSporkID); it != mapSporksCachedActive.end() && it->second) {
     222     2369948 :             return true;
     223             :         }
     224     6373034 :     }
     225             : 
     226     4003086 :     SporkValue nSporkValue = GetSporkValue(nSporkID);
     227             :     // Get time is somewhat costly it looks like
     228     4003086 :     bool ret = nSporkValue < GetAdjustedTime();
     229             :     // Only cache true values
     230     4003086 :     if (ret) {
     231        2475 :         LOCK(cs_cache);
     232        2475 :         mapSporksCachedActive[nSporkID] = ret;
     233        2475 :     }
     234     4003086 :     return ret;
     235     6373034 : }
     236             : 
     237     6262525 : SporkValue CSporkManager::GetSporkValue(SporkId nSporkID) const
     238             : {
     239             :     // Harden all sporks on Mainnet
     240     6262525 :     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     6262516 :     LOCK(cs);
     250             : 
     251     6262516 :     if (auto opt_sporkValue = SporkValueIfActive(nSporkID)) {
     252      606440 :         return *opt_sporkValue;
     253             :     }
     254             : 
     255             : 
     256     5656076 :     if (auto optSpork = util::find_if_opt(sporkDefs,
     257    19243227 :                                           [&nSporkID](const auto& sporkDef) { return sporkDef.sporkId == nSporkID; })) {
     258     5656076 :         return optSpork->defaultValue;
     259             :     } else {
     260           0 :         LogPrint(BCLog::SPORK, "CSporkManager::GetSporkValue -- Unknown Spork ID %d\n", nSporkID);
     261           0 :         return -1;
     262             :     }
     263     6262525 : }
     264             : 
     265         405 : SporkId CSporkManager::GetSporkIDByName(std::string_view strName)
     266             : {
     267         405 :     if (auto optSpork = util::find_if_opt(sporkDefs,
     268        1287 :                                           [&strName](const auto& sporkDef) { return sporkDef.name == strName; })) {
     269         405 :         return optSpork->sporkId;
     270             :     }
     271             : 
     272           0 :     LogPrint(BCLog::SPORK, "CSporkManager::GetSporkIDByName -- Unknown Spork name '%s'\n", strName);
     273           0 :     return SPORK_INVALID;
     274         405 : }
     275             : 
     276        6990 : std::optional<CSporkMessage> CSporkManager::GetSporkByHash(const uint256& hash) const
     277             : {
     278        6990 :     LOCK(cs);
     279             : 
     280        6990 :     if (const auto it = mapSporksByHash.find(hash); it != mapSporksByHash.end())
     281        3432 :         return {it->second};
     282             : 
     283        3558 :     return std::nullopt;
     284        6990 : }
     285             : 
     286        3043 : bool CSporkManager::SetSporkAddress(const std::string& strAddress)
     287             : {
     288        3043 :     LOCK(cs);
     289        3043 :     CTxDestination dest = DecodeDestination(strAddress);
     290        3043 :     const PKHash* pkhash = std::get_if<PKHash>(&dest);
     291        3043 :     if (!pkhash) {
     292           0 :         LogPrintf("CSporkManager::SetSporkAddress -- Failed to parse spork address\n");
     293           0 :         return false;
     294             :     }
     295        3043 :     setSporkPubKeyIDs.insert(ToKeyID(*pkhash));
     296        3043 :     return true;
     297        3043 : }
     298             : 
     299        2935 : bool CSporkManager::SetMinSporkKeys(int minSporkKeys)
     300             : {
     301        2935 :     LOCK(cs);
     302        2935 :     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        2935 :     nMinSporkKeys = minSporkKeys;
     307        2935 :     return true;
     308        2935 : }
     309             : 
     310         291 : bool CSporkManager::SetPrivKey(const std::string& strPrivKey)
     311             : {
     312         291 :     CKey key;
     313         291 :     CPubKey pubKey;
     314         291 :     if (!CMessageSigner::GetKeysFromSecret(strPrivKey, key, pubKey)) {
     315           0 :         LogPrintf("CSporkManager::SetPrivKey -- Failed to parse private key\n");
     316           0 :         return false;
     317             :     }
     318             : 
     319         291 :     LOCK(cs);
     320         291 :     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         291 :     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         291 :     LogPrintf("CSporkManager::SetPrivKey -- Successfully initialized as spork signer\n");
     332         291 :     sporkPrivKey = key;
     333         291 :     return true;
     334         291 : }
     335             : 
     336        5619 : std::string SporkStore::ToString() const
     337             : {
     338        5619 :     LOCK(cs);
     339        5619 :     return strprintf("Sporks: %llu", mapSporksActive.size());
     340        5619 : }
     341             : 
     342        6735 : uint256 CSporkMessage::GetHash() const
     343             : {
     344        6735 :     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         696 : bool CSporkMessage::Sign(const CKey& key)
     357             : {
     358         696 :     if (!key.IsValid()) {
     359           0 :         LogPrintf("CSporkMessage::Sign -- signing key is not valid\n");
     360           0 :         return false;
     361             :     }
     362             : 
     363         696 :     CKeyID pubKeyId = key.GetPubKey().GetID();
     364             : 
     365             :     // Harden Spork6 so that it is active on testnet and no other networks
     366        1392 :     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         696 :         std::string strMessage = ToString(nSporkID) + ToString(nValue) + ToString(nTimeSigned);
     380             : 
     381         696 :         if (!CMessageSigner::SignMessage(strMessage, vchSig, key)) {
     382           0 :             LogPrintf("CSporkMessage::Sign -- SignMessage() failed\n");
     383           0 :             return false;
     384             :         }
     385             : 
     386         696 :         if (!CMessageSigner::VerifyMessage(pubKeyId, vchSig, strMessage, strError)) {
     387           0 :             LogPrintf("CSporkMessage::Sign -- VerifyMessage() failed, error: %s\n", strError);
     388           0 :             return false;
     389             :         }
     390         696 :     }
     391             : 
     392         696 :     return true;
     393         696 : }
     394             : 
     395        2283 : bool CSporkMessage::CheckSignature(const CKeyID& pubKeyId) const
     396             : {
     397             :     // Harden Spork6 so that it is active on testnet and no other networks
     398        4566 :     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        2283 :         std::string strMessage = ToString(nSporkID) + ToString(nValue) + ToString(nTimeSigned);
     407             : 
     408        2283 :         if (!CMessageSigner::VerifyMessage(pubKeyId, vchSig, strMessage, strError)) {
     409           3 :             LogPrint(BCLog::SPORK, "CSporkMessage::CheckSignature -- VerifyMessage() failed, error: %s\n", strError);
     410           3 :             return false;
     411             :         }
     412        2283 :     }
     413             : 
     414        2280 :     return true;
     415        2283 : }
     416             : 
     417        2796 : std::optional<CKeyID> CSporkMessage::GetSignerKeyID() const
     418             : {
     419        2796 :     CPubKey pubkeyFromSig;
     420             :     // Harden Spork6 so that it is active on testnet and no other networks
     421        2796 :     if (Params().NetworkIDString() == CBaseChainParams::TESTNET) {
     422           0 :         if (!pubkeyFromSig.RecoverCompact(GetSignatureHash(), vchSig)) {
     423           0 :             return std::nullopt;
     424             :         }
     425           0 :     } else {
     426        2796 :         std::string strMessage = ToString(nSporkID) + ToString(nValue) + ToString(nTimeSigned);
     427        2796 :         CHashWriter ss(SER_GETHASH, 0);
     428        2796 :         ss << MESSAGE_MAGIC;
     429        2796 :         ss << strMessage;
     430        2796 :         if (!pubkeyFromSig.RecoverCompact(ss.GetHash(), vchSig)) {
     431           0 :             return std::nullopt;
     432             :         }
     433        2796 :     }
     434             : 
     435        2796 :     return {pubkeyFromSig.GetID()};
     436        2796 : }

Generated by: LCOV version 1.16