neobytes/src/masternode-sync.cpp
Oleg Girko b9c67258ba Backport Bitcoin PR#9609: net: fix remaining net assertions (#1575) + Dashify
* Dont deserialize nVersion into CNode, should fix #9212

* net: deserialize the entire version message locally

This avoids having some vars set if the version negotiation fails.

Also copy it all into CNode at the same site. nVersion and
fSuccessfullyConnected are set last, as they are the gates for the other vars.
Make them atomic for that reason.

* net: don't run callbacks on nodes that haven't completed the version handshake

Since ForEach* are can be used to send messages to  all nodes, the caller may
end up sending a message before the version handshake is complete. To limit
this, filter out these nodes. While we're at it, may as well filter out
disconnected nodes as well.

Delete unused methods rather than updating them.

* net: Disallow sending messages until the version handshake is complete

This is a change in behavior, though it's much more sane now than before.

* net: log an error rather than asserting if send version is misused

Also cleaned up the comments and moved from the header to the .cpp so that
logging headers aren't needed from net.h

* Implement conditions for ForEachNode() and ForNode() methods of CConnman.

A change making ForEachNode() and ForNode() methods ignore nodes that
have not completed initial handshake have been backported from Bitcoin.
Unfortunately, some Dash-specific code needs to iterate over all nodes.

This change introduces additional condition argument to these methods.
This argument is a functional object that should return true for nodes
that should be taken into account, not ignored.

Two functional objects are provided in CConnman namespace:
* FullyConnectedOnly returns true for nodes that have handshake completed,
* AllNodes returns true for all nodes.

Overloads for ForEachNode() and ForNode() methods without condition argument
are left for compatibility with non-Dash-specific code.
They use FullyConnectedOnly functional object for condition.

Signed-off-by: Oleg Girko <ol@infoserver.lv>

* Iterate over all nodes in Dash-specific code using AllNodes condition.

Use AllNodes functional object as newly introduced condition argument for
ForEachNode() and ForNode() methods of CConnman to iterate over all nodes
where needed in Dash-specific code.

Signed-off-by: Oleg Girko <ol@infoserver.lv>
2017-08-17 21:37:22 +03:00

403 lines
20 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 "spork.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 = 0;
nTimeLastFailure = 0;
}
void CMasternodeSync::BumpAssetLastTime(std::string strFuncName)
{
if(IsSynced() || IsFailed()) return;
nTimeLastBumped = GetTime();
if(fDebug) LogPrintf("CMasternodeSync::BumpAssetLastTime -- %s\n", strFuncName);
}
std::string CMasternodeSync::GetAssetName()
{
switch(nRequestedMasternodeAssets)
{
case(MASTERNODE_SYNC_INITIAL): return "MASTERNODE_SYNC_INITIAL";
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()
{
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();
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();
// TODO: Find out whether we can just use LOCK instead of:
// TRY_LOCK(cs_vNodes, lockRecv);
// if(lockRecv) { ... }
g_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 _("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()
{
// TODO: Find out whether we can just use LOCK instead of:
// TRY_LOCK(cs_vNodes, lockRecv);
// if(!lockRecv) return;
g_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()
{
static int nTick = 0;
if(nTick++ % MASTERNODE_SYNC_TICK_SECONDS != 0) return;
if(!pCurrentBlockIndex) 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();
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();
}
return;
}
// gradually request the rest of the votes after sync finished
if(IsSynced()) {
std::vector<CNode*> vNodesCopy = g_connman->CopyNodeVector();
governance.RequestGovernanceObjectVotes(vNodesCopy);
g_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 = g_connman->CopyNodeVector();
BOOST_FOREACH(CNode* pnode, vNodesCopy)
{
// 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) {
g_connman->PushMessageWithVersion(pnode, INIT_PROTO_VERSION, NetMsgType::GETSPORKS); //get current network sporks
} else if(nRequestedMasternodeAttempt < 4) {
mnodeman.DsegUpdate(pnode);
} else if(nRequestedMasternodeAttempt < 6) {
int nMnCount = mnodeman.CountMasternodes();
g_connman->PushMessage(pnode, NetMsgType::MASTERNODEPAYMENTSYNC, nMnCount); //sync payment votes
SendGovernanceSyncRequest(pnode);
} else {
nRequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;
}
nRequestedMasternodeAttempt++;
g_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
g_connman->PushMessageWithVersion(pnode, INIT_PROTO_VERSION, NetMsgType::GETSPORKS);
LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- requesting sporks from peer %d\n", nTick, nRequestedMasternodeAssets, pnode->id);
}
// 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();
g_connman->ReleaseNodeVector(vNodesCopy);
return;
}
SwitchToNextAsset();
g_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);
g_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();
g_connman->ReleaseNodeVector(vNodesCopy);
return;
}
SwitchToNextAsset();
g_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();
g_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)
g_connman->PushMessage(pnode, NetMsgType::MASTERNODEPAYMENTSYNC, mnpayments.GetStorageLimit());
// ask node for missing pieces only (old nodes will not be asked)
mnpayments.RequestLowDataPaymentBlocks(pnode);
g_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();
g_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);
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();
g_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);
g_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
g_connman->ReleaseNodeVector(vNodesCopy);
}
void CMasternodeSync::SendGovernanceSyncRequest(CNode* pnode)
{
if(pnode->nVersion >= GOVERNANCE_FILTER_PROTO_VERSION) {
CBloomFilter filter;
filter.clear();
g_connman->PushMessage(pnode, NetMsgType::MNGOVERNANCESYNC, uint256(), filter);
}
else {
g_connman->PushMessage(pnode, NetMsgType::MNGOVERNANCESYNC, uint256());
}
}
void CMasternodeSync::UpdatedBlockTip(const CBlockIndex *pindexNew, bool fInitialDownload)
{
pCurrentBlockIndex = pindexNew;
if(fDebug) LogPrintf("CMasternodeSync::UpdatedBlockTip -- pCurrentBlockIndex->nHeight: %d fInitialDownload=%d\n", pCurrentBlockIndex->nHeight, fInitialDownload);
// nothing to do here if we failed to sync previousely,
// just wait till status reset after a cooldown (see ProcessTick)
if(IsFailed()) return;
// switch from MASTERNODE_SYNC_INITIAL to the next "asset"
// the first time we are out of IBD mode (and only the first time)
if(!fInitialDownload && !IsBlockchainSynced()) SwitchToNextAsset();
// postpone timeout each time new block arrives while we are syncing
if(!IsSynced()) BumpAssetLastTime("CMasternodeSync::UpdatedBlockTip");
}