Merge #20721: Net: Move ping data to net_processing

a5e15ae45c scripted-diff: rename ping members (John Newbery)
45dcf22661 [net processing] Move ping data fields to net processing (John Newbery)
dd2646d12c [net processing] Move ping timeout logic to net processing (John Newbery)
0b43b81f69 [net processing] Move send ping message logic into function (John Newbery)
1a07600b4b [net] Add RunInactivityChecks() (John Newbery)
f8b3058992 [net processing] Add Peer& arg to MaybeDiscourageAndDisconnect() (John Newbery)

Pull request description:

  This continues the work of moving application layer data into net_processing, by moving all ping data into the new Peer object added in #19607.

  For motivation, see #19398.

ACKs for top commit:
  glozow:
    reACK a5e15ae45c
  MarcoFalke:
    review ACK a5e15ae45c 🥉
  amitiuttarwar:
    ACK a5e15ae45c

Tree-SHA512: fb84241613d6a6e1f2832fa5378030b5877a02e8308188f57ab545a6eaf2ab731a93abb7dcd3a7f7285bb66700f938096378a8e90cd6a3e6f3309f81d85a344e
This commit is contained in:
fanquake 2021-02-16 17:52:40 +08:00
commit 9bbf08bf98
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
7 changed files with 136 additions and 105 deletions

View File

@ -601,21 +601,8 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap)
stats.minFeeFilter = 0;
}
// It is common for nodes with good ping times to suddenly become lagged,
// due to a new block arriving or other large transfer.
// Merely reporting pingtime might fool the caller into thinking the node was still responsive,
// since pingtime does not update until the ping is complete, which might take a while.
// So, if a ping is taking an unusually long time in flight,
// the caller can immediately detect that this is happening.
std::chrono::microseconds ping_wait{0};
if ((0 != nPingNonceSent) && (0 != m_ping_start.load().count())) {
ping_wait = GetTime<std::chrono::microseconds>() - m_ping_start.load();
}
// Raw ping time is in microseconds, but show it to user as whole seconds (Bitcoin users should be well used to small numbers with many decimal places by now :)
stats.m_ping_usec = nPingUsecTime;
stats.m_min_ping_usec = nMinPingUsecTime;
stats.m_ping_wait_usec = count_microseconds(ping_wait);
stats.m_ping_usec = m_last_ping_time;
stats.m_min_ping_usec = m_min_ping_time;
// Leave string empty if addrLocal invalid (not filled in yet)
CService addrLocalUnlocked = GetAddrLocal();
@ -837,7 +824,7 @@ size_t CConnman::SocketSendData(CNode& node) const
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
{
return a.nMinPingUsecTime > b.nMinPingUsecTime;
return a.m_min_ping_time > b.m_min_ping_time;
}
static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
@ -992,7 +979,7 @@ bool CConnman::AttemptToEvictConnection()
peer_relay_txes = node->m_tx_relay->fRelayTxes;
peer_filter_not_null = node->m_tx_relay->pfilter != nullptr;
}
NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime,
NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->m_min_ping_time,
node->nLastBlockTime, node->nLastTXTime,
HasAllDesirableServiceFlags(node->nServices),
peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup,
@ -1221,18 +1208,17 @@ void CConnman::NotifyNumConnectionsChanged()
}
}
bool CConnman::RunInactivityChecks(const CNode& node) const
{
return GetSystemTimeInSeconds() > node.nTimeConnected + m_peer_connect_timeout;
}
bool CConnman::InactivityCheck(const CNode& node) const
{
// Use non-mockable system time (otherwise these timers will pop when we
// use setmocktime in the tests).
int64_t now = GetSystemTimeInSeconds();
if (now <= node.nTimeConnected + m_peer_connect_timeout) {
// Only run inactivity checks if the peer has been connected longer
// than m_peer_connect_timeout.
return false;
}
if (node.nLastRecv == 0 || node.nLastSend == 0) {
LogPrint(BCLog::NET, "socket no message in first %i seconds, %d %d peer=%d\n", m_peer_connect_timeout, node.nLastRecv != 0, node.nLastSend != 0, node.GetId());
return true;
@ -1248,14 +1234,6 @@ bool CConnman::InactivityCheck(const CNode& node) const
return true;
}
if (node.nPingNonceSent && node.m_ping_start.load() + std::chrono::seconds{TIMEOUT_INTERVAL} < GetTime<std::chrono::microseconds>()) {
// We use mockable time for ping timeouts. This means that setmocktime
// may cause pings to time out for peers that have been connected for
// longer than m_peer_connect_timeout.
LogPrint(BCLog::NET, "ping timeout: %fs peer=%d\n", 0.000001 * count_microseconds(GetTime<std::chrono::microseconds>() - node.m_ping_start.load()), node.GetId());
return true;
}
if (!node.fSuccessfullyConnected) {
LogPrint(BCLog::NET, "version handshake timeout peer=%d\n", node.GetId());
return true;
@ -1537,7 +1515,7 @@ void CConnman::SocketHandler()
if (bytes_sent) RecordBytesSent(bytes_sent);
}
if (InactivityCheck(*pnode)) pnode->fDisconnect = true;
if (RunInactivityChecks(*pnode) && InactivityCheck(*pnode)) pnode->fDisconnect = true;
}
{
LOCK(cs_vNodes);

View File

@ -260,7 +260,6 @@ public:
mapMsgCmdSize mapRecvBytesPerMsgCmd;
NetPermissionFlags m_permissionFlags;
int64_t m_ping_usec;
int64_t m_ping_wait_usec;
int64_t m_min_ping_usec;
CAmount minFeeFilter;
// Our address, as reported by the peer
@ -591,17 +590,12 @@ public:
* in CConnman::AttemptToEvictConnection. */
std::atomic<int64_t> nLastTXTime{0};
// Ping time measurement:
// The pong reply we're expecting, or 0 if no pong expected.
std::atomic<uint64_t> nPingNonceSent{0};
/** When the last ping was sent, or 0 if no ping was ever sent */
std::atomic<std::chrono::microseconds> m_ping_start{0us};
// Last measured round-trip time.
std::atomic<int64_t> nPingUsecTime{0};
// Best measured round-trip time.
std::atomic<int64_t> nMinPingUsecTime{std::numeric_limits<int64_t>::max()};
// Whether a ping is requested.
std::atomic<bool> fPingQueued{false};
/** Last measured round-trip time. Used only for RPC/GUI stats/debugging.*/
std::atomic<int64_t> m_last_ping_time{0};
/** Lowest measured round-trip time. Used as an inbound peer eviction
* criterium in CConnman::AttemptToEvictConnection. */
std::atomic<int64_t> m_min_ping_time{std::numeric_limits<int64_t>::max()};
CNode(NodeId id, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, ConnectionType conn_type_in, bool inbound_onion);
~CNode();
@ -721,6 +715,12 @@ public:
std::string ConnectionTypeAsString() const { return ::ConnectionTypeAsString(m_conn_type); }
/** A ping-pong round trip has completed successfully. Update latest and minimum ping times. */
void PongReceived(std::chrono::microseconds ping_time) {
m_last_ping_time = count_microseconds(ping_time);
m_min_ping_time = std::min(m_min_ping_time.load(), count_microseconds(ping_time));
}
private:
const NodeId id;
const uint64_t nLocalHostNonce;
@ -1022,6 +1022,9 @@ public:
void SetAsmap(std::vector<bool> asmap) { addrman.m_asmap = std::move(asmap); }
/** Return true if the peer has been connected for long enough to do inactivity checks. */
bool RunInactivityChecks(const CNode& node) const;
private:
struct ListenSocket {
public:
@ -1250,7 +1253,7 @@ struct NodeEvictionCandidate
{
NodeId id;
int64_t nTimeConnected;
int64_t nMinPingUsecTime;
int64_t m_min_ping_time;
int64_t nLastBlockTime;
int64_t nLastTXTime;
bool fRelevantServices;

View File

@ -219,6 +219,13 @@ struct Peer {
/** This peer's reported block height when we connected */
std::atomic<int> m_starting_height{-1};
/** The pong reply we're expecting, or 0 if no pong expected. */
std::atomic<uint64_t> m_ping_nonce_sent{0};
/** When the last ping was sent, or 0 if no ping was ever sent */
std::atomic<std::chrono::microseconds> m_ping_start{0us};
/** Whether a ping has been requested by the user */
std::atomic<bool> m_ping_queued{false};
/** Set of txids to reconsider once their parent transactions have been accepted **/
std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans);
@ -256,6 +263,7 @@ public:
void CheckForStaleTipAndEvictPeers() override;
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) override;
bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; }
void SendPings() override;
void SetBestHeight(int height) override { m_best_height = height; };
void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override;
void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv,
@ -302,9 +310,10 @@ private:
/** Maybe disconnect a peer and discourage future connections from its address.
*
* @param[in] pnode The node to check.
* @param[in] peer The peer object to check.
* @return True if the peer was marked for disconnection in this function
*/
bool MaybeDiscourageAndDisconnect(CNode& pnode);
bool MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer);
void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans);
/** Process a single headers message from a peer. */
@ -323,6 +332,10 @@ private:
/** Send a version message to a peer */
void PushNodeVersion(CNode& pnode, int64_t nTime);
/** Send a ping message every PING_INTERVAL or if requested via RPC. May
* mark the peer to be disconnected if a ping has timed out. */
void MaybeSendPing(CNode& node_to, Peer& peer);
const CChainParams& m_chainparams;
CConnman& m_connman;
/** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */
@ -1088,6 +1101,18 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats)
PeerRef peer = GetPeerRef(nodeid);
if (peer == nullptr) return false;
stats.m_starting_height = peer->m_starting_height;
// It is common for nodes with good ping times to suddenly become lagged,
// due to a new block arriving or other large transfer.
// Merely reporting pingtime might fool the caller into thinking the node was still responsive,
// since pingtime does not update until the ping is complete, which might take a while.
// So, if a ping is taking an unusually long time in flight,
// the caller can immediately detect that this is happening.
std::chrono::microseconds ping_wait{0};
if ((0 != peer->m_ping_nonce_sent) && (0 != peer->m_ping_start.load().count())) {
ping_wait = GetTime<std::chrono::microseconds>() - peer->m_ping_start.load();
}
stats.m_ping_wait_usec = count_microseconds(ping_wait);
return true;
}
@ -1627,6 +1652,12 @@ bool static AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED
return g_chainman.m_blockman.LookupBlockIndex(block_hash) != nullptr;
}
void PeerManagerImpl::SendPings()
{
LOCK(m_peer_mutex);
for(auto& it : m_peer_map) it.second->m_ping_queued = true;
}
void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman)
{
connman.ForEachNode([&txid, &wtxid](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
@ -3837,15 +3868,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
vRecv >> nonce;
// Only process pong message if there is an outstanding ping (old ping without nonce should never pong)
if (pfrom.nPingNonceSent != 0) {
if (nonce == pfrom.nPingNonceSent) {
if (peer->m_ping_nonce_sent != 0) {
if (nonce == peer->m_ping_nonce_sent) {
// Matching pong received, this ping is no longer outstanding
bPingFinished = true;
const auto ping_time = ping_end - pfrom.m_ping_start.load();
const auto ping_time = ping_end - peer->m_ping_start.load();
if (ping_time.count() >= 0) {
// Successful ping time measurement, replace previous
pfrom.nPingUsecTime = count_microseconds(ping_time);
pfrom.nMinPingUsecTime = std::min(pfrom.nMinPingUsecTime.load(), count_microseconds(ping_time));
// Let connman know about this successful ping-pong
pfrom.PongReceived(ping_time);
} else {
// This should never happen
sProblem = "Timing mishap";
@ -3872,12 +3902,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
LogPrint(BCLog::NET, "pong peer=%d: %s, %x expected, %x received, %u bytes\n",
pfrom.GetId(),
sProblem,
pfrom.nPingNonceSent,
peer->m_ping_nonce_sent,
nonce,
nAvail);
}
if (bPingFinished) {
pfrom.nPingNonceSent = 0;
peer->m_ping_nonce_sent = 0;
}
return;
}
@ -3996,43 +4026,39 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode)
bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer)
{
const NodeId peer_id{pnode.GetId()};
PeerRef peer = GetPeerRef(peer_id);
if (peer == nullptr) return false;
{
LOCK(peer->m_misbehavior_mutex);
LOCK(peer.m_misbehavior_mutex);
// There's nothing to do if the m_should_discourage flag isn't set
if (!peer->m_should_discourage) return false;
if (!peer.m_should_discourage) return false;
peer->m_should_discourage = false;
peer.m_should_discourage = false;
} // peer.m_misbehavior_mutex
if (pnode.HasPermission(PF_NOBAN)) {
// We never disconnect or discourage peers for bad behavior if they have the NOBAN permission flag
LogPrintf("Warning: not punishing noban peer %d!\n", peer_id);
LogPrintf("Warning: not punishing noban peer %d!\n", peer.m_id);
return false;
}
if (pnode.IsManualConn()) {
// We never disconnect or discourage manual peers for bad behavior
LogPrintf("Warning: not punishing manually connected peer %d!\n", peer_id);
LogPrintf("Warning: not punishing manually connected peer %d!\n", peer.m_id);
return false;
}
if (pnode.addr.IsLocal()) {
// We disconnect local peers for bad behavior but don't discourage (since that would discourage
// all peers on the same local address)
LogPrintf("Warning: disconnecting but not discouraging local peer %d!\n", peer_id);
LogPrintf("Warning: disconnecting but not discouraging local peer %d!\n", peer.m_id);
pnode.fDisconnect = true;
return true;
}
// Normal case: Disconnect the peer and discourage all nodes sharing the address
LogPrint(BCLog::NET, "Disconnecting and discouraging peer %d!\n", peer_id);
LogPrint(BCLog::NET, "Disconnecting and discouraging peer %d!\n", peer.m_id);
if (m_banman) m_banman->Discourage(pnode.addr);
m_connman.DisconnectNode(pnode.addr);
return true;
@ -4295,6 +4321,50 @@ void PeerManagerImpl::CheckForStaleTipAndEvictPeers()
}
}
void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer)
{
// Use mockable time for ping timeouts.
// This means that setmocktime may cause pings to time out.
auto now = GetTime<std::chrono::microseconds>();
if (m_connman.RunInactivityChecks(node_to) && peer.m_ping_nonce_sent &&
now > peer.m_ping_start.load() + std::chrono::seconds{TIMEOUT_INTERVAL}) {
LogPrint(BCLog::NET, "ping timeout: %fs peer=%d\n", 0.000001 * count_microseconds(now - peer.m_ping_start.load()), peer.m_id);
node_to.fDisconnect = true;
return;
}
const CNetMsgMaker msgMaker(node_to.GetCommonVersion());
bool pingSend = false;
if (peer.m_ping_queued) {
// RPC ping request by user
pingSend = true;
}
if (peer.m_ping_nonce_sent == 0 && now > peer.m_ping_start.load() + PING_INTERVAL) {
// Ping automatically sent as a latency probe & keepalive.
pingSend = true;
}
if (pingSend) {
uint64_t nonce = 0;
while (nonce == 0) {
GetRandBytes((unsigned char*)&nonce, sizeof(nonce));
}
peer.m_ping_queued = false;
peer.m_ping_start = now;
if (node_to.GetCommonVersion() > BIP0031_VERSION) {
peer.m_ping_nonce_sent = nonce;
m_connman.PushMessage(&node_to, msgMaker.Make(NetMsgType::PING, nonce));
} else {
// Peer is too old to support ping command with nonce, pong will never arrive.
peer.m_ping_nonce_sent = 0;
m_connman.PushMessage(&node_to, msgMaker.Make(NetMsgType::PING));
}
}
}
namespace {
class CompareInvMempoolOrder
{
@ -4319,11 +4389,12 @@ public:
bool PeerManagerImpl::SendMessages(CNode* pto)
{
PeerRef peer = GetPeerRef(pto->GetId());
if (!peer) return false;
const Consensus::Params& consensusParams = m_chainparams.GetConsensus();
// We must call MaybeDiscourageAndDisconnect first, to ensure that we'll
// disconnect misbehaving peers even before the version handshake is complete.
if (MaybeDiscourageAndDisconnect(*pto)) return true;
if (MaybeDiscourageAndDisconnect(*pto, *peer)) return true;
// Don't send anything until the version handshake is complete
if (!pto->fSuccessfullyConnected || pto->fDisconnect)
@ -4332,34 +4403,10 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
// If we get here, the outgoing message serialization version is set and can't change.
const CNetMsgMaker msgMaker(pto->GetCommonVersion());
//
// Message: ping
//
bool pingSend = false;
if (pto->fPingQueued) {
// RPC ping request by user
pingSend = true;
}
if (pto->nPingNonceSent == 0 && pto->m_ping_start.load() + PING_INTERVAL < GetTime<std::chrono::microseconds>()) {
// Ping automatically sent as a latency probe & keepalive.
pingSend = true;
}
if (pingSend) {
uint64_t nonce = 0;
while (nonce == 0) {
GetRandBytes((unsigned char*)&nonce, sizeof(nonce));
}
pto->fPingQueued = false;
pto->m_ping_start = GetTime<std::chrono::microseconds>();
if (pto->GetCommonVersion() > BIP0031_VERSION) {
pto->nPingNonceSent = nonce;
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::PING, nonce));
} else {
// Peer is too old to support ping command with nonce, pong will never arrive.
pto->nPingNonceSent = 0;
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::PING));
}
}
MaybeSendPing(*pto, *peer);
// MaybeSendPing may have marked peer for disconnection
if (pto->fDisconnect) return true;
{
LOCK(cs_main);

View File

@ -30,6 +30,7 @@ struct CNodeStateStats {
int nSyncHeight = -1;
int nCommonHeight = -1;
int m_starting_height = -1;
int64_t m_ping_wait_usec;
std::vector<int> vHeightInFlight;
};
@ -47,6 +48,9 @@ public:
/** Whether this node ignores txs received over p2p. */
virtual bool IgnoresIncomingTxs() = 0;
/** Send ping message to all peers */
virtual void SendPings() = 0;
/** Set the best height */
virtual void SetBestHeight(int height) = 0;

View File

@ -1115,7 +1115,6 @@ void RPCConsole::updateDetailWidget()
ui->peerBytesRecv->setText(GUIUtil::formatBytes(stats->nodeStats.nRecvBytes));
ui->peerConnTime->setText(GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nTimeConnected));
ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_ping_usec));
ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStats.m_ping_wait_usec));
ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_usec));
ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion));
@ -1149,6 +1148,7 @@ void RPCConsole::updateDetailWidget()
ui->peerCommonHeight->setText(tr("Unknown"));
ui->peerHeight->setText(QString::number(stats->nodeStateStats.m_starting_height));
ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStateStats.m_ping_wait_usec));
}
ui->detailWidget->show();

View File

@ -77,13 +77,12 @@ static RPCHelpMan ping()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
NodeContext& node = EnsureNodeContext(request.context);
if(!node.connman)
if (!node.peerman) {
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
}
// Request that each node send a ping during next message processing pass
node.connman->ForEachNode([](CNode* pnode) {
pnode->fPingQueued = true;
});
node.peerman->SendPings();
return NullUniValue;
},
};
@ -209,8 +208,8 @@ static RPCHelpMan getpeerinfo()
if (stats.m_min_ping_usec < std::numeric_limits<int64_t>::max()) {
obj.pushKV("minping", ((double)stats.m_min_ping_usec) / 1e6);
}
if (stats.m_ping_wait_usec > 0) {
obj.pushKV("pingwait", ((double)stats.m_ping_wait_usec) / 1e6);
if (fStateStats && statestats.m_ping_wait_usec > 0) {
obj.pushKV("pingwait", ((double)statestats.m_ping_wait_usec) / 1e6);
}
obj.pushKV("version", stats.nVersion);
// Use the sanitized form of subver here, to avoid tricksy remote peers from

View File

@ -794,7 +794,7 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_c
candidates.push_back({
/* id */ id,
/* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)),
/* nMinPingUsecTime */ static_cast<int64_t>(random_context.randrange(100)),
/* m_min_ping_time */ static_cast<int64_t>(random_context.randrange(100)),
/* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)),
/* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)),
/* fRelevantServices */ random_context.randbool(),
@ -854,7 +854,7 @@ BOOST_AUTO_TEST_CASE(node_eviction_test)
// from eviction.
BOOST_CHECK(!IsEvicted(
number_of_nodes, [](NodeEvictionCandidate& candidate) {
candidate.nMinPingUsecTime = candidate.id;
candidate.m_min_ping_time = candidate.id;
},
{0, 1, 2, 3, 4, 5, 6, 7}, random_context));
@ -901,7 +901,7 @@ BOOST_AUTO_TEST_CASE(node_eviction_test)
BOOST_CHECK(!IsEvicted(
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected
candidate.nMinPingUsecTime = candidate.id; // 8 protected
candidate.m_min_ping_time = candidate.id; // 8 protected
candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected
candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected
},