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/budgetproposal.h"
7 : #include "chainparams.h"
8 : #include "script/standard.h"
9 : #include "utilstrencodings.h"
10 :
11 369 : CBudgetProposal::CBudgetProposal():
12 : nAllotted(0),
13 : fValid(true),
14 : strInvalid(""),
15 : strProposalName("unknown"),
16 : strURL(""),
17 : nBlockStart(0),
18 : nBlockEnd(0),
19 : address(),
20 : nAmount(0),
21 : nFeeTXHash(UINT256_ZERO),
22 369 : nTime(0)
23 369 : {}
24 :
25 38 : CBudgetProposal::CBudgetProposal(const std::string& name,
26 : const std::string& url,
27 : int paycount,
28 : const CScript& payee,
29 : const CAmount& amount,
30 : int blockstart,
31 38 : const uint256& nfeetxhash):
32 : nAllotted(0),
33 : fValid(true),
34 : strInvalid(""),
35 : strProposalName(name),
36 : strURL(url),
37 : nBlockStart(blockstart),
38 : address(payee),
39 38 : nAmount(amount),
40 : nFeeTXHash(nfeetxhash),
41 114 : nTime(0)
42 : {
43 : // calculate the expiration block
44 38 : nBlockEnd = nBlockStart + (Params().GetConsensus().nBudgetCycleBlocks + 1) * paycount;
45 38 : }
46 :
47 : // initialize from network broadcast message
48 165 : bool CBudgetProposal::ParseBroadcast(CDataStream& broadcast)
49 : {
50 165 : *this = CBudgetProposal();
51 165 : try {
52 165 : broadcast >> LIMITED_STRING(strProposalName, 20);
53 165 : broadcast >> LIMITED_STRING(strURL, 64);
54 165 : broadcast >> nTime;
55 165 : broadcast >> nBlockStart;
56 165 : broadcast >> nBlockEnd;
57 165 : broadcast >> nAmount;
58 165 : broadcast >> address;
59 165 : broadcast >> nFeeTXHash;
60 0 : } catch (std::exception& e) {
61 0 : return error("Unable to deserialize proposal broadcast: %s", e.what());
62 : }
63 : return true;
64 : }
65 :
66 5155 : void CBudgetProposal::SyncVotes(CNode* pfrom, bool fPartial, int& nInvCount) const
67 : {
68 6158 : for (const auto& it: mapVotes) {
69 1003 : const CBudgetVote& vote = it.second;
70 1003 : if (vote.IsValid() && (!fPartial || !vote.IsSynced())) {
71 356 : pfrom->PushInventory(CInv(MSG_BUDGET_VOTE, vote.GetHash()));
72 356 : nInvCount++;
73 : }
74 : }
75 5155 : }
76 :
77 1412 : bool CBudgetProposal::IsHeavilyDownvoted(int mnCount)
78 : {
79 1412 : if (GetNays() - GetYeas() > 3 * mnCount / 10) {
80 0 : strInvalid = "Heavily Downvoted";
81 0 : return true;
82 : }
83 : return false;
84 : }
85 :
86 203 : bool CBudgetProposal::CheckStartEnd()
87 : {
88 : // block start must be a superblock
89 406 : if (nBlockStart < 0 ||
90 203 : nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) {
91 0 : strInvalid = "Invalid nBlockStart";
92 0 : return false;
93 : }
94 :
95 203 : if (nBlockEnd < nBlockStart) {
96 0 : strInvalid = "Invalid nBlockEnd (end before start)";
97 0 : return false;
98 : }
99 :
100 203 : if (GetTotalPaymentCount() > Params().GetConsensus().nMaxProposalPayments) {
101 0 : strInvalid = "Invalid payment count";
102 0 : return false;
103 : }
104 :
105 : return true;
106 : }
107 :
108 203 : bool CBudgetProposal::CheckAmount(const CAmount& nTotalBudget)
109 : {
110 : // check minimum amount
111 203 : if (nAmount < PROPOSAL_MIN_AMOUNT) {
112 0 : strInvalid = "Invalid nAmount (too low)";
113 0 : return false;
114 : }
115 :
116 : // check maximum amount
117 : // can only pay out 10% of the possible coins (min value of coins)
118 203 : if (nAmount > nTotalBudget) {
119 0 : strInvalid = "Invalid nAmount (too high)";
120 0 : return false;
121 : }
122 :
123 : return true;
124 : }
125 :
126 203 : bool CBudgetProposal::CheckAddress()
127 : {
128 : // !TODO: There might be an issue with multisig in the coinbase on mainnet
129 : // we will add support for it in a future release.
130 203 : if (address.IsPayToScriptHash()) {
131 0 : strInvalid = "Multisig is not currently supported.";
132 0 : return false;
133 : }
134 :
135 : // Check address
136 406 : CTxDestination dest;
137 203 : if (!ExtractDestination(address, dest, false)) {
138 0 : strInvalid = "Invalid script";
139 : return false;
140 : }
141 203 : if (!IsValidDestination(dest)) {
142 203 : strInvalid = "Invalid recipient address";
143 : return false;
144 : }
145 :
146 : return true;
147 : }
148 :
149 : /* TODO: Add this to IsWellFormed() for the next hard-fork
150 : * This will networkly reject malformed proposal names and URLs
151 : */
152 0 : bool CBudgetProposal::CheckStrings()
153 : {
154 0 : if (strProposalName != SanitizeString(strProposalName)) {
155 0 : strInvalid = "Proposal name contains illegal characters.";
156 0 : return false;
157 : }
158 0 : if (strURL != SanitizeString(strURL)) {
159 0 : strInvalid = "Proposal URL contains illegal characters.";
160 : }
161 : }
162 :
163 203 : bool CBudgetProposal::IsWellFormed(const CAmount& nTotalBudget)
164 : {
165 203 : return CheckStartEnd() && CheckAmount(nTotalBudget) && CheckAddress();
166 : }
167 :
168 1637 : bool CBudgetProposal::updateExpired(int nCurrentHeight)
169 : {
170 1637 : if (IsExpired(nCurrentHeight)) {
171 0 : strInvalid = "Proposal expired";
172 0 : return true;
173 : }
174 : return false;
175 : }
176 :
177 1637 : bool CBudgetProposal::UpdateValid(int nCurrentHeight, int mnCount)
178 : {
179 1637 : fValid = false;
180 :
181 : // Never kill a proposal before the first superblock
182 1637 : if (nCurrentHeight > nBlockStart && IsHeavilyDownvoted(mnCount)) {
183 : return false;
184 : }
185 :
186 1637 : if (updateExpired(nCurrentHeight)) {
187 : return false;
188 : }
189 :
190 1637 : fValid = true;
191 1637 : strInvalid.clear();
192 1637 : return true;
193 : }
194 :
195 1089 : bool CBudgetProposal::IsEstablished() const
196 : {
197 1089 : return nTime < GetAdjustedTime() - Params().GetConsensus().nProposalEstablishmentTime;
198 : }
199 :
200 539 : bool CBudgetProposal::IsPassing(int nBlockStartBudget, int nBlockEndBudget, int mnCount) const
201 : {
202 539 : if (!fValid)
203 : return false;
204 :
205 539 : if (this->nBlockStart > nBlockStartBudget)
206 : return false;
207 :
208 539 : if (this->nBlockEnd < nBlockEndBudget)
209 : return false;
210 :
211 539 : if (GetYeas() - GetNays() <= mnCount / 10)
212 : return false;
213 :
214 43 : if (!IsEstablished())
215 0 : return false;
216 :
217 : return true;
218 : }
219 :
220 1637 : bool CBudgetProposal::IsExpired(int nCurrentHeight) const
221 : {
222 1637 : return nBlockEnd < nCurrentHeight;
223 : }
224 :
225 45 : bool CBudgetProposal::AddOrUpdateVote(const CBudgetVote& vote, std::string& strError)
226 : {
227 90 : std::string strAction = "New vote inserted:";
228 90 : const COutPoint& mnId = vote.GetVin().prevout;
229 45 : const int64_t voteTime = vote.GetTime();
230 :
231 90 : if (mapVotes.count(mnId)) {
232 0 : const int64_t& oldTime = mapVotes[mnId].GetTime();
233 0 : if (oldTime > voteTime) {
234 0 : strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString());
235 0 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
236 0 : return false;
237 : }
238 0 : if (voteTime - oldTime < BUDGET_VOTE_UPDATE_MIN) {
239 0 : strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n",
240 0 : vote.GetHash().ToString(), voteTime - oldTime, BUDGET_VOTE_UPDATE_MIN);
241 0 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
242 0 : return false;
243 : }
244 0 : strAction = "Existing vote updated:";
245 : }
246 :
247 45 : mapVotes[mnId] = vote;
248 90 : LogPrint(BCLog::MNBUDGET, "%s: %s %s\n", __func__, strAction.c_str(), vote.GetHash().ToString().c_str());
249 :
250 : return true;
251 : }
252 :
253 54 : UniValue CBudgetProposal::GetVotesArray() const
254 : {
255 54 : UniValue ret(UniValue::VARR);
256 192 : for (const auto& it: mapVotes) {
257 138 : ret.push_back(it.second.ToJSON());
258 : }
259 54 : return ret;
260 : }
261 :
262 2097 : void CBudgetProposal::SetSynced(bool synced)
263 : {
264 2521 : for (auto& it: mapVotes) {
265 424 : CBudgetVote& vote = it.second;
266 424 : if (synced) {
267 289 : if (vote.IsValid()) vote.SetSynced(true);
268 : } else {
269 424 : vote.SetSynced(false);
270 : }
271 : }
272 2097 : }
273 :
274 507 : double CBudgetProposal::GetRatio() const
275 : {
276 507 : int yeas = GetYeas();
277 507 : int nays = GetNays();
278 :
279 507 : if (yeas + nays == 0) return 0.0f;
280 :
281 44 : return ((double)(yeas) / (double)(yeas + nays));
282 : }
283 :
284 37861 : int CBudgetProposal::GetVoteCount(CBudgetVote::VoteDirection vd) const
285 : {
286 37861 : int ret = 0;
287 45231 : for (const auto& it : mapVotes) {
288 7370 : const CBudgetVote& vote = it.second;
289 7370 : if (vote.GetDirection() == vd && vote.IsValid())
290 3265 : ret++;
291 : }
292 37861 : return ret;
293 : }
294 :
295 1724 : int CBudgetProposal::GetBlockStartCycle() const
296 : {
297 : //end block is half way through the next cycle (so the proposal will be removed much after the payment is sent)
298 1724 : return GetBlockCycle(nBlockStart);
299 : }
300 :
301 2231 : int CBudgetProposal::GetBlockCycle(int nHeight)
302 : {
303 2231 : return nHeight - nHeight % Params().GetConsensus().nBudgetCycleBlocks;
304 : }
305 :
306 2231 : int CBudgetProposal::GetBlockEndCycle() const
307 : {
308 2231 : return nBlockEnd;
309 :
310 : }
311 :
312 1724 : int CBudgetProposal::GetTotalPaymentCount() const
313 : {
314 1724 : return (GetBlockEndCycle() - GetBlockStartCycle()) / Params().GetConsensus().nBudgetCycleBlocks;
315 : }
316 :
317 507 : int CBudgetProposal::GetRemainingPaymentCount(int nCurrentHeight) const
318 : {
319 : // If the proposal is already finished (passed the end block cycle), the payments value will be negative
320 507 : int nPayments = (GetBlockEndCycle() - GetBlockCycle(nCurrentHeight)) / Params().GetConsensus().nBudgetCycleBlocks - 1;
321 : // Take the lowest value
322 507 : return std::min(nPayments, GetTotalPaymentCount());
323 : }
324 :
325 : // return broadcast serialization
326 165 : CDataStream CBudgetProposal::GetBroadcast() const
327 : {
328 165 : CDataStream broadcast(SER_NETWORK, PROTOCOL_VERSION);
329 165 : broadcast.reserve(1000);
330 165 : broadcast << LIMITED_STRING(strProposalName, 20);
331 165 : broadcast << LIMITED_STRING(strURL, 64);
332 165 : broadcast << nTime;
333 165 : broadcast << nBlockStart;
334 165 : broadcast << nBlockEnd;
335 165 : broadcast << nAmount;
336 165 : broadcast << address;
337 165 : broadcast << nFeeTXHash;
338 165 : return broadcast;
339 : }
340 :
341 168 : void CBudgetProposal::Relay()
342 : {
343 168 : CInv inv(MSG_BUDGET_PROPOSAL, GetHash());
344 168 : g_connman->RelayInv(inv);
345 168 : }
346 :
|