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/budgetmanager.h"
7 :
8 : #include "consensus/validation.h"
9 : #include "evo/deterministicmns.h"
10 : #include "masternodeman.h"
11 : #include "netmessagemaker.h"
12 : #include "tiertwo/tiertwo_sync_state.h"
13 : #include "tiertwo/netfulfilledman.h"
14 : #include "util/validation.h"
15 : #include "validation.h" // GetTransaction, cs_main
16 :
17 : #ifdef ENABLE_WALLET
18 : #include "wallet/wallet.h" // future: use interface instead.
19 : #endif
20 :
21 :
22 : #define BUDGET_ORPHAN_VOTES_CLEANUP_SECONDS (60 * 60) // One hour.
23 : // Request type used in the net requests manager to block peers asking budget sync too often
24 : static const std::string BUDGET_SYNC_REQUEST_RECV = "budget-sync-recv";
25 :
26 : CBudgetManager g_budgetman;
27 :
28 : // Used to check both proposals and finalized-budgets collateral txes
29 : bool CheckCollateral(const uint256& nTxCollateralHash, const uint256& nExpectedHash, std::string& strError, int64_t& nTime, int nCurrentHeight, bool fBudgetFinalization);
30 :
31 356 : void CBudgetManager::ReloadMapSeen()
32 : {
33 1068 : const auto reloadSeenMap = [](auto& mutex1, auto& mutex2, const auto& mapBudgets, auto& mapSeen, auto& mapOrphans) {
34 1424 : LOCK2(mutex1, mutex2);
35 712 : mapSeen.clear();
36 712 : mapOrphans.clear();
37 712 : for (const auto& b : mapBudgets) {
38 0 : for (const auto& it : b.second.mapVotes) {
39 0 : const auto& vote = it.second;
40 0 : if (vote.IsValid()) {
41 0 : mapSeen.emplace(vote.GetHash(), vote);
42 : }
43 : }
44 : }
45 712 : };
46 :
47 356 : reloadSeenMap(cs_proposals, cs_votes, mapProposals, mapSeenProposalVotes, mapOrphanProposalVotes);
48 356 : reloadSeenMap(cs_budgets, cs_finalizedvotes, mapFinalizedBudgets, mapSeenFinalizedBudgetVotes, mapOrphanFinalizedBudgetVotes);
49 356 : }
50 :
51 177 : void CBudgetManager::CheckOrphanVotes()
52 : {
53 177 : {
54 354 : LOCK2(cs_proposals, cs_votes);
55 178 : for (auto itOrphanVotes = mapOrphanProposalVotes.begin(); itOrphanVotes != mapOrphanProposalVotes.end();) {
56 1 : auto itProposal = mapProposals.find(itOrphanVotes->first);
57 1 : if (itProposal != mapProposals.end()) {
58 : // Proposal found.
59 1 : CBudgetProposal* bp = &(itProposal->second);
60 : // Try to add orphan votes
61 2 : for (const CBudgetVote& vote : itOrphanVotes->second.first) {
62 2 : std::string strError;
63 1 : if (!bp->AddOrUpdateVote(vote, strError)) {
64 0 : LogPrint(BCLog::MNBUDGET, "Unable to add orphan vote for proposal: %s\n", strError);
65 : }
66 : }
67 : // Remove entry from the map
68 1 : itOrphanVotes = mapOrphanProposalVotes.erase(itOrphanVotes);
69 : } else {
70 1 : ++itOrphanVotes;
71 : }
72 : }
73 : }
74 :
75 177 : {
76 354 : LOCK2(cs_budgets, cs_finalizedvotes);
77 194 : for (auto itOrphanVotes = mapOrphanFinalizedBudgetVotes.begin(); itOrphanVotes != mapOrphanFinalizedBudgetVotes.end();) {
78 17 : auto itFinalBudget = mapFinalizedBudgets.find(itOrphanVotes->first);
79 17 : if (itFinalBudget != mapFinalizedBudgets.end()) {
80 : // Finalized budget found.
81 1 : CFinalizedBudget* fb = &(itFinalBudget->second);
82 : // Try to add orphan votes
83 4 : for (const CFinalizedBudgetVote& vote : itOrphanVotes->second.first) {
84 6 : std::string strError;
85 3 : if (!fb->AddOrUpdateVote(vote, strError)) {
86 0 : LogPrint(BCLog::MNBUDGET, "Unable to add orphan vote for final budget: %s\n", strError);
87 : }
88 : }
89 : // Remove entry from the map
90 1 : itOrphanVotes = mapOrphanFinalizedBudgetVotes.erase(itOrphanVotes);
91 : } else {
92 17 : ++itOrphanVotes;
93 : }
94 : }
95 : }
96 :
97 177 : LogPrint(BCLog::MNBUDGET,"%s: Done\n", __func__);
98 177 : }
99 :
100 4 : uint256 CBudgetManager::SubmitFinalBudget()
101 : {
102 4 : static int nSubmittedHeight = 0; // height at which final budget was submitted last time
103 4 : int nCurrentHeight = GetBestHeight();
104 :
105 4 : const int nBlocksPerCycle = Params().GetConsensus().nBudgetCycleBlocks;
106 4 : int nBlockStart = nCurrentHeight - nCurrentHeight % nBlocksPerCycle + nBlocksPerCycle;
107 4 : if (nSubmittedHeight >= nBlockStart){
108 0 : LogPrint(BCLog::MNBUDGET,"%s: nSubmittedHeight(=%ld) < nBlockStart(=%ld) condition not fulfilled.\n",
109 : __func__, nSubmittedHeight, nBlockStart);
110 0 : return UINT256_ZERO;
111 : }
112 :
113 : // Submit final budget during the last 2 days (2880 blocks) before payment for Mainnet, about 9 minutes (9 blocks) for Testnet
114 4 : int finalizationWindow = ((nBlocksPerCycle / 30) * 2);
115 :
116 4 : if (Params().IsTestnet()) {
117 : // NOTE: 9 blocks for testnet is way to short to have any masternode submit an automatic vote on the finalized(!) budget,
118 : // because those votes are only submitted/relayed once every 56 blocks in CFinalizedBudget::AutoCheck()
119 :
120 0 : finalizationWindow = 64; // 56 + 4 finalization confirmations + 4 minutes buffer for propagation
121 : }
122 :
123 4 : int nFinalizationStart = nBlockStart - finalizationWindow;
124 :
125 4 : int nOffsetToStart = nFinalizationStart - nCurrentHeight;
126 :
127 4 : if (nBlockStart - nCurrentHeight > finalizationWindow) {
128 0 : LogPrint(BCLog::MNBUDGET,"%s: Too early for finalization. Current block is %ld, next Superblock is %ld.\n", __func__, nCurrentHeight, nBlockStart);
129 0 : LogPrint(BCLog::MNBUDGET,"%s: First possible block for finalization: %ld. Last possible block for finalization: %ld. " /* Continued */
130 : "You have to wait for %ld block(s) until Budget finalization will be possible\n", __func__, nFinalizationStart, nBlockStart, nOffsetToStart);
131 0 : return UINT256_ZERO;
132 : }
133 :
134 4 : std::vector<CBudgetProposal> vBudgetProposals = GetBudget();
135 8 : std::string strBudgetName = "main";
136 8 : std::vector<CTxBudgetPayment> vecTxBudgetPayments;
137 :
138 8 : for (const auto& p : vBudgetProposals) {
139 8 : CTxBudgetPayment txBudgetPayment;
140 4 : txBudgetPayment.nProposalHash = p.GetHash();
141 4 : txBudgetPayment.payee = p.GetPayee();
142 4 : txBudgetPayment.nAmount = p.GetAllotted();
143 4 : vecTxBudgetPayments.push_back(txBudgetPayment);
144 : }
145 :
146 4 : if (vecTxBudgetPayments.size() < 1) {
147 0 : LogPrint(BCLog::MNBUDGET,"%s: Found No Proposals For Period\n", __func__);
148 0 : return UINT256_ZERO;
149 : }
150 :
151 8 : CFinalizedBudget tempBudget(strBudgetName, nBlockStart, vecTxBudgetPayments, UINT256_ZERO);
152 4 : const uint256& budgetHash = tempBudget.GetHash();
153 4 : if (HaveFinalizedBudget(budgetHash)) {
154 0 : LogPrint(BCLog::MNBUDGET,"%s: Budget already exists - %s\n", __func__, budgetHash.ToString());
155 0 : nSubmittedHeight = nCurrentHeight;
156 0 : return UINT256_ZERO;
157 : }
158 :
159 : // See if collateral tx exists
160 4 : if (!mapUnconfirmedFeeTx.count(budgetHash)) {
161 : // create the collateral tx, send it to the network and return
162 2 : CTransactionRef wtx;
163 : // Get our change address
164 2 : if (vpwallets.empty() || !vpwallets[0]) {
165 0 : LogPrint(BCLog::MNBUDGET,"%s: Wallet not found\n", __func__);
166 0 : return UINT256_ZERO;
167 : }
168 : // Exit if wallet is locked
169 2 : if (vpwallets[0]->IsLocked()) {
170 0 : LogPrint(BCLog::MNBUDGET, "%s: Wallet is locked, can't make collateral transaction.\n", __func__);
171 0 : return UINT256_ZERO;
172 : }
173 4 : CReserveKey keyChange(vpwallets[0]);
174 2 : if (!vpwallets[0]->CreateBudgetFeeTX(wtx, budgetHash, keyChange, BUDGET_FEE_TX)) {
175 0 : LogPrint(BCLog::MNBUDGET,"%s: Can't make collateral transaction\n", __func__);
176 0 : return UINT256_ZERO;
177 : }
178 : // Send the tx to the network
179 4 : const CWallet::CommitResult& res = vpwallets[0]->CommitTransaction(wtx, keyChange, g_connman.get());
180 2 : if (res.status == CWallet::CommitStatus::OK) {
181 2 : const uint256& collateraltxid = wtx->GetHash();
182 2 : mapUnconfirmedFeeTx.emplace(budgetHash, collateraltxid);
183 4 : LogPrint(BCLog::MNBUDGET,"%s: Collateral sent. txid: %s\n", __func__, collateraltxid.ToString());
184 2 : return budgetHash;
185 : }
186 0 : return UINT256_ZERO;
187 : }
188 :
189 : // Collateral tx already exists, see if it's mature enough.
190 4 : CFinalizedBudget fb(strBudgetName, nBlockStart, vecTxBudgetPayments, mapUnconfirmedFeeTx.at(budgetHash));
191 2 : if (!AddFinalizedBudget(fb)) {
192 0 : return UINT256_ZERO;
193 : }
194 2 : fb.Relay();
195 2 : nSubmittedHeight = nCurrentHeight;
196 4 : LogPrint(BCLog::MNBUDGET,"%s: Done! %s\n", __func__, budgetHash.ToString());
197 2 : return budgetHash;
198 : }
199 :
200 14 : void CBudgetManager::SetBudgetProposalsStr(CFinalizedBudget& finalizedBudget) const
201 : {
202 14 : const std::vector<uint256>& vHashes = finalizedBudget.GetProposalsHashes();
203 28 : std::string strProposals = "";
204 14 : {
205 14 : LOCK(cs_proposals);
206 28 : for (const uint256& hash: vHashes) {
207 42 : const std::string token = (mapProposals.count(hash) ? mapProposals.at(hash).GetName() : hash.ToString());
208 28 : strProposals += (strProposals == "" ? "" : ", ") + token;
209 : }
210 : }
211 42 : finalizedBudget.SetProposalsStr(strProposals);
212 14 : }
213 :
214 39 : std::string CBudgetManager::GetFinalizedBudgetStatus(const uint256& nHash) const
215 : {
216 39 : CFinalizedBudget fb;
217 39 : if (!GetFinalizedBudget(nHash, fb))
218 0 : return strprintf("ERROR: cannot find finalized budget %s\n", nHash.ToString());
219 :
220 78 : std::string retBadHashes = "";
221 78 : std::string retBadPayeeOrAmount = "";
222 39 : int nBlockStart = fb.GetBlockStart();
223 39 : int nBlockEnd = fb.GetBlockEnd();
224 :
225 78 : for (int nBlockHeight = nBlockStart; nBlockHeight <= nBlockEnd; nBlockHeight++) {
226 78 : CTxBudgetPayment budgetPayment;
227 39 : if (!fb.GetBudgetPaymentByBlock(nBlockHeight, budgetPayment)) {
228 0 : LogPrint(BCLog::MNBUDGET,"%s: Couldn't find budget payment for block %lld\n", __func__, nBlockHeight);
229 0 : continue;
230 : }
231 :
232 78 : CBudgetProposal bp;
233 39 : if (!GetProposal(budgetPayment.nProposalHash, bp)) {
234 0 : retBadHashes += (retBadHashes == "" ? "" : ", ") + budgetPayment.nProposalHash.ToString();
235 0 : continue;
236 : }
237 :
238 78 : if (bp.GetPayee() != budgetPayment.payee || bp.GetAmount() != budgetPayment.nAmount) {
239 0 : retBadPayeeOrAmount += (retBadPayeeOrAmount == "" ? "" : ", ") + budgetPayment.nProposalHash.ToString();
240 : }
241 : }
242 :
243 78 : if (retBadHashes == "" && retBadPayeeOrAmount == "") return "OK";
244 :
245 0 : if (retBadHashes != "") retBadHashes = "Unknown proposal(s) hash! Check this proposal(s) before voting: " + retBadHashes;
246 0 : if (retBadPayeeOrAmount != "") retBadPayeeOrAmount = "Budget payee/nAmount doesn't match our proposal(s)! "+ retBadPayeeOrAmount;
247 :
248 0 : return retBadHashes + " -- " + retBadPayeeOrAmount;
249 : }
250 :
251 17 : bool CBudgetManager::AddFinalizedBudget(CFinalizedBudget& finalizedBudget, CNode* pfrom)
252 : {
253 17 : AssertLockNotHeld(cs_budgets); // need to lock cs_main here (CheckCollateral)
254 17 : const uint256& nHash = finalizedBudget.GetHash();
255 :
256 51 : if (WITH_LOCK(cs_budgets, return mapFinalizedBudgets.count(nHash))) {
257 0 : LogPrint(BCLog::MNBUDGET,"%s: finalized budget %s already added\n", __func__, nHash.ToString());
258 0 : return false;
259 : }
260 :
261 17 : if (!finalizedBudget.IsWellFormed(GetTotalBudget(finalizedBudget.GetBlockStart()))) {
262 0 : LogPrint(BCLog::MNBUDGET,"%s: invalid finalized budget: %s %s\n", __func__, nHash.ToString(), finalizedBudget.IsInvalidLogStr());
263 0 : return false;
264 : }
265 :
266 34 : std::string strError;
267 17 : int nCurrentHeight = GetBestHeight();
268 17 : const uint256& feeTxId = finalizedBudget.GetFeeTXHash();
269 17 : if (!CheckCollateral(feeTxId, nHash, strError, finalizedBudget.nTime, nCurrentHeight, true)) {
270 0 : LogPrint(BCLog::MNBUDGET,"%s: invalid finalized budget (%s) collateral id=%s - %s\n",
271 : __func__, nHash.ToString(), feeTxId.ToString(), strError);
272 17 : finalizedBudget.SetStrInvalid(strError);
273 : return false;
274 : }
275 :
276 : // update expiration
277 17 : if (!finalizedBudget.UpdateValid(nCurrentHeight)) {
278 0 : LogPrint(BCLog::MNBUDGET,"%s: invalid finalized budget: %s %s\n", __func__, nHash.ToString(), finalizedBudget.IsInvalidLogStr());
279 0 : return false;
280 : }
281 :
282 : // Compare budget payments with existent proposals, don't care on the order, just verify proposals existence.
283 17 : std::vector<CBudgetProposal> vBudget = GetBudget();
284 34 : std::map<uint256, CBudgetProposal> mapWinningProposals;
285 32 : for (const CBudgetProposal& p: vBudget) { mapWinningProposals.emplace(p.GetHash(), p); }
286 17 : if (!finalizedBudget.CheckProposals(mapWinningProposals)) {
287 6 : finalizedBudget.SetStrInvalid("Invalid proposals");
288 3 : LogPrint(BCLog::MNBUDGET,"%s: Budget finalization does not match with winning proposals\n", __func__);
289 : // just for now (until v6), request proposals and budget sync in case we are missing them
290 3 : if (pfrom) {
291 2 : CNetMsgMaker maker(pfrom->GetSendVersion());
292 : // First, request single proposals that we don't have.
293 4 : for (const auto& propId : finalizedBudget.GetProposalsHashes()) {
294 2 : if (!g_budgetman.HaveProposal(propId)) {
295 1 : g_connman->PushMessage(pfrom, maker.Make(NetMsgType::BUDGETVOTESYNC, propId));
296 : }
297 : }
298 :
299 : // Second a full budget sync for missing votes and the budget finalization that we are rejecting here.
300 : // Note: this will not make any effect on peers with version <= 70923 as they, invalidly, are blocking
301 : // follow-up budget sync request for the entire node life cycle.
302 2 : uint256 n;
303 2 : g_connman->PushMessage(pfrom, maker.Make(NetMsgType::BUDGETVOTESYNC, n));
304 : }
305 3 : return false;
306 : }
307 :
308 : // Add budget finalization.
309 14 : SetBudgetProposalsStr(finalizedBudget);
310 14 : ForceAddFinalizedBudget(nHash, feeTxId, finalizedBudget);
311 :
312 56 : LogPrint(BCLog::MNBUDGET,"%s: finalized budget %s [%s (%s)] added\n",
313 : __func__, nHash.ToString(), finalizedBudget.GetName(), finalizedBudget.GetProposalsStr());
314 : return true;
315 : }
316 :
317 19 : void CBudgetManager::ForceAddFinalizedBudget(const uint256& nHash, const uint256& feeTxId, const CFinalizedBudget& finalizedBudget)
318 : {
319 19 : LOCK(cs_budgets);
320 19 : mapFinalizedBudgets.emplace(nHash, finalizedBudget);
321 : // Add to feeTx index
322 19 : mapFeeTxToBudget.emplace(feeTxId, nHash);
323 : // Remove the budget from the unconfirmed map, if it was there
324 36 : if (mapUnconfirmedFeeTx.count(nHash))
325 19 : mapUnconfirmedFeeTx.erase(nHash);
326 19 : }
327 :
328 184 : bool CBudgetManager::AddProposal(CBudgetProposal& budgetProposal)
329 : {
330 184 : AssertLockNotHeld(cs_proposals); // need to lock cs_main here (CheckCollateral)
331 184 : const uint256& nHash = budgetProposal.GetHash();
332 :
333 552 : if (WITH_LOCK(cs_proposals, return mapProposals.count(nHash))) {
334 0 : LogPrint(BCLog::MNBUDGET,"%s: proposal %s already added\n", __func__, nHash.ToString());
335 0 : return false;
336 : }
337 :
338 184 : if (!budgetProposal.IsWellFormed(GetTotalBudget(budgetProposal.GetBlockStart()))) {
339 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid budget proposal %s %s\n", __func__, nHash.ToString(), budgetProposal.IsInvalidLogStr());
340 0 : return false;
341 : }
342 :
343 368 : std::string strError;
344 184 : int nCurrentHeight = GetBestHeight();
345 184 : const uint256& feeTxId = budgetProposal.GetFeeTXHash();
346 184 : if (!CheckCollateral(feeTxId, nHash, strError, budgetProposal.nTime, nCurrentHeight, false)) {
347 0 : LogPrint(BCLog::MNBUDGET,"%s: invalid budget proposal (%s) collateral id=%s - %s\n",
348 : __func__, nHash.ToString(), feeTxId.ToString(), strError);
349 184 : budgetProposal.SetStrInvalid(strError);
350 : return false;
351 : }
352 :
353 : // update expiration / heavily-downvoted
354 184 : int mnCount = mnodeman.CountEnabled();
355 184 : if (!budgetProposal.UpdateValid(nCurrentHeight, mnCount)) {
356 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid budget proposal %s %s\n", __func__, nHash.ToString(), budgetProposal.IsInvalidLogStr());
357 0 : return false;
358 : }
359 :
360 184 : {
361 184 : LOCK(cs_proposals);
362 184 : mapProposals.emplace(nHash, budgetProposal);
363 : // Add to feeTx index
364 184 : mapFeeTxToProposal.emplace(feeTxId, nHash);
365 : }
366 552 : LogPrint(BCLog::MNBUDGET,"%s: budget proposal %s [%s] added\n", __func__, nHash.ToString(), budgetProposal.GetName());
367 :
368 : return true;
369 : }
370 :
371 1167 : void CBudgetManager::CheckAndRemove()
372 : {
373 1167 : int nCurrentHeight = GetBestHeight();
374 1167 : std::map<uint256, CFinalizedBudget> tmpMapFinalizedBudgets;
375 1167 : std::map<uint256, CBudgetProposal> tmpMapProposals;
376 :
377 : // Get MN count, used for the heavily down-voted check
378 1167 : int mnCount = mnodeman.CountEnabled();
379 :
380 : // Check Proposals first
381 1167 : {
382 1167 : LOCK(cs_proposals);
383 1167 : LogPrint(BCLog::MNBUDGET, "%s: mapProposals cleanup - size before: %d\n", __func__, mapProposals.size());
384 2620 : for (auto& it: mapProposals) {
385 1453 : CBudgetProposal* pbudgetProposal = &(it.second);
386 1453 : if (!pbudgetProposal->UpdateValid(nCurrentHeight, mnCount)) {
387 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid budget proposal %s %s\n", __func__, (it.first).ToString(), pbudgetProposal->IsInvalidLogStr());
388 1453 : mapFeeTxToProposal.erase(pbudgetProposal->GetFeeTXHash());
389 : } else {
390 5812 : LogPrint(BCLog::MNBUDGET,"%s: Found valid budget proposal: %s %s\n", __func__,
391 : pbudgetProposal->GetName(), pbudgetProposal->GetFeeTXHash().ToString());
392 2906 : tmpMapProposals.emplace(pbudgetProposal->GetHash(), *pbudgetProposal);
393 : }
394 : }
395 : // Remove invalid entries by overwriting complete map
396 1167 : mapProposals.swap(tmpMapProposals);
397 1167 : LogPrint(BCLog::MNBUDGET, "%s: mapProposals cleanup - size after: %d\n", __func__, mapProposals.size());
398 : }
399 :
400 : // Then check finalized budgets
401 1167 : {
402 1167 : LOCK(cs_budgets);
403 1167 : LogPrint(BCLog::MNBUDGET, "%s: mapFinalizedBudgets cleanup - size before: %d\n", __func__, mapFinalizedBudgets.size());
404 1256 : for (auto& it: mapFinalizedBudgets) {
405 89 : CFinalizedBudget* pfinalizedBudget = &(it.second);
406 89 : if (!pfinalizedBudget->UpdateValid(nCurrentHeight)) {
407 3 : LogPrint(BCLog::MNBUDGET,"%s: Invalid finalized budget %s %s\n", __func__, (it.first).ToString(), pfinalizedBudget->IsInvalidLogStr());
408 89 : mapFeeTxToBudget.erase(pfinalizedBudget->GetFeeTXHash());
409 : } else {
410 352 : LogPrint(BCLog::MNBUDGET,"%s: Found valid finalized budget: %s %s\n", __func__,
411 : pfinalizedBudget->GetName(), pfinalizedBudget->GetFeeTXHash().ToString());
412 176 : tmpMapFinalizedBudgets.emplace(pfinalizedBudget->GetHash(), *pfinalizedBudget);
413 : }
414 : }
415 : // Remove invalid entries by overwriting complete map
416 1167 : mapFinalizedBudgets = tmpMapFinalizedBudgets;
417 1167 : LogPrint(BCLog::MNBUDGET, "%s: mapFinalizedBudgets cleanup - size after: %d\n", __func__, mapFinalizedBudgets.size());
418 : }
419 : // Masternodes vote on valid ones
420 1167 : VoteOnFinalizedBudgets();
421 1167 : }
422 :
423 111345 : void CBudgetManager::RemoveByFeeTxId(const uint256& feeTxId)
424 : {
425 111345 : {
426 111345 : LOCK(cs_proposals);
427 : // Is this collateral related to a proposal?
428 111345 : const auto& it = mapFeeTxToProposal.find(feeTxId);
429 111345 : if (it != mapFeeTxToProposal.end()) {
430 : // Remove proposal
431 0 : CBudgetProposal* p = FindProposal(it->second);
432 0 : if (p) {
433 0 : LogPrintf("%s: Removing proposal %s (collateral disconnected, id=%s)\n", __func__, p->GetName(), feeTxId.ToString());
434 0 : {
435 : // Erase seen/orphan votes
436 0 : LOCK(cs_votes);
437 0 : for (const auto& vote: p->GetVotes()) {
438 0 : const uint256& hash{vote.second.GetHash()};
439 0 : mapSeenProposalVotes.erase(hash);
440 0 : mapOrphanProposalVotes.erase(hash);
441 : }
442 : }
443 : // Erase proposal object
444 0 : mapProposals.erase(it->second);
445 : }
446 : // Remove from collateral index
447 0 : mapFeeTxToProposal.erase(it);
448 0 : return;
449 : }
450 : }
451 111345 : {
452 222690 : LOCK(cs_budgets);
453 : // Is this collateral related to a finalized budget?
454 111345 : const auto& it = mapFeeTxToBudget.find(feeTxId);
455 111345 : if (it != mapFeeTxToBudget.end()) {
456 : // Remove finalized budget
457 0 : CFinalizedBudget* b = FindFinalizedBudget(it->second);
458 0 : if (b) {
459 0 : LogPrintf("%s: Removing finalized budget %s (collateral disconnected, id=%s)\n", __func__, b->GetName(), feeTxId.ToString());
460 0 : {
461 : // Erase seen/orphan votes
462 0 : LOCK(cs_finalizedvotes);
463 0 : for (const uint256& hash: b->GetVotesHashes()) {
464 0 : mapSeenFinalizedBudgetVotes.erase(hash);
465 0 : mapOrphanFinalizedBudgetVotes.erase(hash);
466 : }
467 : }
468 : // Erase finalized budget object
469 0 : mapFinalizedBudgets.erase(it->second);
470 : }
471 : // Remove from collateral index
472 0 : mapFeeTxToBudget.erase(it);
473 : }
474 : }
475 : }
476 :
477 5756 : CBudgetManager::HighestFinBudget CBudgetManager::GetBudgetWithHighestVoteCount(int chainHeight) const
478 : {
479 5756 : LOCK(cs_budgets);
480 5756 : int highestVoteCount = 0;
481 5756 : const CFinalizedBudget* pHighestBudget = nullptr;
482 10847 : for (const auto& it: mapFinalizedBudgets) {
483 5091 : const CFinalizedBudget* pfinalizedBudget = &(it.second);
484 5091 : int voteCount = pfinalizedBudget->GetVoteCount();
485 9995 : if (voteCount > highestVoteCount &&
486 5091 : chainHeight >= pfinalizedBudget->GetBlockStart() &&
487 3417 : chainHeight <= pfinalizedBudget->GetBlockEnd()) {
488 : pHighestBudget = pfinalizedBudget;
489 : highestVoteCount = voteCount;
490 : }
491 : }
492 11512 : return {pHighestBudget, highestVoteCount};
493 : }
494 :
495 5689 : int CBudgetManager::GetHighestVoteCount(int chainHeight) const
496 : {
497 5689 : const auto& highestBudFin = GetBudgetWithHighestVoteCount(chainHeight);
498 5689 : return (highestBudFin.m_budget_fin ? highestBudFin.m_vote_count : -1);
499 : }
500 :
501 2941 : bool CBudgetManager::GetPayeeAndAmount(int chainHeight, CScript& payeeRet, CAmount& nAmountRet) const
502 : {
503 2941 : int nCountThreshold;
504 2941 : if (!IsBudgetPaymentBlock(chainHeight, nCountThreshold))
505 : return false;
506 :
507 40 : const auto& highestBudFin = GetBudgetWithHighestVoteCount(chainHeight);
508 40 : const CFinalizedBudget* pfb = highestBudFin.m_budget_fin;
509 40 : return pfb && pfb->GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet) && highestBudFin.m_vote_count > nCountThreshold;
510 : }
511 :
512 2383 : bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet) const
513 : {
514 2383 : CScript payeeRet;
515 2383 : return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet);
516 : }
517 :
518 558 : bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const
519 : {
520 558 : if (nHeight <= 0) return false;
521 :
522 1116 : CScript payee;
523 558 : CAmount nAmount = 0;
524 :
525 558 : if (!GetPayeeAndAmount(nHeight, payee, nAmount))
526 : return false;
527 :
528 8 : CAmount blockValue = GetBlockValue(nHeight);
529 :
530 : // Starting from PIVX v6.0 masternode and budgets are paid in the coinbase tx of PoS blocks
531 11 : const bool fPayCoinstake = fProofOfStake &&
532 3 : !Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0);
533 :
534 8 : if (fProofOfStake) {
535 3 : if (fPayCoinstake) {
536 2 : unsigned int i = txCoinstake.vout.size();
537 2 : txCoinstake.vout.resize(i + 1);
538 2 : txCoinstake.vout[i].scriptPubKey = payee;
539 2 : txCoinstake.vout[i].nValue = nAmount;
540 : } else {
541 1 : txCoinbase.vout.resize(1);
542 1 : txCoinbase.vout[0].scriptPubKey = payee;
543 1 : txCoinbase.vout[0].nValue = nAmount;
544 : }
545 : } else {
546 : //miners get the full amount on these blocks
547 5 : txCoinbase.vout[0].nValue = blockValue;
548 5 : txCoinbase.vout.resize(2);
549 :
550 : //these are super blocks, so their value can be much larger than normal
551 5 : txCoinbase.vout[1].scriptPubKey = payee;
552 5 : txCoinbase.vout[1].nValue = nAmount;
553 : }
554 :
555 566 : CTxDestination address;
556 8 : ExtractDestination(payee, address);
557 11 : LogPrint(BCLog::MNBUDGET,"%s: Budget payment to %s for %lld\n", __func__, EncodeDestination(address), nAmount);
558 8 : return true;
559 : }
560 :
561 1167 : void CBudgetManager::VoteOnFinalizedBudgets()
562 : {
563 : // function called only from initialized masternodes
564 1167 : if (!fMasterNode) {
565 773 : LogPrint(BCLog::MNBUDGET,"%s: Not a masternode\n", __func__);
566 1163 : return;
567 : }
568 :
569 : // Do this 1 in 4 blocks -- spread out the voting activity
570 : // -- this function is only called every fourteenth block, so this is really 1 in 56 blocks
571 394 : if (GetRandInt(4) != 0) {
572 296 : LogPrint(BCLog::MNBUDGET,"%s: waiting\n", __func__);
573 296 : return;
574 : }
575 :
576 : // Get the active masternode (operator) key
577 102 : CTxIn mnVin;
578 102 : Optional<CKey> mnKey{nullopt};
579 102 : CBLSSecretKey blsKey;
580 98 : if (!GetActiveMasternodeKeys(mnVin, mnKey, blsKey)) {
581 94 : return;
582 : }
583 :
584 95 : std::vector<CBudgetProposal> vBudget = GetBudget();
585 91 : if (vBudget.empty()) {
586 87 : LogPrint(BCLog::MNBUDGET,"%s: No proposal can be finalized\n", __func__);
587 87 : return;
588 : }
589 :
590 8 : std::map<uint256, CBudgetProposal> mapWinningProposals;
591 8 : for (const CBudgetProposal& p: vBudget) {
592 8 : mapWinningProposals.emplace(p.GetHash(), p);
593 : }
594 : // Vector containing the hash of finalized budgets to sign
595 8 : std::vector<uint256> vBudgetHashes;
596 4 : {
597 4 : LOCK(cs_budgets);
598 6 : for (auto& it: mapFinalizedBudgets) {
599 2 : CFinalizedBudget* pfb = &(it.second);
600 : // we only need to check this once
601 2 : if (pfb->IsAutoChecked()) continue;
602 1 : pfb->SetAutoChecked(true);
603 : //only vote for exact matches
604 1 : if (strBudgetMode == "auto") {
605 : // compare budget payments with winning proposals
606 1 : if (!pfb->CheckProposals(mapWinningProposals)) {
607 0 : continue;
608 : }
609 : }
610 : // exact match found. add budget hash to sign it later.
611 1 : vBudgetHashes.emplace_back(pfb->GetHash());
612 : }
613 : }
614 :
615 : // Sign finalized budgets
616 5 : for (const uint256& budgetHash: vBudgetHashes) {
617 1 : CFinalizedBudgetVote vote(mnVin, budgetHash);
618 1 : if (mnKey != nullopt) {
619 : // Legacy MN
620 0 : if (!vote.Sign(*mnKey, mnKey->GetPubKey().GetID())) {
621 0 : LogPrintf("%s: Failure to sign budget %s\n", __func__, budgetHash.ToString());
622 0 : continue;
623 : }
624 : } else {
625 : // DMN
626 1 : if (!vote.Sign(blsKey)) {
627 0 : LogPrintf("%s: Failure to sign budget %s with DMN\n", __func__, budgetHash.ToString());
628 0 : continue;
629 : }
630 : }
631 1 : std::string strError = "";
632 1 : if (!UpdateFinalizedBudget(vote, nullptr, strError)) {
633 1 : LogPrintf("%s: Error submitting vote - %s\n", __func__, strError);
634 2 : continue;
635 : }
636 0 : LogPrint(BCLog::MNBUDGET, "%s: new finalized budget vote signed: %s\n", __func__, vote.GetHash().ToString());
637 0 : AddSeenFinalizedBudgetVote(vote);
638 0 : vote.Relay();
639 : }
640 : }
641 :
642 0 : CFinalizedBudget* CBudgetManager::FindFinalizedBudget(const uint256& nHash)
643 : {
644 0 : AssertLockHeld(cs_budgets);
645 0 : auto it = mapFinalizedBudgets.find(nHash);
646 0 : return it != mapFinalizedBudgets.end() ? &(it->second) : nullptr;
647 : }
648 :
649 157 : const CBudgetProposal* CBudgetManager::FindProposalByName(const std::string& strProposalName) const
650 : {
651 157 : LOCK(cs_proposals);
652 :
653 157 : int64_t nYesCountMax = std::numeric_limits<int64_t>::min();
654 157 : const CBudgetProposal* pbudgetProposal = nullptr;
655 :
656 2504 : for (const auto& it: mapProposals) {
657 2347 : const CBudgetProposal& proposal = it.second;
658 2347 : int64_t nYesCount = proposal.GetYeas() - proposal.GetNays();
659 7041 : if (proposal.GetName() == strProposalName && nYesCount > nYesCountMax) {
660 157 : pbudgetProposal = &proposal;
661 157 : nYesCountMax = nYesCount;
662 : }
663 : }
664 :
665 314 : return pbudgetProposal;
666 : }
667 :
668 0 : CBudgetProposal* CBudgetManager::FindProposal(const uint256& nHash)
669 : {
670 0 : AssertLockHeld(cs_proposals);
671 0 : auto it = mapProposals.find(nHash);
672 0 : return it != mapProposals.end() ? &(it->second) : nullptr;
673 : }
674 :
675 39 : bool CBudgetManager::GetProposal(const uint256& nHash, CBudgetProposal& bp) const
676 : {
677 78 : LOCK(cs_proposals);
678 39 : auto it = mapProposals.find(nHash);
679 39 : if (it == mapProposals.end()) return false;
680 39 : bp = it->second;
681 : return true;
682 : }
683 :
684 39 : bool CBudgetManager::GetFinalizedBudget(const uint256& nHash, CFinalizedBudget& fb) const
685 : {
686 78 : LOCK(cs_budgets);
687 39 : auto it = mapFinalizedBudgets.find(nHash);
688 39 : if (it == mapFinalizedBudgets.end()) return false;
689 39 : fb = it->second;
690 : return true;
691 : }
692 :
693 5689 : bool CBudgetManager::IsBudgetPaymentBlock(int nBlockHeight, int& nCountThreshold) const
694 : {
695 5689 : int nHighestCount = GetHighestVoteCount(nBlockHeight);
696 5689 : int nCountEnabled = mnodeman.CountEnabled();
697 5689 : int nFivePercent = nCountEnabled / 20;
698 : // threshold for highest finalized budgets (highest vote count - 10% of active masternodes)
699 5689 : nCountThreshold = nHighestCount - (nCountEnabled / 10);
700 : // reduce the threshold if there are less than 10 enabled masternodes
701 5689 : if (nCountThreshold == nHighestCount) nCountThreshold--;
702 :
703 5689 : LogPrint(BCLog::MNBUDGET,"%s: nHighestCount: %lli, 5%% of Masternodes: %lli.\n",
704 : __func__, nHighestCount, nFivePercent);
705 :
706 : // If budget doesn't have 5% of the network votes, then we should pay a masternode instead
707 5689 : return (nHighestCount > nFivePercent);
708 : }
709 :
710 2721 : bool CBudgetManager::IsBudgetPaymentBlock(int nBlockHeight) const
711 : {
712 2721 : int nCountThreshold;
713 2721 : return IsBudgetPaymentBlock(nBlockHeight, nCountThreshold);
714 : }
715 :
716 27 : TrxValidationStatus CBudgetManager::IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const
717 : {
718 27 : int nCountThreshold = 0;
719 27 : if (!IsBudgetPaymentBlock(nBlockHeight, nCountThreshold)) {
720 : // If budget doesn't have 5% of the network votes, then we should pay a masternode instead
721 : return TrxValidationStatus::InValid;
722 : }
723 :
724 : // check the highest finalized budgets (- 10% to assist in consensus)
725 27 : bool fThreshold = false;
726 27 : {
727 27 : LOCK(cs_budgets);
728 : // Get the finalized budget with the highest amount of votes..
729 27 : const auto& highestBudFin = GetBudgetWithHighestVoteCount(nBlockHeight);
730 27 : const CFinalizedBudget* highestVotesBudget = highestBudFin.m_budget_fin;
731 27 : if (highestVotesBudget) {
732 : // Need to surpass the threshold
733 27 : if (highestBudFin.m_vote_count > nCountThreshold) {
734 27 : fThreshold = true;
735 27 : if (highestVotesBudget->IsTransactionValid(txNew, nBlockHash, nBlockHeight) ==
736 : TrxValidationStatus::Valid) {
737 48 : return TrxValidationStatus::Valid;
738 : }
739 : }
740 : // tx not valid
741 3 : LogPrint(BCLog::MNBUDGET, "%s: ignoring budget. Out of range or tx not valid.\n", __func__);
742 : }
743 : }
744 :
745 : // If not enough masternodes autovoted for any of the finalized budgets or if none of the txs
746 : // are valid, we should pay a masternode instead
747 3 : return fThreshold ? TrxValidationStatus::InValid : TrxValidationStatus::VoteThreshold;
748 : }
749 :
750 164 : std::vector<CBudgetProposal*> CBudgetManager::GetAllProposalsOrdered()
751 : {
752 164 : LOCK(cs_proposals);
753 164 : std::vector<CBudgetProposal*> vBudgetProposalRet;
754 1087 : for (auto& it: mapProposals) {
755 923 : CBudgetProposal* pbudgetProposal = &(it.second);
756 923 : RemoveStaleVotesOnProposal(pbudgetProposal);
757 923 : vBudgetProposalRet.push_back(pbudgetProposal);
758 : }
759 164 : std::sort(vBudgetProposalRet.begin(), vBudgetProposalRet.end(), CBudgetProposal::PtrHigherYes);
760 328 : return vBudgetProposalRet;
761 : }
762 :
763 136 : std::vector<CBudgetProposal> CBudgetManager::GetBudget()
764 : {
765 272 : LOCK(cs_proposals);
766 :
767 136 : int nHeight = GetBestHeight();
768 136 : if (nHeight <= 0)
769 0 : return {};
770 :
771 : // ------- Get proposals ordered by votes (highest to lowest)
772 272 : std::vector<CBudgetProposal*> vProposalsOrdered = GetAllProposalsOrdered();
773 :
774 : // ------- Grab The Budgets In Order
775 272 : std::vector<CBudgetProposal> vBudgetProposalsRet;
776 136 : CAmount nBudgetAllocated = 0;
777 :
778 136 : const int nBlocksPerCycle = Params().GetConsensus().nBudgetCycleBlocks;
779 136 : int nBlockStart = nHeight - nHeight % nBlocksPerCycle + nBlocksPerCycle;
780 136 : int nBlockEnd = nBlockStart + nBlocksPerCycle - 1;
781 136 : int mnCount = mnodeman.CountEnabled();
782 136 : CAmount nTotalBudget = GetTotalBudget(nBlockStart);
783 :
784 675 : for (CBudgetProposal* pbudgetProposal: vProposalsOrdered) {
785 1617 : LogPrint(BCLog::MNBUDGET,"%s: Processing Budget %s\n", __func__, pbudgetProposal->GetName());
786 : //prop start/end should be inside this period
787 539 : if (pbudgetProposal->IsPassing(nBlockStart, nBlockEnd, mnCount)) {
788 43 : LogPrint(BCLog::MNBUDGET,"%s: - Check 1 passed: valid=%d | %ld <= %ld | %ld >= %ld | Yeas=%d Nays=%d Count=%d | established=%d\n",
789 : __func__, pbudgetProposal->IsValid(), pbudgetProposal->GetBlockStart(), nBlockStart, pbudgetProposal->GetBlockEnd(),
790 : nBlockEnd, pbudgetProposal->GetYeas(), pbudgetProposal->GetNays(), mnCount / 10, pbudgetProposal->IsEstablished());
791 :
792 43 : if (pbudgetProposal->GetAmount() + nBudgetAllocated <= nTotalBudget) {
793 43 : pbudgetProposal->SetAllotted(pbudgetProposal->GetAmount());
794 43 : nBudgetAllocated += pbudgetProposal->GetAmount();
795 43 : vBudgetProposalsRet.emplace_back(*pbudgetProposal);
796 43 : LogPrint(BCLog::MNBUDGET,"%s: - Check 2 passed: Budget added\n", __func__);
797 : } else {
798 0 : pbudgetProposal->SetAllotted(0);
799 0 : LogPrint(BCLog::MNBUDGET,"%s: - Check 2 failed: no amount allotted\n", __func__);
800 : }
801 :
802 : } else {
803 496 : LogPrint(BCLog::MNBUDGET,"%s: - Check 1 failed: valid=%d | %ld <= %ld | %ld >= %ld | Yeas=%d Nays=%d Count=%d | established=%d\n",
804 : __func__, pbudgetProposal->IsValid(), pbudgetProposal->GetBlockStart(), nBlockStart, pbudgetProposal->GetBlockEnd(),
805 : nBlockEnd, pbudgetProposal->GetYeas(), pbudgetProposal->GetNays(), mnodeman.CountEnabled() / 10,
806 : pbudgetProposal->IsEstablished());
807 : }
808 :
809 : }
810 :
811 136 : return vBudgetProposalsRet;
812 : }
813 :
814 43 : std::vector<CFinalizedBudget*> CBudgetManager::GetFinalizedBudgets()
815 : {
816 43 : LOCK(cs_budgets);
817 :
818 43 : std::vector<CFinalizedBudget*> vFinalizedBudgetsRet;
819 :
820 : // ------- Grab The Budgets In Order
821 84 : for (auto& it: mapFinalizedBudgets) {
822 41 : vFinalizedBudgetsRet.push_back(&(it.second));
823 : }
824 43 : std::sort(vFinalizedBudgetsRet.begin(), vFinalizedBudgetsRet.end(), CFinalizedBudget::PtrGreater);
825 :
826 86 : return vFinalizedBudgetsRet;
827 : }
828 :
829 0 : std::string CBudgetManager::GetRequiredPaymentsString(int nBlockHeight)
830 : {
831 0 : LOCK(cs_budgets);
832 :
833 0 : std::string ret = "unknown-budget";
834 :
835 0 : std::map<uint256, CFinalizedBudget>::iterator it = mapFinalizedBudgets.begin();
836 0 : while (it != mapFinalizedBudgets.end()) {
837 0 : CFinalizedBudget* pfinalizedBudget = &((*it).second);
838 0 : if (nBlockHeight >= pfinalizedBudget->GetBlockStart() && nBlockHeight <= pfinalizedBudget->GetBlockEnd()) {
839 0 : CTxBudgetPayment payment;
840 0 : if (pfinalizedBudget->GetBudgetPaymentByBlock(nBlockHeight, payment)) {
841 0 : if (ret == "unknown-budget") {
842 0 : ret = payment.nProposalHash.ToString();
843 : } else {
844 0 : ret += ",";
845 0 : ret += payment.nProposalHash.ToString();
846 : }
847 : } else {
848 0 : LogPrint(BCLog::MNBUDGET,"%s: Couldn't find budget payment for block %d\n", __func__, nBlockHeight);
849 : }
850 : }
851 :
852 0 : ++it;
853 : }
854 :
855 0 : return ret;
856 : }
857 :
858 29647 : CAmount CBudgetManager::GetTotalBudget(int nHeight)
859 : {
860 : // 100% of block reward after V5.5 upgrade
861 29647 : CAmount nSubsidy = GetBlockValue(nHeight);
862 :
863 : // 20% of block reward prior to V5.5 upgrade
864 29647 : if (nHeight <= Params().GetConsensus().vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight) {
865 25900 : nSubsidy /= 5;
866 : }
867 :
868 : // multiplied by the number of blocks in a cycle (144 on testnet, 30*1440 on mainnet)
869 29647 : return nSubsidy * Params().GetConsensus().nBudgetCycleBlocks;
870 : }
871 :
872 45 : void CBudgetManager::AddSeenProposalVote(const CBudgetVote& vote)
873 : {
874 45 : LOCK(cs_votes);
875 90 : mapSeenProposalVotes.emplace(vote.GetHash(), vote);
876 45 : }
877 :
878 36 : void CBudgetManager::AddSeenFinalizedBudgetVote(const CFinalizedBudgetVote& vote)
879 : {
880 36 : LOCK(cs_finalizedvotes);
881 72 : mapSeenFinalizedBudgetVotes.emplace(vote.GetHash(), vote);
882 36 : }
883 :
884 2344 : void CBudgetManager::RemoveStaleVotesOnProposal(CBudgetProposal* prop)
885 : {
886 2344 : AssertLockHeld(cs_proposals);
887 4688 : LogPrint(BCLog::MNBUDGET, "Cleaning proposal votes for %s. Before: YES=%d, NO=%d\n",
888 : prop->GetName(), prop->GetYeas(), prop->GetNays());
889 :
890 2344 : auto it = prop->mapVotes.begin();
891 2806 : while (it != prop->mapVotes.end()) {
892 462 : auto mnList = deterministicMNManager->GetListAtChainTip();
893 924 : auto dmn = mnList.GetMNByCollateral(it->first);
894 462 : if (dmn) {
895 86 : (*it).second.SetValid(!dmn->IsPoSeBanned());
896 : } else {
897 : // -- Legacy System (!TODO: remove after enforcement) --
898 376 : CMasternode* pmn = mnodeman.Find(it->first);
899 639 : (*it).second.SetValid(pmn && pmn->IsEnabled());
900 : }
901 462 : ++it;
902 : }
903 :
904 4688 : LogPrint(BCLog::MNBUDGET, "Cleaned proposal votes for %s. After: YES=%d, NO=%d\n",
905 : prop->GetName(), prop->GetYeas(), prop->GetNays());
906 2344 : }
907 :
908 87 : void CBudgetManager::RemoveStaleVotesOnFinalBudget(CFinalizedBudget* fbud)
909 : {
910 87 : AssertLockHeld(cs_budgets);
911 261 : LogPrint(BCLog::MNBUDGET, "Cleaning finalized budget votes for [%s (%s)]. Before: %d\n",
912 : fbud->GetName(), fbud->GetProposalsStr(), fbud->GetVoteCount());
913 :
914 87 : auto it = fbud->mapVotes.begin();
915 344 : while (it != fbud->mapVotes.end()) {
916 257 : auto mnList = deterministicMNManager->GetListAtChainTip();
917 514 : auto dmn = mnList.GetMNByCollateral(it->first);
918 257 : if (dmn) {
919 29 : (*it).second.SetValid(!dmn->IsPoSeBanned());
920 : } else {
921 : // -- Legacy System (!TODO: remove after enforcement) --
922 228 : CMasternode* pmn = mnodeman.Find(it->first);
923 344 : (*it).second.SetValid(pmn && pmn->IsEnabled());
924 : }
925 257 : ++it;
926 : }
927 261 : LogPrint(BCLog::MNBUDGET, "Cleaned finalized budget votes for [%s (%s)]. After: %d\n",
928 : fbud->GetName(), fbud->GetProposalsStr(), fbud->GetVoteCount());
929 87 : }
930 :
931 38 : CDataStream CBudgetManager::GetProposalVoteSerialized(const uint256& voteHash) const
932 : {
933 38 : LOCK(cs_votes);
934 38 : CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
935 38 : ss.reserve(1000);
936 38 : ss << mapSeenProposalVotes.at(voteHash);
937 76 : return ss;
938 : }
939 :
940 163 : CDataStream CBudgetManager::GetProposalSerialized(const uint256& propHash) const
941 : {
942 163 : LOCK(cs_proposals);
943 326 : return mapProposals.at(propHash).GetBroadcast();
944 : }
945 :
946 31 : CDataStream CBudgetManager::GetFinalizedBudgetVoteSerialized(const uint256& voteHash) const
947 : {
948 31 : LOCK(cs_finalizedvotes);
949 31 : CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
950 31 : ss.reserve(1000);
951 31 : ss << mapSeenFinalizedBudgetVotes.at(voteHash);
952 62 : return ss;
953 : }
954 :
955 13 : CDataStream CBudgetManager::GetFinalizedBudgetSerialized(const uint256& budgetHash) const
956 : {
957 13 : LOCK(cs_budgets);
958 26 : return mapFinalizedBudgets.at(budgetHash).GetBroadcast();
959 : }
960 :
961 0 : bool CBudgetManager::AddAndRelayProposalVote(const CBudgetVote& vote, std::string& strError)
962 : {
963 0 : if (UpdateProposal(vote, nullptr, strError)) {
964 0 : AddSeenProposalVote(vote);
965 0 : vote.Relay();
966 0 : return true;
967 : }
968 : return false;
969 : }
970 :
971 38613 : void CBudgetManager::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload)
972 : {
973 52291 : if (g_tiertwo_sync_state.GetSyncPhase() <= MASTERNODE_SYNC_BUDGET) return;
974 :
975 14789 : if (strBudgetMode == "suggest") { //suggest the budget we see
976 0 : SubmitFinalBudget();
977 : }
978 :
979 14789 : int nCurrentHeight = GetBestHeight();
980 : //this function should be called 1/14 blocks, allowing up to 100 votes per day on all proposals
981 14789 : if (nCurrentHeight % 14 != 0) return;
982 :
983 : // incremental sync with our peers
984 1111 : if (g_tiertwo_sync_state.IsSynced()) {
985 1111 : LogPrint(BCLog::MNBUDGET,"%s: incremental sync started\n", __func__);
986 : // Once every 7 days, try to relay the complete budget data
987 1111 : if (GetRandInt(Params().IsRegTestNet() ? 2 : 720) == 0) {
988 539 : ResetSync();
989 : }
990 :
991 1111 : CBudgetManager* manager = this;
992 1111 : g_connman->ForEachNode([manager](CNode* pnode){
993 11011 : if (pnode->nVersion >= ActiveProtocol())
994 11011 : manager->Sync(pnode, true);
995 11011 : });
996 1111 : MarkSynced();
997 : }
998 :
999 : // remove expired/heavily downvoted budgets
1000 1111 : CheckAndRemove();
1001 :
1002 1111 : {
1003 1111 : LOCK(cs_proposals);
1004 1111 : LogPrint(BCLog::MNBUDGET,"%s: mapProposals cleanup - size: %d\n", __func__, mapProposals.size());
1005 2532 : for (auto& it: mapProposals) {
1006 1421 : RemoveStaleVotesOnProposal(&it.second);
1007 : }
1008 : }
1009 1111 : {
1010 1111 : LOCK(cs_budgets);
1011 1111 : LogPrint(BCLog::MNBUDGET,"%s: mapFinalizedBudgets cleanup - size: %d\n", __func__, mapFinalizedBudgets.size());
1012 1198 : for (auto& it: mapFinalizedBudgets) {
1013 87 : RemoveStaleVotesOnFinalBudget(&it.second);
1014 : }
1015 : }
1016 :
1017 1111 : int64_t now = GetTime();
1018 3333 : const auto cleanOrphans = [now](auto& mutex, auto& mapOrphans, auto& mapSeen) {
1019 2222 : LOCK(mutex);
1020 2222 : for (auto it = mapOrphans.begin() ; it != mapOrphans.end();) {
1021 0 : int64_t lastReceivedVoteTime = it->second.second;
1022 0 : if (lastReceivedVoteTime + BUDGET_ORPHAN_VOTES_CLEANUP_SECONDS < now) {
1023 : // Clean seen votes
1024 0 : for (const auto& voteIt : it->second.first) {
1025 0 : mapSeen.erase(voteIt.GetHash());
1026 : }
1027 : // Remove proposal orphan votes
1028 0 : it = mapOrphans.erase(it);
1029 : } else {
1030 2222 : it++;
1031 : }
1032 : }
1033 3333 : };
1034 :
1035 : // Clean orphan proposal votes if no parent arrived after an hour.
1036 1111 : cleanOrphans(cs_votes, mapOrphanProposalVotes, mapSeenProposalVotes);
1037 : // Clean orphan budget votes if no parent arrived after an hour.
1038 1111 : cleanOrphans(cs_finalizedvotes, mapOrphanFinalizedBudgetVotes, mapSeenFinalizedBudgetVotes);
1039 :
1040 : // Once every 2 weeks (1/14 * 1/1440), clean the seen maps
1041 1111 : if (g_tiertwo_sync_state.IsSynced() && GetRandInt(1440) == 0) {
1042 1 : ReloadMapSeen();
1043 : }
1044 :
1045 1111 : LogPrint(BCLog::MNBUDGET,"%s: PASSED\n", __func__);
1046 : }
1047 :
1048 731 : int CBudgetManager::ProcessBudgetVoteSync(const uint256& nProp, CNode* pfrom)
1049 : {
1050 1462 : if (nProp.IsNull()) {
1051 2184 : LOCK2(cs_budgets, cs_proposals);
1052 728 : if (!(pfrom->addr.IsRFC1918() || pfrom->addr.IsLocal())) {
1053 0 : if (g_netfulfilledman.HasFulfilledRequest(pfrom->addr, BUDGET_SYNC_REQUEST_RECV)) {
1054 0 : LogPrint(BCLog::MASTERNODE, "budgetsync - peer %i already asked for budget sync\n", pfrom->GetId());
1055 : // let's not be so hard with the node for now.
1056 0 : return 10;
1057 : }
1058 : }
1059 : }
1060 :
1061 1462 : if (nProp.IsNull()) Sync(pfrom, false /* fPartial */);
1062 3 : else SyncSingleItem(pfrom, nProp);
1063 731 : LogPrint(BCLog::MNBUDGET, "mnvs - Sent Masternode votes to peer %i\n", pfrom->GetId());
1064 : return 0;
1065 : }
1066 :
1067 165 : int CBudgetManager::ProcessProposal(CBudgetProposal& proposal)
1068 : {
1069 165 : const uint256& nHash = proposal.GetHash();
1070 165 : if (HaveProposal(nHash)) {
1071 0 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1072 0 : return 0;
1073 : }
1074 165 : if (!AddProposal(proposal)) {
1075 : return 0;
1076 : }
1077 :
1078 : // Relay only if we are synchronized
1079 : // Makes no sense to relay proposals to the peers from where we are syncing them.
1080 165 : if (g_tiertwo_sync_state.IsSynced()) proposal.Relay();
1081 165 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1082 :
1083 330 : LogPrint(BCLog::MNBUDGET, "mprop (new) %s\n", nHash.ToString());
1084 : //We might have active votes for this proposal that are valid now
1085 165 : CheckOrphanVotes();
1086 : return 0;
1087 : }
1088 :
1089 45 : bool CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom, CValidationState& state)
1090 : {
1091 45 : const uint256& voteID = vote.GetHash();
1092 :
1093 45 : if (HaveSeenProposalVote(voteID)) {
1094 0 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1095 0 : return false;
1096 : }
1097 :
1098 90 : std::string err;
1099 45 : if (vote.GetTime() > GetTime() + (60 * 60)) {
1100 0 : err = strprintf("new vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n",
1101 0 : vote.GetHash().ToString(), vote.GetTime(), GetTime() + (60 * 60));
1102 0 : return state.Invalid(false, REJECT_INVALID, "bad-mvote", err);
1103 : }
1104 :
1105 90 : const CTxIn& voteVin = vote.GetVin();
1106 :
1107 : // See if this vote was signed with a deterministic masternode
1108 90 : auto mnList = deterministicMNManager->GetListAtChainTip();
1109 90 : auto dmn = mnList.GetMNByCollateral(voteVin.prevout);
1110 45 : if (dmn) {
1111 20 : const std::string& mn_protx_id = dmn->proTxHash.ToString();
1112 :
1113 10 : if (dmn->IsPoSeBanned()) {
1114 0 : err = strprintf("masternode (%s) not valid or PoSe banned", mn_protx_id);
1115 0 : return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err);
1116 : }
1117 :
1118 10 : AddSeenProposalVote(vote);
1119 :
1120 10 : if (!vote.CheckSignature(dmn->pdmnState->keyIDVoting)) {
1121 0 : err = strprintf("invalid mvote sig from dmn: %s", mn_protx_id);
1122 0 : return state.DoS(100, false, REJECT_INVALID, "bad-mvote-sig", false, err);
1123 : }
1124 :
1125 10 : if (!UpdateProposal(vote, pfrom, err)) {
1126 0 : return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, strprintf("%s (%s)", err, mn_protx_id));
1127 : }
1128 :
1129 : // Relay only if we are synchronized
1130 : // Makes no sense to relay votes to the peers from where we are syncing them.
1131 10 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1132 10 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1133 30 : LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n",
1134 : voteID.ToString(), vote.GetProposalHash().ToString(), mn_protx_id);
1135 10 : return true;
1136 : }
1137 :
1138 : // -- Legacy System (!TODO: remove after enforcement) --
1139 :
1140 35 : CMasternode* pmn = mnodeman.Find(voteVin.prevout);
1141 35 : if (!pmn) {
1142 0 : err = strprintf("unknown masternode - vin: %s", voteVin.prevout.ToString());
1143 : // Ask for MN only if we finished syncing the MN list.
1144 0 : if (pfrom && g_tiertwo_sync_state.IsMasternodeListSynced()) mnodeman.AskForMN(pfrom, voteVin);
1145 0 : return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err);
1146 : }
1147 :
1148 35 : if (!pmn->IsEnabled()) {
1149 0 : return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, "masternode not valid");
1150 : }
1151 :
1152 35 : AddSeenProposalVote(vote);
1153 :
1154 35 : if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) {
1155 0 : if (g_tiertwo_sync_state.IsSynced()) {
1156 0 : err = strprintf("signature from masternode %s invalid", voteVin.prevout.ToString());
1157 0 : return state.DoS(20, false, REJECT_INVALID, "bad-mvote-sig", false, err);
1158 : }
1159 : return false;
1160 : }
1161 :
1162 35 : if (!UpdateProposal(vote, pfrom, err)) {
1163 2 : return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err);
1164 : }
1165 :
1166 : // Relay only if we are synchronized
1167 : // Makes no sense to relay votes to the peers from where we are syncing them.
1168 34 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1169 34 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1170 136 : LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n",
1171 : voteID.ToString(), vote.GetProposalHash().ToString(), voteVin.prevout.ToString());
1172 : return true;
1173 : }
1174 :
1175 14 : int CBudgetManager::ProcessFinalizedBudget(CFinalizedBudget& finalbudget, CNode* pfrom)
1176 : {
1177 :
1178 14 : const uint256& nHash = finalbudget.GetHash();
1179 14 : if (HaveFinalizedBudget(nHash)) {
1180 0 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1181 0 : return 0;
1182 : }
1183 14 : if (!AddFinalizedBudget(finalbudget, pfrom)) {
1184 : return 0;
1185 : }
1186 :
1187 : // Relay only if we are synchronized
1188 : // Makes no sense to relay finalizations to the peers from where we are syncing them.
1189 12 : if (g_tiertwo_sync_state.IsSynced()) finalbudget.Relay();
1190 12 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1191 :
1192 24 : LogPrint(BCLog::MNBUDGET, "fbs (new) %s\n", nHash.ToString());
1193 : //we might have active votes for this budget that are now valid
1194 12 : CheckOrphanVotes();
1195 : return 0;
1196 : }
1197 :
1198 36 : bool CBudgetManager::ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode* pfrom, CValidationState& state)
1199 : {
1200 36 : const uint256& voteID = vote.GetHash();
1201 :
1202 36 : if (HaveSeenFinalizedBudgetVote(voteID)) {
1203 0 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1204 0 : return false;
1205 : }
1206 :
1207 72 : std::string err;
1208 36 : if (vote.GetTime() > GetTime() + (60 * 60)) {
1209 0 : err = strprintf("new vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n",
1210 0 : vote.GetHash().ToString(), vote.GetTime(), GetTime() + (60 * 60));
1211 0 : return state.Invalid(false, REJECT_INVALID, "bad-fbvote", err);
1212 : }
1213 :
1214 72 : const CTxIn& voteVin = vote.GetVin();
1215 :
1216 : // See if this vote was signed with a deterministic masternode
1217 72 : auto mnList = deterministicMNManager->GetListAtChainTip();
1218 72 : auto dmn = mnList.GetMNByCollateral(voteVin.prevout);
1219 36 : if (dmn) {
1220 18 : const std::string& mn_protx_id = dmn->proTxHash.ToString();
1221 :
1222 9 : if (dmn->IsPoSeBanned()) {
1223 0 : err = strprintf("masternode (%s) not valid or PoSe banned", mn_protx_id);
1224 0 : return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err);
1225 : }
1226 :
1227 9 : AddSeenFinalizedBudgetVote(vote);
1228 :
1229 9 : if (!vote.CheckSignature(dmn->pdmnState->pubKeyOperator.Get())) {
1230 0 : err = strprintf("invalid fbvote sig from dmn: %s", mn_protx_id);
1231 0 : return state.DoS(100, false, REJECT_INVALID, "bad-fbvote-sig", false, err);
1232 : }
1233 :
1234 9 : if (!UpdateFinalizedBudget(vote, pfrom, err)) {
1235 3 : return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, strprintf("%s (%s)", err, mn_protx_id));
1236 : }
1237 :
1238 : // Relay only if we are synchronized
1239 : // Makes no sense to relay votes to the peers from where we are syncing them.
1240 8 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1241 8 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1242 24 : LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from dmn %s\n",
1243 : voteID.ToString(), vote.GetBudgetHash().ToString(), mn_protx_id);
1244 8 : return true;
1245 : }
1246 :
1247 : // -- Legacy System (!TODO: remove after enforcement) --
1248 27 : CMasternode* pmn = mnodeman.Find(voteVin.prevout);
1249 27 : if (!pmn) {
1250 0 : err = strprintf("unknown masternode - vin: %s", voteVin.prevout.ToString());
1251 : // Ask for MN only if we finished syncing the MN list.
1252 0 : if (pfrom && g_tiertwo_sync_state.IsMasternodeListSynced()) mnodeman.AskForMN(pfrom, voteVin);
1253 0 : return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err);
1254 : }
1255 :
1256 27 : if (!pmn->IsEnabled()) {
1257 0 : return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, "masternode not valid");
1258 : }
1259 :
1260 27 : AddSeenFinalizedBudgetVote(vote);
1261 :
1262 27 : if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) {
1263 0 : if (g_tiertwo_sync_state.IsSynced()) {
1264 0 : err = strprintf("signature from masternode %s invalid", voteVin.prevout.ToString());
1265 0 : return state.DoS(20, false, REJECT_INVALID, "bad-fbvote-sig", false, err);
1266 : }
1267 : return false;
1268 : }
1269 :
1270 27 : if (!UpdateFinalizedBudget(vote, pfrom, err)) {
1271 4 : return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err);
1272 : }
1273 :
1274 : // Relay only if we are synchronized
1275 : // Makes no sense to relay votes to the peers from where we are syncing them.
1276 25 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1277 25 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1278 100 : LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from mn %s\n",
1279 : voteID.ToString(), vote.GetBudgetHash().ToString(), voteVin.prevout.ToString());
1280 : return true;
1281 : }
1282 :
1283 55744 : bool CBudgetManager::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv, int& banScore)
1284 : {
1285 55744 : banScore = ProcessMessageInner(pfrom, strCommand, vRecv);
1286 55744 : return banScore == 0;
1287 : }
1288 :
1289 55744 : int CBudgetManager::ProcessMessageInner(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
1290 : {
1291 55744 : if (!g_tiertwo_sync_state.IsBlockchainSynced()) return 0;
1292 :
1293 55734 : if (strCommand == NetMsgType::BUDGETVOTESYNC) {
1294 : // Masternode vote sync
1295 731 : uint256 nProp;
1296 731 : vRecv >> nProp;
1297 731 : return ProcessBudgetVoteSync(nProp, pfrom);
1298 : }
1299 :
1300 55003 : if (strCommand == NetMsgType::BUDGETPROPOSAL) {
1301 : // Masternode Proposal
1302 330 : CBudgetProposal proposal;
1303 165 : if (!proposal.ParseBroadcast(vRecv)) {
1304 : return 20;
1305 : }
1306 165 : {
1307 : // Clear inv request
1308 165 : LOCK(cs_main);
1309 165 : g_connman->RemoveAskFor(proposal.GetHash(), MSG_BUDGET_PROPOSAL);
1310 : }
1311 165 : return ProcessProposal(proposal);
1312 : }
1313 :
1314 54838 : if (strCommand == NetMsgType::BUDGETVOTE) {
1315 76 : CBudgetVote vote;
1316 38 : vRecv >> vote;
1317 38 : vote.SetValid(true);
1318 :
1319 38 : {
1320 : // Clear inv request
1321 38 : LOCK(cs_main);
1322 38 : g_connman->RemoveAskFor(vote.GetHash(), MSG_BUDGET_VOTE);
1323 : }
1324 :
1325 76 : CValidationState state;
1326 38 : if (!ProcessProposalVote(vote, pfrom, state)) {
1327 1 : int nDos = 0;
1328 1 : if (state.IsInvalid(nDos)) {
1329 2 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, FormatStateMessage(state));
1330 : }
1331 1 : return nDos;
1332 : }
1333 : return 0;
1334 : }
1335 :
1336 54800 : if (strCommand == NetMsgType::FINALBUDGET) {
1337 : // Finalized Budget Suggestion
1338 28 : CFinalizedBudget finalbudget;
1339 14 : if (!finalbudget.ParseBroadcast(vRecv)) {
1340 : return 20;
1341 : }
1342 14 : {
1343 : // Clear inv request
1344 14 : LOCK(cs_main);
1345 14 : g_connman->RemoveAskFor(finalbudget.GetHash(), MSG_BUDGET_FINALIZED);
1346 : }
1347 14 : return ProcessFinalizedBudget(finalbudget, pfrom);
1348 : }
1349 :
1350 54786 : if (strCommand == NetMsgType::FINALBUDGETVOTE) {
1351 62 : CFinalizedBudgetVote vote;
1352 31 : vRecv >> vote;
1353 31 : vote.SetValid(true);
1354 :
1355 31 : {
1356 : // Clear inv request
1357 31 : LOCK(cs_main);
1358 31 : g_connman->RemoveAskFor(vote.GetHash(), MSG_BUDGET_FINALIZED_VOTE);
1359 : }
1360 :
1361 62 : CValidationState state;
1362 31 : if (!ProcessFinalizedBudgetVote(vote, pfrom, state)) {
1363 3 : int nDos = 0;
1364 3 : if (state.IsInvalid(nDos)) {
1365 6 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, FormatStateMessage(state));
1366 : }
1367 3 : return nDos;
1368 : }
1369 : return 0;
1370 : }
1371 :
1372 : // nothing was done
1373 : return 0;
1374 : }
1375 :
1376 2005 : void CBudgetManager::SetSynced(bool synced)
1377 : {
1378 2005 : {
1379 2005 : LOCK(cs_proposals);
1380 4102 : for (auto& it: mapProposals) {
1381 2097 : CBudgetProposal* pbudgetProposal = &(it.second);
1382 2097 : if (pbudgetProposal && pbudgetProposal->IsValid()) {
1383 : //mark votes
1384 2097 : pbudgetProposal->SetSynced(synced);
1385 : }
1386 : }
1387 : }
1388 2005 : {
1389 2005 : LOCK(cs_budgets);
1390 2134 : for (auto& it: mapFinalizedBudgets) {
1391 129 : CFinalizedBudget* pfinalizedBudget = &(it.second);
1392 129 : if (pfinalizedBudget && pfinalizedBudget->IsValid()) {
1393 : //mark votes
1394 129 : pfinalizedBudget->SetSynced(synced);
1395 : }
1396 : }
1397 : }
1398 2005 : }
1399 :
1400 : template<typename T>
1401 4 : static bool relayItemIfFound(const uint256& itemHash, CNode* pfrom, RecursiveMutex& cs, std::map<uint256, T>& map, const char* type)
1402 : {
1403 4 : CNetMsgMaker msgMaker(pfrom->GetSendVersion());
1404 8 : LOCK(cs);
1405 4 : const auto& it = map.find(itemHash);
1406 4 : if (it == map.end()) return false;
1407 3 : T* item = &(it->second);
1408 3 : if (!item->IsValid()) return true; // don't broadcast invalid items
1409 3 : g_connman->PushMessage(pfrom, msgMaker.Make(type, item->GetBroadcast()));
1410 3 : int nInvCount = 1;
1411 3 : item->SyncVotes(pfrom, false /* fPartial */, nInvCount);
1412 3 : LogPrint(BCLog::MNBUDGET, "%s: single %s sent %d items\n", __func__, type, nInvCount);
1413 : return true;
1414 : }
1415 :
1416 : template<typename T>
1417 23478 : static void relayInventoryItems(CNode* pfrom, RecursiveMutex& cs, std::map<uint256, T>& map, bool fPartial, GetDataMsg invType, const int mn_sync_budget_type)
1418 : {
1419 23478 : CNetMsgMaker msgMaker(pfrom->GetSendVersion());
1420 23478 : int nInvCount = 0;
1421 : {
1422 46956 : LOCK(cs);
1423 28949 : for (auto& it: map) {
1424 5471 : T* item = &(it.second);
1425 5471 : if (item && item->IsValid()) {
1426 5471 : pfrom->PushInventory(CInv(invType, item->GetHash()));
1427 5471 : nInvCount++;
1428 5471 : item->SyncVotes(pfrom, fPartial, nInvCount);
1429 : }
1430 : }
1431 : }
1432 23478 : g_connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, mn_sync_budget_type, nInvCount));
1433 23478 : LogPrint(BCLog::MNBUDGET, "%s: sent %d items\n", __func__, nInvCount);
1434 23478 : }
1435 :
1436 3 : void CBudgetManager::SyncSingleItem(CNode* pfrom, const uint256& nProp)
1437 : {
1438 6 : if (nProp.IsNull()) return;
1439 : // Try first to relay a proposal
1440 3 : if (relayItemIfFound<CBudgetProposal>(nProp, pfrom, cs_proposals, mapProposals, NetMsgType::BUDGETPROPOSAL)) {
1441 : return;
1442 : }
1443 : // Try now to relay a finalization
1444 1 : if (relayItemIfFound<CFinalizedBudget>(nProp, pfrom, cs_budgets, mapFinalizedBudgets, NetMsgType::FINALBUDGET)) {
1445 : return;
1446 : }
1447 0 : LogPrint(BCLog::MNBUDGET, "%s: single request budget item not found\n", __func__);
1448 : }
1449 :
1450 :
1451 11739 : void CBudgetManager::Sync(CNode* pfrom, bool fPartial)
1452 : {
1453 : // Full budget sync request.
1454 11739 : relayInventoryItems<CBudgetProposal>(pfrom, cs_proposals, mapProposals, fPartial, MSG_BUDGET_PROPOSAL, MASTERNODE_SYNC_BUDGET_PROP);
1455 11739 : relayInventoryItems<CFinalizedBudget>(pfrom, cs_budgets, mapFinalizedBudgets, fPartial, MSG_BUDGET_FINALIZED, MASTERNODE_SYNC_BUDGET_FIN);
1456 :
1457 11739 : if (!fPartial) {
1458 : // We are not going to answer full budget sync requests for an hour (chainparams.FulfilledRequestExpireTime()).
1459 : // The remote peer can still do single prop and mnv sync requests if needed.
1460 728 : g_netfulfilledman.AddFulfilledRequest(pfrom->addr, BUDGET_SYNC_REQUEST_RECV);
1461 : }
1462 11739 : }
1463 :
1464 : template<typename T>
1465 4 : static void TryAppendOrphanVoteMap(const T& vote,
1466 : const uint256& parentHash,
1467 : std::map<uint256, std::pair<std::vector<T>, int64_t>>& mapOrphan,
1468 : std::map<uint256, T>& mapSeen)
1469 : {
1470 4 : if (mapOrphan.size() > ORPHAN_VOTES_CACHE_LIMIT) {
1471 : // future: notify user about this
1472 0 : mapSeen.erase(vote.GetHash());
1473 : } else {
1474 : // Append orphan vote
1475 4 : const auto& it = mapOrphan.find(parentHash);
1476 4 : if (it != mapOrphan.end()) {
1477 : // Check size limit and erase it from the seen map if we already passed it
1478 2 : if (it->second.first.size() > ORPHAN_VOTES_CACHE_LIMIT) {
1479 : // future: check if the MN already voted and replace vote
1480 0 : mapSeen.erase(vote.GetHash());
1481 : } else {
1482 2 : it->second.first.emplace_back(vote);
1483 2 : it->second.second = GetTime();
1484 : }
1485 : } else {
1486 4 : mapOrphan.emplace(parentHash, std::make_pair<std::vector<T>, int64_t>({vote}, GetTime()));
1487 : }
1488 : }
1489 4 : }
1490 :
1491 45 : bool CBudgetManager::UpdateProposal(const CBudgetVote& vote, CNode* pfrom, std::string& strError)
1492 : {
1493 90 : LOCK(cs_proposals);
1494 :
1495 45 : const uint256& nProposalHash = vote.GetProposalHash();
1496 45 : const auto& itProposal = mapProposals.find(nProposalHash);
1497 45 : if (itProposal == mapProposals.end()) {
1498 1 : if (pfrom) {
1499 : // only ask for missing items after our syncing process is complete --
1500 : // otherwise we'll think a full sync succeeded when they return a result
1501 1 : if (!g_tiertwo_sync_state.IsSynced()) return false;
1502 :
1503 2 : LogPrint(BCLog::MNBUDGET,"%s: Unknown proposal %d, asking for source proposal\n", __func__, nProposalHash.ToString());
1504 1 : {
1505 1 : LOCK(cs_votes);
1506 1 : TryAppendOrphanVoteMap<CBudgetVote>(vote, nProposalHash, mapOrphanProposalVotes, mapSeenProposalVotes);
1507 : }
1508 :
1509 1 : if (!g_netfulfilledman.HasItemRequest(pfrom->addr, nProposalHash)) {
1510 1 : g_connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::BUDGETVOTESYNC, nProposalHash));
1511 1 : g_netfulfilledman.AddItemRequest(pfrom->addr, nProposalHash);
1512 : }
1513 : }
1514 :
1515 45 : strError = "Proposal not found!";
1516 : return false;
1517 : }
1518 :
1519 : // Add or update vote
1520 44 : return itProposal->second.AddOrUpdateVote(vote, strError);
1521 : }
1522 :
1523 37 : bool CBudgetManager::UpdateFinalizedBudget(const CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError)
1524 : {
1525 74 : LOCK(cs_budgets);
1526 :
1527 37 : const uint256& nBudgetHash = vote.GetBudgetHash();
1528 37 : if (!mapFinalizedBudgets.count(nBudgetHash)) {
1529 3 : if (pfrom) {
1530 : // only ask for missing items after our syncing process is complete --
1531 : // otherwise we'll think a full sync succeeded when they return a result
1532 3 : if (!g_tiertwo_sync_state.IsSynced()) return false;
1533 :
1534 6 : LogPrint(BCLog::MNBUDGET,"%s: Unknown Finalized Proposal %s, asking for source budget\n", __func__, nBudgetHash.ToString());
1535 3 : {
1536 3 : LOCK(cs_finalizedvotes);
1537 3 : TryAppendOrphanVoteMap<CFinalizedBudgetVote>(vote, nBudgetHash, mapOrphanFinalizedBudgetVotes, mapSeenFinalizedBudgetVotes);
1538 : }
1539 :
1540 3 : if (!g_netfulfilledman.HasItemRequest(pfrom->addr, nBudgetHash)) {
1541 1 : g_connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::BUDGETVOTESYNC, nBudgetHash));
1542 1 : g_netfulfilledman.AddItemRequest(pfrom->addr, nBudgetHash);
1543 : }
1544 : }
1545 :
1546 3 : strError = "Finalized Budget " + nBudgetHash.ToString() + " not found!";
1547 3 : return false;
1548 : }
1549 68 : LogPrint(BCLog::MNBUDGET,"%s: Finalized Proposal %s added\n", __func__, nBudgetHash.ToString());
1550 34 : return mapFinalizedBudgets[nBudgetHash].AddOrUpdateVote(vote, strError);
1551 : }
1552 :
1553 136 : std::string CBudgetManager::ToString() const
1554 : {
1555 272 : unsigned int nProposals = WITH_LOCK(cs_proposals, return mapProposals.size(); );
1556 272 : unsigned int nBudgets = WITH_LOCK(cs_budgets, return mapFinalizedBudgets.size(); );
1557 :
1558 136 : unsigned int nSeenVotes = 0, nOrphanVotes = 0;
1559 136 : {
1560 136 : LOCK(cs_votes);
1561 136 : nSeenVotes = mapSeenProposalVotes.size();
1562 136 : nOrphanVotes = mapOrphanProposalVotes.size();
1563 : }
1564 :
1565 136 : unsigned int nSeenFinalizedVotes = 0, nOrphanFinalizedVotes = 0;
1566 136 : {
1567 136 : LOCK(cs_finalizedvotes);
1568 136 : nSeenFinalizedVotes = mapSeenFinalizedBudgetVotes.size();
1569 136 : nOrphanFinalizedVotes = mapOrphanFinalizedBudgetVotes.size();
1570 : }
1571 :
1572 136 : return strprintf("Proposals: %d - Finalized Budgets: %d - "
1573 : "Proposal Votes: %d (orphan: %d) - "
1574 : "Finalized Budget Votes: %d (orphan: %d)",
1575 : nProposals, nBudgets,
1576 136 : nSeenVotes, nOrphanVotes, nSeenFinalizedVotes, nOrphanFinalizedVotes);
1577 : }
1578 :
1579 :
1580 : /*
1581 : * Check Collateral
1582 : */
1583 201 : bool CheckCollateralConfs(const uint256& nTxCollateralHash, int nCurrentHeight, int nProposalHeight, std::string& strError)
1584 : {
1585 201 : const int nRequiredConfs = Params().GetConsensus().nBudgetFeeConfirmations;
1586 201 : const int nConf = nCurrentHeight - nProposalHeight + 1;
1587 :
1588 201 : if (nConf < nRequiredConfs) {
1589 0 : strError = strprintf("Collateral requires at least %d confirmations - %d confirmations (current height: %d, fee tx height: %d)",
1590 0 : nRequiredConfs, nConf, nCurrentHeight, nProposalHeight);
1591 0 : LogPrint(BCLog::MNBUDGET,"%s: %s\n", __func__, strError);
1592 0 : return false;
1593 : }
1594 : return true;
1595 : }
1596 :
1597 201 : bool CheckCollateral(const uint256& nTxCollateralHash, const uint256& nExpectedHash, std::string& strError, int64_t& nTime, int nCurrentHeight, bool fBudgetFinalization)
1598 : {
1599 201 : CTransactionRef txCollateral;
1600 201 : uint256 nBlockHash;
1601 201 : if (!GetTransaction(nTxCollateralHash, txCollateral, nBlockHash, true)) {
1602 0 : strError = strprintf("Can't find collateral tx %s", nTxCollateralHash.ToString());
1603 0 : return false;
1604 : }
1605 :
1606 201 : if (txCollateral->vout.size() < 1) return false;
1607 201 : if (txCollateral->nLockTime != 0) return false;
1608 :
1609 402 : CScript findScript;
1610 201 : findScript << OP_RETURN << ToByteVector(nExpectedHash);
1611 :
1612 201 : bool foundOpReturn = false;
1613 335 : for (const CTxOut &o : txCollateral->vout) {
1614 335 : if (!o.scriptPubKey.IsPayToPublicKeyHash() && !o.scriptPubKey.IsUnspendable()) {
1615 0 : strError = strprintf("Invalid Script %s", txCollateral->ToString());
1616 0 : return false;
1617 : }
1618 335 : if (fBudgetFinalization) {
1619 : // Collateral for budget finalization
1620 : // Note: there are still old valid budgets out there, but the check for the new 5 PIV finalization collateral
1621 : // will also cover the old 50 PIV finalization collateral.
1622 150 : LogPrint(BCLog::MNBUDGET, "Final Budget: o.scriptPubKey(%s) == findScript(%s) ?\n", HexStr(o.scriptPubKey), HexStr(findScript));
1623 30 : if (o.scriptPubKey == findScript) {
1624 17 : LogPrint(BCLog::MNBUDGET, "Final Budget: o.nValue(%ld) >= BUDGET_FEE_TX(%ld) ?\n", o.nValue, BUDGET_FEE_TX);
1625 17 : if(o.nValue >= BUDGET_FEE_TX) {
1626 : foundOpReturn = true;
1627 : break;
1628 : }
1629 : }
1630 : } else {
1631 : // Collateral for normal budget proposal
1632 1525 : LogPrint(BCLog::MNBUDGET, "Normal Budget: o.scriptPubKey(%s) == findScript(%s) ?\n", HexStr(o.scriptPubKey), HexStr(findScript));
1633 305 : if (o.scriptPubKey == findScript) {
1634 184 : LogPrint(BCLog::MNBUDGET, "Normal Budget: o.nValue(%ld) >= PROPOSAL_FEE_TX(%ld) ?\n", o.nValue, PROPOSAL_FEE_TX);
1635 184 : if(o.nValue >= PROPOSAL_FEE_TX) {
1636 : foundOpReturn = true;
1637 : break;
1638 : }
1639 : }
1640 : }
1641 : }
1642 :
1643 201 : if (!foundOpReturn) {
1644 0 : strError = strprintf("Couldn't find opReturn %s in %s", nExpectedHash.ToString(), txCollateral->ToString());
1645 0 : return false;
1646 : }
1647 :
1648 : // Retrieve block height (checking that it's in the active chain) and time
1649 : // both get set in CBudgetProposal/CFinalizedBudget by the caller (AddProposal/AddFinalizedBudget)
1650 402 : if (nBlockHash.IsNull()) {
1651 0 : strError = strprintf("Collateral transaction %s is unconfirmed", nTxCollateralHash.ToString());
1652 0 : return false;
1653 : }
1654 201 : nTime = 0;
1655 201 : int nProposalHeight = 0;
1656 201 : {
1657 201 : LOCK(cs_main);
1658 201 : CBlockIndex* pindex = LookupBlockIndex(nBlockHash);
1659 402 : if (pindex && chainActive.Contains(pindex)) {
1660 201 : nProposalHeight = pindex->nHeight;
1661 201 : nTime = pindex->nTime;
1662 : }
1663 : }
1664 :
1665 201 : if (!nProposalHeight) {
1666 0 : strError = strprintf("Collateral transaction %s not in Active chain", nTxCollateralHash.ToString());
1667 0 : return false;
1668 : }
1669 :
1670 201 : return CheckCollateralConfs(nTxCollateralHash, nCurrentHeight, nProposalHeight, strError);
1671 : }
|