dash/src/masternode-sync.cpp
Pieter Wuille 5c0b55a7c0 Merge #9128: net: Decouple CConnman and message serialization
c7be56d net: push only raw data into CConnman (Cory Fields)
2ec935d net: add CVectorWriter and CNetMsgMaker (Cory Fields)
b7695c2 net: No need to check individually for disconnection anymore (Cory Fields)
fedea8a net: don't send any messages before handshake or after requested disconnect (Cory Fields)
d74e352 net: Set feelers to disconnect at the end of the version message (Cory Fields)
2018-01-17 17:27:22 +01:00

490 lines
23 KiB
C++

// Copyright (c) 2014-2017 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "activemasternode.h"
#include "checkpoints.h"
#include "governance.h"
#include "validation.h"
#include "masternode.h"
#include "masternode-payments.h"
#include "masternode-sync.h"
#include "masternodeman.h"
#include "netfulfilledman.h"
#include "netmessagemaker.h"
#include "spork.h"
#include "ui_interface.h"
#include "util.h"
class CMasternodeSync;
CMasternodeSync masternodeSync;
void CMasternodeSync::Fail()
{
nTimeLastFailure = GetTime();
nRequestedMasternodeAssets = MASTERNODE_SYNC_FAILED;
}
void CMasternodeSync::Reset()
{
nRequestedMasternodeAssets = MASTERNODE_SYNC_INITIAL;
nRequestedMasternodeAttempt = 0;
nTimeAssetSyncStarted = GetTime();
nTimeLastBumped = GetTime();
nTimeLastFailure = 0;
}
void CMasternodeSync::BumpAssetLastTime(std::string strFuncName)
{
if(IsSynced() || IsFailed()) return;
nTimeLastBumped = GetTime();
LogPrint("mnsync", "CMasternodeSync::BumpAssetLastTime -- %s\n", strFuncName);
}
std::string CMasternodeSync::GetAssetName()
{
switch(nRequestedMasternodeAssets)
{
case(MASTERNODE_SYNC_INITIAL): return "MASTERNODE_SYNC_INITIAL";
case(MASTERNODE_SYNC_WAITING): return "MASTERNODE_SYNC_WAITING";
case(MASTERNODE_SYNC_LIST): return "MASTERNODE_SYNC_LIST";
case(MASTERNODE_SYNC_MNW): return "MASTERNODE_SYNC_MNW";
case(MASTERNODE_SYNC_GOVERNANCE): return "MASTERNODE_SYNC_GOVERNANCE";
case(MASTERNODE_SYNC_FAILED): return "MASTERNODE_SYNC_FAILED";
case MASTERNODE_SYNC_FINISHED: return "MASTERNODE_SYNC_FINISHED";
default: return "UNKNOWN";
}
}
void CMasternodeSync::SwitchToNextAsset(CConnman& connman)
{
switch(nRequestedMasternodeAssets)
{
case(MASTERNODE_SYNC_FAILED):
throw std::runtime_error("Can't switch to next asset from failed, should use Reset() first!");
break;
case(MASTERNODE_SYNC_INITIAL):
ClearFulfilledRequests(connman);
nRequestedMasternodeAssets = MASTERNODE_SYNC_WAITING;
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
break;
case(MASTERNODE_SYNC_WAITING):
ClearFulfilledRequests(connman);
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
nRequestedMasternodeAssets = MASTERNODE_SYNC_LIST;
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
break;
case(MASTERNODE_SYNC_LIST):
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
nRequestedMasternodeAssets = MASTERNODE_SYNC_MNW;
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
break;
case(MASTERNODE_SYNC_MNW):
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
nRequestedMasternodeAssets = MASTERNODE_SYNC_GOVERNANCE;
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
break;
case(MASTERNODE_SYNC_GOVERNANCE):
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
nRequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;
uiInterface.NotifyAdditionalDataSyncProgressChanged(1);
//try to activate our masternode if possible
activeMasternode.ManageState(connman);
// TODO: Find out whether we can just use LOCK instead of:
// TRY_LOCK(cs_vNodes, lockRecv);
// if(lockRecv) { ... }
connman.ForEachNode(CConnman::AllNodes, [](CNode* pnode) {
netfulfilledman.AddFulfilledRequest(pnode->addr, "full-sync");
});
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Sync has finished\n");
break;
}
nRequestedMasternodeAttempt = 0;
nTimeAssetSyncStarted = GetTime();
BumpAssetLastTime("CMasternodeSync::SwitchToNextAsset");
}
std::string CMasternodeSync::GetSyncStatus()
{
switch (masternodeSync.nRequestedMasternodeAssets) {
case MASTERNODE_SYNC_INITIAL: return _("Synchroning blockchain...");
case MASTERNODE_SYNC_WAITING: return _("Synchronization pending...");
case MASTERNODE_SYNC_LIST: return _("Synchronizing masternodes...");
case MASTERNODE_SYNC_MNW: return _("Synchronizing masternode payments...");
case MASTERNODE_SYNC_GOVERNANCE: return _("Synchronizing governance objects...");
case MASTERNODE_SYNC_FAILED: return _("Synchronization failed");
case MASTERNODE_SYNC_FINISHED: return _("Synchronization finished");
default: return "";
}
}
void CMasternodeSync::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
{
if (strCommand == NetMsgType::SYNCSTATUSCOUNT) { //Sync status count
//do not care about stats if sync process finished or failed
if(IsSynced() || IsFailed()) return;
int nItemID;
int nCount;
vRecv >> nItemID >> nCount;
LogPrintf("SYNCSTATUSCOUNT -- got inventory count: nItemID=%d nCount=%d peer=%d\n", nItemID, nCount, pfrom->id);
}
}
void CMasternodeSync::ClearFulfilledRequests(CConnman& connman)
{
// TODO: Find out whether we can just use LOCK instead of:
// TRY_LOCK(cs_vNodes, lockRecv);
// if(!lockRecv) return;
connman.ForEachNode(CConnman::AllNodes, [](CNode* pnode) {
netfulfilledman.RemoveFulfilledRequest(pnode->addr, "spork-sync");
netfulfilledman.RemoveFulfilledRequest(pnode->addr, "masternode-list-sync");
netfulfilledman.RemoveFulfilledRequest(pnode->addr, "masternode-payment-sync");
netfulfilledman.RemoveFulfilledRequest(pnode->addr, "governance-sync");
netfulfilledman.RemoveFulfilledRequest(pnode->addr, "full-sync");
});
}
void CMasternodeSync::ProcessTick(CConnman& connman)
{
static int nTick = 0;
if(nTick++ % MASTERNODE_SYNC_TICK_SECONDS != 0) return;
// reset the sync process if the last call to this function was more than 60 minutes ago (client was in sleep mode)
static int64_t nTimeLastProcess = GetTime();
if(GetTime() - nTimeLastProcess > 60*60) {
LogPrintf("CMasternodeSync::HasSyncFailures -- WARNING: no actions for too long, restarting sync...\n");
Reset();
SwitchToNextAsset(connman);
nTimeLastProcess = GetTime();
return;
}
nTimeLastProcess = GetTime();
// reset sync status in case of any other sync failure
if(IsFailed()) {
if(nTimeLastFailure + (1*60) < GetTime()) { // 1 minute cooldown after failed sync
LogPrintf("CMasternodeSync::HasSyncFailures -- WARNING: failed to sync, trying again...\n");
Reset();
SwitchToNextAsset(connman);
}
return;
}
// gradually request the rest of the votes after sync finished
if(IsSynced()) {
std::vector<CNode*> vNodesCopy = connman.CopyNodeVector();
governance.RequestGovernanceObjectVotes(vNodesCopy, connman);
connman.ReleaseNodeVector(vNodesCopy);
return;
}
// Calculate "progress" for LOG reporting / GUI notification
double nSyncProgress = double(nRequestedMasternodeAttempt + (nRequestedMasternodeAssets - 1) * 8) / (8*4);
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nRequestedMasternodeAttempt %d nSyncProgress %f\n", nTick, nRequestedMasternodeAssets, nRequestedMasternodeAttempt, nSyncProgress);
uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress);
std::vector<CNode*> vNodesCopy = connman.CopyNodeVector();
BOOST_FOREACH(CNode* pnode, vNodesCopy)
{
CNetMsgMaker msgMaker(pnode->GetSendVersion());
CNetMsgMaker msgMakerInitProto(INIT_PROTO_VERSION);
// Don't try to sync any data from outbound "masternode" connections -
// they are temporary and should be considered unreliable for a sync process.
// Inbound connection this early is most likely a "masternode" connection
// initiated from another node, so skip it too.
if(pnode->fMasternode || (fMasterNode && pnode->fInbound)) continue;
// QUICK MODE (REGTEST ONLY!)
if(Params().NetworkIDString() == CBaseChainParams::REGTEST)
{
if(nRequestedMasternodeAttempt <= 2) {
connman.PushMessage(pnode, msgMakerInitProto.Make(NetMsgType::GETSPORKS)); //get current network sporks
} else if(nRequestedMasternodeAttempt < 4) {
mnodeman.DsegUpdate(pnode, connman);
} else if(nRequestedMasternodeAttempt < 6) {
int nMnCount = mnodeman.CountMasternodes();
connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MASTERNODEPAYMENTSYNC, nMnCount)); //sync payment votes
SendGovernanceSyncRequest(pnode, connman);
} else {
nRequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;
}
nRequestedMasternodeAttempt++;
connman.ReleaseNodeVector(vNodesCopy);
return;
}
// NORMAL NETWORK MODE - TESTNET/MAINNET
{
if(netfulfilledman.HasFulfilledRequest(pnode->addr, "full-sync")) {
// We already fully synced from this node recently,
// disconnect to free this connection slot for another peer.
pnode->fDisconnect = true;
LogPrintf("CMasternodeSync::ProcessTick -- disconnecting from recently synced peer %d\n", pnode->id);
continue;
}
// SPORK : ALWAYS ASK FOR SPORKS AS WE SYNC
if(!netfulfilledman.HasFulfilledRequest(pnode->addr, "spork-sync")) {
// always get sporks first, only request once from each peer
netfulfilledman.AddFulfilledRequest(pnode->addr, "spork-sync");
// get current network sporks
connman.PushMessage(pnode, msgMakerInitProto.Make(NetMsgType::GETSPORKS));
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- requesting sporks from peer %d\n", nTick, nRequestedMasternodeAssets, pnode->id);
}
// INITIAL TIMEOUT
if(nRequestedMasternodeAssets == MASTERNODE_SYNC_WAITING) {
if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
// At this point we know that:
// a) there are peers (because we are looping on at least one of them);
// b) we waited for at least MASTERNODE_SYNC_TIMEOUT_SECONDS since we reached
// the headers tip the last time (i.e. since we switched from
// MASTERNODE_SYNC_INITIAL to MASTERNODE_SYNC_WAITING and bumped time);
// c) there were no blocks (UpdatedBlockTip, NotifyHeaderTip) or headers (AcceptedBlockHeader)
// for at least MASTERNODE_SYNC_TIMEOUT_SECONDS.
// We must be at the tip already, let's move to the next asset.
SwitchToNextAsset(connman);
}
}
// MNLIST : SYNC MASTERNODE LIST FROM OTHER CONNECTED CLIENTS
if(nRequestedMasternodeAssets == MASTERNODE_SYNC_LIST) {
LogPrint("masternode", "CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nRequestedMasternodeAssets, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped);
// check for timeout first
if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- timeout\n", nTick, nRequestedMasternodeAssets);
if (nRequestedMasternodeAttempt == 0) {
LogPrintf("CMasternodeSync::ProcessTick -- ERROR: failed to sync %s\n", GetAssetName());
// there is no way we can continue without masternode list, fail here and try later
Fail();
connman.ReleaseNodeVector(vNodesCopy);
return;
}
SwitchToNextAsset(connman);
connman.ReleaseNodeVector(vNodesCopy);
return;
}
// only request once from each peer
if(netfulfilledman.HasFulfilledRequest(pnode->addr, "masternode-list-sync")) continue;
netfulfilledman.AddFulfilledRequest(pnode->addr, "masternode-list-sync");
if (pnode->nVersion < mnpayments.GetMinMasternodePaymentsProto()) continue;
nRequestedMasternodeAttempt++;
mnodeman.DsegUpdate(pnode, connman);
connman.ReleaseNodeVector(vNodesCopy);
return; //this will cause each peer to get one request each six seconds for the various assets we need
}
// MNW : SYNC MASTERNODE PAYMENT VOTES FROM OTHER CONNECTED CLIENTS
if(nRequestedMasternodeAssets == MASTERNODE_SYNC_MNW) {
LogPrint("mnpayments", "CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nRequestedMasternodeAssets, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped);
// check for timeout first
// This might take a lot longer than MASTERNODE_SYNC_TIMEOUT_SECONDS due to new blocks,
// but that should be OK and it should timeout eventually.
if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- timeout\n", nTick, nRequestedMasternodeAssets);
if (nRequestedMasternodeAttempt == 0) {
LogPrintf("CMasternodeSync::ProcessTick -- ERROR: failed to sync %s\n", GetAssetName());
// probably not a good idea to proceed without winner list
Fail();
connman.ReleaseNodeVector(vNodesCopy);
return;
}
SwitchToNextAsset(connman);
connman.ReleaseNodeVector(vNodesCopy);
return;
}
// 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()) {
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- found enough data\n", nTick, nRequestedMasternodeAssets);
SwitchToNextAsset(connman);
connman.ReleaseNodeVector(vNodesCopy);
return;
}
// only request once from each peer
if(netfulfilledman.HasFulfilledRequest(pnode->addr, "masternode-payment-sync")) continue;
netfulfilledman.AddFulfilledRequest(pnode->addr, "masternode-payment-sync");
if(pnode->nVersion < mnpayments.GetMinMasternodePaymentsProto()) continue;
nRequestedMasternodeAttempt++;
// ask node for all payment votes it has (new nodes will only return votes for future payments)
connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MASTERNODEPAYMENTSYNC, mnpayments.GetStorageLimit()));
// ask node for missing pieces only (old nodes will not be asked)
mnpayments.RequestLowDataPaymentBlocks(pnode, connman);
connman.ReleaseNodeVector(vNodesCopy);
return; //this will cause each peer to get one request each six seconds for the various assets we need
}
// GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS
if(nRequestedMasternodeAssets == MASTERNODE_SYNC_GOVERNANCE) {
LogPrint("gobject", "CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nRequestedMasternodeAssets, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped);
// check for timeout first
if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- timeout\n", nTick, nRequestedMasternodeAssets);
if(nRequestedMasternodeAttempt == 0) {
LogPrintf("CMasternodeSync::ProcessTick -- WARNING: failed to sync %s\n", GetAssetName());
// it's kind of ok to skip this for now, hopefully we'll catch up later?
}
SwitchToNextAsset(connman);
connman.ReleaseNodeVector(vNodesCopy);
return;
}
// only request obj sync once from each peer, then request votes on per-obj basis
if(netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) {
int nObjsLeftToAsk = governance.RequestGovernanceObjectVotes(pnode, connman);
static int64_t nTimeNoObjectsLeft = 0;
// check for data
if(nObjsLeftToAsk == 0) {
static int nLastTick = 0;
static int nLastVotes = 0;
if(nTimeNoObjectsLeft == 0) {
// asked all objects for votes for the first time
nTimeNoObjectsLeft = GetTime();
}
// make sure the condition below is checked only once per tick
if(nLastTick == nTick) continue;
if(GetTime() - nTimeNoObjectsLeft > MASTERNODE_SYNC_TIMEOUT_SECONDS &&
governance.GetVoteCount() - nLastVotes < std::max(int(0.0001 * nLastVotes), MASTERNODE_SYNC_TICK_SECONDS)
) {
// We already asked for all objects, waited for MASTERNODE_SYNC_TIMEOUT_SECONDS
// after that and less then 0.01% or MASTERNODE_SYNC_TICK_SECONDS
// (i.e. 1 per second) votes were recieved during the last tick.
// We can be pretty sure that we are done syncing.
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- asked for all objects, nothing to do\n", nTick, nRequestedMasternodeAssets);
// reset nTimeNoObjectsLeft to be able to use the same condition on resync
nTimeNoObjectsLeft = 0;
SwitchToNextAsset(connman);
connman.ReleaseNodeVector(vNodesCopy);
return;
}
nLastTick = nTick;
nLastVotes = governance.GetVoteCount();
}
continue;
}
netfulfilledman.AddFulfilledRequest(pnode->addr, "governance-sync");
if (pnode->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) continue;
nRequestedMasternodeAttempt++;
SendGovernanceSyncRequest(pnode, connman);
connman.ReleaseNodeVector(vNodesCopy);
return; //this will cause each peer to get one request each six seconds for the various assets we need
}
}
}
// looped through all nodes, release them
connman.ReleaseNodeVector(vNodesCopy);
}
void CMasternodeSync::SendGovernanceSyncRequest(CNode* pnode, CConnman& connman)
{
CNetMsgMaker msgMaker(pnode->GetSendVersion());
if(pnode->nVersion >= GOVERNANCE_FILTER_PROTO_VERSION) {
CBloomFilter filter;
filter.clear();
connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, uint256(), filter));
}
else {
connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, uint256()));
}
}
void CMasternodeSync::AcceptedBlockHeader(const CBlockIndex *pindexNew)
{
LogPrint("mnsync", "CMasternodeSync::AcceptedBlockHeader -- pindexNew->nHeight: %d\n", pindexNew->nHeight);
if (!IsBlockchainSynced()) {
// Postpone timeout each time new block header arrives while we are still syncing blockchain
BumpAssetLastTime("CMasternodeSync::AcceptedBlockHeader");
}
}
void CMasternodeSync::NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload, CConnman& connman)
{
LogPrint("mnsync", "CMasternodeSync::NotifyHeaderTip -- pindexNew->nHeight: %d fInitialDownload=%d\n", pindexNew->nHeight, fInitialDownload);
if (IsFailed() || IsSynced() || !pindexBestHeader)
return;
if (!IsBlockchainSynced()) {
// Postpone timeout each time new block arrives while we are still syncing blockchain
BumpAssetLastTime("CMasternodeSync::NotifyHeaderTip");
}
}
void CMasternodeSync::UpdatedBlockTip(const CBlockIndex *pindexNew, bool fInitialDownload, CConnman& connman)
{
LogPrint("mnsync", "CMasternodeSync::UpdatedBlockTip -- pindexNew->nHeight: %d fInitialDownload=%d\n", pindexNew->nHeight, fInitialDownload);
if (IsFailed() || IsSynced() || !pindexBestHeader)
return;
if (!IsBlockchainSynced()) {
// Postpone timeout each time new block arrives while we are still syncing blockchain
BumpAssetLastTime("CMasternodeSync::UpdatedBlockTip");
}
if (fInitialDownload) {
// switched too early
if (IsBlockchainSynced()) {
Reset();
}
// no need to check any further while still in IBD mode
return;
}
// Note: since we sync headers first, it should be ok to use this
static bool fReachedBestHeader = false;
bool fReachedBestHeaderNew = pindexNew->GetBlockHash() == pindexBestHeader->GetBlockHash();
if (fReachedBestHeader && !fReachedBestHeaderNew) {
// Switching from true to false means that we previousely stuck syncing headers for some reason,
// probably initial timeout was not enough,
// because there is no way we can update tip not having best header
Reset();
fReachedBestHeader = false;
return;
}
fReachedBestHeader = fReachedBestHeaderNew;
LogPrint("mnsync", "CMasternodeSync::UpdatedBlockTip -- pindexNew->nHeight: %d pindexBestHeader->nHeight: %d fInitialDownload=%d fReachedBestHeader=%d\n",
pindexNew->nHeight, pindexBestHeader->nHeight, fInitialDownload, fReachedBestHeader);
if (!IsBlockchainSynced() && fReachedBestHeader) {
// Reached best header while being in initial mode.
// We must be at the tip already, let's move to the next asset.
SwitchToNextAsset(connman);
}
}