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 1320 : 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 660 : m_connman{connman},
30 660 : m_dmnman{dmnman},
31 660 : m_govman{govman},
32 660 : m_superblocks{superblocks},
33 660 : m_mn_activeman{mn_activeman},
34 660 : m_chainman{chainman},
35 660 : m_mn_sync{mn_sync}
36 660 : {
37 660 : }
38 :
39 1320 : GovernanceSigner::~GovernanceSigner() = default;
40 :
41 81273 : std::optional<const CSuperblock> GovernanceSigner::CreateSuperblockCandidate(int nHeight) const
42 : {
43 81273 : if (!m_govman.IsValid()) return std::nullopt;
44 81273 : if (!m_mn_sync.IsSynced()) return std::nullopt;
45 159538 : if (nHeight % Params().GetConsensus().nSuperblockCycle <
46 79769 : Params().GetConsensus().nSuperblockCycle - Params().GetConsensus().nSuperblockMaturityWindow)
47 39630 : return std::nullopt;
48 40139 : if (HasAlreadyVotedFundingTrigger()) return std::nullopt;
49 :
50 39483 : const auto approvedProposals = m_govman.GetApprovedProposals(m_dmnman.GetListAtChainTip());
51 39483 : if (approvedProposals.empty()) {
52 39279 : LogPrint(BCLog::GOBJECT, "%s -- nHeight:%d empty approvedProposals\n", __func__, nHeight);
53 39279 : return std::nullopt;
54 : }
55 :
56 204 : std::vector<CGovernancePayment> payments;
57 : int nLastSuperblock;
58 : int nNextSuperblock;
59 :
60 204 : CSuperblock::GetNearestSuperblocksHeights(nHeight, nLastSuperblock, nNextSuperblock);
61 204 : auto SBEpochTime = static_cast<int64_t>(GetTime<std::chrono::seconds>().count() +
62 204 : (nNextSuperblock - nHeight) * 2.62 * 60);
63 204 : auto governanceBudget = CSuperblock::GetPaymentsLimit(m_chainman.ActiveChain(), nNextSuperblock);
64 :
65 204 : CAmount budgetAllocated{};
66 780 : for (const auto& proposal : approvedProposals) {
67 : // Extract payment address and amount from proposal
68 576 : UniValue jproposal = proposal->GetJSONObject();
69 :
70 576 : CTxDestination dest = DecodeDestination(jproposal["payment_address"].getValStr());
71 576 : if (!IsValidDestination(dest)) continue;
72 :
73 576 : CAmount nAmount{};
74 : try {
75 576 : nAmount = ParsePaymentAmount(jproposal["payment_amount"].getValStr());
76 576 : } 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 576 : CGovernancePayment payment(dest, nAmount, proposal->GetHash());
83 576 : if (!payment.IsValid()) continue;
84 :
85 : // Skip proposals that are too expensive
86 576 : if (budgetAllocated + payment.nAmount > governanceBudget) continue;
87 :
88 408 : int64_t windowStart = jproposal["start_epoch"].getInt<int64_t>() - count_seconds(GOVERNANCE_FUDGE_WINDOW);
89 408 : 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 408 : 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 408 : 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 408 : budgetAllocated += payment.nAmount;
104 :
105 : // Add the payment
106 408 : payments.push_back(payment);
107 576 : }
108 :
109 : // No proposals made the cut
110 204 : 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 408 : std::sort(payments.begin(), payments.end(), [](const CGovernancePayment& a, const CGovernancePayment& b) {
117 204 : return UintToArith256(a.proposalHash) > UintToArith256(b.proposalHash);
118 : });
119 :
120 : // Create Superblock
121 204 : return CSuperblock(nNextSuperblock, std::move(payments));
122 81273 : }
123 :
124 81273 : std::optional<const CGovernanceObject> GovernanceSigner::CreateGovernanceTrigger(const std::optional<const CSuperblock>& sb_opt)
125 : {
126 : // no sb_opt, no trigger
127 81273 : if (!sb_opt.has_value()) return std::nullopt;
128 :
129 : // TODO: Check if nHashParentIn, nRevision and nCollateralHashIn are correct
130 204 : LOCK(::cs_main);
131 :
132 : // Check if identical trigger (equal DataHash()) is already created (signed by other masternode)
133 204 : CGovernanceObject gov_sb(uint256(), 1, GetAdjustedTime(), uint256(), sb_opt.value().GetHexStrData());
134 258 : 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 54 : 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 150 : const CBlockIndex* tip = m_chainman.ActiveChain().Tip();
141 150 : const auto mnList = m_dmnman.GetListForBlock(tip);
142 150 : const auto mn_payees = mnList.GetProjectedMNPayees(tip);
143 :
144 150 : if (mn_payees.empty()) {
145 0 : LogPrint(BCLog::GOBJECT, "%s -- payee list is empty\n", __func__);
146 0 : return std::nullopt;
147 : }
148 :
149 150 : if (mn_payees.front()->proTxHash != m_mn_activeman.GetProTxHash()) {
150 118 : LogPrint(BCLog::GOBJECT, "%s -- we are not the payee, skipping\n", __func__);
151 118 : return std::nullopt;
152 : }
153 32 : gov_sb.SetMasternodeOutpoint(m_mn_activeman.GetOutPoint());
154 32 : gov_sb.SetSignature(m_mn_activeman.SignBasic(gov_sb.GetSignatureHash()));
155 :
156 32 : 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 32 : 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 32 : m_govman.AddGovernanceObject(gov_sb, "<local>");
169 32 : return std::make_optional<CGovernanceObject>(gov_sb);
170 81273 : }
171 :
172 81273 : void GovernanceSigner::VoteGovernanceTriggers(const std::optional<const CGovernanceObject>& trigger_opt)
173 : {
174 : // only active masternodes can vote on triggers
175 81273 : if (m_mn_activeman.GetProTxHash().IsNull()) return;
176 :
177 71623 : LOCK(::cs_main);
178 :
179 71623 : if (trigger_opt.has_value()) {
180 : // We should never vote "yes" on another trigger or the same trigger twice
181 86 : assert(!votedFundingYesTriggerHash.has_value());
182 : // Vote YES-FUNDING for the trigger we like, unless we already did
183 86 : const uint256 gov_sb_hash = trigger_opt.value().GetHash();
184 86 : bool voted_already{false};
185 86 : 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 86 : if (!voted_already) {
203 : // No previous VOTE_SIGNAL_FUNDING was found, vote now
204 86 : if (VoteFundingTrigger(gov_sb_hash, VOTE_OUTCOME_YES)) {
205 86 : LogPrint(BCLog::GOBJECT, "%s -- Voting YES-FUNDING for new trigger:%s success\n", __func__,
206 : gov_sb_hash.ToString());
207 86 : votedFundingYesTriggerHash = gov_sb_hash;
208 86 : } 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 86 : }
215 86 : }
216 :
217 : // Vote NO-FUNDING for the rest of the active triggers
218 71623 : const auto activeTriggers = m_superblocks.GetActiveTriggers();
219 75459 : for (const auto& trigger : activeTriggers) {
220 3836 : auto govobj = m_govman.FindGovernanceObject(trigger->GetGovernanceObjHash());
221 3836 : 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 3836 : const uint256 trigger_hash = govobj->GetHash();
228 3836 : if (trigger->GetBlockHeight() <= m_govman.GetCachedBlockHeight()) {
229 : // ignore triggers from the past
230 2500 : LogPrint(BCLog::GOBJECT, "%s -- Not voting NO-FUNDING for outdated trigger:%s\n", __func__,
231 : trigger_hash.ToString());
232 2500 : continue;
233 : }
234 1336 : if (trigger_hash == votedFundingYesTriggerHash) {
235 : // Skip actual trigger
236 823 : LogPrint(BCLog::GOBJECT, "%s -- Not voting NO-FUNDING for trigger:%s, we voted yes for it already\n",
237 : __func__, trigger_hash.ToString());
238 823 : continue;
239 : }
240 956 : if (vote_rec_t voteRecord; govobj->GetCurrentMNVotes(m_mn_activeman.GetOutPoint(), voteRecord)) {
241 443 : const auto& strFunc = __func__;
242 886 : if (std::ranges::any_of(voteRecord.mapInstances, [&](const auto& voteInstancePair) {
243 443 : if (voteInstancePair.first == VOTE_SIGNAL_FUNDING) {
244 443 : 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 443 : return true;
249 : }
250 0 : return false;
251 443 : })) {
252 443 : continue;
253 : }
254 0 : }
255 70 : 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 70 : LogPrint(BCLog::GOBJECT, "%s -- Voting NO-FUNDING for trigger:%s success\n", __func__, trigger_hash.ToString());
261 3836 : }
262 81273 : }
263 :
264 156 : bool GovernanceSigner::VoteFundingTrigger(const uint256& nHash, const vote_outcome_enum_t outcome)
265 : {
266 156 : CGovernanceVote vote(m_mn_activeman.GetOutPoint(), nHash, VOTE_SIGNAL_FUNDING, outcome);
267 156 : vote.SetTime(GetAdjustedTime());
268 156 : vote.SetSignature(m_mn_activeman.SignBasic(vote.GetSignatureHash()));
269 :
270 156 : CGovernanceException exception;
271 156 : 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 156 : return true;
278 156 : }
279 :
280 40139 : bool GovernanceSigner::HasAlreadyVotedFundingTrigger() const
281 : {
282 40139 : return votedFundingYesTriggerHash.has_value();
283 : }
284 :
285 81 : void GovernanceSigner::ResetVotedFundingTrigger()
286 : {
287 81 : votedFundingYesTriggerHash = std::nullopt;
288 81 : }
289 :
290 81273 : void GovernanceSigner::UpdatedBlockTip(const CBlockIndex* pindex)
291 : {
292 81273 : const auto sb_opt = CreateSuperblockCandidate(pindex->nHeight);
293 81273 : const auto trigger_opt = CreateGovernanceTrigger(sb_opt);
294 81273 : VoteGovernanceTriggers(trigger_opt);
295 81273 : CSuperblock_sptr pSuperblock;
296 81273 : if (m_superblocks.GetBestSuperblock(m_dmnman.GetListAtChainTip(), pSuperblock, pindex->nHeight)) {
297 81 : ResetVotedFundingTrigger();
298 81 : }
299 81273 : }
|