@ -116,6 +116,9 @@ namespace {
/** Number of peers from which we're downloading blocks. */
/** Number of peers from which we're downloading blocks. */
int nPeersWithValidatedDownloads = 0 ;
int nPeersWithValidatedDownloads = 0 ;
/** Number of outbound peers with m_chain_sync.m_protect. */
int g_outbound_peers_with_protect_from_disconnect = 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 ;
@ -193,6 +196,33 @@ struct CNodeState {
*/
*/
bool fSupportsDesiredCmpctVersion ;
bool fSupportsDesiredCmpctVersion ;
/** State used to enforce CHAIN_SYNC_TIMEOUT
* Only in effect for outbound , non - manual connections , with
* m_protect = = false
* Algorithm : if a peer ' s best known block has less work than our tip ,
* set a timeout CHAIN_SYNC_TIMEOUT seconds in the future :
* - If at timeout their best known block now has more work than our tip
* when the timeout was set , then either reset the timeout or clear it
* ( after comparing against our current tip ' s work )
* - If at timeout their best known block still has less work than our
* tip did when the timeout was set , then send a getheaders message ,
* and set a shorter timeout , HEADERS_RESPONSE_TIME seconds in future .
* If their best known block is still behind when that new timeout is
* reached , disconnect .
*/
struct ChainSyncTimeoutState {
//! A timeout used for checking whether our peer has sufficiently synced
int64_t m_timeout ;
//! A header with the work we require on our peer's chain
const CBlockIndex * m_work_header ;
//! After timeout is reached, set to true after sending getheaders
bool m_sent_getheaders ;
//! Whether this peer is protected from disconnection due to a bad/slow chain
bool m_protect ;
} ;
ChainSyncTimeoutState m_chain_sync ;
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 ;
@ -215,6 +245,7 @@ struct CNodeState {
fHaveWitness = false ;
fHaveWitness = false ;
fWantsCmpctWitness = false ;
fWantsCmpctWitness = false ;
fSupportsDesiredCmpctVersion = false ;
fSupportsDesiredCmpctVersion = false ;
m_chain_sync = { 0 , nullptr , false , false } ;
}
}
} ;
} ;
@ -495,6 +526,13 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<con
} // namespace
} // namespace
// Returns true for outbound peers, excluding manual connections, feelers, and
// one-shots
bool IsOutboundDisconnectionCandidate ( const CNode * node )
{
return ! ( node - > fInbound | | node - > m_manual_connection | | node - > fFeeler | | node - > fOneShot ) ;
}
void PeerLogicValidation : : InitializeNode ( CNode * pnode ) {
void PeerLogicValidation : : InitializeNode ( CNode * pnode ) {
CAddress addr = pnode - > addr ;
CAddress addr = pnode - > addr ;
std : : string addrName = pnode - > GetAddrName ( ) ;
std : : string addrName = pnode - > GetAddrName ( ) ;
@ -527,6 +565,8 @@ void PeerLogicValidation::FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTim
nPreferredDownload - = state - > fPreferredDownload ;
nPreferredDownload - = state - > fPreferredDownload ;
nPeersWithValidatedDownloads - = ( state - > nBlocksInFlightValidHeaders ! = 0 ) ;
nPeersWithValidatedDownloads - = ( state - > nBlocksInFlightValidHeaders ! = 0 ) ;
assert ( nPeersWithValidatedDownloads > = 0 ) ;
assert ( nPeersWithValidatedDownloads > = 0 ) ;
g_outbound_peers_with_protect_from_disconnect - = state - > m_chain_sync . m_protect ;
assert ( g_outbound_peers_with_protect_from_disconnect > = 0 ) ;
mapNodeState . erase ( nodeid ) ;
mapNodeState . erase ( nodeid ) ;
@ -535,6 +575,7 @@ void PeerLogicValidation::FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTim
assert ( mapBlocksInFlight . empty ( ) ) ;
assert ( mapBlocksInFlight . empty ( ) ) ;
assert ( nPreferredDownload = = 0 ) ;
assert ( nPreferredDownload = = 0 ) ;
assert ( nPeersWithValidatedDownloads = = 0 ) ;
assert ( nPeersWithValidatedDownloads = = 0 ) ;
assert ( g_outbound_peers_with_protect_from_disconnect = = 0 ) ;
}
}
LogPrint ( BCLog : : NET , " Cleared nodestate for peer=%d \n " , nodeid ) ;
LogPrint ( BCLog : : NET , " Cleared nodestate for peer=%d \n " , nodeid ) ;
}
}
@ -2315,6 +2356,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
assert ( pindexLast ) ;
assert ( pindexLast ) ;
UpdateBlockAvailability ( pfrom - > GetId ( ) , pindexLast - > GetBlockHash ( ) ) ;
UpdateBlockAvailability ( pfrom - > GetId ( ) , pindexLast - > GetBlockHash ( ) ) ;
// From here, pindexBestKnownBlock should be guaranteed to be non-null,
// because it is set in UpdateBlockAvailability. Some nullptr checks
// are still present, however, as belt-and-suspenders.
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
@ -2387,11 +2432,22 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
// chainActive.Tip()) because we won't start block download
// chainActive.Tip()) because we won't start block download
// until we have a headers chain that has at least
// until we have a headers chain that has at least
// nMinimumChainWork, even if a peer has a chain past our tip,
// nMinimumChainWork, even if a peer has a chain past our tip,
if ( ! ( pfrom - > fInbound | | pfrom - > fWhitelisted | | pfrom - > m_manual_connection ) ) {
// as an anti-DoS measure.
if ( IsOutboundDisconnectionCandidate ( pfrom ) ) {
LogPrintf ( " Disconnecting outbound peer %d -- headers chain has insufficient work \n " , pfrom - > GetId ( ) ) ;
pfrom - > fDisconnect = true ;
pfrom - > fDisconnect = true ;
}
}
}
}
}
}
if ( ! pfrom - > fDisconnect & & IsOutboundDisconnectionCandidate ( pfrom ) & & nodestate - > pindexBestKnownBlock ! = nullptr ) {
// If this is an outbound peer, check to see if we should protect
// 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 ) {
nodestate - > m_chain_sync . m_protect = true ;
+ + g_outbound_peers_with_protect_from_disconnect ;
}
}
}
}
}
}
@ -2790,6 +2846,58 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter
return fMoreWork ;
return fMoreWork ;
}
}
void PeerLogicValidation : : ConsiderEviction ( CNode * pto , int64_t time_in_seconds )
{
AssertLockHeld ( cs_main ) ;
CNodeState & state = * State ( pto - > GetId ( ) ) ;
const CNetMsgMaker msgMaker ( pto - > GetSendVersion ( ) ) ;
if ( ! state . m_chain_sync . m_protect & & IsOutboundDisconnectionCandidate ( pto ) & & state . fSyncStarted ) {
// This is an outbound peer subject to disconnection if they don't
// announce a block with as much work as the current tip within
// CHAIN_SYNC_TIMEOUT + HEADERS_RESPONSE_TIME seconds (note: if
// their chain has more work than ours, we should sync to it,
// unless it's invalid, in which case we should find that out and
// disconnect from them elsewhere).
if ( state . pindexBestKnownBlock ! = nullptr & & state . pindexBestKnownBlock - > nChainWork > = chainActive . Tip ( ) - > nChainWork ) {
if ( state . m_chain_sync . m_timeout ! = 0 ) {
state . m_chain_sync . m_timeout = 0 ;
state . m_chain_sync . m_work_header = nullptr ;
state . m_chain_sync . m_sent_getheaders = false ;
}
} else if ( state . m_chain_sync . m_timeout = = 0 | | ( state . m_chain_sync . m_work_header ! = nullptr & & state . pindexBestKnownBlock ! = nullptr & & state . pindexBestKnownBlock - > nChainWork > = state . m_chain_sync . m_work_header - > nChainWork ) ) {
// Our best block known by this peer is behind our tip, and we're either noticing
// that for the first time, OR this peer was able to catch up to some earlier point
// where we checked against our tip.
// Either way, set a new timeout based on current tip.
state . m_chain_sync . m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT ;
state . m_chain_sync . m_work_header = chainActive . Tip ( ) ;
state . m_chain_sync . m_sent_getheaders = false ;
} else if ( state . m_chain_sync . m_timeout > 0 & & time_in_seconds > state . m_chain_sync . m_timeout ) {
// No evidence yet that our peer has synced to a chain with work equal to that
// of our tip, when we first detected it was behind. Send a single getheaders
// message to give the peer a chance to update us.
if ( state . m_chain_sync . m_sent_getheaders ) {
// They've run out of time to catch up!
LogPrintf ( " Disconnecting outbound peer %d for old chain, best known block = %s \n " , pto - > GetId ( ) , state . pindexBestKnownBlock ! = nullptr ? state . pindexBestKnownBlock - > GetBlockHash ( ) . ToString ( ) : " <none> " ) ;
pto - > fDisconnect = true ;
} else {
LogPrint ( BCLog : : NET , " sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s) \n " , pto - > GetId ( ) , state . pindexBestKnownBlock ! = nullptr ? state . pindexBestKnownBlock - > GetBlockHash ( ) . ToString ( ) : " <none> " , state . m_chain_sync . m_work_header - > GetBlockHash ( ) . ToString ( ) ) ;
connman - > PushMessage ( pto , msgMaker . Make ( NetMsgType : : GETHEADERS , chainActive . GetLocator ( state . m_chain_sync . m_work_header - > pprev ) , uint256 ( ) ) ) ;
state . m_chain_sync . m_sent_getheaders = true ;
constexpr int64_t HEADERS_RESPONSE_TIME = 120 ; // 2 minutes
// Bump the timeout to allow a response, which could clear the timeout
// (if the response shows the peer has synced), reset the timeout (if
// the peer syncs to the required work but not to our tip), or result
// in disconnect (if we advance to the timeout and pindexBestKnownBlock
// has not sufficiently progressed)
state . m_chain_sync . m_timeout = time_in_seconds + HEADERS_RESPONSE_TIME ;
}
}
}
}
class CompareInvMempoolOrder
class CompareInvMempoolOrder
{
{
CTxMemPool * mp ;
CTxMemPool * mp ;
@ -3256,6 +3364,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic<bool>& interruptM
}
}
}
}
// Check that outbound peers have reasonable chains
// GetTime() is used by this anti-DoS logic so we can test this using mocktime
ConsiderEviction ( pto , GetTime ( ) ) ;
//
//
// Message: getdata (blocks)
// Message: getdata (blocks)