LCOV - code coverage report
Current view: top level - src/governance - signing.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 0 165 0.0 %
Date: 2026-06-25 07:23:51 Functions: 0 14 0.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 <governance/signing.h>
       6             : 
       7             : #include <active/masternode.h>
       8             : #include <evo/deterministicmns.h>
       9             : #include <governance/governance.h>
      10             : #include <governance/superblock.h>
      11             : #include <masternode/sync.h>
      12             : 
      13             : #include <chainparams.h>
      14             : #include <logging.h>
      15             : #include <timedata.h>
      16             : #include <util/time.h>
      17             : #include <validation.h>
      18             : 
      19             : #include <algorithm>
      20             : #include <ranges>
      21             : 
      22             : namespace {
      23             : constexpr std::chrono::seconds GOVERNANCE_FUDGE_WINDOW{2h};
      24             : } // anonymous namespace
      25             : 
      26           0 : GovernanceSigner::GovernanceSigner(CConnman& connman, CDeterministicMNManager& dmnman, CGovernanceManager& govman,
      27             :                                    governance::SuperblockManager& superblocks, const CActiveMasternodeManager& mn_activeman,
      28             :                                    const ChainstateManager& chainman, const CMasternodeSync& mn_sync) :
      29           0 :     m_connman{connman},
      30           0 :     m_dmnman{dmnman},
      31           0 :     m_govman{govman},
      32           0 :     m_superblocks{superblocks},
      33           0 :     m_mn_activeman{mn_activeman},
      34           0 :     m_chainman{chainman},
      35           0 :     m_mn_sync{mn_sync}
      36           0 : {
      37           0 : }
      38             : 
      39           0 : GovernanceSigner::~GovernanceSigner() = default;
      40             : 
      41           0 : std::optional<const CSuperblock> GovernanceSigner::CreateSuperblockCandidate(int nHeight) const
      42             : {
      43           0 :     if (!m_govman.IsValid()) return std::nullopt;
      44           0 :     if (!m_mn_sync.IsSynced()) return std::nullopt;
      45           0 :     if (nHeight % Params().GetConsensus().nSuperblockCycle <
      46           0 :         Params().GetConsensus().nSuperblockCycle - Params().GetConsensus().nSuperblockMaturityWindow)
      47           0 :         return std::nullopt;
      48           0 :     if (HasAlreadyVotedFundingTrigger()) return std::nullopt;
      49             : 
      50           0 :     const auto approvedProposals = m_govman.GetApprovedProposals(m_dmnman.GetListAtChainTip());
      51           0 :     if (approvedProposals.empty()) {
      52           0 :         LogPrint(BCLog::GOBJECT, "%s -- nHeight:%d empty approvedProposals\n", __func__, nHeight);
      53           0 :         return std::nullopt;
      54             :     }
      55             : 
      56           0 :     std::vector<CGovernancePayment> payments;
      57             :     int nLastSuperblock;
      58             :     int nNextSuperblock;
      59             : 
      60           0 :     CSuperblock::GetNearestSuperblocksHeights(nHeight, nLastSuperblock, nNextSuperblock);
      61           0 :     auto SBEpochTime = static_cast<int64_t>(GetTime<std::chrono::seconds>().count() +
      62           0 :                                             (nNextSuperblock - nHeight) * 2.62 * 60);
      63           0 :     auto governanceBudget = CSuperblock::GetPaymentsLimit(m_chainman.ActiveChain(), nNextSuperblock);
      64             : 
      65           0 :     CAmount budgetAllocated{};
      66           0 :     for (const auto& proposal : approvedProposals) {
      67             :         // Extract payment address and amount from proposal
      68           0 :         UniValue jproposal = proposal->GetJSONObject();
      69             : 
      70           0 :         CTxDestination dest = DecodeDestination(jproposal["payment_address"].getValStr());
      71           0 :         if (!IsValidDestination(dest)) continue;
      72             : 
      73           0 :         CAmount nAmount{};
      74             :         try {
      75           0 :             nAmount = ParsePaymentAmount(jproposal["payment_amount"].getValStr());
      76           0 :         } catch (const std::runtime_error& e) {
      77           0 :             LogPrint(BCLog::GOBJECT, "%s -- nHeight:%d Skipping payment exception:%s\n", __func__, nHeight, e.what());
      78             :             continue;
      79           0 :         }
      80             : 
      81             :         // Construct CGovernancePayment object and make sure it is valid
      82           0 :         CGovernancePayment payment(dest, nAmount, proposal->GetHash());
      83           0 :         if (!payment.IsValid()) continue;
      84             : 
      85             :         // Skip proposals that are too expensive
      86           0 :         if (budgetAllocated + payment.nAmount > governanceBudget) continue;
      87             : 
      88           0 :         int64_t windowStart = jproposal["start_epoch"].getInt<int64_t>() - count_seconds(GOVERNANCE_FUDGE_WINDOW);
      89           0 :         int64_t windowEnd = jproposal["end_epoch"].getInt<int64_t>() + count_seconds(GOVERNANCE_FUDGE_WINDOW);
      90             : 
      91             :         // Skip proposals if the SB isn't within the proposal time window
      92           0 :         if (SBEpochTime < windowStart) {
      93           0 :             LogPrint(BCLog::GOBJECT, "%s -- nHeight:%d SB:%d windowStart:%d\n", __func__, nHeight, SBEpochTime,
      94             :                      windowStart);
      95           0 :             continue;
      96             :         }
      97           0 :         if (SBEpochTime > windowEnd) {
      98           0 :             LogPrint(BCLog::GOBJECT, "%s -- nHeight:%d SB:%d windowEnd:%d\n", __func__, nHeight, SBEpochTime, windowEnd);
      99           0 :             continue;
     100             :         }
     101             : 
     102             :         // Keep track of total budget allocation
     103           0 :         budgetAllocated += payment.nAmount;
     104             : 
     105             :         // Add the payment
     106           0 :         payments.push_back(payment);
     107           0 :     }
     108             : 
     109             :     // No proposals made the cut
     110           0 :     if (payments.empty()) {
     111           0 :         LogPrint(BCLog::GOBJECT, "%s -- CreateSuperblockCandidate nHeight:%d empty payments\n", __func__, nHeight);
     112           0 :         return std::nullopt;
     113             :     }
     114             : 
     115             :     // Sort by proposal hash descending
     116           0 :     std::sort(payments.begin(), payments.end(), [](const CGovernancePayment& a, const CGovernancePayment& b) {
     117           0 :         return UintToArith256(a.proposalHash) > UintToArith256(b.proposalHash);
     118             :     });
     119             : 
     120             :     // Create Superblock
     121           0 :     return CSuperblock(nNextSuperblock, std::move(payments));
     122           0 : }
     123             : 
     124           0 : std::optional<const CGovernanceObject> GovernanceSigner::CreateGovernanceTrigger(const std::optional<const CSuperblock>& sb_opt)
     125             : {
     126             :     // no sb_opt, no trigger
     127           0 :     if (!sb_opt.has_value()) return std::nullopt;
     128             : 
     129             :     // TODO: Check if nHashParentIn, nRevision and nCollateralHashIn are correct
     130           0 :     LOCK(::cs_main);
     131             : 
     132             :     // Check if identical trigger (equal DataHash()) is already created (signed by other masternode)
     133           0 :     CGovernanceObject gov_sb(uint256(), 1, GetAdjustedTime(), uint256(), sb_opt.value().GetHexStrData());
     134           0 :     if (auto identical_sb = m_govman.FindGovernanceObjectByDataHash(gov_sb.GetDataHash())) {
     135             :         // Somebody submitted a trigger with the same data, support it instead of submitting a duplicate
     136           0 :         return std::make_optional<CGovernanceObject>(*identical_sb);
     137             :     }
     138             : 
     139             :     // Nobody submitted a trigger we'd like to see, so let's do it but only if we are the payee
     140           0 :     const CBlockIndex* tip = m_chainman.ActiveChain().Tip();
     141           0 :     const auto mnList = m_dmnman.GetListForBlock(tip);
     142           0 :     const auto mn_payees = mnList.GetProjectedMNPayees(tip);
     143             : 
     144           0 :     if (mn_payees.empty()) {
     145           0 :         LogPrint(BCLog::GOBJECT, "%s -- payee list is empty\n", __func__);
     146           0 :         return std::nullopt;
     147             :     }
     148             : 
     149           0 :     if (mn_payees.front()->proTxHash != m_mn_activeman.GetProTxHash()) {
     150           0 :         LogPrint(BCLog::GOBJECT, "%s -- we are not the payee, skipping\n", __func__);
     151           0 :         return std::nullopt;
     152             :     }
     153           0 :     gov_sb.SetMasternodeOutpoint(m_mn_activeman.GetOutPoint());
     154           0 :     gov_sb.SetSignature(m_mn_activeman.SignBasic(gov_sb.GetSignatureHash()));
     155             : 
     156           0 :     if (std::string strError; !gov_sb.IsValidLocally(m_dmnman.GetListAtChainTip(), m_chainman, strError, true)) {
     157           0 :         LogPrint(BCLog::GOBJECT, "%s -- Created trigger is invalid:%s\n", __func__, strError);
     158           0 :         return std::nullopt;
     159             :     }
     160             : 
     161           0 :     if (!m_govman.MasternodeRateCheck(gov_sb)) {
     162           0 :         LogPrint(BCLog::GOBJECT, "%s -- Trigger rejected because of rate check failure hash(%s)\n", __func__,
     163             :                  gov_sb.GetHash().ToString());
     164           0 :         return std::nullopt;
     165             :     }
     166             : 
     167             :     // The trigger we just created looks good, submit it
     168           0 :     m_govman.AddGovernanceObject(gov_sb, "<local>");
     169           0 :     return std::make_optional<CGovernanceObject>(gov_sb);
     170           0 : }
     171             : 
     172           0 : void GovernanceSigner::VoteGovernanceTriggers(const std::optional<const CGovernanceObject>& trigger_opt)
     173             : {
     174             :     // only active masternodes can vote on triggers
     175           0 :     if (m_mn_activeman.GetProTxHash().IsNull()) return;
     176             : 
     177           0 :     LOCK(::cs_main);
     178             : 
     179           0 :     if (trigger_opt.has_value()) {
     180             :         // We should never vote "yes" on another trigger or the same trigger twice
     181           0 :         assert(!votedFundingYesTriggerHash.has_value());
     182             :         // Vote YES-FUNDING for the trigger we like, unless we already did
     183           0 :         const uint256 gov_sb_hash = trigger_opt.value().GetHash();
     184           0 :         bool voted_already{false};
     185           0 :         if (vote_rec_t voteRecord; trigger_opt.value().GetCurrentMNVotes(m_mn_activeman.GetOutPoint(), voteRecord)) {
     186           0 :             const auto& strFunc = __func__;
     187             :             // Let's see if there is a VOTE_SIGNAL_FUNDING vote from us already
     188           0 :             voted_already = std::ranges::any_of(voteRecord.mapInstances, [&](const auto& voteInstancePair) {
     189           0 :                 if (voteInstancePair.first == VOTE_SIGNAL_FUNDING) {
     190           0 :                     if (voteInstancePair.second.eOutcome == VOTE_OUTCOME_YES) {
     191           0 :                         votedFundingYesTriggerHash = gov_sb_hash;
     192           0 :                     }
     193           0 :                     LogPrint(BCLog::GOBJECT, /* Continued */
     194             :                              "%s -- Not voting YES-FUNDING for trigger:%s, we voted %s for it already\n", strFunc,
     195             :                              gov_sb_hash.ToString(),
     196             :                              CGovernanceVoting::ConvertOutcomeToString(voteInstancePair.second.eOutcome));
     197           0 :                     return true;
     198             :                 }
     199           0 :                 return false;
     200           0 :             });
     201           0 :         }
     202           0 :         if (!voted_already) {
     203             :             // No previous VOTE_SIGNAL_FUNDING was found, vote now
     204           0 :             if (VoteFundingTrigger(gov_sb_hash, VOTE_OUTCOME_YES)) {
     205           0 :                 LogPrint(BCLog::GOBJECT, "%s -- Voting YES-FUNDING for new trigger:%s success\n", __func__,
     206             :                          gov_sb_hash.ToString());
     207           0 :                 votedFundingYesTriggerHash = gov_sb_hash;
     208           0 :             } else {
     209           0 :                 LogPrint(BCLog::GOBJECT, "%s -- Voting YES-FUNDING for new trigger:%s failed\n", __func__,
     210             :                          gov_sb_hash.ToString());
     211             :                 // this should never happen, bail out
     212           0 :                 return;
     213             :             }
     214           0 :         }
     215           0 :     }
     216             : 
     217             :     // Vote NO-FUNDING for the rest of the active triggers
     218           0 :     const auto activeTriggers = m_superblocks.GetActiveTriggers();
     219           0 :     for (const auto& trigger : activeTriggers) {
     220           0 :         auto govobj = m_govman.FindGovernanceObject(trigger->GetGovernanceObjHash());
     221           0 :         if (!govobj) {
     222           0 :             LogPrint(BCLog::GOBJECT, "%s -- Not voting NO-FUNDING for unknown trigger %s\n", __func__,
     223             :                      trigger->GetGovernanceObjHash().ToString());
     224           0 :             continue;
     225             :         }
     226             : 
     227           0 :         const uint256 trigger_hash = govobj->GetHash();
     228           0 :         if (trigger->GetBlockHeight() <= m_govman.GetCachedBlockHeight()) {
     229             :             // ignore triggers from the past
     230           0 :             LogPrint(BCLog::GOBJECT, "%s -- Not voting NO-FUNDING for outdated trigger:%s\n", __func__,
     231             :                      trigger_hash.ToString());
     232           0 :             continue;
     233             :         }
     234           0 :         if (trigger_hash == votedFundingYesTriggerHash) {
     235             :             // Skip actual trigger
     236           0 :             LogPrint(BCLog::GOBJECT, "%s -- Not voting NO-FUNDING for trigger:%s, we voted yes for it already\n",
     237             :                      __func__, trigger_hash.ToString());
     238           0 :             continue;
     239             :         }
     240           0 :         if (vote_rec_t voteRecord; govobj->GetCurrentMNVotes(m_mn_activeman.GetOutPoint(), voteRecord)) {
     241           0 :             const auto& strFunc = __func__;
     242           0 :             if (std::ranges::any_of(voteRecord.mapInstances, [&](const auto& voteInstancePair) {
     243           0 :                     if (voteInstancePair.first == VOTE_SIGNAL_FUNDING) {
     244           0 :                         LogPrint(BCLog::GOBJECT, /* Continued */
     245             :                                  "%s -- Not voting NO-FUNDING for trigger:%s, we voted %s for it already\n", strFunc,
     246             :                                  trigger_hash.ToString(),
     247             :                                  CGovernanceVoting::ConvertOutcomeToString(voteInstancePair.second.eOutcome));
     248           0 :                         return true;
     249             :                     }
     250           0 :                     return false;
     251           0 :                 })) {
     252           0 :                 continue;
     253             :             }
     254           0 :         }
     255           0 :         if (!VoteFundingTrigger(trigger_hash, VOTE_OUTCOME_NO)) {
     256           0 :             LogPrint(BCLog::GOBJECT, "%s -- Voting NO-FUNDING for trigger:%s failed\n", __func__, trigger_hash.ToString());
     257             :             // failing here is ok-ish
     258           0 :             continue;
     259             :         }
     260           0 :         LogPrint(BCLog::GOBJECT, "%s -- Voting NO-FUNDING for trigger:%s success\n", __func__, trigger_hash.ToString());
     261           0 :     }
     262           0 : }
     263             : 
     264           0 : bool GovernanceSigner::VoteFundingTrigger(const uint256& nHash, const vote_outcome_enum_t outcome)
     265             : {
     266           0 :     CGovernanceVote vote(m_mn_activeman.GetOutPoint(), nHash, VOTE_SIGNAL_FUNDING, outcome);
     267           0 :     vote.SetTime(GetAdjustedTime());
     268           0 :     vote.SetSignature(m_mn_activeman.SignBasic(vote.GetSignatureHash()));
     269             : 
     270           0 :     CGovernanceException exception;
     271           0 :     if (!m_govman.ProcessVoteAndRelay(vote, exception, m_connman)) {
     272           0 :         LogPrint(BCLog::GOBJECT, "%s -- Vote FUNDING %d for trigger:%s failed:%s\n", __func__, outcome,
     273             :                  nHash.ToString(), exception.what());
     274           0 :         return false;
     275             :     }
     276             : 
     277           0 :     return true;
     278           0 : }
     279             : 
     280           0 : bool GovernanceSigner::HasAlreadyVotedFundingTrigger() const
     281             : {
     282           0 :     return votedFundingYesTriggerHash.has_value();
     283             : }
     284             : 
     285           0 : void GovernanceSigner::ResetVotedFundingTrigger()
     286             : {
     287           0 :     votedFundingYesTriggerHash = std::nullopt;
     288           0 : }
     289             : 
     290           0 : void GovernanceSigner::UpdatedBlockTip(const CBlockIndex* pindex)
     291             : {
     292           0 :     const auto sb_opt = CreateSuperblockCandidate(pindex->nHeight);
     293           0 :     const auto trigger_opt = CreateGovernanceTrigger(sb_opt);
     294           0 :     VoteGovernanceTriggers(trigger_opt);
     295           0 :     CSuperblock_sptr pSuperblock;
     296           0 :     if (m_superblocks.GetBestSuperblock(m_dmnman.GetListAtChainTip(), pSuperblock, pindex->nHeight)) {
     297           0 :         ResetVotedFundingTrigger();
     298           0 :     }
     299           0 : }

Generated by: LCOV version 1.16