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 293 : void CBudgetManager::ReloadMapSeen()
32 : {
33 879 : const auto reloadSeenMap = [](auto& mutex1, auto& mutex2, const auto& mapBudgets, auto& mapSeen, auto& mapOrphans) {
34 1172 : LOCK2(mutex1, mutex2);
35 586 : mapSeen.clear();
36 586 : mapOrphans.clear();
37 586 : 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 586 : };
46 :
47 293 : reloadSeenMap(cs_proposals, cs_votes, mapProposals, mapSeenProposalVotes, mapOrphanProposalVotes);
48 293 : reloadSeenMap(cs_budgets, cs_finalizedvotes, mapFinalizedBudgets, mapSeenFinalizedBudgetVotes, mapOrphanFinalizedBudgetVotes);
49 293 : }
50 :
51 25 : void CBudgetManager::CheckOrphanVotes()
52 : {
53 25 : {
54 50 : LOCK2(cs_proposals, cs_votes);
55 25 : for (auto itOrphanVotes = mapOrphanProposalVotes.begin(); itOrphanVotes != mapOrphanProposalVotes.end();) {
56 0 : auto itProposal = mapProposals.find(itOrphanVotes->first);
57 0 : if (itProposal != mapProposals.end()) {
58 : // Proposal found.
59 0 : CBudgetProposal* bp = &(itProposal->second);
60 : // Try to add orphan votes
61 0 : for (const CBudgetVote& vote : itOrphanVotes->second.first) {
62 0 : std::string strError;
63 0 : 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 0 : itOrphanVotes = mapOrphanProposalVotes.erase(itOrphanVotes);
69 : } else {
70 0 : ++itOrphanVotes;
71 : }
72 : }
73 : }
74 :
75 25 : {
76 50 : LOCK2(cs_budgets, cs_finalizedvotes);
77 25 : for (auto itOrphanVotes = mapOrphanFinalizedBudgetVotes.begin(); itOrphanVotes != mapOrphanFinalizedBudgetVotes.end();) {
78 0 : auto itFinalBudget = mapFinalizedBudgets.find(itOrphanVotes->first);
79 0 : if (itFinalBudget != mapFinalizedBudgets.end()) {
80 : // Finalized budget found.
81 0 : CFinalizedBudget* fb = &(itFinalBudget->second);
82 : // Try to add orphan votes
83 0 : for (const CFinalizedBudgetVote& vote : itOrphanVotes->second.first) {
84 0 : std::string strError;
85 0 : 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 0 : itOrphanVotes = mapOrphanFinalizedBudgetVotes.erase(itOrphanVotes);
91 : } else {
92 0 : ++itOrphanVotes;
93 : }
94 : }
95 : }
96 :
97 25 : LogPrint(BCLog::MNBUDGET,"%s: Done\n", __func__);
98 25 : }
99 :
100 2 : uint256 CBudgetManager::SubmitFinalBudget()
101 : {
102 2 : static int nSubmittedHeight = 0; // height at which final budget was submitted last time
103 2 : int nCurrentHeight = GetBestHeight();
104 :
105 2 : const int nBlocksPerCycle = Params().GetConsensus().nBudgetCycleBlocks;
106 2 : int nBlockStart = nCurrentHeight - nCurrentHeight % nBlocksPerCycle + nBlocksPerCycle;
107 2 : 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 2 : int finalizationWindow = ((nBlocksPerCycle / 30) * 2);
115 :
116 2 : 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 2 : int nFinalizationStart = nBlockStart - finalizationWindow;
124 :
125 2 : int nOffsetToStart = nFinalizationStart - nCurrentHeight;
126 :
127 2 : 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 2 : std::vector<CBudgetProposal> vBudgetProposals = GetBudget();
135 4 : std::string strBudgetName = "main";
136 4 : std::vector<CTxBudgetPayment> vecTxBudgetPayments;
137 :
138 4 : for (const auto& p : vBudgetProposals) {
139 4 : CTxBudgetPayment txBudgetPayment;
140 2 : txBudgetPayment.nProposalHash = p.GetHash();
141 2 : txBudgetPayment.payee = p.GetPayee();
142 2 : txBudgetPayment.nAmount = p.GetAllotted();
143 2 : vecTxBudgetPayments.push_back(txBudgetPayment);
144 : }
145 :
146 2 : 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 4 : CFinalizedBudget tempBudget(strBudgetName, nBlockStart, vecTxBudgetPayments, UINT256_ZERO);
152 2 : const uint256& budgetHash = tempBudget.GetHash();
153 2 : 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 2 : if (!mapUnconfirmedFeeTx.count(budgetHash)) {
161 : // create the collateral tx, send it to the network and return
162 1 : CTransactionRef wtx;
163 : // Get our change address
164 1 : 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 1 : 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 2 : CReserveKey keyChange(vpwallets[0]);
174 1 : 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 2 : const CWallet::CommitResult& res = vpwallets[0]->CommitTransaction(wtx, keyChange, g_connman.get());
180 1 : if (res.status == CWallet::CommitStatus::OK) {
181 1 : const uint256& collateraltxid = wtx->GetHash();
182 1 : mapUnconfirmedFeeTx.emplace(budgetHash, collateraltxid);
183 2 : LogPrint(BCLog::MNBUDGET,"%s: Collateral sent. txid: %s\n", __func__, collateraltxid.ToString());
184 1 : return budgetHash;
185 : }
186 0 : return UINT256_ZERO;
187 : }
188 :
189 : // Collateral tx already exists, see if it's mature enough.
190 2 : CFinalizedBudget fb(strBudgetName, nBlockStart, vecTxBudgetPayments, mapUnconfirmedFeeTx.at(budgetHash));
191 1 : if (!AddFinalizedBudget(fb)) {
192 0 : return UINT256_ZERO;
193 : }
194 1 : fb.Relay();
195 1 : nSubmittedHeight = nCurrentHeight;
196 2 : LogPrint(BCLog::MNBUDGET,"%s: Done! %s\n", __func__, budgetHash.ToString());
197 1 : return budgetHash;
198 : }
199 :
200 5 : void CBudgetManager::SetBudgetProposalsStr(CFinalizedBudget& finalizedBudget) const
201 : {
202 5 : const std::vector<uint256>& vHashes = finalizedBudget.GetProposalsHashes();
203 10 : std::string strProposals = "";
204 5 : {
205 5 : LOCK(cs_proposals);
206 10 : for (const uint256& hash: vHashes) {
207 15 : const std::string token = (mapProposals.count(hash) ? mapProposals.at(hash).GetName() : hash.ToString());
208 10 : strProposals += (strProposals == "" ? "" : ", ") + token;
209 : }
210 : }
211 15 : finalizedBudget.SetProposalsStr(strProposals);
212 5 : }
213 :
214 4 : std::string CBudgetManager::GetFinalizedBudgetStatus(const uint256& nHash) const
215 : {
216 4 : CFinalizedBudget fb;
217 4 : if (!GetFinalizedBudget(nHash, fb))
218 0 : return strprintf("ERROR: cannot find finalized budget %s\n", nHash.ToString());
219 :
220 8 : std::string retBadHashes = "";
221 8 : std::string retBadPayeeOrAmount = "";
222 4 : int nBlockStart = fb.GetBlockStart();
223 4 : int nBlockEnd = fb.GetBlockEnd();
224 :
225 8 : for (int nBlockHeight = nBlockStart; nBlockHeight <= nBlockEnd; nBlockHeight++) {
226 8 : CTxBudgetPayment budgetPayment;
227 4 : 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 8 : CBudgetProposal bp;
233 4 : if (!GetProposal(budgetPayment.nProposalHash, bp)) {
234 0 : retBadHashes += (retBadHashes == "" ? "" : ", ") + budgetPayment.nProposalHash.ToString();
235 0 : continue;
236 : }
237 :
238 8 : if (bp.GetPayee() != budgetPayment.payee || bp.GetAmount() != budgetPayment.nAmount) {
239 0 : retBadPayeeOrAmount += (retBadPayeeOrAmount == "" ? "" : ", ") + budgetPayment.nProposalHash.ToString();
240 : }
241 : }
242 :
243 8 : 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 6 : bool CBudgetManager::AddFinalizedBudget(CFinalizedBudget& finalizedBudget, CNode* pfrom)
252 : {
253 6 : AssertLockNotHeld(cs_budgets); // need to lock cs_main here (CheckCollateral)
254 6 : const uint256& nHash = finalizedBudget.GetHash();
255 :
256 18 : 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 6 : 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 12 : std::string strError;
267 6 : int nCurrentHeight = GetBestHeight();
268 6 : const uint256& feeTxId = finalizedBudget.GetFeeTXHash();
269 6 : 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 6 : finalizedBudget.SetStrInvalid(strError);
273 : return false;
274 : }
275 :
276 : // update expiration
277 6 : 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 6 : std::vector<CBudgetProposal> vBudget = GetBudget();
284 12 : std::map<uint256, CBudgetProposal> mapWinningProposals;
285 12 : for (const CBudgetProposal& p: vBudget) { mapWinningProposals.emplace(p.GetHash(), p); }
286 6 : if (!finalizedBudget.CheckProposals(mapWinningProposals)) {
287 2 : finalizedBudget.SetStrInvalid("Invalid proposals");
288 1 : 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 1 : if (pfrom) {
291 0 : CNetMsgMaker maker(pfrom->GetSendVersion());
292 : // First, request single proposals that we don't have.
293 0 : for (const auto& propId : finalizedBudget.GetProposalsHashes()) {
294 0 : if (!g_budgetman.HaveProposal(propId)) {
295 0 : 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 0 : uint256 n;
303 0 : g_connman->PushMessage(pfrom, maker.Make(NetMsgType::BUDGETVOTESYNC, n));
304 : }
305 1 : return false;
306 : }
307 :
308 : // Add budget finalization.
309 5 : SetBudgetProposalsStr(finalizedBudget);
310 5 : ForceAddFinalizedBudget(nHash, feeTxId, finalizedBudget);
311 :
312 20 : 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 10 : void CBudgetManager::ForceAddFinalizedBudget(const uint256& nHash, const uint256& feeTxId, const CFinalizedBudget& finalizedBudget)
318 : {
319 10 : LOCK(cs_budgets);
320 10 : mapFinalizedBudgets.emplace(nHash, finalizedBudget);
321 : // Add to feeTx index
322 10 : mapFeeTxToBudget.emplace(feeTxId, nHash);
323 : // Remove the budget from the unconfirmed map, if it was there
324 19 : if (mapUnconfirmedFeeTx.count(nHash))
325 10 : mapUnconfirmedFeeTx.erase(nHash);
326 10 : }
327 :
328 24 : bool CBudgetManager::AddProposal(CBudgetProposal& budgetProposal)
329 : {
330 24 : AssertLockNotHeld(cs_proposals); // need to lock cs_main here (CheckCollateral)
331 24 : const uint256& nHash = budgetProposal.GetHash();
332 :
333 72 : 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 24 : 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 48 : std::string strError;
344 24 : int nCurrentHeight = GetBestHeight();
345 24 : const uint256& feeTxId = budgetProposal.GetFeeTXHash();
346 24 : 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 24 : budgetProposal.SetStrInvalid(strError);
350 : return false;
351 : }
352 :
353 : // update expiration / heavily-downvoted
354 24 : int mnCount = mnodeman.CountEnabled();
355 24 : 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 24 : {
361 24 : LOCK(cs_proposals);
362 24 : mapProposals.emplace(nHash, budgetProposal);
363 : // Add to feeTx index
364 24 : mapFeeTxToProposal.emplace(feeTxId, nHash);
365 : }
366 72 : LogPrint(BCLog::MNBUDGET,"%s: budget proposal %s [%s] added\n", __func__, nHash.ToString(), budgetProposal.GetName());
367 :
368 : return true;
369 : }
370 :
371 274 : void CBudgetManager::CheckAndRemove()
372 : {
373 274 : int nCurrentHeight = GetBestHeight();
374 274 : std::map<uint256, CFinalizedBudget> tmpMapFinalizedBudgets;
375 274 : std::map<uint256, CBudgetProposal> tmpMapProposals;
376 :
377 : // Get MN count, used for the heavily down-voted check
378 274 : int mnCount = mnodeman.CountEnabled();
379 :
380 : // Check Proposals first
381 274 : {
382 274 : LOCK(cs_proposals);
383 274 : LogPrint(BCLog::MNBUDGET, "%s: mapProposals cleanup - size before: %d\n", __func__, mapProposals.size());
384 317 : for (auto& it: mapProposals) {
385 43 : CBudgetProposal* pbudgetProposal = &(it.second);
386 43 : if (!pbudgetProposal->UpdateValid(nCurrentHeight, mnCount)) {
387 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid budget proposal %s %s\n", __func__, (it.first).ToString(), pbudgetProposal->IsInvalidLogStr());
388 43 : mapFeeTxToProposal.erase(pbudgetProposal->GetFeeTXHash());
389 : } else {
390 172 : LogPrint(BCLog::MNBUDGET,"%s: Found valid budget proposal: %s %s\n", __func__,
391 : pbudgetProposal->GetName(), pbudgetProposal->GetFeeTXHash().ToString());
392 86 : tmpMapProposals.emplace(pbudgetProposal->GetHash(), *pbudgetProposal);
393 : }
394 : }
395 : // Remove invalid entries by overwriting complete map
396 274 : mapProposals.swap(tmpMapProposals);
397 274 : LogPrint(BCLog::MNBUDGET, "%s: mapProposals cleanup - size after: %d\n", __func__, mapProposals.size());
398 : }
399 :
400 : // Then check finalized budgets
401 274 : {
402 274 : LOCK(cs_budgets);
403 274 : LogPrint(BCLog::MNBUDGET, "%s: mapFinalizedBudgets cleanup - size before: %d\n", __func__, mapFinalizedBudgets.size());
404 279 : for (auto& it: mapFinalizedBudgets) {
405 5 : CFinalizedBudget* pfinalizedBudget = &(it.second);
406 5 : if (!pfinalizedBudget->UpdateValid(nCurrentHeight)) {
407 0 : LogPrint(BCLog::MNBUDGET,"%s: Invalid finalized budget %s %s\n", __func__, (it.first).ToString(), pfinalizedBudget->IsInvalidLogStr());
408 5 : mapFeeTxToBudget.erase(pfinalizedBudget->GetFeeTXHash());
409 : } else {
410 20 : LogPrint(BCLog::MNBUDGET,"%s: Found valid finalized budget: %s %s\n", __func__,
411 : pfinalizedBudget->GetName(), pfinalizedBudget->GetFeeTXHash().ToString());
412 10 : tmpMapFinalizedBudgets.emplace(pfinalizedBudget->GetHash(), *pfinalizedBudget);
413 : }
414 : }
415 : // Remove invalid entries by overwriting complete map
416 274 : mapFinalizedBudgets = tmpMapFinalizedBudgets;
417 274 : LogPrint(BCLog::MNBUDGET, "%s: mapFinalizedBudgets cleanup - size after: %d\n", __func__, mapFinalizedBudgets.size());
418 : }
419 : // Masternodes vote on valid ones
420 274 : VoteOnFinalizedBudgets();
421 274 : }
422 :
423 54872 : void CBudgetManager::RemoveByFeeTxId(const uint256& feeTxId)
424 : {
425 54872 : {
426 54872 : LOCK(cs_proposals);
427 : // Is this collateral related to a proposal?
428 54872 : const auto& it = mapFeeTxToProposal.find(feeTxId);
429 54872 : 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 54872 : {
452 109744 : LOCK(cs_budgets);
453 : // Is this collateral related to a finalized budget?
454 54872 : const auto& it = mapFeeTxToBudget.find(feeTxId);
455 54872 : 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 2110 : CBudgetManager::HighestFinBudget CBudgetManager::GetBudgetWithHighestVoteCount(int chainHeight) const
478 : {
479 2110 : LOCK(cs_budgets);
480 2110 : int highestVoteCount = 0;
481 2110 : const CFinalizedBudget* pHighestBudget = nullptr;
482 4030 : for (const auto& it: mapFinalizedBudgets) {
483 1920 : const CFinalizedBudget* pfinalizedBudget = &(it.second);
484 1920 : int voteCount = pfinalizedBudget->GetVoteCount();
485 3686 : if (voteCount > highestVoteCount &&
486 1920 : chainHeight >= pfinalizedBudget->GetBlockStart() &&
487 301 : chainHeight <= pfinalizedBudget->GetBlockEnd()) {
488 : pHighestBudget = pfinalizedBudget;
489 : highestVoteCount = voteCount;
490 : }
491 : }
492 4220 : return {pHighestBudget, highestVoteCount};
493 : }
494 :
495 2056 : int CBudgetManager::GetHighestVoteCount(int chainHeight) const
496 : {
497 2056 : const auto& highestBudFin = GetBudgetWithHighestVoteCount(chainHeight);
498 2056 : return (highestBudFin.m_budget_fin ? highestBudFin.m_vote_count : -1);
499 : }
500 :
501 1182 : bool CBudgetManager::GetPayeeAndAmount(int chainHeight, CScript& payeeRet, CAmount& nAmountRet) const
502 : {
503 1182 : int nCountThreshold;
504 1182 : if (!IsBudgetPaymentBlock(chainHeight, nCountThreshold))
505 : return false;
506 :
507 33 : const auto& highestBudFin = GetBudgetWithHighestVoteCount(chainHeight);
508 33 : const CFinalizedBudget* pfb = highestBudFin.m_budget_fin;
509 33 : return pfb && pfb->GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet) && highestBudFin.m_vote_count > nCountThreshold;
510 : }
511 :
512 849 : bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet) const
513 : {
514 849 : CScript payeeRet;
515 849 : return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet);
516 : }
517 :
518 333 : bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const
519 : {
520 333 : if (nHeight <= 0) return false;
521 :
522 666 : CScript payee;
523 333 : CAmount nAmount = 0;
524 :
525 333 : if (!GetPayeeAndAmount(nHeight, payee, nAmount))
526 : return false;
527 :
528 7 : CAmount blockValue = GetBlockValue(nHeight);
529 :
530 : // Starting from PIVX v6.0 masternode and budgets are paid in the coinbase tx of PoS blocks
531 9 : const bool fPayCoinstake = fProofOfStake &&
532 2 : !Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0);
533 :
534 7 : if (fProofOfStake) {
535 2 : 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 0 : txCoinbase.vout.resize(1);
542 0 : txCoinbase.vout[0].scriptPubKey = payee;
543 0 : 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 340 : CTxDestination address;
556 7 : ExtractDestination(payee, address);
557 9 : LogPrint(BCLog::MNBUDGET,"%s: Budget payment to %s for %lld\n", __func__, EncodeDestination(address), nAmount);
558 7 : return true;
559 : }
560 :
561 274 : void CBudgetManager::VoteOnFinalizedBudgets()
562 : {
563 : // function called only from initialized masternodes
564 274 : if (!fMasterNode) {
565 261 : LogPrint(BCLog::MNBUDGET,"%s: Not a masternode\n", __func__);
566 271 : 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 13 : if (GetRandInt(4) != 0) {
572 9 : LogPrint(BCLog::MNBUDGET,"%s: waiting\n", __func__);
573 9 : return;
574 : }
575 :
576 : // Get the active masternode (operator) key
577 7 : CTxIn mnVin;
578 7 : Optional<CKey> mnKey{nullopt};
579 7 : CBLSSecretKey blsKey;
580 4 : if (!GetActiveMasternodeKeys(mnVin, mnKey, blsKey)) {
581 1 : return;
582 : }
583 :
584 7 : std::vector<CBudgetProposal> vBudget = GetBudget();
585 4 : if (vBudget.empty()) {
586 1 : LogPrint(BCLog::MNBUDGET,"%s: No proposal can be finalized\n", __func__);
587 1 : return;
588 : }
589 :
590 6 : std::map<uint256, CBudgetProposal> mapWinningProposals;
591 6 : for (const CBudgetProposal& p: vBudget) {
592 6 : mapWinningProposals.emplace(p.GetHash(), p);
593 : }
594 : // Vector containing the hash of finalized budgets to sign
595 6 : std::vector<uint256> vBudgetHashes;
596 3 : {
597 3 : LOCK(cs_budgets);
598 4 : for (auto& it: mapFinalizedBudgets) {
599 1 : CFinalizedBudget* pfb = &(it.second);
600 : // we only need to check this once
601 1 : 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 4 : for (const uint256& budgetHash: vBudgetHashes) {
617 1 : CFinalizedBudgetVote vote(mnVin, budgetHash);
618 1 : if (mnKey != nullopt) {
619 : // Legacy MN
620 2 : 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 0 : 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 22 : const CBudgetProposal* CBudgetManager::FindProposalByName(const std::string& strProposalName) const
650 : {
651 22 : LOCK(cs_proposals);
652 :
653 22 : int64_t nYesCountMax = std::numeric_limits<int64_t>::min();
654 22 : const CBudgetProposal* pbudgetProposal = nullptr;
655 :
656 344 : for (const auto& it: mapProposals) {
657 322 : const CBudgetProposal& proposal = it.second;
658 322 : int64_t nYesCount = proposal.GetYeas() - proposal.GetNays();
659 966 : if (proposal.GetName() == strProposalName && nYesCount > nYesCountMax) {
660 22 : pbudgetProposal = &proposal;
661 22 : nYesCountMax = nYesCount;
662 : }
663 : }
664 :
665 44 : 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 4 : bool CBudgetManager::GetProposal(const uint256& nHash, CBudgetProposal& bp) const
676 : {
677 8 : LOCK(cs_proposals);
678 4 : auto it = mapProposals.find(nHash);
679 4 : if (it == mapProposals.end()) return false;
680 4 : bp = it->second;
681 : return true;
682 : }
683 :
684 4 : bool CBudgetManager::GetFinalizedBudget(const uint256& nHash, CFinalizedBudget& fb) const
685 : {
686 8 : LOCK(cs_budgets);
687 4 : auto it = mapFinalizedBudgets.find(nHash);
688 4 : if (it == mapFinalizedBudgets.end()) return false;
689 4 : fb = it->second;
690 : return true;
691 : }
692 :
693 2056 : bool CBudgetManager::IsBudgetPaymentBlock(int nBlockHeight, int& nCountThreshold) const
694 : {
695 2056 : int nHighestCount = GetHighestVoteCount(nBlockHeight);
696 2056 : int nCountEnabled = mnodeman.CountEnabled();
697 2056 : int nFivePercent = nCountEnabled / 20;
698 : // threshold for highest finalized budgets (highest vote count - 10% of active masternodes)
699 2056 : nCountThreshold = nHighestCount - (nCountEnabled / 10);
700 : // reduce the threshold if there are less than 10 enabled masternodes
701 2056 : if (nCountThreshold == nHighestCount) nCountThreshold--;
702 :
703 2056 : 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 2056 : return (nHighestCount > nFivePercent);
708 : }
709 :
710 853 : bool CBudgetManager::IsBudgetPaymentBlock(int nBlockHeight) const
711 : {
712 853 : int nCountThreshold;
713 853 : return IsBudgetPaymentBlock(nBlockHeight, nCountThreshold);
714 : }
715 :
716 21 : TrxValidationStatus CBudgetManager::IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const
717 : {
718 21 : int nCountThreshold = 0;
719 21 : 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 21 : bool fThreshold = false;
726 21 : {
727 21 : LOCK(cs_budgets);
728 : // Get the finalized budget with the highest amount of votes..
729 21 : const auto& highestBudFin = GetBudgetWithHighestVoteCount(nBlockHeight);
730 21 : const CFinalizedBudget* highestVotesBudget = highestBudFin.m_budget_fin;
731 21 : if (highestVotesBudget) {
732 : // Need to surpass the threshold
733 21 : if (highestBudFin.m_vote_count > nCountThreshold) {
734 21 : fThreshold = true;
735 21 : if (highestVotesBudget->IsTransactionValid(txNew, nBlockHash, nBlockHeight) ==
736 : TrxValidationStatus::Valid) {
737 36 : 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 19 : std::vector<CBudgetProposal*> CBudgetManager::GetAllProposalsOrdered()
751 : {
752 19 : LOCK(cs_proposals);
753 19 : std::vector<CBudgetProposal*> vBudgetProposalRet;
754 128 : for (auto& it: mapProposals) {
755 109 : CBudgetProposal* pbudgetProposal = &(it.second);
756 109 : RemoveStaleVotesOnProposal(pbudgetProposal);
757 109 : vBudgetProposalRet.push_back(pbudgetProposal);
758 : }
759 19 : std::sort(vBudgetProposalRet.begin(), vBudgetProposalRet.end(), CBudgetProposal::PtrHigherYes);
760 38 : return vBudgetProposalRet;
761 : }
762 :
763 17 : std::vector<CBudgetProposal> CBudgetManager::GetBudget()
764 : {
765 34 : LOCK(cs_proposals);
766 :
767 17 : int nHeight = GetBestHeight();
768 17 : if (nHeight <= 0)
769 0 : return {};
770 :
771 : // ------- Get proposals ordered by votes (highest to lowest)
772 34 : std::vector<CBudgetProposal*> vProposalsOrdered = GetAllProposalsOrdered();
773 :
774 : // ------- Grab The Budgets In Order
775 34 : std::vector<CBudgetProposal> vBudgetProposalsRet;
776 17 : CAmount nBudgetAllocated = 0;
777 :
778 17 : const int nBlocksPerCycle = Params().GetConsensus().nBudgetCycleBlocks;
779 17 : int nBlockStart = nHeight - nHeight % nBlocksPerCycle + nBlocksPerCycle;
780 17 : int nBlockEnd = nBlockStart + nBlocksPerCycle - 1;
781 17 : int mnCount = mnodeman.CountEnabled();
782 17 : CAmount nTotalBudget = GetTotalBudget(nBlockStart);
783 :
784 94 : for (CBudgetProposal* pbudgetProposal: vProposalsOrdered) {
785 231 : LogPrint(BCLog::MNBUDGET,"%s: Processing Budget %s\n", __func__, pbudgetProposal->GetName());
786 : //prop start/end should be inside this period
787 77 : if (pbudgetProposal->IsPassing(nBlockStart, nBlockEnd, mnCount)) {
788 16 : 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 16 : if (pbudgetProposal->GetAmount() + nBudgetAllocated <= nTotalBudget) {
793 16 : pbudgetProposal->SetAllotted(pbudgetProposal->GetAmount());
794 16 : nBudgetAllocated += pbudgetProposal->GetAmount();
795 16 : vBudgetProposalsRet.emplace_back(*pbudgetProposal);
796 16 : 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 61 : 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 17 : return vBudgetProposalsRet;
812 : }
813 :
814 6 : std::vector<CFinalizedBudget*> CBudgetManager::GetFinalizedBudgets()
815 : {
816 6 : LOCK(cs_budgets);
817 :
818 6 : std::vector<CFinalizedBudget*> vFinalizedBudgetsRet;
819 :
820 : // ------- Grab The Budgets In Order
821 12 : for (auto& it: mapFinalizedBudgets) {
822 6 : vFinalizedBudgetsRet.push_back(&(it.second));
823 : }
824 6 : std::sort(vFinalizedBudgetsRet.begin(), vFinalizedBudgetsRet.end(), CFinalizedBudget::PtrGreater);
825 :
826 12 : 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 23018 : CAmount CBudgetManager::GetTotalBudget(int nHeight)
859 : {
860 : // 100% of block reward after V5.5 upgrade
861 23018 : CAmount nSubsidy = GetBlockValue(nHeight);
862 :
863 : // 20% of block reward prior to V5.5 upgrade
864 23018 : if (nHeight <= Params().GetConsensus().vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight) {
865 20487 : nSubsidy /= 5;
866 : }
867 :
868 : // multiplied by the number of blocks in a cycle (144 on testnet, 30*1440 on mainnet)
869 23018 : return nSubsidy * Params().GetConsensus().nBudgetCycleBlocks;
870 : }
871 :
872 17 : void CBudgetManager::AddSeenProposalVote(const CBudgetVote& vote)
873 : {
874 17 : LOCK(cs_votes);
875 34 : mapSeenProposalVotes.emplace(vote.GetHash(), vote);
876 17 : }
877 :
878 11 : void CBudgetManager::AddSeenFinalizedBudgetVote(const CFinalizedBudgetVote& vote)
879 : {
880 11 : LOCK(cs_finalizedvotes);
881 22 : mapSeenFinalizedBudgetVotes.emplace(vote.GetHash(), vote);
882 11 : }
883 :
884 152 : void CBudgetManager::RemoveStaleVotesOnProposal(CBudgetProposal* prop)
885 : {
886 152 : AssertLockHeld(cs_proposals);
887 304 : LogPrint(BCLog::MNBUDGET, "Cleaning proposal votes for %s. Before: YES=%d, NO=%d\n",
888 : prop->GetName(), prop->GetYeas(), prop->GetNays());
889 :
890 152 : auto it = prop->mapVotes.begin();
891 221 : while (it != prop->mapVotes.end()) {
892 69 : auto mnList = deterministicMNManager->GetListAtChainTip();
893 138 : auto dmn = mnList.GetMNByCollateral(it->first);
894 69 : if (dmn) {
895 7 : (*it).second.SetValid(!dmn->IsPoSeBanned());
896 : } else {
897 : // -- Legacy System (!TODO: remove after enforcement) --
898 62 : CMasternode* pmn = mnodeman.Find(it->first);
899 128 : (*it).second.SetValid(pmn && pmn->IsEnabled());
900 : }
901 69 : ++it;
902 : }
903 :
904 304 : LogPrint(BCLog::MNBUDGET, "Cleaned proposal votes for %s. After: YES=%d, NO=%d\n",
905 : prop->GetName(), prop->GetYeas(), prop->GetNays());
906 152 : }
907 :
908 5 : void CBudgetManager::RemoveStaleVotesOnFinalBudget(CFinalizedBudget* fbud)
909 : {
910 5 : AssertLockHeld(cs_budgets);
911 15 : LogPrint(BCLog::MNBUDGET, "Cleaning finalized budget votes for [%s (%s)]. Before: %d\n",
912 : fbud->GetName(), fbud->GetProposalsStr(), fbud->GetVoteCount());
913 :
914 5 : auto it = fbud->mapVotes.begin();
915 16 : while (it != fbud->mapVotes.end()) {
916 11 : auto mnList = deterministicMNManager->GetListAtChainTip();
917 22 : auto dmn = mnList.GetMNByCollateral(it->first);
918 11 : if (dmn) {
919 1 : (*it).second.SetValid(!dmn->IsPoSeBanned());
920 : } else {
921 : // -- Legacy System (!TODO: remove after enforcement) --
922 10 : CMasternode* pmn = mnodeman.Find(it->first);
923 22 : (*it).second.SetValid(pmn && pmn->IsEnabled());
924 : }
925 11 : ++it;
926 : }
927 15 : LogPrint(BCLog::MNBUDGET, "Cleaned finalized budget votes for [%s (%s)]. After: %d\n",
928 : fbud->GetName(), fbud->GetProposalsStr(), fbud->GetVoteCount());
929 5 : }
930 :
931 20 : CDataStream CBudgetManager::GetProposalVoteSerialized(const uint256& voteHash) const
932 : {
933 20 : LOCK(cs_votes);
934 20 : CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
935 20 : ss.reserve(1000);
936 20 : ss << mapSeenProposalVotes.at(voteHash);
937 40 : return ss;
938 : }
939 :
940 52 : CDataStream CBudgetManager::GetProposalSerialized(const uint256& propHash) const
941 : {
942 52 : LOCK(cs_proposals);
943 104 : return mapProposals.at(propHash).GetBroadcast();
944 : }
945 :
946 13 : CDataStream CBudgetManager::GetFinalizedBudgetVoteSerialized(const uint256& voteHash) const
947 : {
948 13 : LOCK(cs_finalizedvotes);
949 13 : CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
950 13 : ss.reserve(1000);
951 13 : ss << mapSeenFinalizedBudgetVotes.at(voteHash);
952 26 : return ss;
953 : }
954 :
955 6 : CDataStream CBudgetManager::GetFinalizedBudgetSerialized(const uint256& budgetHash) const
956 : {
957 6 : LOCK(cs_budgets);
958 12 : 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 21970 : void CBudgetManager::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload)
972 : {
973 24999 : if (g_tiertwo_sync_state.GetSyncPhase() <= MASTERNODE_SYNC_BUDGET) return;
974 :
975 3250 : if (strBudgetMode == "suggest") { //suggest the budget we see
976 0 : SubmitFinalBudget();
977 : }
978 :
979 3250 : int nCurrentHeight = GetBestHeight();
980 : //this function should be called 1/14 blocks, allowing up to 100 votes per day on all proposals
981 3250 : if (nCurrentHeight % 14 != 0) return;
982 :
983 : // incremental sync with our peers
984 221 : if (g_tiertwo_sync_state.IsSynced()) {
985 221 : LogPrint(BCLog::MNBUDGET,"%s: incremental sync started\n", __func__);
986 : // Once every 7 days, try to relay the complete budget data
987 221 : if (GetRandInt(Params().IsRegTestNet() ? 2 : 720) == 0) {
988 99 : ResetSync();
989 : }
990 :
991 221 : CBudgetManager* manager = this;
992 221 : g_connman->ForEachNode([manager](CNode* pnode){
993 1205 : if (pnode->nVersion >= ActiveProtocol())
994 1205 : manager->Sync(pnode, true);
995 1205 : });
996 221 : MarkSynced();
997 : }
998 :
999 : // remove expired/heavily downvoted budgets
1000 221 : CheckAndRemove();
1001 :
1002 221 : {
1003 221 : LOCK(cs_proposals);
1004 221 : LogPrint(BCLog::MNBUDGET,"%s: mapProposals cleanup - size: %d\n", __func__, mapProposals.size());
1005 264 : for (auto& it: mapProposals) {
1006 43 : RemoveStaleVotesOnProposal(&it.second);
1007 : }
1008 : }
1009 221 : {
1010 221 : LOCK(cs_budgets);
1011 221 : LogPrint(BCLog::MNBUDGET,"%s: mapFinalizedBudgets cleanup - size: %d\n", __func__, mapFinalizedBudgets.size());
1012 226 : for (auto& it: mapFinalizedBudgets) {
1013 5 : RemoveStaleVotesOnFinalBudget(&it.second);
1014 : }
1015 : }
1016 :
1017 221 : int64_t now = GetTime();
1018 663 : const auto cleanOrphans = [now](auto& mutex, auto& mapOrphans, auto& mapSeen) {
1019 442 : LOCK(mutex);
1020 442 : 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 442 : it++;
1031 : }
1032 : }
1033 663 : };
1034 :
1035 : // Clean orphan proposal votes if no parent arrived after an hour.
1036 221 : cleanOrphans(cs_votes, mapOrphanProposalVotes, mapSeenProposalVotes);
1037 : // Clean orphan budget votes if no parent arrived after an hour.
1038 221 : cleanOrphans(cs_finalizedvotes, mapOrphanFinalizedBudgetVotes, mapSeenFinalizedBudgetVotes);
1039 :
1040 : // Once every 2 weeks (1/14 * 1/1440), clean the seen maps
1041 221 : if (g_tiertwo_sync_state.IsSynced() && GetRandInt(1440) == 0) {
1042 0 : ReloadMapSeen();
1043 : }
1044 :
1045 221 : LogPrint(BCLog::MNBUDGET,"%s: PASSED\n", __func__);
1046 : }
1047 :
1048 239 : int CBudgetManager::ProcessBudgetVoteSync(const uint256& nProp, CNode* pfrom)
1049 : {
1050 478 : if (nProp.IsNull()) {
1051 711 : LOCK2(cs_budgets, cs_proposals);
1052 237 : 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 478 : if (nProp.IsNull()) Sync(pfrom, false /* fPartial */);
1062 2 : else SyncSingleItem(pfrom, nProp);
1063 239 : LogPrint(BCLog::MNBUDGET, "mnvs - Sent Masternode votes to peer %i\n", pfrom->GetId());
1064 : return 0;
1065 : }
1066 :
1067 21 : int CBudgetManager::ProcessProposal(CBudgetProposal& proposal)
1068 : {
1069 21 : const uint256& nHash = proposal.GetHash();
1070 21 : if (HaveProposal(nHash)) {
1071 0 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1072 0 : return 0;
1073 : }
1074 21 : 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 21 : if (g_tiertwo_sync_state.IsSynced()) proposal.Relay();
1081 21 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1082 :
1083 42 : LogPrint(BCLog::MNBUDGET, "mprop (new) %s\n", nHash.ToString());
1084 : //We might have active votes for this proposal that are valid now
1085 21 : CheckOrphanVotes();
1086 : return 0;
1087 : }
1088 :
1089 17 : bool CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom, CValidationState& state)
1090 : {
1091 17 : const uint256& voteID = vote.GetHash();
1092 :
1093 17 : if (HaveSeenProposalVote(voteID)) {
1094 0 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1095 0 : return false;
1096 : }
1097 :
1098 34 : std::string err;
1099 17 : 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 34 : const CTxIn& voteVin = vote.GetVin();
1106 :
1107 : // See if this vote was signed with a deterministic masternode
1108 34 : auto mnList = deterministicMNManager->GetListAtChainTip();
1109 34 : auto dmn = mnList.GetMNByCollateral(voteVin.prevout);
1110 17 : if (dmn) {
1111 2 : const std::string& mn_protx_id = dmn->proTxHash.ToString();
1112 :
1113 1 : 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 1 : AddSeenProposalVote(vote);
1119 :
1120 1 : 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 1 : 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 1 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1132 1 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1133 3 : LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n",
1134 : voteID.ToString(), vote.GetProposalHash().ToString(), mn_protx_id);
1135 1 : return true;
1136 : }
1137 :
1138 : // -- Legacy System (!TODO: remove after enforcement) --
1139 :
1140 16 : CMasternode* pmn = mnodeman.Find(voteVin.prevout);
1141 16 : 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 16 : if (!pmn->IsEnabled()) {
1149 0 : return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, "masternode not valid");
1150 : }
1151 :
1152 16 : AddSeenProposalVote(vote);
1153 :
1154 16 : 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 16 : if (!UpdateProposal(vote, pfrom, err)) {
1163 0 : 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 16 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1169 16 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1170 64 : 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 4 : int CBudgetManager::ProcessFinalizedBudget(CFinalizedBudget& finalbudget, CNode* pfrom)
1176 : {
1177 :
1178 4 : const uint256& nHash = finalbudget.GetHash();
1179 4 : if (HaveFinalizedBudget(nHash)) {
1180 0 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1181 0 : return 0;
1182 : }
1183 4 : 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 4 : if (g_tiertwo_sync_state.IsSynced()) finalbudget.Relay();
1190 4 : g_tiertwo_sync_state.AddedBudgetItem(nHash);
1191 :
1192 8 : LogPrint(BCLog::MNBUDGET, "fbs (new) %s\n", nHash.ToString());
1193 : //we might have active votes for this budget that are now valid
1194 4 : CheckOrphanVotes();
1195 : return 0;
1196 : }
1197 :
1198 11 : bool CBudgetManager::ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode* pfrom, CValidationState& state)
1199 : {
1200 11 : const uint256& voteID = vote.GetHash();
1201 :
1202 11 : if (HaveSeenFinalizedBudgetVote(voteID)) {
1203 0 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1204 0 : return false;
1205 : }
1206 :
1207 22 : std::string err;
1208 11 : 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 22 : const CTxIn& voteVin = vote.GetVin();
1215 :
1216 : // See if this vote was signed with a deterministic masternode
1217 22 : auto mnList = deterministicMNManager->GetListAtChainTip();
1218 22 : auto dmn = mnList.GetMNByCollateral(voteVin.prevout);
1219 11 : if (dmn) {
1220 2 : const std::string& mn_protx_id = dmn->proTxHash.ToString();
1221 :
1222 1 : 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 1 : AddSeenFinalizedBudgetVote(vote);
1228 :
1229 1 : 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 1 : if (!UpdateFinalizedBudget(vote, pfrom, err)) {
1235 0 : 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 1 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1241 1 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1242 3 : LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from dmn %s\n",
1243 : voteID.ToString(), vote.GetBudgetHash().ToString(), mn_protx_id);
1244 1 : return true;
1245 : }
1246 :
1247 : // -- Legacy System (!TODO: remove after enforcement) --
1248 10 : CMasternode* pmn = mnodeman.Find(voteVin.prevout);
1249 10 : 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 10 : if (!pmn->IsEnabled()) {
1257 0 : return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, "masternode not valid");
1258 : }
1259 :
1260 10 : AddSeenFinalizedBudgetVote(vote);
1261 :
1262 10 : 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 10 : if (!UpdateFinalizedBudget(vote, pfrom, err)) {
1271 0 : 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 10 : if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
1277 10 : g_tiertwo_sync_state.AddedBudgetItem(voteID);
1278 40 : 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 51037 : bool CBudgetManager::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv, int& banScore)
1284 : {
1285 51037 : banScore = ProcessMessageInner(pfrom, strCommand, vRecv);
1286 51037 : return banScore == 0;
1287 : }
1288 :
1289 51037 : int CBudgetManager::ProcessMessageInner(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
1290 : {
1291 51037 : if (!g_tiertwo_sync_state.IsBlockchainSynced()) return 0;
1292 :
1293 51036 : if (strCommand == NetMsgType::BUDGETVOTESYNC) {
1294 : // Masternode vote sync
1295 239 : uint256 nProp;
1296 239 : vRecv >> nProp;
1297 239 : return ProcessBudgetVoteSync(nProp, pfrom);
1298 : }
1299 :
1300 50797 : if (strCommand == NetMsgType::BUDGETPROPOSAL) {
1301 : // Masternode Proposal
1302 42 : CBudgetProposal proposal;
1303 21 : if (!proposal.ParseBroadcast(vRecv)) {
1304 : return 20;
1305 : }
1306 21 : {
1307 : // Clear inv request
1308 21 : LOCK(cs_main);
1309 21 : g_connman->RemoveAskFor(proposal.GetHash(), MSG_BUDGET_PROPOSAL);
1310 : }
1311 21 : return ProcessProposal(proposal);
1312 : }
1313 :
1314 50776 : if (strCommand == NetMsgType::BUDGETVOTE) {
1315 24 : CBudgetVote vote;
1316 12 : vRecv >> vote;
1317 12 : vote.SetValid(true);
1318 :
1319 12 : {
1320 : // Clear inv request
1321 12 : LOCK(cs_main);
1322 12 : g_connman->RemoveAskFor(vote.GetHash(), MSG_BUDGET_VOTE);
1323 : }
1324 :
1325 24 : CValidationState state;
1326 12 : if (!ProcessProposalVote(vote, pfrom, state)) {
1327 0 : int nDos = 0;
1328 0 : if (state.IsInvalid(nDos)) {
1329 0 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, FormatStateMessage(state));
1330 : }
1331 0 : return nDos;
1332 : }
1333 : return 0;
1334 : }
1335 :
1336 50764 : if (strCommand == NetMsgType::FINALBUDGET) {
1337 : // Finalized Budget Suggestion
1338 8 : CFinalizedBudget finalbudget;
1339 4 : if (!finalbudget.ParseBroadcast(vRecv)) {
1340 : return 20;
1341 : }
1342 4 : {
1343 : // Clear inv request
1344 4 : LOCK(cs_main);
1345 4 : g_connman->RemoveAskFor(finalbudget.GetHash(), MSG_BUDGET_FINALIZED);
1346 : }
1347 4 : return ProcessFinalizedBudget(finalbudget, pfrom);
1348 : }
1349 :
1350 50760 : if (strCommand == NetMsgType::FINALBUDGETVOTE) {
1351 16 : CFinalizedBudgetVote vote;
1352 8 : vRecv >> vote;
1353 8 : vote.SetValid(true);
1354 :
1355 8 : {
1356 : // Clear inv request
1357 8 : LOCK(cs_main);
1358 8 : g_connman->RemoveAskFor(vote.GetHash(), MSG_BUDGET_FINALIZED_VOTE);
1359 : }
1360 :
1361 16 : CValidationState state;
1362 8 : if (!ProcessFinalizedBudgetVote(vote, pfrom, state)) {
1363 0 : int nDos = 0;
1364 0 : if (state.IsInvalid(nDos)) {
1365 0 : LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, FormatStateMessage(state));
1366 : }
1367 0 : return nDos;
1368 : }
1369 : return 0;
1370 : }
1371 :
1372 : // nothing was done
1373 : return 0;
1374 : }
1375 :
1376 613 : void CBudgetManager::SetSynced(bool synced)
1377 : {
1378 613 : {
1379 613 : LOCK(cs_proposals);
1380 692 : for (auto& it: mapProposals) {
1381 79 : CBudgetProposal* pbudgetProposal = &(it.second);
1382 79 : if (pbudgetProposal && pbudgetProposal->IsValid()) {
1383 : //mark votes
1384 79 : pbudgetProposal->SetSynced(synced);
1385 : }
1386 : }
1387 : }
1388 613 : {
1389 613 : LOCK(cs_budgets);
1390 621 : for (auto& it: mapFinalizedBudgets) {
1391 8 : CFinalizedBudget* pfinalizedBudget = &(it.second);
1392 8 : if (pfinalizedBudget && pfinalizedBudget->IsValid()) {
1393 : //mark votes
1394 8 : pfinalizedBudget->SetSynced(synced);
1395 : }
1396 : }
1397 : }
1398 613 : }
1399 :
1400 : template<typename T>
1401 3 : static bool relayItemIfFound(const uint256& itemHash, CNode* pfrom, RecursiveMutex& cs, std::map<uint256, T>& map, const char* type)
1402 : {
1403 3 : CNetMsgMaker msgMaker(pfrom->GetSendVersion());
1404 6 : LOCK(cs);
1405 3 : const auto& it = map.find(itemHash);
1406 3 : if (it == map.end()) return false;
1407 2 : T* item = &(it->second);
1408 2 : if (!item->IsValid()) return true; // don't broadcast invalid items
1409 2 : g_connman->PushMessage(pfrom, msgMaker.Make(type, item->GetBroadcast()));
1410 2 : int nInvCount = 1;
1411 2 : item->SyncVotes(pfrom, false /* fPartial */, nInvCount);
1412 2 : LogPrint(BCLog::MNBUDGET, "%s: single %s sent %d items\n", __func__, type, nInvCount);
1413 : return true;
1414 : }
1415 :
1416 : template<typename T>
1417 2884 : static void relayInventoryItems(CNode* pfrom, RecursiveMutex& cs, std::map<uint256, T>& map, bool fPartial, GetDataMsg invType, const int mn_sync_budget_type)
1418 : {
1419 2884 : CNetMsgMaker msgMaker(pfrom->GetSendVersion());
1420 2884 : int nInvCount = 0;
1421 : {
1422 5768 : LOCK(cs);
1423 3038 : for (auto& it: map) {
1424 154 : T* item = &(it.second);
1425 154 : if (item && item->IsValid()) {
1426 154 : pfrom->PushInventory(CInv(invType, item->GetHash()));
1427 154 : nInvCount++;
1428 154 : item->SyncVotes(pfrom, fPartial, nInvCount);
1429 : }
1430 : }
1431 : }
1432 2884 : g_connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, mn_sync_budget_type, nInvCount));
1433 2884 : LogPrint(BCLog::MNBUDGET, "%s: sent %d items\n", __func__, nInvCount);
1434 2884 : }
1435 :
1436 2 : void CBudgetManager::SyncSingleItem(CNode* pfrom, const uint256& nProp)
1437 : {
1438 4 : if (nProp.IsNull()) return;
1439 : // Try first to relay a proposal
1440 2 : 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 1442 : void CBudgetManager::Sync(CNode* pfrom, bool fPartial)
1452 : {
1453 : // Full budget sync request.
1454 1442 : relayInventoryItems<CBudgetProposal>(pfrom, cs_proposals, mapProposals, fPartial, MSG_BUDGET_PROPOSAL, MASTERNODE_SYNC_BUDGET_PROP);
1455 1442 : relayInventoryItems<CFinalizedBudget>(pfrom, cs_budgets, mapFinalizedBudgets, fPartial, MSG_BUDGET_FINALIZED, MASTERNODE_SYNC_BUDGET_FIN);
1456 :
1457 1442 : 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 237 : g_netfulfilledman.AddFulfilledRequest(pfrom->addr, BUDGET_SYNC_REQUEST_RECV);
1461 : }
1462 1442 : }
1463 :
1464 : template<typename T>
1465 0 : 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 0 : 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 0 : const auto& it = mapOrphan.find(parentHash);
1476 0 : if (it != mapOrphan.end()) {
1477 : // Check size limit and erase it from the seen map if we already passed it
1478 0 : 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 0 : it->second.first.emplace_back(vote);
1483 0 : it->second.second = GetTime();
1484 : }
1485 : } else {
1486 0 : mapOrphan.emplace(parentHash, std::make_pair<std::vector<T>, int64_t>({vote}, GetTime()));
1487 : }
1488 : }
1489 0 : }
1490 :
1491 17 : bool CBudgetManager::UpdateProposal(const CBudgetVote& vote, CNode* pfrom, std::string& strError)
1492 : {
1493 34 : LOCK(cs_proposals);
1494 :
1495 17 : const uint256& nProposalHash = vote.GetProposalHash();
1496 17 : const auto& itProposal = mapProposals.find(nProposalHash);
1497 17 : if (itProposal == mapProposals.end()) {
1498 0 : 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 0 : if (!g_tiertwo_sync_state.IsSynced()) return false;
1502 :
1503 0 : LogPrint(BCLog::MNBUDGET,"%s: Unknown proposal %d, asking for source proposal\n", __func__, nProposalHash.ToString());
1504 0 : {
1505 0 : LOCK(cs_votes);
1506 0 : TryAppendOrphanVoteMap<CBudgetVote>(vote, nProposalHash, mapOrphanProposalVotes, mapSeenProposalVotes);
1507 : }
1508 :
1509 0 : if (!g_netfulfilledman.HasItemRequest(pfrom->addr, nProposalHash)) {
1510 0 : g_connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::BUDGETVOTESYNC, nProposalHash));
1511 0 : g_netfulfilledman.AddItemRequest(pfrom->addr, nProposalHash);
1512 : }
1513 : }
1514 :
1515 17 : strError = "Proposal not found!";
1516 : return false;
1517 : }
1518 :
1519 : // Add or update vote
1520 17 : return itProposal->second.AddOrUpdateVote(vote, strError);
1521 : }
1522 :
1523 12 : bool CBudgetManager::UpdateFinalizedBudget(const CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError)
1524 : {
1525 24 : LOCK(cs_budgets);
1526 :
1527 12 : const uint256& nBudgetHash = vote.GetBudgetHash();
1528 12 : if (!mapFinalizedBudgets.count(nBudgetHash)) {
1529 0 : 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 0 : if (!g_tiertwo_sync_state.IsSynced()) return false;
1533 :
1534 0 : LogPrint(BCLog::MNBUDGET,"%s: Unknown Finalized Proposal %s, asking for source budget\n", __func__, nBudgetHash.ToString());
1535 0 : {
1536 0 : LOCK(cs_finalizedvotes);
1537 0 : TryAppendOrphanVoteMap<CFinalizedBudgetVote>(vote, nBudgetHash, mapOrphanFinalizedBudgetVotes, mapSeenFinalizedBudgetVotes);
1538 : }
1539 :
1540 0 : if (!g_netfulfilledman.HasItemRequest(pfrom->addr, nBudgetHash)) {
1541 0 : g_connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::BUDGETVOTESYNC, nBudgetHash));
1542 0 : g_netfulfilledman.AddItemRequest(pfrom->addr, nBudgetHash);
1543 : }
1544 : }
1545 :
1546 0 : strError = "Finalized Budget " + nBudgetHash.ToString() + " not found!";
1547 0 : return false;
1548 : }
1549 24 : LogPrint(BCLog::MNBUDGET,"%s: Finalized Proposal %s added\n", __func__, nBudgetHash.ToString());
1550 12 : return mapFinalizedBudgets[nBudgetHash].AddOrUpdateVote(vote, strError);
1551 : }
1552 :
1553 129 : std::string CBudgetManager::ToString() const
1554 : {
1555 258 : unsigned int nProposals = WITH_LOCK(cs_proposals, return mapProposals.size(); );
1556 258 : unsigned int nBudgets = WITH_LOCK(cs_budgets, return mapFinalizedBudgets.size(); );
1557 :
1558 129 : unsigned int nSeenVotes = 0, nOrphanVotes = 0;
1559 129 : {
1560 129 : LOCK(cs_votes);
1561 129 : nSeenVotes = mapSeenProposalVotes.size();
1562 129 : nOrphanVotes = mapOrphanProposalVotes.size();
1563 : }
1564 :
1565 129 : unsigned int nSeenFinalizedVotes = 0, nOrphanFinalizedVotes = 0;
1566 129 : {
1567 129 : LOCK(cs_finalizedvotes);
1568 129 : nSeenFinalizedVotes = mapSeenFinalizedBudgetVotes.size();
1569 129 : nOrphanFinalizedVotes = mapOrphanFinalizedBudgetVotes.size();
1570 : }
1571 :
1572 129 : return strprintf("Proposals: %d - Finalized Budgets: %d - "
1573 : "Proposal Votes: %d (orphan: %d) - "
1574 : "Finalized Budget Votes: %d (orphan: %d)",
1575 : nProposals, nBudgets,
1576 129 : nSeenVotes, nOrphanVotes, nSeenFinalizedVotes, nOrphanFinalizedVotes);
1577 : }
1578 :
1579 :
1580 : /*
1581 : * Check Collateral
1582 : */
1583 30 : bool CheckCollateralConfs(const uint256& nTxCollateralHash, int nCurrentHeight, int nProposalHeight, std::string& strError)
1584 : {
1585 30 : const int nRequiredConfs = Params().GetConsensus().nBudgetFeeConfirmations;
1586 30 : const int nConf = nCurrentHeight - nProposalHeight + 1;
1587 :
1588 30 : 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 30 : bool CheckCollateral(const uint256& nTxCollateralHash, const uint256& nExpectedHash, std::string& strError, int64_t& nTime, int nCurrentHeight, bool fBudgetFinalization)
1598 : {
1599 30 : CTransactionRef txCollateral;
1600 30 : uint256 nBlockHash;
1601 30 : 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 30 : if (txCollateral->vout.size() < 1) return false;
1607 30 : if (txCollateral->nLockTime != 0) return false;
1608 :
1609 60 : CScript findScript;
1610 30 : findScript << OP_RETURN << ToByteVector(nExpectedHash);
1611 :
1612 30 : bool foundOpReturn = false;
1613 45 : for (const CTxOut &o : txCollateral->vout) {
1614 45 : if (!o.scriptPubKey.IsPayToPublicKeyHash() && !o.scriptPubKey.IsUnspendable()) {
1615 0 : strError = strprintf("Invalid Script %s", txCollateral->ToString());
1616 0 : return false;
1617 : }
1618 45 : 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 40 : LogPrint(BCLog::MNBUDGET, "Final Budget: o.scriptPubKey(%s) == findScript(%s) ?\n", HexStr(o.scriptPubKey), HexStr(findScript));
1623 8 : if (o.scriptPubKey == findScript) {
1624 6 : LogPrint(BCLog::MNBUDGET, "Final Budget: o.nValue(%ld) >= BUDGET_FEE_TX(%ld) ?\n", o.nValue, BUDGET_FEE_TX);
1625 6 : if(o.nValue >= BUDGET_FEE_TX) {
1626 : foundOpReturn = true;
1627 : break;
1628 : }
1629 : }
1630 : } else {
1631 : // Collateral for normal budget proposal
1632 185 : LogPrint(BCLog::MNBUDGET, "Normal Budget: o.scriptPubKey(%s) == findScript(%s) ?\n", HexStr(o.scriptPubKey), HexStr(findScript));
1633 37 : if (o.scriptPubKey == findScript) {
1634 24 : LogPrint(BCLog::MNBUDGET, "Normal Budget: o.nValue(%ld) >= PROPOSAL_FEE_TX(%ld) ?\n", o.nValue, PROPOSAL_FEE_TX);
1635 24 : if(o.nValue >= PROPOSAL_FEE_TX) {
1636 : foundOpReturn = true;
1637 : break;
1638 : }
1639 : }
1640 : }
1641 : }
1642 :
1643 30 : 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 60 : if (nBlockHash.IsNull()) {
1651 0 : strError = strprintf("Collateral transaction %s is unconfirmed", nTxCollateralHash.ToString());
1652 0 : return false;
1653 : }
1654 30 : nTime = 0;
1655 30 : int nProposalHeight = 0;
1656 30 : {
1657 30 : LOCK(cs_main);
1658 30 : CBlockIndex* pindex = LookupBlockIndex(nBlockHash);
1659 60 : if (pindex && chainActive.Contains(pindex)) {
1660 30 : nProposalHeight = pindex->nHeight;
1661 30 : nTime = pindex->nTime;
1662 : }
1663 : }
1664 :
1665 30 : if (!nProposalHeight) {
1666 0 : strError = strprintf("Collateral transaction %s not in Active chain", nTxCollateralHash.ToString());
1667 0 : return false;
1668 : }
1669 :
1670 30 : return CheckCollateralConfs(nTxCollateralHash, nCurrentHeight, nProposalHeight, strError);
1671 : }
|