diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 80699fe458..ba446921bb 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -302,7 +302,7 @@ void CMasternodePayments::ProcessMessage(CNode* pfrom, std::string& strCommand, return; } - int nFirstBlock = pCurrentBlockIndex->nHeight - mnodeman.CountEnabled()*1.25; + int nFirstBlock = pCurrentBlockIndex->nHeight - GetStorageLimit(); if(winner.nBlockHeight < nFirstBlock || winner.nBlockHeight > pCurrentBlockIndex->nHeight+20) { LogPrint("mnpayments", "MNWINNER -- winner out of range: nFirstBlock=%d, nBlockHeight=%d, nHeight=%d\n", nFirstBlock, winner.nBlockHeight, pCurrentBlockIndex->nHeight); return; @@ -692,8 +692,8 @@ void CMasternodePayments::Sync(CNode* node, int nCountNeeded) if(!pCurrentBlockIndex) return; - int nCount = (mnodeman.CountEnabled()*1.25); - if(nCountNeeded > nCount) nCountNeeded = nCount; + int nLimit = GetStorageLimit(); + if(nCountNeeded > nLimit) nCountNeeded = nLimit; int nInvCount = 0; std::map::iterator it = mapMasternodePayeeVotes.begin(); @@ -768,6 +768,11 @@ bool CMasternodePayments::IsEnoughData(int nMnCount) { return false; } +int CMasternodePayments::GetStorageLimit() +{ + return std::max(int(mnodeman.size() * nStorageCoeff), nMinBlocksToStore); +} + void CMasternodePayments::UpdatedBlockTip(const CBlockIndex *pindex) { pCurrentBlockIndex = pindex; @@ -776,4 +781,6 @@ void CMasternodePayments::UpdatedBlockTip(const CBlockIndex *pindex) if (!fLiteMode && masternodeSync.IsMasternodeListSynced()) { ProcessBlock(pindex->nHeight + 10); } + // normal wallet does not need to update this every block, doing update on rpc call should be enough + if(fMasterNode) mnodeman.UpdateLastPaid(pindex); } diff --git a/src/masternode-payments.h b/src/masternode-payments.h index d5c5ba56c2..affe870a10 100644 --- a/src/masternode-payments.h +++ b/src/masternode-payments.h @@ -272,6 +272,7 @@ public: } bool IsEnoughData(int nMnCount); + int GetStorageLimit(); ADD_SERIALIZE_METHODS; diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index 9829c1ae00..a6cad7851f 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -296,7 +296,7 @@ void CMasternodeSync::ProcessTick() // check for data // if mnpayments already has enough blocks and votes, switch to the next asset // try to fetch data from at least two peers though - if(nRequestedMasternodeAttempt > 1 && mnpayments.IsEnoughData(nMnCount)) { + if(nRequestedMasternodeAttempt > 1 && mnpayments.IsEnoughData(mnpayments.GetStorageLimit())) { LogPrintf("CMasternodeSync::Process -- nTick %d nRequestedMasternodeAssets %d -- found enough data\n", nTick, nRequestedMasternodeAssets); SwitchToNextAsset(); return; @@ -309,7 +309,7 @@ void CMasternodeSync::ProcessTick() if(pnode->nVersion < mnpayments.GetMinMasternodePaymentsProto()) continue; nRequestedMasternodeAttempt++; - pnode->PushMessage(NetMsgType::MNWINNERSSYNC, nMnCount); //sync payees + pnode->PushMessage(NetMsgType::MNWINNERSSYNC, mnpayments.GetStorageLimit()); //sync payees return; //this will cause each peer to get one request each six seconds for the various assets we need diff --git a/src/masternode.cpp b/src/masternode.cpp index c2e6e7f572..a8c7cb32fc 100644 --- a/src/masternode.cpp +++ b/src/masternode.cpp @@ -30,8 +30,9 @@ CMasternode::CMasternode() activeState = MASTERNODE_ENABLED; sigTime = GetAdjustedTime(); lastPing = CMasternodePing(); - cacheInputAge = 0; - cacheInputAgeBlock = 0; + nTimeLastPaid = 0; + nBlockLastPaid = 0; + nCacheCollateralBlock = 0; unitTest = false; allowFreeTx = true; protocolVersion = PROTOCOL_VERSION; @@ -52,8 +53,9 @@ CMasternode::CMasternode(const CMasternode& other) activeState = other.activeState; sigTime = other.sigTime; lastPing = other.lastPing; - cacheInputAge = other.cacheInputAge; - cacheInputAgeBlock = other.cacheInputAgeBlock; + nTimeLastPaid = other.nTimeLastPaid; + nBlockLastPaid = other.nBlockLastPaid; + nCacheCollateralBlock = other.nCacheCollateralBlock; unitTest = other.unitTest; allowFreeTx = other.allowFreeTx; protocolVersion = other.protocolVersion; @@ -74,8 +76,9 @@ CMasternode::CMasternode(const CMasternodeBroadcast& mnb) activeState = MASTERNODE_ENABLED; sigTime = mnb.sigTime; lastPing = mnb.lastPing; - cacheInputAge = 0; - cacheInputAgeBlock = 0; + nTimeLastPaid = 0; + nBlockLastPaid = 0; + nCacheCollateralBlock = 0; unitTest = false; allowFreeTx = true; protocolVersion = mnb.protocolVersion; @@ -200,65 +203,64 @@ void CMasternode::Check(bool forceCheck) activeState = MASTERNODE_ENABLED; // OK } -int64_t CMasternode::SecondsSincePayment() { - int64_t sec = (GetAdjustedTime() - GetLastPaid()); - int64_t month = 60*60*24*30; - if(sec < month) return sec; //if it's less than 30 days, give seconds - - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - ss << vin; - ss << sigTime; - uint256 hash = ss.GetHash(); - - // return some deterministic value for unknown/unpaid but force it to be more than 30 days old - return month + UintToArith256(hash).GetCompact(false); -} - -int64_t CMasternode::GetLastPaid() { - CBlockIndex *pindexPrev = NULL; +int CMasternode::GetCollateralAge() +{ + int nHeight; { - LOCK(cs_main); - pindexPrev = chainActive.Tip(); - if(!pindexPrev) return 0; + TRY_LOCK(cs_main, lockMain); + if(!lockMain || !chainActive.Tip()) return -1; + nHeight = chainActive.Height(); } - - CScript mnpayee; - mnpayee = GetScriptForDestination(pubkey.GetID()); - - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - ss << vin; - ss << sigTime; - uint256 hash = ss.GetHash(); - - // use a deterministic offset to break a tie -- 2.5 minutes - int64_t nOffset = UintToArith256(hash).GetCompact(false) % 150; - - const CBlockIndex *BlockReading = pindexPrev; - - int nMnCount = mnodeman.CountEnabled()*1.25; - int n = 0; - for (unsigned int i = 1; BlockReading && BlockReading->nHeight > 0; i++) { - if(n >= nMnCount){ - return 0; + if (nCacheCollateralBlock == 0) { + int nInputAge = GetInputAge(vin); + if(nInputAge > 0) { + nCacheCollateralBlock = nHeight - nInputAge; + } else { + return nInputAge; } - n++; + } - if(mnpayments.mapMasternodeBlocks.count(BlockReading->nHeight)){ - /* - Search for this payee, with at least 2 votes. This will aid in consensus allowing the network - to converge on the same payees quickly, then keep the same schedule. - */ - if(mnpayments.mapMasternodeBlocks[BlockReading->nHeight].HasPayeeWithVotes(mnpayee, 2)){ - return BlockReading->nTime + nOffset; - } + return nHeight - nCacheCollateralBlock; +} + +void CMasternode::UpdateLastPaid(const CBlockIndex *pindex, int nMaxBlocksToScanBack) +{ + if(!pindex) return; + + const CBlockIndex *BlockReading = pindex; + + CScript mnpayee = GetScriptForDestination(pubkey.GetID()); + // LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s\n", vin.prevout.ToStringShort()); + + LOCK(cs_mapMasternodeBlocks); + + for (int i = 0; BlockReading && BlockReading->nHeight > nBlockLastPaid && i < nMaxBlocksToScanBack; i++) { + if(mnpayments.mapMasternodeBlocks.count(BlockReading->nHeight) && + mnpayments.mapMasternodeBlocks[BlockReading->nHeight].HasPayeeWithVotes(mnpayee, 2)) + { + CBlock block; + if(!ReadBlockFromDisk(block, BlockReading, Params().GetConsensus())) // shouldn't really happen + continue; + + CAmount nMasternodePayment = GetMasternodePayment(BlockReading->nHeight, block.vtx[0].GetValueOut()); + + BOOST_FOREACH(CTxOut txout, block.vtx[0].vout) + if(mnpayee == txout.scriptPubKey && nMasternodePayment == txout.nValue) { + nBlockLastPaid = BlockReading->nHeight; + nTimeLastPaid = BlockReading->nTime; + LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- found new %d\n", vin.prevout.ToStringShort(), nBlockLastPaid); + return; + } } if (BlockReading->pprev == NULL) { assert(BlockReading); break; } BlockReading = BlockReading->pprev; } - return 0; + // Last payment for this masternode wasn't found in latest mnpayments blocks + // or it was found in mnpayments blocks but wasn't found in the blockchain. + // LogPrint("masternode", "CMasternode::UpdateLastPaidBlock -- searching for block with payment to %s -- keeping old %d\n", vin.prevout.ToStringShort(), nBlockLastPaid); } CMasternodeBroadcast::CMasternodeBroadcast() @@ -271,8 +273,9 @@ CMasternodeBroadcast::CMasternodeBroadcast() activeState = MASTERNODE_ENABLED; sigTime = GetAdjustedTime(); lastPing = CMasternodePing(); - cacheInputAge = 0; - cacheInputAgeBlock = 0; + nTimeLastPaid = 0; + nBlockLastPaid = 0; + nCacheCollateralBlock = 0; unitTest = false; allowFreeTx = true; protocolVersion = PROTOCOL_VERSION; @@ -291,8 +294,9 @@ CMasternodeBroadcast::CMasternodeBroadcast(CService newAddr, CTxIn newVin, CPubK activeState = MASTERNODE_ENABLED; sigTime = GetAdjustedTime(); lastPing = CMasternodePing(); - cacheInputAge = 0; - cacheInputAgeBlock = 0; + nTimeLastPaid = 0; + nBlockLastPaid = 0; + nCacheCollateralBlock = 0; unitTest = false; allowFreeTx = true; protocolVersion = protocolVersionIn; @@ -311,8 +315,9 @@ CMasternodeBroadcast::CMasternodeBroadcast(const CMasternode& mn) activeState = mn.activeState; sigTime = mn.sigTime; lastPing = mn.lastPing; - cacheInputAge = mn.cacheInputAge; - cacheInputAgeBlock = mn.cacheInputAgeBlock; + nTimeLastPaid = mn.nTimeLastPaid; + nBlockLastPaid = mn.nBlockLastPaid; + nCacheCollateralBlock = mn.nCacheCollateralBlock; unitTest = mn.unitTest; allowFreeTx = mn.allowFreeTx; protocolVersion = mn.protocolVersion; diff --git a/src/masternode.h b/src/masternode.h index 2ebad9fcb9..1e69b79a4e 100644 --- a/src/masternode.h +++ b/src/masternode.h @@ -122,8 +122,9 @@ public: std::vector vchSig; int activeState; int64_t sigTime; //mnb message time - int cacheInputAge; - int cacheInputAgeBlock; + int64_t nTimeLastPaid; + int nBlockLastPaid; + int nCacheCollateralBlock; bool unitTest; bool allowFreeTx; int protocolVersion; @@ -155,8 +156,9 @@ public: swap(first.activeState, second.activeState); swap(first.sigTime, second.sigTime); swap(first.lastPing, second.lastPing); - swap(first.cacheInputAge, second.cacheInputAge); - swap(first.cacheInputAgeBlock, second.cacheInputAgeBlock); + swap(first.nTimeLastPaid, second.nTimeLastPaid); + swap(first.nBlockLastPaid, second.nBlockLastPaid); + swap(first.nCacheCollateralBlock, second.nCacheCollateralBlock); swap(first.unitTest, second.unitTest); swap(first.allowFreeTx, second.allowFreeTx); swap(first.protocolVersion, second.protocolVersion); @@ -204,8 +206,9 @@ public: READWRITE(protocolVersion); READWRITE(activeState); READWRITE(lastPing); - READWRITE(cacheInputAge); - READWRITE(cacheInputAgeBlock); + READWRITE(nTimeLastPaid); + READWRITE(nBlockLastPaid); + READWRITE(nCacheCollateralBlock); READWRITE(unitTest); READWRITE(allowFreeTx); READWRITE(nLastDsq); @@ -257,19 +260,6 @@ public: return activeState == MASTERNODE_PRE_ENABLED; } - int GetMasternodeInputAge() - { - LOCK(cs_main); - if(chainActive.Tip() == NULL) return 0; - - if(cacheInputAge == 0){ - cacheInputAge = GetInputAge(vin); - cacheInputAgeBlock = chainActive.Tip()->nHeight; - } - - return cacheInputAge + (chainActive.Tip()->nHeight - cacheInputAgeBlock); - } - std::string Status() { std::string strStatus = "unknown"; @@ -283,7 +273,11 @@ public: return strStatus; } - int64_t GetLastPaid(); + int GetCollateralAge(); + + int GetLastPaidTime() { return nTimeLastPaid; } + int GetLastPaidBlock() { return nBlockLastPaid; } + void UpdateLastPaid(const CBlockIndex *pindex, int nMaxBlocksToScanBack); }; diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index d618ab0682..55efa140a9 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -17,12 +17,12 @@ /** Masternode manager */ CMasternodeMan mnodeman; -struct CompareLastPaid +struct CompareLastPaidBlock { - bool operator()(const pair& t1, - const pair& t2) const + bool operator()(const std::pair& t1, + const std::pair& t2) const { - return t1.first < t2.first; + return (t1.first != t2.first) ? (t1.first < t2.first) : (t1.second < t2.second); } }; @@ -311,7 +311,7 @@ CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeight LOCK(cs); CMasternode *pBestMasternode = NULL; - std::vector > vecMasternodeLastPaid; + std::vector > vecMasternodeLastPaid; /* Make a vector with all of the last paid times @@ -333,9 +333,9 @@ CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeight if(fFilterSigTime && mn.sigTime + (nMnCount*2.6*60) > GetAdjustedTime()) continue; //make sure it has as many confirmations as there are masternodes - if(mn.GetMasternodeInputAge() < nMnCount) continue; + if(mn.GetCollateralAge() < nMnCount) continue; - vecMasternodeLastPaid.push_back(make_pair(mn.SecondsSincePayment(), mn.vin)); + vecMasternodeLastPaid.push_back(std::make_pair(mn.GetLastPaidBlock(), mn.vin)); } nCount = (int)vecMasternodeLastPaid.size(); @@ -343,17 +343,17 @@ CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeight //when the network is in the process of upgrading, don't penalize nodes that recently restarted if(fFilterSigTime && nCount < nMnCount/3) return GetNextMasternodeInQueueForPayment(nBlockHeight, false, nCount); - // Sort them high to low - sort(vecMasternodeLastPaid.rbegin(), vecMasternodeLastPaid.rend(), CompareLastPaid()); + // Sort them low to high + sort(vecMasternodeLastPaid.begin(), vecMasternodeLastPaid.end(), CompareLastPaidBlock()); // Look at 1/10 of the oldest nodes (by last payment), calculate their scores and pay the best one // -- This doesn't look at who is being paid in the +8-10 blocks, allowing for double payments very rarely // -- 1/100 payments should be a double payment on mainnet - (1/(3000/10))*2 // -- (chance per block * chances before IsScheduled will fire) int nTenthNetwork = CountEnabled()/10; - int nCountTenth = 0; + int nCountTenth = 0; arith_uint256 nHigh = 0; - BOOST_FOREACH (PAIRTYPE(int64_t, CTxIn)& s, vecMasternodeLastPaid){ + BOOST_FOREACH (PAIRTYPE(int, CTxIn)& s, vecMasternodeLastPaid){ CMasternode* pmn = Find(s.second); if(!pmn) break; @@ -769,3 +769,22 @@ bool CMasternodeMan::CheckMnbAndUpdateMasternodeList(CMasternodeBroadcast mnb, i return true; } + +void CMasternodeMan::UpdateLastPaid(const CBlockIndex *pindex) { + if(fLiteMode) return; + + static bool IsFirstRun = true; + // Do full scan on first run or if we are not a masternode + // (MNs should update this info on every block, so limited scan should be enough for them) + int nMaxBlocksToScanBack = (IsFirstRun || !fMasterNode) ? mnpayments.GetStorageLimit() : MASTERNODES_LAST_PAID_SCAN_BLOCKS; + + // LogPrint("mnpayments", "CMasternodeMan::UpdateLastPaid -- nHeight=%d, nMaxBlocksToScanBack=%d, IsFirstRun=%s\n", + // pindex->nHeight, nMaxBlocksToScanBack, IsFirstRun ? "true" : "false"); + + BOOST_FOREACH(CMasternode& mn, vMasternodes) { + mn.UpdateLastPaid(pindex, nMaxBlocksToScanBack); + } + + // every time is like the first time if winners list is not synced + IsFirstRun = !masternodeSync.IsWinnersListSynced(); +} diff --git a/src/masternodeman.h b/src/masternodeman.h index 944d6a62a0..4c74e17868 100644 --- a/src/masternodeman.h +++ b/src/masternodeman.h @@ -24,6 +24,8 @@ extern CMasternodeMan mnodeman; class CMasternodeMan { private: + static const int MASTERNODES_LAST_PAID_SCAN_BLOCKS = 100; + // critical section to protect the inner data structures mutable CCriticalSection cs; @@ -134,6 +136,7 @@ public: /// Perform complete check and only then update list and maps bool CheckMnbAndUpdateMasternodeList(CMasternodeBroadcast mnb, int& nDos); + void UpdateLastPaid(const CBlockIndex *pindex); }; #endif diff --git a/src/rpcmasternode.cpp b/src/rpcmasternode.cpp index 83cb3753e8..8956c662bd 100644 --- a/src/rpcmasternode.cpp +++ b/src/rpcmasternode.cpp @@ -477,9 +477,10 @@ UniValue masternodelist(const UniValue& params, bool fHelp) if (params.size() >= 1) strMode = params[0].get_str(); if (params.size() == 2) strFilter = params[1].get_str(); - if (fHelp || - (strMode != "status" && strMode != "vin" && strMode != "pubkey" && strMode != "lastseen" && strMode != "activeseconds" && strMode != "rank" && strMode != "addr" - && strMode != "protocol" && strMode != "full" && strMode != "lastpaid")) + if (fHelp || ( + strMode != "activeseconds" && strMode != "addr" && strMode != "full" && + strMode != "lastseen" && strMode != "lastpaidtime" && strMode != "lastpaidblock" && + strMode != "protocol" && strMode != "pubkey" && strMode != "rank" && strMode != "status")) { throw runtime_error( "masternodelist ( \"mode\" \"filter\" )\n" @@ -492,10 +493,11 @@ UniValue masternodelist(const UniValue& params, bool fHelp) " activeseconds - Print number of seconds masternode recognized by the network as enabled\n" " (since latest issued \"masternode start/start-many/start-alias\")\n" " addr - Print ip address associated with a masternode (can be additionally filtered, partial match)\n" - " full - Print info in format 'status protocol pubkey IP lastseen activeseconds lastpaid'\n" + " full - Print info in format 'status protocol pubkey IP lastseen activeseconds lastpaidtime'\n" " (can be additionally filtered, partial match)\n" " lastseen - Print timestamp of when a masternode was last seen on the network\n" - " lastpaid - The last time a node was paid on the network\n" + " lastpaidblock - Print the last block height a node was paid on the network\n" + " lastpaidtime - Print the last time a node was paid on the network\n" " protocol - Print protocol of a masternode (can be additionally filtered, exact match))\n" " pubkey - Print public key associated with a masternode (can be additionally filtered,\n" " partial match)\n" @@ -505,6 +507,15 @@ UniValue masternodelist(const UniValue& params, bool fHelp) ); } + if (strMode == "full" || strMode == "lastpaidtime" || strMode == "lastpaidblock") { + CBlockIndex* pindex; + { + LOCK(cs_main); + pindex = chainActive.Tip(); + } + mnodeman.UpdateLastPaid(pindex); + } + UniValue obj(UniValue::VOBJ); if (strMode == "rank") { std::vector > vMasternodeRanks = mnodeman.GetMasternodeRanks(chainActive.Tip()->nHeight); @@ -536,7 +547,7 @@ UniValue masternodelist(const UniValue& params, bool fHelp) mn.addr.ToString() << " " << (int64_t)mn.lastPing.sigTime << " " << setw(8) << (int64_t)(mn.lastPing.sigTime - mn.sigTime) << " " << - (int64_t)mn.GetLastPaid(); + (int64_t)mn.GetLastPaidTime(); std::string output = stringStream.str(); stringStream << " " << strVin; if(strFilter !="" && stringStream.str().find(strFilter) == string::npos && @@ -545,10 +556,12 @@ UniValue masternodelist(const UniValue& params, bool fHelp) } else if (strMode == "lastseen") { if(strFilter !="" && strVin.find(strFilter) == string::npos) continue; obj.push_back(Pair(strVin, (int64_t)mn.lastPing.sigTime)); - } else if (strMode == "lastpaid"){ - if(strFilter !="" && mn.vin.prevout.hash.ToString().find(strFilter) == string::npos && - strVin.find(strFilter) == string::npos) continue; - obj.push_back(Pair(strVin, (int64_t)mn.GetLastPaid())); + } else if (strMode == "lastpaidblock") { + if (strFilter !="" && strVin.find(strFilter) == std::string::npos) continue; + obj.push_back(Pair(strVin, mn.GetLastPaidBlock())); + } else if (strMode == "lastpaidtime") { + if (strFilter !="" && strVin.find(strFilter) == std::string::npos) continue; + obj.push_back(Pair(strVin, mn.GetLastPaidTime())); } else if (strMode == "protocol") { if(strFilter !="" && strFilter != strprintf("%d", mn.protocolVersion) && strVin.find(strFilter) == string::npos) continue;