Line data Source code
1 : // Copyright (c) 2014-2015 The Dash developers
2 : // Copyright (c) 2015-2022 The PIVX Core developers
3 : // Distributed under the MIT/X11 software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "budget/finalizedbudget.h"
7 :
8 : #include "masternodeman.h"
9 : #include "validation.h"
10 :
11 67 : CFinalizedBudget::CFinalizedBudget() :
12 : fAutoChecked(false),
13 : fValid(true),
14 : strInvalid(),
15 : mapVotes(),
16 : strBudgetName(""),
17 : nBlockStart(0),
18 : vecBudgetPayments(),
19 : nFeeTXHash(UINT256_ZERO),
20 : strProposals(""),
21 67 : nTime(0)
22 67 : { }
23 :
24 13 : CFinalizedBudget::CFinalizedBudget(const std::string& name,
25 : int blockstart,
26 : const std::vector<CTxBudgetPayment>& vecBudgetPaymentsIn,
27 13 : const uint256& nfeetxhash):
28 : fAutoChecked(false),
29 : fValid(true),
30 : strInvalid(),
31 : mapVotes(),
32 : strBudgetName(name),
33 : nBlockStart(blockstart),
34 : vecBudgetPayments(vecBudgetPaymentsIn),
35 : nFeeTXHash(nfeetxhash),
36 : strProposals(""),
37 26 : nTime(0)
38 13 : { }
39 :
40 14 : bool CFinalizedBudget::ParseBroadcast(CDataStream& broadcast)
41 : {
42 14 : *this = CFinalizedBudget();
43 14 : try {
44 14 : broadcast >> LIMITED_STRING(strBudgetName, 20);
45 14 : broadcast >> nBlockStart;
46 14 : broadcast >> vecBudgetPayments;
47 14 : broadcast >> nFeeTXHash;
48 0 : } catch (std::exception& e) {
49 0 : return error("Unable to deserialize finalized budget broadcast: %s", e.what());
50 : }
51 : return true;
52 : }
53 :
54 48 : bool CFinalizedBudget::AddOrUpdateVote(const CFinalizedBudgetVote& vote, std::string& strError)
55 : {
56 96 : const COutPoint& mnId = vote.GetVin().prevout;
57 48 : const int64_t voteTime = vote.GetTime();
58 96 : std::string strAction = "New vote inserted:";
59 :
60 95 : if (mapVotes.count(mnId)) {
61 1 : const int64_t oldTime = mapVotes[mnId].GetTime();
62 1 : if (oldTime > voteTime) {
63 0 : strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString());
64 0 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
65 0 : return false;
66 : }
67 1 : if (voteTime - oldTime < BUDGET_VOTE_UPDATE_MIN) {
68 2 : strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n",
69 2 : vote.GetHash().ToString(), voteTime - oldTime, BUDGET_VOTE_UPDATE_MIN);
70 1 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
71 1 : return false;
72 : }
73 0 : strAction = "Existing vote updated:";
74 : }
75 :
76 47 : mapVotes[mnId] = vote;
77 83 : LogPrint(BCLog::MNBUDGET, "%s: %s %s\n", __func__, strAction.c_str(), vote.GetHash().ToString().c_str());
78 : return true;
79 : }
80 :
81 0 : UniValue CFinalizedBudget::GetVotesObject() const
82 : {
83 0 : UniValue ret(UniValue::VOBJ);
84 0 : for (const auto& it: mapVotes) {
85 0 : const CFinalizedBudgetVote& vote = it.second;
86 0 : ret.pushKV(vote.GetVin().prevout.ToStringShort(), vote.ToJSON());
87 : }
88 0 : return ret;
89 : }
90 :
91 129 : void CFinalizedBudget::SetSynced(bool synced)
92 : {
93 511 : for (auto& it: mapVotes) {
94 382 : CFinalizedBudgetVote& vote = it.second;
95 382 : if (synced) {
96 257 : if (vote.IsValid()) vote.SetSynced(true);
97 : } else {
98 382 : vote.SetSynced(false);
99 : }
100 : }
101 129 : }
102 :
103 18 : bool CFinalizedBudget::CheckProposals(const std::map<uint256, CBudgetProposal>& mapWinningProposals) const
104 : {
105 18 : if (mapWinningProposals.empty()) {
106 2 : LogPrint(BCLog::MNBUDGET,"%s: No Budget-Proposals found, aborting\n", __func__);
107 2 : return false;
108 : }
109 :
110 16 : if (mapWinningProposals.size() != vecBudgetPayments.size()) {
111 0 : LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposal length (%ld) doesn't match Budget-Payment length (%ld).\n",
112 : __func__, mapWinningProposals.size(), vecBudgetPayments.size());
113 0 : return false;
114 : }
115 :
116 32 : for (unsigned int i = 0; i < vecBudgetPayments.size(); i++) {
117 32 : LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - nProp %d %s\n", __func__, i, vecBudgetPayments[i].nProposalHash.ToString());
118 48 : LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - Payee %d %s\n", __func__, i, HexStr(vecBudgetPayments[i].payee));
119 16 : LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - nAmount %d %lli\n", __func__, i, vecBudgetPayments[i].nAmount);
120 : }
121 :
122 32 : for (const auto& it: mapWinningProposals) {
123 32 : LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - nProp %s\n", __func__, (it.first).ToString());
124 48 : LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - Payee %s\n", __func__, HexStr((it.second).GetPayee()));
125 16 : LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - nAmount %lli\n", __func__, (it.second).GetAmount());
126 : }
127 :
128 31 : for (const CTxBudgetPayment& p : vecBudgetPayments) {
129 16 : const auto& it = mapWinningProposals.find(p.nProposalHash);
130 16 : if (it == mapWinningProposals.end()) {
131 2 : LogPrint(BCLog::MNBUDGET,"%s: Proposal %s not found\n", __func__, p.nProposalHash.ToString());
132 1 : return false;
133 : }
134 :
135 15 : const CBudgetProposal& prop = it->second;
136 15 : if (p.payee != prop.GetPayee()) {
137 0 : LogPrint(BCLog::MNBUDGET,"%s: payee doesn't match %s != %s\n", __func__, HexStr(p.payee), HexStr(prop.GetPayee()));
138 0 : return false;
139 : }
140 :
141 15 : if (p.nAmount != prop.GetAmount()) {
142 0 : LogPrint(BCLog::MNBUDGET,"%s: payee amount doesn't match %lli != %lli\n", __func__, p.nAmount, prop.GetAmount());
143 0 : return false;
144 : }
145 : }
146 :
147 15 : LogPrint(BCLog::MNBUDGET,"%s: Finalized Budget Matches! Submitting Vote.\n", __func__);
148 : return true;
149 : }
150 :
151 17 : CAmount CFinalizedBudget::GetTotalPayout() const
152 : {
153 17 : CAmount ret = 0;
154 :
155 34 : for (auto & vecBudgetPayment : vecBudgetPayments) {
156 17 : ret += vecBudgetPayment.nAmount;
157 : }
158 :
159 17 : return ret;
160 : }
161 :
162 16 : std::vector<uint256> CFinalizedBudget::GetProposalsHashes() const
163 : {
164 16 : std::vector<uint256> vHashes;
165 32 : for (const CTxBudgetPayment& budgetPayment : vecBudgetPayments) {
166 16 : vHashes.push_back(budgetPayment.nProposalHash);
167 : }
168 16 : return vHashes;
169 : }
170 :
171 319 : void CFinalizedBudget::SyncVotes(CNode* pfrom, bool fPartial, int& nInvCount) const
172 : {
173 1269 : for (const auto& it: mapVotes) {
174 950 : const CFinalizedBudgetVote& vote = it.second;
175 950 : if (vote.IsValid() && (!fPartial || !vote.IsSynced())) {
176 335 : pfrom->PushInventory(CInv(MSG_BUDGET_FINALIZED_VOTE, vote.GetHash()));
177 335 : nInvCount++;
178 : }
179 : }
180 319 : }
181 :
182 17 : bool CFinalizedBudget::CheckStartEnd()
183 : {
184 17 : if (nBlockStart == 0) {
185 0 : strInvalid = "Invalid BlockStart == 0";
186 0 : return false;
187 : }
188 :
189 : // Must be the correct block for payment to happen (once a month)
190 17 : if (nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) {
191 0 : strInvalid = "Invalid BlockStart";
192 0 : return false;
193 : }
194 :
195 : // The following 2 checks check the same (basically if vecBudgetPayments.size() > 100)
196 17 : if (GetBlockEnd() - nBlockStart + 1 > (int) MAX_PROPOSALS_PER_CYCLE) {
197 0 : strInvalid = "Invalid BlockEnd";
198 0 : return false;
199 : }
200 17 : if ((int)vecBudgetPayments.size() > (int) MAX_PROPOSALS_PER_CYCLE) {
201 0 : strInvalid = "Invalid budget payments count (too many)";
202 0 : return false;
203 : }
204 :
205 : return true;
206 : }
207 :
208 17 : bool CFinalizedBudget::CheckAmount(const CAmount& nTotalBudget)
209 : {
210 : // Can only pay out 10% of the possible coins (min value of coins)
211 17 : if (GetTotalPayout() > nTotalBudget) {
212 0 : strInvalid = "Invalid Payout (more than max)";
213 0 : return false;
214 : }
215 :
216 : return true;
217 : }
218 :
219 17 : bool CFinalizedBudget::CheckName()
220 : {
221 17 : if (strBudgetName == "") {
222 0 : strInvalid = "Invalid Budget Name";
223 0 : return false;
224 : }
225 :
226 : return true;
227 : }
228 :
229 106 : bool CFinalizedBudget::updateExpired(int nCurrentHeight)
230 : {
231 : // Remove finalized budgets 2 * MAX_PROPOSALS_PER_CYCLE blocks after their end
232 106 : const int nBlockEnd = GetBlockEnd();
233 106 : if (nCurrentHeight >= nBlockEnd + 2 * (int) MAX_PROPOSALS_PER_CYCLE) {
234 1 : strInvalid = strprintf("(ends at block %ld) too old and obsolete (current %ld)", nBlockEnd, nCurrentHeight);
235 1 : return true;
236 : }
237 :
238 : return false;
239 : }
240 :
241 17 : bool CFinalizedBudget::IsWellFormed(const CAmount& nTotalBudget)
242 : {
243 17 : return CheckStartEnd() && CheckAmount(nTotalBudget) && CheckName();
244 : }
245 :
246 106 : bool CFinalizedBudget::UpdateValid(int nCurrentHeight)
247 : {
248 106 : fValid = false;
249 :
250 106 : if (updateExpired(nCurrentHeight)) {
251 : return false;
252 : }
253 :
254 105 : fValid = true;
255 105 : strInvalid.clear();
256 105 : return true;
257 : }
258 :
259 5310 : int CFinalizedBudget::GetVoteCount() const
260 : {
261 5310 : int ret = 0;
262 20069 : for (const auto& it : mapVotes) {
263 14759 : if (it.second.IsValid()) {
264 10380 : ret++;
265 : }
266 : }
267 5310 : return ret;
268 : }
269 :
270 0 : std::vector<uint256> CFinalizedBudget::GetVotesHashes() const
271 : {
272 0 : std::vector<uint256> vRet;
273 0 : for (const auto& it: mapVotes) {
274 0 : vRet.push_back(it.second.GetHash());
275 : }
276 0 : return vRet;
277 : }
278 :
279 27 : bool CFinalizedBudget::IsPaidAlready(const uint256& nProposalHash, const uint256& nBlockHash, int nBlockHeight) const
280 : {
281 : // Remove budget-payments from former/future payment cycles
282 27 : int nPaidBlockHeight = 0;
283 27 : uint256 nOldProposalHash;
284 :
285 27 : for(auto it = mapPayment_History.begin(); it != mapPayment_History.end(); /* No incrementation needed */ ) {
286 27 : nPaidBlockHeight = (*it).second.second;
287 27 : if((nPaidBlockHeight < GetBlockStart()) || (nPaidBlockHeight > GetBlockEnd())) {
288 0 : nOldProposalHash = (*it).first;
289 0 : LogPrint(BCLog::MNBUDGET, "%s: Budget Proposal %s, Block %d from old cycle deleted\n",
290 : __func__, nOldProposalHash.ToString().c_str(), nPaidBlockHeight);
291 0 : it = mapPayment_History.erase(it);
292 : } else {
293 81 : ++it;
294 : }
295 : }
296 :
297 : // Now that we only have payments from the current payment cycle check if this budget was paid already
298 27 : if(mapPayment_History.count(nProposalHash) == 0) {
299 : // New proposal payment, insert into map for checks with later blocks from this cycle
300 13 : mapPayment_History.emplace(std::piecewise_construct,
301 13 : std::forward_as_tuple(nProposalHash),
302 13 : std::forward_as_tuple(nBlockHash, nBlockHeight));
303 33 : LogPrint(BCLog::MNBUDGET, "%s: Budget Proposal %s, Block %d (%s) added to payment history (size=%d)\n",
304 : __func__, nProposalHash.ToString(), nBlockHeight, nBlockHash.ToString(), mapPayment_History.size());
305 13 : return false;
306 : }
307 : // This budget payment was already checked/paid
308 14 : const uint256& nPaidBlockHash = mapPayment_History.at(nProposalHash).first;
309 :
310 : // If we are checking a different block, and the paid one is on chain
311 : // -> reject transaction so it gets paid to a masternode instead
312 14 : if (nBlockHash != nPaidBlockHash) {
313 18 : LOCK(cs_main);
314 9 : CBlockIndex* pindex = LookupBlockIndex(nPaidBlockHash);
315 11 : return pindex && chainActive.Contains(pindex);
316 : }
317 :
318 : // Re-checking same block. Not a double payment.
319 : return false;
320 : }
321 :
322 27 : TrxValidationStatus CFinalizedBudget::IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const
323 : {
324 27 : const int nBlockEnd = GetBlockEnd();
325 27 : if (nBlockHeight > nBlockEnd) {
326 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid block - height: %d end: %d\n", __func__, nBlockHeight, nBlockEnd);
327 0 : return TrxValidationStatus::InValid;
328 : }
329 27 : if (nBlockHeight < nBlockStart) {
330 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid block - height: %d start: %d\n", __func__, nBlockHeight, nBlockStart);
331 0 : return TrxValidationStatus::InValid;
332 : }
333 :
334 27 : const int nCurrentBudgetPayment = nBlockHeight - nBlockStart;
335 27 : if (nCurrentBudgetPayment > (int)vecBudgetPayments.size() - 1) {
336 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid last block - current budget payment: %d of %d\n",
337 : __func__, nCurrentBudgetPayment + 1, (int)vecBudgetPayments.size());
338 0 : return TrxValidationStatus::InValid;
339 : }
340 :
341 : // Check if this proposal was paid already. If so, pay a masternode instead
342 27 : if(IsPaidAlready(vecBudgetPayments[nCurrentBudgetPayment].nProposalHash, nBlockHash, nBlockHeight)) {
343 0 : LogPrint(BCLog::MNBUDGET,"%s: Double Budget Payment of %d for proposal %d detected. Paying a masternode instead.\n",
344 : __func__, vecBudgetPayments[nCurrentBudgetPayment].nAmount, vecBudgetPayments[nCurrentBudgetPayment].nProposalHash.GetHex());
345 : // No matter what we've found before, stop all checks here. In future releases there might be more than one budget payment
346 : // per block, so even if the first one was not paid yet this one disables all budget payments for this block.
347 0 : return TrxValidationStatus::DoublePayment;
348 : }
349 :
350 : // Search the payment
351 27 : const CScript& scriptExpected = vecBudgetPayments[nCurrentBudgetPayment].payee;
352 27 : const CAmount& amountExpected = vecBudgetPayments[nCurrentBudgetPayment].nAmount;
353 : // Budget payment is usually the last output of coinstake txes, iterate backwards
354 33 : for (auto out = txNew.vout.rbegin(); out != txNew.vout.rend(); ++out) {
355 86 : LogPrint(BCLog::MNBUDGET,"%s: nCurrentBudgetPayment=%d, payee=%s == out.scriptPubKey=%s, amount=%ld == out.nValue=%ld\n",
356 : __func__, nCurrentBudgetPayment, HexStr(scriptExpected), HexStr(out->scriptPubKey), amountExpected, out->nValue);
357 30 : if (scriptExpected == out->scriptPubKey && amountExpected == out->nValue) {
358 : // payment found
359 38 : LogPrint(BCLog::MNBUDGET,"%s: Found valid Budget Payment of %d for proposal %d\n",
360 : __func__, amountExpected, vecBudgetPayments[nCurrentBudgetPayment].nProposalHash.GetHex());
361 24 : return TrxValidationStatus::Valid;
362 : }
363 : }
364 :
365 : // payment not found
366 30 : CTxDestination address1;
367 3 : ExtractDestination(scriptExpected, address1);
368 3 : LogPrint(BCLog::MNBUDGET,"%s: Missing required payment - %s: %d c: %d\n",
369 : __func__, EncodeDestination(address1), amountExpected, nCurrentBudgetPayment);
370 3 : return TrxValidationStatus::InValid;
371 : }
372 :
373 39 : bool CFinalizedBudget::GetBudgetPaymentByBlock(int64_t nBlockHeight, CTxBudgetPayment& payment) const
374 : {
375 39 : int i = nBlockHeight - GetBlockStart();
376 39 : if (i < 0) return false;
377 39 : if (i > (int)vecBudgetPayments.size() - 1) return false;
378 39 : payment = vecBudgetPayments[i];
379 39 : return true;
380 : }
381 :
382 41 : bool CFinalizedBudget::GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, CAmount& nAmount) const
383 : {
384 41 : int i = nBlockHeight - GetBlockStart();
385 41 : if (i < 0) return false;
386 41 : if (i > (int)vecBudgetPayments.size() - 1) return false;
387 41 : payee = vecBudgetPayments[i].payee;
388 41 : nAmount = vecBudgetPayments[i].nAmount;
389 41 : return true;
390 : }
391 :
392 : // return broadcast serialization
393 14 : CDataStream CFinalizedBudget::GetBroadcast() const
394 : {
395 14 : CDataStream broadcast(SER_NETWORK, PROTOCOL_VERSION);
396 14 : broadcast.reserve(1000);
397 14 : broadcast << LIMITED_STRING(strBudgetName, 20);
398 14 : broadcast << nBlockStart;
399 14 : broadcast << vecBudgetPayments;
400 14 : broadcast << nFeeTXHash;
401 14 : return broadcast;
402 : }
403 :
404 :
405 2 : bool CFinalizedBudget::operator>(const CFinalizedBudget& other) const
406 : {
407 2 : const int count = GetVoteCount();
408 2 : const int otherCount = other.GetVoteCount();
409 :
410 2 : if (count == otherCount) return UintToArith256(GetFeeTXHash()) > UintToArith256(other.GetFeeTXHash());
411 :
412 2 : return count > otherCount;
413 : }
414 :
415 13 : void CFinalizedBudget::Relay()
416 : {
417 13 : CInv inv(MSG_BUDGET_FINALIZED, GetHash());
418 13 : g_connman->RelayInv(inv);
419 13 : }
|