mirror of
https://github.com/dashpay/dash.git
synced 2024-12-29 13:59:06 +01:00
501 lines
15 KiB
C++
501 lines
15 KiB
C++
// 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 "core.h"
|
|
#include "util.h"
|
|
#include "addrman.h"
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
/** Object for who's going to get paid on which blocks */
|
|
CMasternodePayments masternodePayments;
|
|
// keep track of Masternode votes I've seen
|
|
map<uint256, CMasternodePaymentWinner> mapSeenMasternodeVotes;
|
|
// keep track of the scanning errors I've seen
|
|
map<uint256, int> mapSeenMasternodeScanningErrors;
|
|
// cache block hashes as we calculate them
|
|
std::map<int64_t, uint256> 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
|
|
//this is required in litemode
|
|
CMasternodePaymentWinner winner;
|
|
vRecv >> winner;
|
|
|
|
if(chainActive.Tip() == NULL) return;
|
|
|
|
uint256 hash = winner.GetHash();
|
|
if(mapSeenMasternodeVotes.count(hash)) {
|
|
if(fDebug) LogPrintf("mnw - seen vote %s Height %d bestHeight %d\n", hash.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 Height %d bestHeight %d\n", winner.vin.ToString().c_str(), winner.nBlockHeight, chainActive.Tip()->nHeight);
|
|
return;
|
|
}
|
|
|
|
if(winner.vin.nSequence != std::numeric_limits<unsigned int>::max()){
|
|
LogPrintf("mnw - invalid nSequence\n");
|
|
Misbehaving(pfrom->GetId(), 100);
|
|
return;
|
|
}
|
|
|
|
LogPrintf("mnw - winning vote %s Height %d bestHeight %d\n", winner.vin.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<int64_t, CTxIn>& t1,
|
|
const pair<int64_t, CTxIn>& 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<unsigned char>();
|
|
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;
|
|
nScanningErrorCount = 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;
|
|
nScanningErrorCount = other.nScanningErrorCount;
|
|
}
|
|
|
|
CMasternode::CMasternode(CService newAddr, CTxIn newVin, CPubKey newPubkey, std::vector<unsigned char> 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;
|
|
nScanningErrorCount = 0;
|
|
donationAddress = newDonationAddress;
|
|
donationPercentage = newDonationPercentage;
|
|
}
|
|
|
|
//
|
|
// 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(aux));
|
|
|
|
uint256 r = (hash3 > hash2 ? hash3 - hash2 : hash2 - hash3);
|
|
|
|
return r;
|
|
}
|
|
|
|
void CMasternode::Check()
|
|
{
|
|
LOCK(cs_main);
|
|
|
|
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;
|
|
CTransaction tx = CTransaction();
|
|
CTxOut vout = CTxOut(999.99*COIN, darkSendPool.collateralPubKey);
|
|
tx.vin.push_back(vin);
|
|
tx.vout.push_back(vout);
|
|
|
|
if(!AcceptableInputs(mempool, state, tx)){
|
|
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<std::string>(winner.nBlockHeight) + winner.payee.ToString();
|
|
std::string strPubKey = (Params().NetworkID() == CChainParams::MAIN) ? strMainPubKey : strTestPubKey;
|
|
CPubKey pubkey(ParseHex(strPubKey));
|
|
|
|
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<std::string>(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;
|
|
|
|
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()
|
|
{
|
|
if(chainActive.Tip() == NULL) return;
|
|
|
|
int nLimit = std::max(((int)mnodeman.size())*2, 1000);
|
|
|
|
vector<CMasternodePaymentWinner>::iterator it;
|
|
for(it=vWinning.begin();it<vWinning.end();it++){
|
|
if(chainActive.Tip()->nHeight - (*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)
|
|
{
|
|
if(!enabled) return false;
|
|
CMasternodePaymentWinner newWinner;
|
|
int nMinimumAge = mnodeman.CountEnabled();
|
|
|
|
uint256 hash;
|
|
if(!GetBlockHash(hash, nBlockHeight-10)) return false;
|
|
int nHash;
|
|
memcpy(&nHash, &hash, 2);
|
|
|
|
std::vector<CTxIn> 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);
|
|
if(pmn != NULL &&
|
|
(RegTest() || (!RegTest() && pmn->GetMasternodeInputAge() > nMinimumAge && pmn->lastTimeSeen - pmn->sigTime > nMinimumAge * 2.5 * 60)))
|
|
{
|
|
newWinner.score = 0;
|
|
newWinner.nBlockHeight = nBlockHeight;
|
|
newWinner.vin = pmn->vin;
|
|
|
|
if(pmn->donationPercentage > 0 && (nHash % 100) < pmn->donationPercentage) {
|
|
newWinner.payee = pmn->donationAddress;
|
|
} else {
|
|
newWinner.payee.SetDestination(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) < pmn->donationPercentage) {
|
|
newWinner.payee = pmn->donationAddress;
|
|
} else {
|
|
newWinner.payee.SetDestination(pmn->pubkey.GetID());
|
|
}
|
|
|
|
break; // we found active MN
|
|
}
|
|
}
|
|
}
|
|
|
|
if(newWinner.nBlockHeight == 0) return false;
|
|
|
|
if(Sign(newWinner))
|
|
{
|
|
if(AddWinningMasternode(newWinner))
|
|
{
|
|
Relay(newWinner);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CMasternodePayments::Relay(CMasternodePaymentWinner& winner)
|
|
{
|
|
CInv inv(MSG_MASTERNODE_WINNER, winner.GetHash());
|
|
|
|
vector<CInv> vInv;
|
|
vInv.push_back(inv);
|
|
LOCK(cs_vNodes);
|
|
BOOST_FOREACH(CNode* pnode, vNodes){
|
|
pnode->PushMessage("inv", vInv);
|
|
}
|
|
}
|
|
|
|
void CMasternodePayments::Sync(CNode* node)
|
|
{
|
|
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;
|
|
}
|
|
}
|