Refactored masternode payments system

- Replaced coinbase cache in favor of using the masternode payments voting system only
- Syncing masternode payments now supports up to the syncing the entire payment list
This commit is contained in:
Evan Duffield 2015-07-20 15:09:42 -07:00
parent d193cc2d91
commit 37f55a3181
13 changed files with 57 additions and 416 deletions

View File

@ -91,7 +91,6 @@ BITCOIN_CORE_H = \
primitives/block.h \
primitives/transaction.h \
core_io.h \
coinbase-payee.h \
crypter.h \
darksend.h \
darksend-relay.h \
@ -178,7 +177,6 @@ libbitcoin_server_a_SOURCES = \
addrman.cpp \
alert.cpp \
bloom.cpp \
coinbase-payee.cpp \
chain.cpp \
checkpoints.cpp \
init.cpp \
@ -279,7 +277,6 @@ libbitcoin_common_a_SOURCES = \
allocators.cpp \
amount.cpp \
base58.cpp \
coinbase-payee.cpp \
chainparams.cpp \
darksend.cpp \
darksend-relay.cpp \

View File

@ -4,7 +4,6 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "chain.h"
#include "coinbase-payee.h"
using namespace std;

View File

@ -1,273 +0,0 @@
// 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 "coinbase-payee.h"
#include "util.h"
#include "addrman.h"
#include "masternode.h"
#include "darksend.h"
#include "masternodeman.h"
#include <boost/filesystem.hpp>
CCoinbasePayee coinbasePayee;
//
// CCoinbasePayeeDB
//
CCoinbasePayeeDB::CCoinbasePayeeDB()
{
pathDB = GetDataDir() / "coinbase-payee.dat";
strMagicMessage = "CoinbasePayeeDB";
}
bool CCoinbasePayeeDB::Write(const CCoinbasePayee& objToSave)
{
int64_t nStart = GetTimeMillis();
// serialize, checksum data up to that point, then append checksum
CDataStream ssObj(SER_DISK, CLIENT_VERSION);
ssObj << strMagicMessage; // coinbase payee cache file specific magic message
ssObj << FLATDATA(Params().MessageStart()); // network specific magic number
ssObj << objToSave;
uint256 hash = Hash(ssObj.begin(), ssObj.end());
ssObj << hash;
// open output file, and associate with CAutoFile
FILE *file = fopen(pathDB.string().c_str(), "wb");
CAutoFile fileout(file, SER_DISK, CLIENT_VERSION);
if (fileout.IsNull())
return error("%s : Failed to open file %s", __func__, pathDB.string());
// Write and commit header, data
try {
fileout << ssObj;
}
catch (std::exception &e) {
return error("%s : Serialize or I/O error - %s", __func__, e.what());
}
fileout.fclose();
LogPrintf("Written info to coinbase-payee.dat %dms\n", GetTimeMillis() - nStart);
return true;
}
CCoinbasePayeeDB::ReadResult CCoinbasePayeeDB::Read(CCoinbasePayee& objToLoad)
{
int64_t nStart = GetTimeMillis();
// open input file, and associate with CAutoFile
FILE *file = fopen(pathDB.string().c_str(), "rb");
CAutoFile filein(file, SER_DISK, CLIENT_VERSION);
if (filein.IsNull())
{
error("%s : Failed to open file %s", __func__, pathDB.string());
return FileError;
}
// use file size to size memory buffer
int fileSize = boost::filesystem::file_size(pathDB);
int dataSize = fileSize - sizeof(uint256);
// Don't try to resize to a negative number if file is small
if (dataSize < 0)
dataSize = 0;
vector<unsigned char> vchData;
vchData.resize(dataSize);
uint256 hashIn;
// read data and checksum from file
try {
filein.read((char *)&vchData[0], dataSize);
filein >> hashIn;
}
catch (std::exception &e) {
error("%s : Deserialize or I/O error - %s", __func__, e.what());
return HashReadError;
}
filein.fclose();
CDataStream ssObj(vchData, SER_DISK, CLIENT_VERSION);
// verify stored checksum matches input data
uint256 hashTmp = Hash(ssObj.begin(), ssObj.end());
if (hashIn != hashTmp)
{
error("%s : Checksum mismatch, data corrupted", __func__);
return IncorrectHash;
}
unsigned char pchMsgTmp[4];
std::string strMagicMessageTmp;
try {
// de-serialize file header (coinbase payee cache file specific magic message) and ..
ssObj >> strMagicMessageTmp;
// ... verify the message matches predefined one
if (strMagicMessage != strMagicMessageTmp)
{
error("%s : Invalid coinbase payee cache magic message", __func__);
return IncorrectMagicMessage;
}
// de-serialize file header (network specific magic number) and ..
ssObj >> FLATDATA(pchMsgTmp);
// ... verify the network matches ours
if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp)))
{
error("%s : Invalid network magic number", __func__);
return IncorrectMagicNumber;
}
// de-serialize data into CCoinbasePayee object
ssObj >> objToLoad;
}
catch (std::exception &e) {
objToLoad.Clear();
error("%s : Deserialize or I/O error - %s", __func__, e.what());
return IncorrectFormat;
}
objToLoad.CleanUp(); // clean out expired
LogPrintf("Loaded info from coinbase-payee.dat %dms\n", GetTimeMillis() - nStart);
LogPrintf(" %s\n", objToLoad.ToString());
return Ok;
}
void DumpCoinbasePayees()
{
return; //disable the cache
int64_t nStart = GetTimeMillis();
CCoinbasePayeeDB mndb;
CCoinbasePayee temp;
LogPrintf("Verifying coinbase-payee.dat format...\n");
CCoinbasePayeeDB::ReadResult readResult = mndb.Read(temp);
// there was an error and it was not an error on file openning => do not proceed
if (readResult == CCoinbasePayeeDB::FileError)
LogPrintf("Missing payees cache file - coinbase-payee.dat, will try to recreate\n");
else if (readResult != CCoinbasePayeeDB::Ok)
{
LogPrintf("Error reading coinbase-payee.dat: ");
if(readResult == CCoinbasePayeeDB::IncorrectFormat)
LogPrintf("magic is ok but data has invalid format, will try to recreate\n");
else
{
LogPrintf("file format is unknown or invalid, please fix it manually\n");
return;
}
}
LogPrintf("Writting info to coinbase-payee.dat...\n");
mndb.Write(coinbasePayee);
LogPrintf("Coinbase payee dump finished %dms\n", GetTimeMillis() - nStart);
}
void CCoinbasePayee::BuildIndex(bool bForced)
{
if(mapPaidTime.size() > 0 && !bForced) {
LogPrintf("CCoinbasePayee::BuildIndex - coinbase cache exists, skipping BuildIndex\n");
return;
} else if(bForced) {
if(fDebug) LogPrintf("CCoinbasePayee::BuildIndex - Rebuilding coinbase cache\n");
mapPaidTime.clear();
}
//scan last 30 days worth of blocks, run processBlockCoinbaseTX for each
CBlockIndex* pindexPrev = chainActive.Tip();
int count = 0;
for (unsigned int i = 1; pindexPrev && pindexPrev->nHeight > 0; i++) {
count++;
if(count > 18000) return;
CBlock block;
if (ReadBlockFromDisk(block, pindexPrev)) {
ProcessBlockCoinbaseTX(block.vtx[0], block.nTime);
}
if (pindexPrev->pprev == NULL) { assert(pindexPrev); break; }
pindexPrev = pindexPrev->pprev;
}
return;
}
// Reprocess the last 120 blocks and overwrite the lastpaid times (incase we switch chains)
// TODO : Keep track of the block hashes and reprocess until we find one we've processed
void CCoinbasePayee::ReprocessChain()
{
CBlockIndex* pindexPrev = chainActive.Tip();
int count = 0;
for (unsigned int i = 1; pindexPrev && pindexPrev->nHeight > 0; i++) {
count++;
if(count > 120) return;
CBlock block;
if (ReadBlockFromDisk(block, pindexPrev)) {
ProcessBlockCoinbaseTX(block.vtx[0], block.nTime, true);
}
if (pindexPrev->pprev == NULL) { assert(pindexPrev); break; }
pindexPrev = pindexPrev->pprev;
}
return;
}
void CCoinbasePayee::ProcessBlockCoinbaseTX(CTransaction& txCoinbase, int64_t nTime, bool fOverwrite)
{
if (!txCoinbase.IsCoinBase()){
LogPrintf("ERROR: CCoinbasePayee::ProcessBlockCoinbaseTX - tx is not coinbase\n");
return;
}
BOOST_FOREACH(CTxOut out, txCoinbase.vout){
uint256 h = GetScriptHash(out.scriptPubKey);
if(fDebug) LogPrintf("CCoinbasePayee::ProcessBlockCoinbaseTX - %s - %d\n", h.ToString(), nTime);
if(mapPaidTime.count(h)){
if(mapPaidTime[h] < nTime || fOverwrite) {
mapPaidTime[h] = nTime;
} else {
if(fDebug) LogPrintf("CCoinbasePayee::ProcessBlockCoinbaseTX - not updated -- %s - %d\n", h.ToString(), nTime);
}
} else {
mapPaidTime[h] = nTime;
}
}
}
int64_t CCoinbasePayee::GetLastPaid(CScript& pubkey)
{
uint256 h = GetScriptHash(pubkey);
if(mapPaidTime.count(h)){
return mapPaidTime[h];
}
return 0;
}
void CCoinbasePayee::CleanUp()
{
// std::map<uint256, int64_t>::iterator it = mapPaidTime.begin();
// while(it != mapPaidTime.end())
// {
// //keep 30 days of history
// if((*it).second < GetAdjustedTime() - (60*60*24*30)) {
// mapPaidTime.erase(it++);
// } else {
// ++it;
// }
// }
}

View File

@ -1,97 +0,0 @@
// 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.
#ifndef MASTERNODE_LASTPAID_H
#define MASTERNODE_LASTPAID_H
#include "main.h"
#include "sync.h"
#include "net.h"
#include "key.h"
#include "util.h"
#include "base58.h"
#include <boost/lexical_cast.hpp>
using namespace std;
class CCoinbasePayee;
extern CCoinbasePayee coinbasePayee;
void DumpCoinbasePayees();
/** Save Budget Manager (coinbase-payee.dat)
*/
class CCoinbasePayeeDB
{
private:
boost::filesystem::path pathDB;
std::string strMagicMessage;
public:
enum ReadResult {
Ok,
FileError,
HashReadError,
IncorrectHash,
IncorrectMagicMessage,
IncorrectMagicNumber,
IncorrectFormat
};
CCoinbasePayeeDB();
bool Write(const CCoinbasePayee &objToSave);
ReadResult Read(CCoinbasePayee& objToLoad);
};
//
// Coinbase Payee : Keep track of the last time addresses were paid up to a few weeks (used for masternode payments)
//
class CCoinbasePayee
{
private:
// critical section to protect the inner data structures
mutable CCriticalSection cs;
uint256 GetScriptHash(CScript& pubkey){
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
ss << pubkey;
uint256 h1 = ss.GetHash();
return h1;
}
public:
map<uint256, int64_t> mapPaidTime;
CCoinbasePayee() {
mapPaidTime.clear();
}
void BuildIndex(bool bForced=false);
void ReprocessChain();
void ProcessBlockCoinbaseTX(CTransaction& txCoinbase, int64_t nTime, bool fOverwrite=false);
int64_t GetLastPaid(CScript& pubkey);
void CleanUp();
void Clear(){
LogPrintf("CoinbasePayee object cleared\n");
mapPaidTime.clear();
}
std::string ToString() {
std::string strMessage = boost::lexical_cast<std::string>((int)mapPaidTime.size()) + " objects";
return strMessage;
}
ADD_SERIALIZE_METHODS;
//for saving to the serialized db
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(mapPaidTime);
}
};
#endif

View File

@ -13,7 +13,6 @@
#include "addrman.h"
#include "amount.h"
#include "checkpoints.h"
#include "coinbase-payee.h"
#include "compat/sanity.h"
#include "key.h"
#include "main.h"
@ -172,7 +171,6 @@ void PrepareShutdown()
StopNode();
DumpMasternodes();
DumpBudgets();
DumpCoinbasePayees();
UnregisterNodeSignals(GetNodeSignals());
if (fFeeEstimatesInitialized)
@ -1437,22 +1435,6 @@ bool AppInit2(boost::thread_group& threadGroup)
LogPrintf("file format is unknown or invalid, please fix it manually\n");
}
// Disable loading the coinbase cache
// CCoinbasePayeeDB payeedb;
// CCoinbasePayeeDB::ReadResult readResult3 = payeedb.Read(coinbasePayee);
// if (readResult3 == CCoinbasePayeeDB::FileError)
// LogPrintf("Missing payee cache - coinbase-payee.dat, will try to recreate\n");
// else if (readResult3 != CCoinbasePayeeDB::Ok)
// {
// LogPrintf("Error reading coinbase-payee.dat: ");
// if(readResult3 == CCoinbasePayeeDB::IncorrectFormat)
// LogPrintf("magic is ok but data has invalid format, will try to recreate\n");
// else
// LogPrintf("file format is unknown or invalid, please fix it manually\n");
// }
fMasterNode = GetBoolArg("-masternode", false);
if((fMasterNode || masternodeConfig.getCount() > -1) && fTxIndex == false) {
@ -1564,8 +1546,6 @@ bool AppInit2(boost::thread_group& threadGroup)
*/
darkSendPool.InitCollateralAddress();
coinbasePayee.BuildIndex(true);
coinbasePayee.ReprocessChain();
threadGroup.create_thread(boost::bind(&ThreadCheckDarkSendPool));

View File

@ -27,7 +27,7 @@ class CConsensusVote;
class CTransaction;
class CTransactionLock;
static const int MIN_INSTANTX_PROTO_VERSION = 70092;
static const int MIN_INSTANTX_PROTO_VERSION = 70093;
extern map<uint256, CTransaction> mapTxLockReq;
extern map<uint256, CTransaction> mapTxLockReqRejected;

View File

@ -11,7 +11,6 @@
#include "chainparams.h"
#include "checkpoints.h"
#include "checkqueue.h"
#include "coinbase-payee.h"
#include "init.h"
#include "instantx.h"
#include "darksend.h"
@ -3264,7 +3263,6 @@ bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDis
if (masternodeSync.RequestedMasternodeAssets > MASTERNODE_SYNC_LIST) {
CScript payee;
CTxIn vin;
coinbasePayee.ReprocessChain();
darkSendPool.NewBlock();
masternodePayments.ProcessBlock(GetHeight()+10);
if (masternodeSync.RequestedMasternodeAssets > MASTERNODE_SYNC_BUDGET)

View File

@ -176,6 +176,9 @@ void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st
if (strCommand == "mnget") { //Masternode Payments Request Sync
if(fLiteMode) return; //disable all Darksend/Masternode related functionality
int nCountNeeded;
vRecv >> nCountNeeded;
if(pfrom->HasFulfilledRequest("mnget")) {
LogPrintf("mnget - peer already asked me for the list\n");
Misbehaving(pfrom->GetId(), 20);
@ -183,7 +186,7 @@ void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st
}
pfrom->FulfilledRequest("mnget");
masternodePayments.Sync(pfrom);
masternodePayments.Sync(pfrom, nCountNeeded);
LogPrintf("mnget - Sent Masternode winners to %s\n", pfrom->addr.ToString().c_str());
}
else if (strCommand == "mnw") { //Masternode Payments Declare Winner
@ -200,8 +203,9 @@ void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st
return;
}
if(winner.nBlockHeight < chainActive.Tip()->nHeight - 10 || winner.nBlockHeight > chainActive.Tip()->nHeight+20){
LogPrintf("mnw - winner out of range - Height %d bestHeight %d\n", winner.nBlockHeight, chainActive.Tip()->nHeight);
int nFirstBlock = (masternodeSync.IsSynced() ? (chainActive.Tip()->nHeight - (mnodeman.CountEnabled()*1.1)) : chainActive.Tip()->nHeight - 10);
if(winner.nBlockHeight < nFirstBlock || winner.nBlockHeight > chainActive.Tip()->nHeight+20){
LogPrintf("mnw - winner out of range - FirstBlock %d Height %d bestHeight %d\n", nFirstBlock, winner.nBlockHeight, chainActive.Tip()->nHeight);
return;
}
@ -270,7 +274,7 @@ bool CMasternodePayments::IsScheduled(CMasternode& mn, int nNotBlockHeight)
mnpayee = GetScriptForDestination(mn.pubkey.GetID());
CScript payee;
for(int64_t h = pindexPrev->nHeight; h <= pindexPrev->nHeight+10; h++){
for(int64_t h = pindexPrev->nHeight-1; h <= pindexPrev->nHeight+11; h++){
if(h == nNotBlockHeight) continue;
if(mapMasternodeBlocks.count(h)){
if(mapMasternodeBlocks[h].GetPayee(payee)){
@ -548,7 +552,6 @@ bool CMasternodePaymentWinner::SignatureValid()
boost::lexical_cast<std::string>(nBlockHeight) +
payee.ToString();
std::string errorMessage = "";
if(!darkSendSigner.VerifyMessage(pmn->pubkey2, vchSig, strMessage, errorMessage)){
return error("CMasternodePaymentWinner::SignatureValid() - Got bad Masternode address signature %s \n", vinMasternode.ToString().c_str());
@ -560,16 +563,19 @@ bool CMasternodePaymentWinner::SignatureValid()
return false;
}
void CMasternodePayments::Sync(CNode* node)
void CMasternodePayments::Sync(CNode* node, int nCountNeeded)
{
LOCK(cs_masternodepayments);
if(chainActive.Tip() == NULL) return;
int nCount = (mnodeman.CountEnabled()*1.1);
if(nCountNeeded > nCount) nCountNeeded = nCount;
std::map<uint256, CMasternodePaymentWinner>::iterator it = mapMasternodePayeeVotes.begin();
while(it != mapMasternodePayeeVotes.end()) {
CMasternodePaymentWinner winner = (*it).second;
if(winner.nBlockHeight >= chainActive.Tip()->nHeight-10 && winner.nBlockHeight <= chainActive.Tip()->nHeight + 20)
if(winner.nBlockHeight >= chainActive.Tip()->nHeight-nCountNeeded && winner.nBlockHeight <= chainActive.Tip()->nHeight + 20)
node->PushMessage("mnw", winner);
++it;
}

View File

@ -97,6 +97,15 @@ public:
return (nVotes > -1);
}
bool HasPayeeWithVotes(CScript payee, int nVotesReq)
{
BOOST_FOREACH(CMasternodePayee& p, vecPayments){
if(p.nVotes > nVotesReq && p.scriptPubKey == payee) return true;
}
return false;
}
bool IsTransactionValid(const CTransaction& txNew);
std::string GetRequiredPaymentsString();
@ -191,7 +200,7 @@ public:
bool AddWinningMasternode(CMasternodePaymentWinner& winner);
bool ProcessBlock(int nBlockHeight);
void Sync(CNode* node);
void Sync(CNode* node, int nCountNeeded);
void CleanPaymentList();
int LastPayment(CMasternode& mn);

View File

@ -153,7 +153,8 @@ void CMasternodeSync::Process()
if((lastMasternodeWinner == 0 || lastMasternodeWinner > GetTime() - MASTERNODE_SYNC_TIMEOUT)
&& RequestedMasternodeAttempt <= 2){
pnode->PushMessage("mnget"); //sync payees
int nCountNeeded = (mnodeman.CountEnabled()*1.1);
pnode->PushMessage("mnget", nCountNeeded); //sync payees
RequestedMasternodeAttempt++;
}
return;

View File

@ -4,7 +4,6 @@
#include "masternode.h"
#include "masternodeman.h"
#include "coinbase-payee.h"
#include "darksend.h"
#include "util.h"
#include "sync.h"
@ -210,7 +209,7 @@ int64_t CMasternode::SecondsSincePayment() {
CScript pubkeyScript;
pubkeyScript = GetScriptForDestination(pubkey.GetID());
int64_t sec = (GetAdjustedTime() - coinbasePayee.GetLastPaid(pubkeyScript));
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
@ -224,10 +223,26 @@ int64_t CMasternode::SecondsSincePayment() {
}
int64_t CMasternode::GetLastPaid() {
CScript pubkeyScript;
pubkeyScript = GetScriptForDestination(pubkey.GetID());
CBlockIndex* pindexPrev = chainActive.Tip();
if(pindexPrev == NULL) return false;
return coinbasePayee.GetLastPaid(pubkeyScript);
CScript mnpayee;
mnpayee = GetScriptForDestination(pubkey.GetID());
for(int64_t h = pindexPrev->nHeight-mnodeman.CountEnabled()*0.95; h <= pindexPrev->nHeight+10; h++){
if(mapMasternodeBlocks.count(h)){
/*
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(mapMasternodeBlocks[h].HasPayeeWithVotes(mnpayee, 2)){
int64_t nTimeEstimate = pindexPrev->nTime - (pindexPrev->nHeight - h)*2.6;
return nTimeEstimate;
}
}
}
return 0;
}
CMasternodeBroadcast::CMasternodeBroadcast()

View File

@ -450,9 +450,15 @@ Value masternode(const Array& params, bool fHelp)
if (strCommand == "winners")
{
int nLast = 10;
if (params.size() >= 2){
nLast = params[1].get_int();
}
Object obj;
for(int nHeight = chainActive.Tip()->nHeight-10; nHeight < chainActive.Tip()->nHeight+20; nHeight++)
for(int nHeight = chainActive.Tip()->nHeight-nLast; nHeight < chainActive.Tip()->nHeight+20; nHeight++)
{
obj.push_back(Pair(strprintf("%d", nHeight), GetRequiredPaymentsString(nHeight)));
}

View File

@ -10,7 +10,7 @@
* network protocol versioning
*/
static const int PROTOCOL_VERSION = 70092;
static const int PROTOCOL_VERSION = 70093;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
@ -22,16 +22,16 @@ static const int GETHEADERS_VERSION = 70077;
static const int MIN_PEER_PROTO_VERSION = 70066;
//! minimum peer version accepted by DarksendPool
static const int MIN_POOL_PEER_PROTO_VERSION = 70092;
static const int MIN_POOL_PEER_PROTO_VERSION = 70093;
//! minimum peer version for masternode budgets
static const int MIN_BUDGET_PEER_PROTO_VERSION = 70092;
static const int MIN_BUDGET_PEER_PROTO_VERSION = 70093;
//! minimum peer version that can receive masternode payments
// V1 - Last protocol version before update
// V2 - Newest protocol version
static const int MIN_MASTERNODE_PAYMENT_PROTO_VERSION_1 = 70066;
static const int MIN_MASTERNODE_PAYMENT_PROTO_VERSION_2 = 70092;
static const int MIN_MASTERNODE_PAYMENT_PROTO_VERSION_2 = 70093;
//! nTime field added to CAddress, starting with this version;
//! if possible, avoid requesting addresses nodes older than this