// Copyright (c) 2014-2015 The Dash developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "masternode.h" #include "masternodeman.h" #include "darksend.h" #include "util.h" #include "sync.h" #include "addrman.h" #include CCriticalSection cs_masternodepayments; /** Object for who's going to get paid on which blocks */ CMasternodePayments masternodePayments; // keep track of Masternode votes I've seen map mapSeenMasternodeVotes; // keep track of the scanning errors I've seen map mapSeenMasternodeScanningErrors; // cache block hashes as we calculate them std::map mapCacheBlockHashes; void ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) { if(IsInitialBlockDownload()) return; if (strCommand == "mnget") { //Masternode Payments Request Sync if(fLiteMode) return; //disable all Darksend/Masternode related functionality if(pfrom->HasFulfilledRequest("mnget")) { LogPrintf("mnget - peer already asked me for the list\n"); Misbehaving(pfrom->GetId(), 20); return; } pfrom->FulfilledRequest("mnget"); masternodePayments.Sync(pfrom); LogPrintf("mnget - Sent Masternode winners to %s\n", pfrom->addr.ToString().c_str()); } else if (strCommand == "mnw") { //Masternode Payments Declare Winner LOCK(cs_masternodepayments); //this is required in litemode CMasternodePaymentWinner winner; vRecv >> winner; if(chainActive.Tip() == NULL) return; CTxDestination address1; ExtractDestination(winner.payee, address1); CBitcoinAddress address2(address1); uint256 hash = winner.GetHash(); if(mapSeenMasternodeVotes.count(hash)) { if(fDebug) LogPrintf("mnw - seen vote %s Addr %s Height %d bestHeight %d\n", hash.ToString().c_str(), address2.ToString().c_str(), winner.nBlockHeight, chainActive.Tip()->nHeight); return; } if(winner.nBlockHeight < chainActive.Tip()->nHeight - 10 || winner.nBlockHeight > chainActive.Tip()->nHeight+20){ LogPrintf("mnw - winner out of range %s Addr %s Height %d bestHeight %d\n", winner.vin.ToString().c_str(), address2.ToString().c_str(), winner.nBlockHeight, chainActive.Tip()->nHeight); return; } if(winner.vin.nSequence != std::numeric_limits::max()){ LogPrintf("mnw - invalid nSequence\n"); Misbehaving(pfrom->GetId(), 100); return; } LogPrintf("mnw - winning vote - Vin %s Addr %s Height %d bestHeight %d\n", winner.vin.ToString().c_str(), address2.ToString().c_str(), winner.nBlockHeight, chainActive.Tip()->nHeight); if(!masternodePayments.CheckSignature(winner)){ LogPrintf("mnw - invalid signature\n"); Misbehaving(pfrom->GetId(), 100); return; } mapSeenMasternodeVotes.insert(make_pair(hash, winner)); if(masternodePayments.AddWinningMasternode(winner)){ masternodePayments.Relay(winner); } } } struct CompareValueOnly { bool operator()(const pair& t1, const pair& t2) const { return t1.first < t2.first; } }; //Get the last hash that matches the modulus given. Processed in reverse order bool GetBlockHash(uint256& hash, int nBlockHeight) { if (chainActive.Tip() == NULL) return false; if(nBlockHeight == 0) nBlockHeight = chainActive.Tip()->nHeight; if(mapCacheBlockHashes.count(nBlockHeight)){ hash = mapCacheBlockHashes[nBlockHeight]; return true; } const CBlockIndex *BlockLastSolved = chainActive.Tip(); const CBlockIndex *BlockReading = chainActive.Tip(); if (BlockLastSolved == NULL || BlockLastSolved->nHeight == 0 || chainActive.Tip()->nHeight+1 < nBlockHeight) return false; int nBlocksAgo = 0; if(nBlockHeight > 0) nBlocksAgo = (chainActive.Tip()->nHeight+1)-nBlockHeight; assert(nBlocksAgo >= 0); int n = 0; for (unsigned int i = 1; BlockReading && BlockReading->nHeight > 0; i++) { if(n >= nBlocksAgo){ hash = BlockReading->GetBlockHash(); mapCacheBlockHashes[nBlockHeight] = hash; return true; } n++; if (BlockReading->pprev == NULL) { assert(BlockReading); break; } BlockReading = BlockReading->pprev; } return false; } CMasternode::CMasternode() { LOCK(cs); vin = CTxIn(); addr = CService(); pubkey = CPubKey(); pubkey2 = CPubKey(); sig = std::vector(); activeState = MASTERNODE_ENABLED; sigTime = GetAdjustedTime(); lastDseep = 0; lastTimeSeen = 0; cacheInputAge = 0; cacheInputAgeBlock = 0; unitTest = false; allowFreeTx = true; protocolVersion = MIN_PEER_PROTO_VERSION; nLastDsq = 0; donationAddress = CScript(); donationPercentage = 0; nVote = 0; lastVote = 0; nScanningErrorCount = 0; nLastScanningErrorBlockHeight = 0; } CMasternode::CMasternode(const CMasternode& other) { LOCK(cs); vin = other.vin; addr = other.addr; pubkey = other.pubkey; pubkey2 = other.pubkey2; sig = other.sig; activeState = other.activeState; sigTime = other.sigTime; lastDseep = other.lastDseep; lastTimeSeen = other.lastTimeSeen; cacheInputAge = other.cacheInputAge; cacheInputAgeBlock = other.cacheInputAgeBlock; unitTest = other.unitTest; allowFreeTx = other.allowFreeTx; protocolVersion = other.protocolVersion; nLastDsq = other.nLastDsq; donationAddress = other.donationAddress; donationPercentage = other.donationPercentage; nVote = other.nVote; lastVote = other.lastVote; nScanningErrorCount = other.nScanningErrorCount; nLastScanningErrorBlockHeight = other.nLastScanningErrorBlockHeight; } CMasternode::CMasternode(CService newAddr, CTxIn newVin, CPubKey newPubkey, std::vector newSig, int64_t newSigTime, CPubKey newPubkey2, int protocolVersionIn, CScript newDonationAddress, int newDonationPercentage) { LOCK(cs); vin = newVin; addr = newAddr; pubkey = newPubkey; pubkey2 = newPubkey2; sig = newSig; activeState = MASTERNODE_ENABLED; sigTime = newSigTime; lastDseep = 0; lastTimeSeen = 0; cacheInputAge = 0; cacheInputAgeBlock = 0; unitTest = false; allowFreeTx = true; protocolVersion = protocolVersionIn; nLastDsq = 0; donationAddress = newDonationAddress; donationPercentage = newDonationPercentage; nVote = 0; lastVote = 0; nScanningErrorCount = 0; nLastScanningErrorBlockHeight = 0; } // // Deterministically calculate a given "score" for a Masternode depending on how close it's hash is to // the proof of work for that block. The further away they are the better, the furthest will win the election // and get paid this block // uint256 CMasternode::CalculateScore(int mod, int64_t nBlockHeight) { if(chainActive.Tip() == NULL) return 0; uint256 hash = 0; uint256 aux = vin.prevout.hash + vin.prevout.n; if(!GetBlockHash(hash, nBlockHeight)) return 0; uint256 hash2 = Hash(BEGIN(hash), END(hash)); uint256 hash3 = Hash(BEGIN(hash), END(hash), BEGIN(aux), END(aux)); uint256 r = (hash3 > hash2 ? hash3 - hash2 : hash2 - hash3); return r; } void CMasternode::Check() { //TODO: Random segfault with this line removed TRY_LOCK(cs_main, lockRecv); if(!lockRecv) return; if(nScanningErrorCount >= MASTERNODE_SCANNING_ERROR_THESHOLD) { activeState = MASTERNODE_POS_ERROR; return; } //once spent, stop doing the checks if(activeState == MASTERNODE_VIN_SPENT) return; if(!UpdatedWithin(MASTERNODE_REMOVAL_SECONDS)){ activeState = MASTERNODE_REMOVE; return; } if(!UpdatedWithin(MASTERNODE_EXPIRATION_SECONDS)){ activeState = MASTERNODE_EXPIRED; return; } if(!unitTest){ CValidationState state; CMutableTransaction tx = CMutableTransaction(); CTxOut vout = CTxOut(999.99*COIN, darkSendPool.collateralPubKey); tx.vin.push_back(vin); tx.vout.push_back(vout); if(!AcceptableInputs(mempool, state, tx, false, NULL)){ activeState = MASTERNODE_VIN_SPENT; return; } } activeState = MASTERNODE_ENABLED; // OK } bool CMasternodePayments::CheckSignature(CMasternodePaymentWinner& winner) { //note: need to investigate why this is failing std::string strMessage = winner.vin.ToString().c_str() + boost::lexical_cast(winner.nBlockHeight) + winner.payee.ToString(); CPubKey pubkey(ParseHex(Params().MasternodePaymentPubKey())); std::string errorMessage = ""; if(!darkSendSigner.VerifyMessage(pubkey, winner.vchSig, strMessage, errorMessage)){ return false; } return true; } bool CMasternodePayments::Sign(CMasternodePaymentWinner& winner) { std::string strMessage = winner.vin.ToString().c_str() + boost::lexical_cast(winner.nBlockHeight) + winner.payee.ToString(); CKey key2; CPubKey pubkey2; std::string errorMessage = ""; if(!darkSendSigner.SetKey(strMasterPrivKey, errorMessage, key2, pubkey2)) { LogPrintf("CMasternodePayments::Sign - ERROR: Invalid Masternodeprivkey: '%s'\n", errorMessage.c_str()); return false; } if(!darkSendSigner.SignMessage(strMessage, errorMessage, winner.vchSig, key2)) { LogPrintf("CMasternodePayments::Sign - Sign message failed"); return false; } if(!darkSendSigner.VerifyMessage(pubkey2, winner.vchSig, strMessage, errorMessage)) { LogPrintf("CMasternodePayments::Sign - Verify message failed"); return false; } return true; } uint64_t CMasternodePayments::CalculateScore(uint256 blockHash, CTxIn& vin) { uint256 n1 = blockHash; uint256 n2 = HashX11(BEGIN(n1), END(n1)); uint256 n3 = HashX11(BEGIN(vin.prevout.hash), END(vin.prevout.hash)); uint256 n4 = n3 > n2 ? (n3 - n2) : (n2 - n3); //printf(" -- CMasternodePayments CalculateScore() n2 = %d \n", n2.Get64()); //printf(" -- CMasternodePayments CalculateScore() n3 = %d \n", n3.Get64()); //printf(" -- CMasternodePayments CalculateScore() n4 = %d \n", n4.Get64()); return n4.Get64(); } bool CMasternodePayments::GetBlockPayee(int nBlockHeight, CScript& payee) { BOOST_FOREACH(CMasternodePaymentWinner& winner, vWinning){ if(winner.nBlockHeight == nBlockHeight) { payee = winner.payee; return true; } } return false; } bool CMasternodePayments::GetWinningMasternode(int nBlockHeight, CTxIn& vinOut) { BOOST_FOREACH(CMasternodePaymentWinner& winner, vWinning){ if(winner.nBlockHeight == nBlockHeight) { vinOut = winner.vin; return true; } } return false; } bool CMasternodePayments::AddWinningMasternode(CMasternodePaymentWinner& winnerIn) { uint256 blockHash = 0; if(!GetBlockHash(blockHash, winnerIn.nBlockHeight-576)) { return false; } winnerIn.score = CalculateScore(blockHash, winnerIn.vin); bool foundBlock = false; BOOST_FOREACH(CMasternodePaymentWinner& winner, vWinning){ if(winner.nBlockHeight == winnerIn.nBlockHeight) { foundBlock = true; if(winner.score < winnerIn.score){ winner.score = winnerIn.score; winner.vin = winnerIn.vin; winner.payee = winnerIn.payee; winner.vchSig = winnerIn.vchSig; mapSeenMasternodeVotes.insert(make_pair(winnerIn.GetHash(), winnerIn)); return true; } } } // if it's not in the vector if(!foundBlock){ vWinning.push_back(winnerIn); mapSeenMasternodeVotes.insert(make_pair(winnerIn.GetHash(), winnerIn)); return true; } return false; } void CMasternodePayments::CleanPaymentList() { LOCK(cs_masternodepayments); if(chainActive.Tip() == NULL) return; int nLimit = std::max(((int)mnodeman.size())*2, 1000); vector::iterator it; for(it=vWinning.begin();itnHeight - (*it).nBlockHeight > nLimit){ if(fDebug) LogPrintf("CMasternodePayments::CleanPaymentList - Removing old Masternode payment - block %d\n", (*it).nBlockHeight); vWinning.erase(it); break; } } } bool CMasternodePayments::ProcessBlock(int nBlockHeight) { LOCK(cs_masternodepayments); if(nBlockHeight <= nLastBlockHeight) return false; if(!enabled) return false; CMasternodePaymentWinner newWinner; int nMinimumAge = mnodeman.CountEnabled(); uint256 hash; if(!GetBlockHash(hash, nBlockHeight-10)) return false; unsigned int nHash; memcpy(&nHash, &hash, 2); LogPrintf(" ProcessBlock Start nHeight %d. \n", nBlockHeight); std::vector vecLastPayments; BOOST_REVERSE_FOREACH(CMasternodePaymentWinner& winner, vWinning) { //if we already have the same vin - we have one full payment cycle, break if(vecLastPayments.size() > 0 && std::find(vecLastPayments.begin(), vecLastPayments.end(), winner.vin) != vecLastPayments.end()) break; vecLastPayments.push_back(winner.vin); } // pay to the oldest MN that still had no payment but its input is old enough and it was active long enough CMasternode *pmn = mnodeman.FindOldestNotInVec(vecLastPayments, nMinimumAge, 0); if(pmn != NULL) { newWinner.score = 0; newWinner.nBlockHeight = nBlockHeight; newWinner.vin = pmn->vin; if(pmn->donationPercentage > 0 && (nHash % 100) <= (unsigned int)pmn->donationPercentage) { newWinner.payee = pmn->donationAddress; } else { newWinner.payee = GetScriptForDestination(pmn->pubkey.GetID()); } } //if we can't find new MN to get paid, pick first active MN counting back from the end of vecLastPayments list if(newWinner.nBlockHeight == 0 && nMinimumAge > 0) { BOOST_REVERSE_FOREACH(CTxIn& vinLP, vecLastPayments) { CMasternode* pmn = mnodeman.Find(vinLP); if(pmn != NULL) { pmn->Check(); if(!pmn->IsEnabled()) continue; newWinner.score = 0; newWinner.nBlockHeight = nBlockHeight; newWinner.vin = pmn->vin; if(pmn->donationPercentage > 0 && (nHash % 100) <= (unsigned int)pmn->donationPercentage) { newWinner.payee = pmn->donationAddress; } else { newWinner.payee = GetScriptForDestination(pmn->pubkey.GetID()); } break; // we found active MN } } } if(newWinner.nBlockHeight == 0) return false; CTxDestination address1; ExtractDestination(newWinner.payee, address1); CBitcoinAddress address2(address1); LogPrintf("Winner payee %s nHeight %d. \n", address2.ToString().c_str(), newWinner.nBlockHeight); if(Sign(newWinner)) { if(AddWinningMasternode(newWinner)) { Relay(newWinner); nLastBlockHeight = nBlockHeight; return true; } } return false; } void CMasternodePayments::Relay(CMasternodePaymentWinner& winner) { CInv inv(MSG_MASTERNODE_WINNER, winner.GetHash()); vector vInv; vInv.push_back(inv); LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes){ pnode->PushMessage("inv", vInv); } } void CMasternodePayments::Sync(CNode* node) { LOCK(cs_masternodepayments); BOOST_FOREACH(CMasternodePaymentWinner& winner, vWinning) if(winner.nBlockHeight >= chainActive.Tip()->nHeight-10 && winner.nBlockHeight <= chainActive.Tip()->nHeight + 20) node->PushMessage("mnw", winner); } bool CMasternodePayments::SetPrivKey(std::string strPrivKey) { CMasternodePaymentWinner winner; // Test signing successful, proceed strMasterPrivKey = strPrivKey; Sign(winner); if(CheckSignature(winner)){ LogPrintf("CMasternodePayments::SetPrivKey - Successfully initialized as Masternode payments master\n"); enabled = true; return true; } else { return false; } }