PIVX Core  5.6.99
P2P Digital Currency
finalizedbudget.cpp
Go to the documentation of this file.
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 
7 
8 #include "masternodeman.h"
9 #include "validation.h"
10 
12  fAutoChecked(false),
13  fValid(true),
14  strInvalid(),
15  mapVotes(),
16  strBudgetName(""),
17  nBlockStart(0),
18  vecBudgetPayments(),
19  nFeeTXHash(UINT256_ZERO),
20  strProposals(""),
21  nTime(0)
22 { }
23 
25  int blockstart,
26  const std::vector<CTxBudgetPayment>& vecBudgetPaymentsIn,
27  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  nTime(0)
38 { }
39 
41 {
42  *this = CFinalizedBudget();
43  try {
44  broadcast >> LIMITED_STRING(strBudgetName, 20);
45  broadcast >> nBlockStart;
46  broadcast >> vecBudgetPayments;
47  broadcast >> nFeeTXHash;
48  } catch (std::exception& e) {
49  return error("Unable to deserialize finalized budget broadcast: %s", e.what());
50  }
51  return true;
52 }
53 
54 bool CFinalizedBudget::AddOrUpdateVote(const CFinalizedBudgetVote& vote, std::string& strError)
55 {
56  const COutPoint& mnId = vote.GetVin().prevout;
57  const int64_t voteTime = vote.GetTime();
58  std::string strAction = "New vote inserted:";
59 
60  if (mapVotes.count(mnId)) {
61  const int64_t oldTime = mapVotes[mnId].GetTime();
62  if (oldTime > voteTime) {
63  strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString());
64  LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
65  return false;
66  }
67  if (voteTime - oldTime < BUDGET_VOTE_UPDATE_MIN) {
68  strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n",
69  vote.GetHash().ToString(), voteTime - oldTime, BUDGET_VOTE_UPDATE_MIN);
70  LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
71  return false;
72  }
73  strAction = "Existing vote updated:";
74  }
75 
76  mapVotes[mnId] = vote;
77  LogPrint(BCLog::MNBUDGET, "%s: %s %s\n", __func__, strAction.c_str(), vote.GetHash().ToString().c_str());
78  return true;
79 }
80 
82 {
84  for (const auto& it: mapVotes) {
85  const CFinalizedBudgetVote& vote = it.second;
86  ret.pushKV(vote.GetVin().prevout.ToStringShort(), vote.ToJSON());
87  }
88  return ret;
89 }
90 
91 void CFinalizedBudget::SetSynced(bool synced)
92 {
93  for (auto& it: mapVotes) {
94  CFinalizedBudgetVote& vote = it.second;
95  if (synced) {
96  if (vote.IsValid()) vote.SetSynced(true);
97  } else {
98  vote.SetSynced(false);
99  }
100  }
101 }
102 
103 bool CFinalizedBudget::CheckProposals(const std::map<uint256, CBudgetProposal>& mapWinningProposals) const
104 {
105  if (mapWinningProposals.empty()) {
106  LogPrint(BCLog::MNBUDGET,"%s: No Budget-Proposals found, aborting\n", __func__);
107  return false;
108  }
109 
110  if (mapWinningProposals.size() != vecBudgetPayments.size()) {
111  LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposal length (%ld) doesn't match Budget-Payment length (%ld).\n",
112  __func__, mapWinningProposals.size(), vecBudgetPayments.size());
113  return false;
114  }
115 
116  for (unsigned int i = 0; i < vecBudgetPayments.size(); i++) {
117  LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - nProp %d %s\n", __func__, i, vecBudgetPayments[i].nProposalHash.ToString());
118  LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - Payee %d %s\n", __func__, i, HexStr(vecBudgetPayments[i].payee));
119  LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - nAmount %d %lli\n", __func__, i, vecBudgetPayments[i].nAmount);
120  }
121 
122  for (const auto& it: mapWinningProposals) {
123  LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - nProp %s\n", __func__, (it.first).ToString());
124  LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - Payee %s\n", __func__, HexStr((it.second).GetPayee()));
125  LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - nAmount %lli\n", __func__, (it.second).GetAmount());
126  }
127 
128  for (const CTxBudgetPayment& p : vecBudgetPayments) {
129  const auto& it = mapWinningProposals.find(p.nProposalHash);
130  if (it == mapWinningProposals.end()) {
131  LogPrint(BCLog::MNBUDGET,"%s: Proposal %s not found\n", __func__, p.nProposalHash.ToString());
132  return false;
133  }
134 
135  const CBudgetProposal& prop = it->second;
136  if (p.payee != prop.GetPayee()) {
137  LogPrint(BCLog::MNBUDGET,"%s: payee doesn't match %s != %s\n", __func__, HexStr(p.payee), HexStr(prop.GetPayee()));
138  return false;
139  }
140 
141  if (p.nAmount != prop.GetAmount()) {
142  LogPrint(BCLog::MNBUDGET,"%s: payee amount doesn't match %lli != %lli\n", __func__, p.nAmount, prop.GetAmount());
143  return false;
144  }
145  }
146 
147  LogPrint(BCLog::MNBUDGET,"%s: Finalized Budget Matches! Submitting Vote.\n", __func__);
148  return true;
149 }
150 
152 {
153  CAmount ret = 0;
154 
155  for (auto & vecBudgetPayment : vecBudgetPayments) {
156  ret += vecBudgetPayment.nAmount;
157  }
158 
159  return ret;
160 }
161 
162 std::vector<uint256> CFinalizedBudget::GetProposalsHashes() const
163 {
164  std::vector<uint256> vHashes;
165  for (const CTxBudgetPayment& budgetPayment : vecBudgetPayments) {
166  vHashes.push_back(budgetPayment.nProposalHash);
167  }
168  return vHashes;
169 }
170 
171 void CFinalizedBudget::SyncVotes(CNode* pfrom, bool fPartial, int& nInvCount) const
172 {
173  for (const auto& it: mapVotes) {
174  const CFinalizedBudgetVote& vote = it.second;
175  if (vote.IsValid() && (!fPartial || !vote.IsSynced())) {
177  nInvCount++;
178  }
179  }
180 }
181 
183 {
184  if (nBlockStart == 0) {
185  strInvalid = "Invalid BlockStart == 0";
186  return false;
187  }
188 
189  // Must be the correct block for payment to happen (once a month)
190  if (nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) {
191  strInvalid = "Invalid BlockStart";
192  return false;
193  }
194 
195  // The following 2 checks check the same (basically if vecBudgetPayments.size() > 100)
196  if (GetBlockEnd() - nBlockStart + 1 > (int) MAX_PROPOSALS_PER_CYCLE) {
197  strInvalid = "Invalid BlockEnd";
198  return false;
199  }
200  if ((int)vecBudgetPayments.size() > (int) MAX_PROPOSALS_PER_CYCLE) {
201  strInvalid = "Invalid budget payments count (too many)";
202  return false;
203  }
204 
205  return true;
206 }
207 
208 bool CFinalizedBudget::CheckAmount(const CAmount& nTotalBudget)
209 {
210  // Can only pay out 10% of the possible coins (min value of coins)
211  if (GetTotalPayout() > nTotalBudget) {
212  strInvalid = "Invalid Payout (more than max)";
213  return false;
214  }
215 
216  return true;
217 }
218 
220 {
221  if (strBudgetName == "") {
222  strInvalid = "Invalid Budget Name";
223  return false;
224  }
225 
226  return true;
227 }
228 
229 bool CFinalizedBudget::updateExpired(int nCurrentHeight)
230 {
231  // Remove finalized budgets 2 * MAX_PROPOSALS_PER_CYCLE blocks after their end
232  const int nBlockEnd = GetBlockEnd();
233  if (nCurrentHeight >= nBlockEnd + 2 * (int) MAX_PROPOSALS_PER_CYCLE) {
234  strInvalid = strprintf("(ends at block %ld) too old and obsolete (current %ld)", nBlockEnd, nCurrentHeight);
235  return true;
236  }
237 
238  return false;
239 }
240 
241 bool CFinalizedBudget::IsWellFormed(const CAmount& nTotalBudget)
242 {
243  return CheckStartEnd() && CheckAmount(nTotalBudget) && CheckName();
244 }
245 
246 bool CFinalizedBudget::UpdateValid(int nCurrentHeight)
247 {
248  fValid = false;
249 
250  if (updateExpired(nCurrentHeight)) {
251  return false;
252  }
253 
254  fValid = true;
255  strInvalid.clear();
256  return true;
257 }
258 
260 {
261  int ret = 0;
262  for (const auto& it : mapVotes) {
263  if (it.second.IsValid()) {
264  ret++;
265  }
266  }
267  return ret;
268 }
269 
270 std::vector<uint256> CFinalizedBudget::GetVotesHashes() const
271 {
272  std::vector<uint256> vRet;
273  for (const auto& it: mapVotes) {
274  vRet.push_back(it.second.GetHash());
275  }
276  return vRet;
277 }
278 
279 bool CFinalizedBudget::IsPaidAlready(const uint256& nProposalHash, const uint256& nBlockHash, int nBlockHeight) const
280 {
281  // Remove budget-payments from former/future payment cycles
282  int nPaidBlockHeight = 0;
283  uint256 nOldProposalHash;
284 
285  for(auto it = mapPayment_History.begin(); it != mapPayment_History.end(); /* No incrementation needed */ ) {
286  nPaidBlockHeight = (*it).second.second;
287  if((nPaidBlockHeight < GetBlockStart()) || (nPaidBlockHeight > GetBlockEnd())) {
288  nOldProposalHash = (*it).first;
289  LogPrint(BCLog::MNBUDGET, "%s: Budget Proposal %s, Block %d from old cycle deleted\n",
290  __func__, nOldProposalHash.ToString().c_str(), nPaidBlockHeight);
291  it = mapPayment_History.erase(it);
292  } else {
293  ++it;
294  }
295  }
296 
297  // Now that we only have payments from the current payment cycle check if this budget was paid already
298  if(mapPayment_History.count(nProposalHash) == 0) {
299  // New proposal payment, insert into map for checks with later blocks from this cycle
300  mapPayment_History.emplace(std::piecewise_construct,
301  std::forward_as_tuple(nProposalHash),
302  std::forward_as_tuple(nBlockHash, nBlockHeight));
303  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  return false;
306  }
307  // This budget payment was already checked/paid
308  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  if (nBlockHash != nPaidBlockHash) {
313  LOCK(cs_main);
314  CBlockIndex* pindex = LookupBlockIndex(nPaidBlockHash);
315  return pindex && chainActive.Contains(pindex);
316  }
317 
318  // Re-checking same block. Not a double payment.
319  return false;
320 }
321 
322 TrxValidationStatus CFinalizedBudget::IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const
323 {
324  const int nBlockEnd = GetBlockEnd();
325  if (nBlockHeight > nBlockEnd) {
326  LogPrint(BCLog::MNBUDGET,"%s: Invalid block - height: %d end: %d\n", __func__, nBlockHeight, nBlockEnd);
328  }
329  if (nBlockHeight < nBlockStart) {
330  LogPrint(BCLog::MNBUDGET,"%s: Invalid block - height: %d start: %d\n", __func__, nBlockHeight, nBlockStart);
332  }
333 
334  const int nCurrentBudgetPayment = nBlockHeight - nBlockStart;
335  if (nCurrentBudgetPayment > (int)vecBudgetPayments.size() - 1) {
336  LogPrint(BCLog::MNBUDGET,"%s: Invalid last block - current budget payment: %d of %d\n",
337  __func__, nCurrentBudgetPayment + 1, (int)vecBudgetPayments.size());
339  }
340 
341  // Check if this proposal was paid already. If so, pay a masternode instead
342  if(IsPaidAlready(vecBudgetPayments[nCurrentBudgetPayment].nProposalHash, nBlockHash, nBlockHeight)) {
343  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.
348  }
349 
350  // Search the payment
351  const CScript& scriptExpected = vecBudgetPayments[nCurrentBudgetPayment].payee;
352  const CAmount& amountExpected = vecBudgetPayments[nCurrentBudgetPayment].nAmount;
353  // Budget payment is usually the last output of coinstake txes, iterate backwards
354  for (auto out = txNew.vout.rbegin(); out != txNew.vout.rend(); ++out) {
355  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  if (scriptExpected == out->scriptPubKey && amountExpected == out->nValue) {
358  // payment found
359  LogPrint(BCLog::MNBUDGET,"%s: Found valid Budget Payment of %d for proposal %d\n",
360  __func__, amountExpected, vecBudgetPayments[nCurrentBudgetPayment].nProposalHash.GetHex());
362  }
363  }
364 
365  // payment not found
366  CTxDestination address1;
367  ExtractDestination(scriptExpected, address1);
368  LogPrint(BCLog::MNBUDGET,"%s: Missing required payment - %s: %d c: %d\n",
369  __func__, EncodeDestination(address1), amountExpected, nCurrentBudgetPayment);
371 }
372 
373 bool CFinalizedBudget::GetBudgetPaymentByBlock(int64_t nBlockHeight, CTxBudgetPayment& payment) const
374 {
375  int i = nBlockHeight - GetBlockStart();
376  if (i < 0) return false;
377  if (i > (int)vecBudgetPayments.size() - 1) return false;
378  payment = vecBudgetPayments[i];
379  return true;
380 }
381 
382 bool CFinalizedBudget::GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, CAmount& nAmount) const
383 {
384  int i = nBlockHeight - GetBlockStart();
385  if (i < 0) return false;
386  if (i > (int)vecBudgetPayments.size() - 1) return false;
387  payee = vecBudgetPayments[i].payee;
388  nAmount = vecBudgetPayments[i].nAmount;
389  return true;
390 }
391 
392 // return broadcast serialization
394 {
395  CDataStream broadcast(SER_NETWORK, PROTOCOL_VERSION);
396  broadcast.reserve(1000);
397  broadcast << LIMITED_STRING(strBudgetName, 20);
398  broadcast << nBlockStart;
399  broadcast << vecBudgetPayments;
400  broadcast << nFeeTXHash;
401  return broadcast;
402 }
403 
404 
406 {
407  const int count = GetVoteCount();
408  const int otherCount = other.GetVoteCount();
409 
410  if (count == otherCount) return UintToArith256(GetFeeTXHash()) > UintToArith256(other.GetFeeTXHash());
411 
412  return count > otherCount;
413 }
414 
416 {
418  g_connman->RelayInv(inv);
419 }
int64_t CAmount
Amount in PIV (Can be negative)
Definition: amount.h:13
arith_uint256 UintToArith256(const uint256 &a)
true
Definition: bls_dkg.cpp:153
false
Definition: bls_dkg.cpp:151
const CChainParams & Params()
Return the currently selected parameters.
std::string ToStringShort() const
Definition: transaction.cpp:13
void reserve(size_type n)
Definition: streams.h:168
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:139
CScript GetPayee() const
CAmount GetAmount() const
bool Contains(const CBlockIndex *pindex) const
Efficiently check whether a block is present in this chain.
Definition: chain.h:435
bool GetPayeeAndAmount(int64_t nBlockHeight, CScript &payee, CAmount &nAmount) const
static constexpr unsigned int MAX_PROPOSALS_PER_CYCLE
bool GetBudgetPaymentByBlock(int64_t nBlockHeight, CTxBudgetPayment &payment) const
bool UpdateValid(int nHeight)
std::string strInvalid
std::vector< CTxBudgetPayment > vecBudgetPayments
void SetSynced(bool synced)
const uint256 & GetFeeTXHash() const
bool IsPaidAlready(const uint256 &nProposalHash, const uint256 &nBlockHash, int nBlockHeight) const
bool CheckAmount(const CAmount &nTotalBudget)
bool updateExpired(int nCurrentHeight)
bool AddOrUpdateVote(const CFinalizedBudgetVote &vote, std::string &strError)
TrxValidationStatus IsTransactionValid(const CTransaction &txNew, const uint256 &nBlockHash, int nBlockHeight) const
bool ParseBroadcast(CDataStream &broadcast)
void SyncVotes(CNode *pfrom, bool fPartial, int &nInvCount) const
bool CheckProposals(const std::map< uint256, CBudgetProposal > &mapWinningProposals) const
std::map< COutPoint, CFinalizedBudgetVote > mapVotes
bool operator>(const CFinalizedBudget &other) const
bool IsWellFormed(const CAmount &nTotalBudget)
int GetBlockEnd() const
std::vector< uint256 > GetVotesHashes() const
std::string strBudgetName
CDataStream GetBroadcast() const
CAmount GetTotalPayout() const
std::vector< uint256 > GetProposalsHashes() const
uint256 GetHash() const
int GetVoteCount() const
UniValue GetVotesObject() const
int GetBlockStart() const
void SetSynced(bool _fSynced)
inv message data
Definition: protocol.h:466
Information about a peer.
Definition: net.h:669
void PushInventory(const CInv &inv)
Definition: net.h:914
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:72
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:381
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:244
std::vector< CTxOut > vout
Definition: transaction.h:271
COutPoint prevout
Definition: transaction.h:96
@ VOBJ
Definition: univalue.h:21
bool pushKV(const std::string &key, const UniValue &val)
Definition: univalue.cpp:133
std::string ToString() const
Definition: uint256.cpp:65
256-bit opaque blob.
Definition: uint256.h:138
TrxValidationStatus
@ Valid
Transaction verification failed.
@ DoublePayment
Transaction successfully verified.
std::unique_ptr< CConnman > g_connman
Definition: init.cpp:90
@ LOCK
Definition: lockunlock.h:16
#define LogPrint(category,...)
Definition: logging.h:163
@ MNBUDGET
Definition: logging.h:60
std::string EncodeDestination(const CWDestination &address, const CChainParams::Base58Type addrType)
RecursiveMutex cs_main
Global state.
Definition: validation.cpp:80
@ MSG_BUDGET_FINALIZED_VOTE
Definition: protocol.h:449
@ MSG_BUDGET_FINALIZED
Definition: protocol.h:448
const char * name
Definition: rest.cpp:37
@ SER_NETWORK
Definition: serialize.h:174
#define LIMITED_STRING(obj, n)
Definition: serialize.h:515
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet, bool fColdStake)
Parse a standard scriptPubKey for the destination address.
Definition: standard.cpp:162
boost::variant< CNoDestination, CKeyID, CScriptID, CExchangeKeyID > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:72
bool error(const char *fmt, const Args &... args)
Definition: system.h:77
#define strprintf
Definition: tinyformat.h:1056
const uint256 UINT256_ZERO
constant uint256 instances
Definition: uint256.h:175
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
CChain chainActive
The currently-connected chain of blocks (protected by cs_main).
Definition: validation.cpp:84
CBlockIndex * LookupBlockIndex(const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
Definition: validation.h:345