neobytes/src/masternode.cpp
Evan Duffield e5267319ef Complete implementation of Proof-of-Service
- Ensures ports remain open and client are responsive to IX requests.
- Completely 100% decentralized. This farms out the work of checking the masternode network to the masternode network. 1% of the network is determistically selected to check another 1% of the network each block. It takes six separate checks to deactivate a node, thus making it tamper proof.
- Nodes are kept in the masternode list if they fail enough PoSe checks to deactivate. They will continue to be checked until the operator fixes them. However they will not be paid while they're failing checks.
2015-03-16 09:32:18 -07:00

477 lines
14 KiB
C++

// Copyright (c) 2014-2015 The Darkcoin 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;
int a = 0;
vRecv >> winner >> a;
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;
}
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;
}
CMasternode::CMasternode(CService newAddr, CTxIn newVin, CPubKey newPubkey, std::vector<unsigned char> newSig, int64_t newSigTime, CPubKey newPubkey2, int protocolVersionIn)
{
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;
}
//
// 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 nEnabled = mnodeman.CountEnabled();
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 && pmn->GetMasternodeInputAge() > nEnabled && pmn->lastTimeSeen - pmn->sigTime > nEnabled * 2.5 * 60)
{
newWinner.score = 0;
newWinner.nBlockHeight = nBlockHeight;
newWinner.vin = pmn->vin;
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 && nEnabled > 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;
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)
{
int a = 0;
BOOST_FOREACH(CMasternodePaymentWinner& winner, vWinning)
if(winner.nBlockHeight >= chainActive.Tip()->nHeight-10 && winner.nBlockHeight <= chainActive.Tip()->nHeight + 20)
node->PushMessage("mnw", winner, a);
}
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;
}
}