@ -23,6 +23,7 @@
# include "primitives/transaction.h"
# include "primitives/transaction.h"
# include "random.h"
# include "random.h"
# include "reverse_iterator.h"
# include "reverse_iterator.h"
# include "scheduler.h"
# include "tinyformat.h"
# include "tinyformat.h"
# include "txmempool.h"
# include "txmempool.h"
# include "ui_interface.h"
# include "ui_interface.h"
@ -127,6 +128,9 @@ namespace {
/** Number of outbound peers with m_chain_sync.m_protect. */
/** Number of outbound peers with m_chain_sync.m_protect. */
int g_outbound_peers_with_protect_from_disconnect = 0 ;
int g_outbound_peers_with_protect_from_disconnect = 0 ;
/** When our tip was last updated. */
int64_t g_last_tip_update = 0 ;
/** Relay map, protected by cs_main. */
/** Relay map, protected by cs_main. */
typedef std : : map < uint256 , CTransactionRef > MapRelay ;
typedef std : : map < uint256 , CTransactionRef > MapRelay ;
MapRelay mapRelay ;
MapRelay mapRelay ;
@ -231,6 +235,9 @@ struct CNodeState {
ChainSyncTimeoutState m_chain_sync ;
ChainSyncTimeoutState m_chain_sync ;
//! Time of last new block announcement
int64_t m_last_block_announcement ;
CNodeState ( CAddress addrIn , std : : string addrNameIn ) : address ( addrIn ) , name ( addrNameIn ) {
CNodeState ( CAddress addrIn , std : : string addrNameIn ) : address ( addrIn ) , name ( addrNameIn ) {
fCurrentlyConnected = false ;
fCurrentlyConnected = false ;
nMisbehavior = 0 ;
nMisbehavior = 0 ;
@ -254,6 +261,7 @@ struct CNodeState {
fWantsCmpctWitness = false ;
fWantsCmpctWitness = false ;
fSupportsDesiredCmpctVersion = false ;
fSupportsDesiredCmpctVersion = false ;
m_chain_sync = { 0 , nullptr , false , false } ;
m_chain_sync = { 0 , nullptr , false , false } ;
m_last_block_announcement = 0 ;
}
}
} ;
} ;
@ -427,6 +435,15 @@ void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) {
}
}
}
}
bool TipMayBeStale ( const Consensus : : Params & consensusParams )
{
AssertLockHeld ( cs_main ) ;
if ( g_last_tip_update = = 0 ) {
g_last_tip_update = GetTime ( ) ;
}
return g_last_tip_update < GetTime ( ) - consensusParams . nPowTargetSpacing * 3 & & mapBlocksInFlight . empty ( ) ;
}
// Requires cs_main
// Requires cs_main
bool CanDirectFetch ( const Consensus : : Params & consensusParams )
bool CanDirectFetch ( const Consensus : : Params & consensusParams )
{
{
@ -533,6 +550,15 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<con
} // namespace
} // namespace
// This function is used for testing the stale tip eviction logic, see
// DoS_tests.cpp
void UpdateLastBlockAnnounceTime ( NodeId node , int64_t time_in_seconds )
{
LOCK ( cs_main ) ;
CNodeState * state = State ( node ) ;
if ( state ) state - > m_last_block_announcement = time_in_seconds ;
}
// Returns true for outbound peers, excluding manual connections, feelers, and
// Returns true for outbound peers, excluding manual connections, feelers, and
// one-shots
// one-shots
bool IsOutboundDisconnectionCandidate ( const CNode * node )
bool IsOutboundDisconnectionCandidate ( const CNode * node )
@ -764,9 +790,17 @@ static bool StaleBlockRequestAllowed(const CBlockIndex* pindex, const Consensus:
( GetBlockProofEquivalentTime ( * pindexBestHeader , * pindex , * pindexBestHeader , consensusParams ) < STALE_RELAY_AGE_LIMIT ) ;
( GetBlockProofEquivalentTime ( * pindexBestHeader , * pindex , * pindexBestHeader , consensusParams ) < STALE_RELAY_AGE_LIMIT ) ;
}
}
PeerLogicValidation : : PeerLogicValidation ( CConnman * connmanIn ) : connman ( connmanIn ) {
PeerLogicValidation : : PeerLogicValidation ( CConnman * connmanIn , CScheduler & scheduler ) : connman ( connmanIn ) , m_stale_tip_check_time ( 0 ) {
// Initialize global variables that cannot be constructed at startup.
// Initialize global variables that cannot be constructed at startup.
recentRejects . reset ( new CRollingBloomFilter ( 120000 , 0.000001 ) ) ;
recentRejects . reset ( new CRollingBloomFilter ( 120000 , 0.000001 ) ) ;
const Consensus : : Params & consensusParams = Params ( ) . GetConsensus ( ) ;
// Stale tip checking and peer eviction are on two different timers, but we
// don't want them to get out of sync due to drift in the scheduler, so we
// combine them in one function and schedule at the quicker (peer-eviction)
// timer.
static_assert ( EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL , " peer eviction timer should be less than stale tip check timer " ) ;
scheduler . scheduleEvery ( std : : bind ( & PeerLogicValidation : : CheckForStaleTipAndEvictPeers , this , consensusParams ) , EXTRA_PEER_CHECK_INTERVAL * 1000 ) ;
}
}
void PeerLogicValidation : : BlockConnected ( const std : : shared_ptr < const CBlock > & pblock , const CBlockIndex * pindex , const std : : vector < CTransactionRef > & vtxConflicted ) {
void PeerLogicValidation : : BlockConnected ( const std : : shared_ptr < const CBlock > & pblock , const CBlockIndex * pindex , const std : : vector < CTransactionRef > & vtxConflicted ) {
@ -797,6 +831,8 @@ void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pb
}
}
LogPrint ( BCLog : : MEMPOOL , " Erased %d orphan tx included or conflicted by block \n " , nErased ) ;
LogPrint ( BCLog : : MEMPOOL , " Erased %d orphan tx included or conflicted by block \n " , nErased ) ;
}
}
g_last_tip_update = GetTime ( ) ;
}
}
// All of the following cache a recent block, and are protected by cs_most_recent_block
// All of the following cache a recent block, and are protected by cs_most_recent_block
@ -1215,6 +1251,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
return true ;
return true ;
}
}
bool received_new_header = false ;
const CBlockIndex * pindexLast = nullptr ;
const CBlockIndex * pindexLast = nullptr ;
{
{
LOCK ( cs_main ) ;
LOCK ( cs_main ) ;
@ -1255,6 +1292,12 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
}
}
hashLastBlock = header . GetHash ( ) ;
hashLastBlock = header . GetHash ( ) ;
}
}
// If we don't have the last header, then they'll have given us
// something new (if these headers are valid).
if ( mapBlockIndex . find ( hashLastBlock ) = = mapBlockIndex . end ( ) ) {
received_new_header = true ;
}
}
}
CValidationState state ;
CValidationState state ;
@ -1319,6 +1362,10 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
// because it is set in UpdateBlockAvailability. Some nullptr checks
// because it is set in UpdateBlockAvailability. Some nullptr checks
// are still present, however, as belt-and-suspenders.
// are still present, however, as belt-and-suspenders.
if ( received_new_header & & pindexLast - > nChainWork > chainActive . Tip ( ) - > nChainWork ) {
nodestate - > m_last_block_announcement = GetTime ( ) ;
}
if ( nCount = = MAX_HEADERS_RESULTS ) {
if ( nCount = = MAX_HEADERS_RESULTS ) {
// Headers message had its maximum size; the peer may have more headers.
// Headers message had its maximum size; the peer may have more headers.
// TODO: optimize: if pindexLast is an ancestor of chainActive.Tip or pindexBestHeader, continue
// TODO: optimize: if pindexLast is an ancestor of chainActive.Tip or pindexBestHeader, continue
@ -1403,6 +1450,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
// If this is an outbound peer, check to see if we should protect
// If this is an outbound peer, check to see if we should protect
// it from the bad/lagging chain logic.
// it from the bad/lagging chain logic.
if ( g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT & & nodestate - > pindexBestKnownBlock - > nChainWork > = chainActive . Tip ( ) - > nChainWork & & ! nodestate - > m_chain_sync . m_protect ) {
if ( g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT & & nodestate - > pindexBestKnownBlock - > nChainWork > = chainActive . Tip ( ) - > nChainWork & & ! nodestate - > m_chain_sync . m_protect ) {
LogPrint ( BCLog : : NET , " Protecting outbound peer=%d from eviction \n " , pfrom - > GetId ( ) ) ;
nodestate - > m_chain_sync . m_protect = true ;
nodestate - > m_chain_sync . m_protect = true ;
+ + g_outbound_peers_with_protect_from_disconnect ;
+ + g_outbound_peers_with_protect_from_disconnect ;
}
}
@ -2219,6 +2267,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
CBlockHeaderAndShortTxIDs cmpctblock ;
CBlockHeaderAndShortTxIDs cmpctblock ;
vRecv > > cmpctblock ;
vRecv > > cmpctblock ;
bool received_new_header = false ;
{
{
LOCK ( cs_main ) ;
LOCK ( cs_main ) ;
@ -2228,6 +2278,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
connman - > PushMessage ( pfrom , msgMaker . Make ( NetMsgType : : GETHEADERS , chainActive . GetLocator ( pindexBestHeader ) , uint256 ( ) ) ) ;
connman - > PushMessage ( pfrom , msgMaker . Make ( NetMsgType : : GETHEADERS , chainActive . GetLocator ( pindexBestHeader ) , uint256 ( ) ) ) ;
return true ;
return true ;
}
}
if ( mapBlockIndex . find ( cmpctblock . header . GetHash ( ) ) = = mapBlockIndex . end ( ) ) {
received_new_header = true ;
}
}
}
const CBlockIndex * pindex = nullptr ;
const CBlockIndex * pindex = nullptr ;
@ -2266,6 +2320,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
assert ( pindex ) ;
assert ( pindex ) ;
UpdateBlockAvailability ( pfrom - > GetId ( ) , pindex - > GetBlockHash ( ) ) ;
UpdateBlockAvailability ( pfrom - > GetId ( ) , pindex - > GetBlockHash ( ) ) ;
CNodeState * nodestate = State ( pfrom - > GetId ( ) ) ;
// If this was a new header with more work than our tip, update the
// peer's last block announcement time
if ( received_new_header & & pindex - > nChainWork > chainActive . Tip ( ) - > nChainWork ) {
nodestate - > m_last_block_announcement = GetTime ( ) ;
}
std : : map < uint256 , std : : pair < NodeId , std : : list < QueuedBlock > : : iterator > > : : iterator blockInFlightIt = mapBlocksInFlight . find ( pindex - > GetBlockHash ( ) ) ;
std : : map < uint256 , std : : pair < NodeId , std : : list < QueuedBlock > : : iterator > > : : iterator blockInFlightIt = mapBlocksInFlight . find ( pindex - > GetBlockHash ( ) ) ;
bool fAlreadyInFlight = blockInFlightIt ! = mapBlocksInFlight . end ( ) ;
bool fAlreadyInFlight = blockInFlightIt ! = mapBlocksInFlight . end ( ) ;
@ -2288,8 +2350,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
if ( ! fAlreadyInFlight & & ! CanDirectFetch ( chainparams . GetConsensus ( ) ) )
if ( ! fAlreadyInFlight & & ! CanDirectFetch ( chainparams . GetConsensus ( ) ) )
return true ;
return true ;
CNodeState * nodestate = State ( pfrom - > GetId ( ) ) ;
if ( IsWitnessEnabled ( pindex - > pprev , chainparams . GetConsensus ( ) ) & & ! nodestate - > fSupportsDesiredCmpctVersion ) {
if ( IsWitnessEnabled ( pindex - > pprev , chainparams . GetConsensus ( ) ) & & ! nodestate - > fSupportsDesiredCmpctVersion ) {
// Don't bother trying to process compact blocks from v1 peers
// Don't bother trying to process compact blocks from v1 peers
// after segwit activates.
// after segwit activates.
@ -2967,6 +3027,83 @@ void PeerLogicValidation::ConsiderEviction(CNode *pto, int64_t time_in_seconds)
}
}
}
}
void PeerLogicValidation : : EvictExtraOutboundPeers ( int64_t time_in_seconds )
{
// Check whether we have too many outbound peers
int extra_peers = connman - > GetExtraOutboundCount ( ) ;
if ( extra_peers > 0 ) {
// If we have more outbound peers than we target, disconnect one.
// Pick the outbound peer that least recently announced
// us a new block, with ties broken by choosing the more recent
// connection (higher node id)
NodeId worst_peer = - 1 ;
int64_t oldest_block_announcement = std : : numeric_limits < int64_t > : : max ( ) ;
LOCK ( cs_main ) ;
connman - > ForEachNode ( [ & ] ( CNode * pnode ) {
// Ignore non-outbound peers, or nodes marked for disconnect already
if ( ! IsOutboundDisconnectionCandidate ( pnode ) | | pnode - > fDisconnect ) return ;
CNodeState * state = State ( pnode - > GetId ( ) ) ;
if ( state = = nullptr ) return ; // shouldn't be possible, but just in case
// Don't evict our protected peers
if ( state - > m_chain_sync . m_protect ) return ;
if ( state - > m_last_block_announcement < oldest_block_announcement | | ( state - > m_last_block_announcement = = oldest_block_announcement & & pnode - > GetId ( ) > worst_peer ) ) {
worst_peer = pnode - > GetId ( ) ;
oldest_block_announcement = state - > m_last_block_announcement ;
}
} ) ;
if ( worst_peer ! = - 1 ) {
bool disconnected = connman - > ForNode ( worst_peer , [ & ] ( CNode * pnode ) {
// Only disconnect a peer that has been connected to us for
// some reasonable fraction of our check-frequency, to give
// it time for new information to have arrived.
// Also don't disconnect any peer we're trying to download a
// block from.
CNodeState & state = * State ( pnode - > GetId ( ) ) ;
if ( time_in_seconds - pnode - > nTimeConnected > MINIMUM_CONNECT_TIME & & state . nBlocksInFlight = = 0 ) {
LogPrint ( BCLog : : NET , " disconnecting extra outbound peer=%d (last block announcement received at time %d) \n " , pnode - > GetId ( ) , oldest_block_announcement ) ;
pnode - > fDisconnect = true ;
return true ;
} else {
LogPrint ( BCLog : : NET , " keeping outbound peer=%d chosen for eviction (connect time: %d, blocks_in_flight: %d) \n " , pnode - > GetId ( ) , pnode - > nTimeConnected , state . nBlocksInFlight ) ;
return false ;
}
} ) ;
if ( disconnected ) {
// If we disconnected an extra peer, that means we successfully
// connected to at least one peer after the last time we
// detected a stale tip. Don't try any more extra peers until
// we next detect a stale tip, to limit the load we put on the
// network from these extra connections.
connman - > SetTryNewOutboundPeer ( false ) ;
}
}
}
}
void PeerLogicValidation : : CheckForStaleTipAndEvictPeers ( const Consensus : : Params & consensusParams )
{
if ( connman = = nullptr ) return ;
int64_t time_in_seconds = GetTime ( ) ;
EvictExtraOutboundPeers ( time_in_seconds ) ;
if ( time_in_seconds > m_stale_tip_check_time ) {
LOCK ( cs_main ) ;
// Check whether our tip is stale, and if so, allow using an extra
// outbound peer
if ( TipMayBeStale ( consensusParams ) ) {
LogPrintf ( " Potential stale tip detected, will try using extra outbound peer (last tip update: %d seconds ago) \n " , time_in_seconds - g_last_tip_update ) ;
connman - > SetTryNewOutboundPeer ( true ) ;
} else if ( connman - > GetTryNewOutboundPeer ( ) ) {
connman - > SetTryNewOutboundPeer ( false ) ;
}
m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL ;
}
}
class CompareInvMempoolOrder
class CompareInvMempoolOrder
{
{
CTxMemPool * mp ;
CTxMemPool * mp ;