mirror of
https://github.com/dashpay/dash.git
synced 2024-12-28 05:23:01 +01:00
383 lines
16 KiB
C++
383 lines
16 KiB
C++
// Copyright (c) 2014-2019 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 "governance.h"
|
|
#include "init.h"
|
|
#include "validation.h"
|
|
#include "masternode-payments.h"
|
|
#include "masternode-sync.h"
|
|
#include "netfulfilledman.h"
|
|
#include "netmessagemaker.h"
|
|
#include "ui_interface.h"
|
|
#include "evo/deterministicmns.h"
|
|
|
|
class CMasternodeSync;
|
|
CMasternodeSync masternodeSync;
|
|
|
|
void CMasternodeSync::Fail()
|
|
{
|
|
nTimeLastFailure = GetTime();
|
|
nCurrentAsset = MASTERNODE_SYNC_FAILED;
|
|
}
|
|
|
|
void CMasternodeSync::Reset()
|
|
{
|
|
nCurrentAsset = MASTERNODE_SYNC_INITIAL;
|
|
nTriedPeerCount = 0;
|
|
nTimeAssetSyncStarted = GetTime();
|
|
nTimeLastBumped = GetTime();
|
|
nTimeLastFailure = 0;
|
|
}
|
|
|
|
void CMasternodeSync::BumpAssetLastTime(const std::string& strFuncName)
|
|
{
|
|
if(IsSynced() || IsFailed()) return;
|
|
nTimeLastBumped = GetTime();
|
|
LogPrint("mnsync", "CMasternodeSync::BumpAssetLastTime -- %s\n", strFuncName);
|
|
}
|
|
|
|
std::string CMasternodeSync::GetAssetName()
|
|
{
|
|
switch(nCurrentAsset)
|
|
{
|
|
case(MASTERNODE_SYNC_INITIAL): return "MASTERNODE_SYNC_INITIAL";
|
|
case(MASTERNODE_SYNC_WAITING): return "MASTERNODE_SYNC_WAITING";
|
|
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(nCurrentAsset)
|
|
{
|
|
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):
|
|
nCurrentAsset = MASTERNODE_SYNC_WAITING;
|
|
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
|
|
break;
|
|
case(MASTERNODE_SYNC_WAITING):
|
|
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
|
|
nCurrentAsset = 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);
|
|
nCurrentAsset = MASTERNODE_SYNC_FINISHED;
|
|
uiInterface.NotifyAdditionalDataSyncProgressChanged(1);
|
|
|
|
connman.ForEachNode(CConnman::AllNodes, [](CNode* pnode) {
|
|
netfulfilledman.AddFulfilledRequest(pnode->addr, "full-sync");
|
|
});
|
|
LogPrintf("CMasternodeSync::SwitchToNextAsset -- Sync has finished\n");
|
|
|
|
break;
|
|
}
|
|
nTriedPeerCount = 0;
|
|
nTimeAssetSyncStarted = GetTime();
|
|
BumpAssetLastTime("CMasternodeSync::SwitchToNextAsset");
|
|
}
|
|
|
|
std::string CMasternodeSync::GetSyncStatus()
|
|
{
|
|
switch (masternodeSync.nCurrentAsset) {
|
|
case MASTERNODE_SYNC_INITIAL: return _("Synchronizing blockchain...");
|
|
case MASTERNODE_SYNC_WAITING: return _("Synchronization pending...");
|
|
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, const 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::ProcessTick(CConnman& connman)
|
|
{
|
|
static int nTick = 0;
|
|
nTick++;
|
|
|
|
// 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::ProcessTick -- WARNING: no actions for too long, restarting sync...\n");
|
|
Reset();
|
|
SwitchToNextAsset(connman);
|
|
nTimeLastProcess = GetTime();
|
|
return;
|
|
}
|
|
|
|
if(GetTime() - nTimeLastProcess < MASTERNODE_SYNC_TICK_SECONDS) {
|
|
// too early, nothing to do here
|
|
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::ProcessTick -- 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(CConnman::FullyConnectedOnly);
|
|
governance.RequestGovernanceObjectVotes(vNodesCopy, connman);
|
|
connman.ReleaseNodeVector(vNodesCopy);
|
|
return;
|
|
}
|
|
|
|
// Calculate "progress" for LOG reporting / GUI notification
|
|
double nSyncProgress = double(nTriedPeerCount + (nCurrentAsset - 1) * 8) / (8*4);
|
|
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d nTriedPeerCount %d nSyncProgress %f\n", nTick, nCurrentAsset, nTriedPeerCount, nSyncProgress);
|
|
uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress);
|
|
|
|
std::vector<CNode*> vNodesCopy = connman.CopyNodeVector(CConnman::FullyConnectedOnly);
|
|
|
|
for (auto& pnode : vNodesCopy)
|
|
{
|
|
CNetMsgMaker msgMaker(pnode->GetSendVersion());
|
|
|
|
// 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 || (fMasternodeMode && pnode->fInbound)) continue;
|
|
|
|
// QUICK MODE (REGTEST ONLY!)
|
|
if(Params().NetworkIDString() == CBaseChainParams::REGTEST)
|
|
{
|
|
if (nCurrentAsset == MASTERNODE_SYNC_WAITING) {
|
|
connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETSPORKS)); //get current network sporks
|
|
SwitchToNextAsset(connman);
|
|
} else if (nCurrentAsset == MASTERNODE_SYNC_GOVERNANCE) {
|
|
SendGovernanceSyncRequest(pnode, connman);
|
|
SwitchToNextAsset(connman);
|
|
}
|
|
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, msgMaker.Make(NetMsgType::GETSPORKS));
|
|
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d -- requesting sporks from peer=%d\n", nTick, nCurrentAsset, pnode->id);
|
|
}
|
|
|
|
// INITIAL TIMEOUT
|
|
|
|
if(nCurrentAsset == 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);
|
|
}
|
|
}
|
|
|
|
// GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS
|
|
|
|
if(nCurrentAsset == MASTERNODE_SYNC_GOVERNANCE) {
|
|
LogPrint("gobject", "CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nCurrentAsset, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped);
|
|
|
|
// check for timeout first
|
|
if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
|
|
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d -- timeout\n", nTick, nCurrentAsset);
|
|
if(nTriedPeerCount == 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 nCurrentAsset %d -- asked for all objects, nothing to do\n", nTick, nCurrentAsset);
|
|
// 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;
|
|
nTriedPeerCount++;
|
|
|
|
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) {
|
|
if (fLiteMode) {
|
|
// nothing to do in lite mode, just finish the process immediately
|
|
nCurrentAsset = MASTERNODE_SYNC_FINISHED;
|
|
return;
|
|
}
|
|
// Reached best header while being in initial mode.
|
|
// We must be at the tip already, let's move to the next asset.
|
|
SwitchToNextAsset(connman);
|
|
}
|
|
}
|
|
|
|
void CMasternodeSync::DoMaintenance(CConnman &connman)
|
|
{
|
|
if (ShutdownRequested()) return;
|
|
|
|
ProcessTick(connman);
|
|
}
|