23
23
#include " primitives/transaction.h"
24
24
#include " random.h"
25
25
#include " reverse_iterator.h"
26
+ #include " scheduler.h"
26
27
#include " tinyformat.h"
27
28
#include " txmempool.h"
28
29
#include " ui_interface.h"
@@ -127,6 +128,9 @@ namespace {
127
128
/* * Number of outbound peers with m_chain_sync.m_protect. */
128
129
int g_outbound_peers_with_protect_from_disconnect = 0 ;
129
130
131
+ /* * When our tip was last updated. */
132
+ int64_t g_last_tip_update = 0 ;
133
+
130
134
/* * Relay map, protected by cs_main. */
131
135
typedef std::map<uint256, CTransactionRef> MapRelay;
132
136
MapRelay mapRelay;
@@ -231,6 +235,9 @@ struct CNodeState {
231
235
232
236
ChainSyncTimeoutState m_chain_sync;
233
237
238
+ // ! Time of last new block announcement
239
+ int64_t m_last_block_announcement;
240
+
234
241
CNodeState (CAddress addrIn, std::string addrNameIn) : address(addrIn), name(addrNameIn) {
235
242
fCurrentlyConnected = false ;
236
243
nMisbehavior = 0 ;
@@ -254,6 +261,7 @@ struct CNodeState {
254
261
fWantsCmpctWitness = false ;
255
262
fSupportsDesiredCmpctVersion = false ;
256
263
m_chain_sync = { 0 , nullptr , false , false };
264
+ m_last_block_announcement = 0 ;
257
265
}
258
266
};
259
267
@@ -427,6 +435,15 @@ void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) {
427
435
}
428
436
}
429
437
438
+ bool TipMayBeStale (const Consensus::Params &consensusParams)
439
+ {
440
+ AssertLockHeld (cs_main);
441
+ if (g_last_tip_update == 0 ) {
442
+ g_last_tip_update = GetTime ();
443
+ }
444
+ return g_last_tip_update < GetTime () - consensusParams.nPowTargetSpacing * 3 && mapBlocksInFlight.empty ();
445
+ }
446
+
430
447
// Requires cs_main
431
448
bool CanDirectFetch (const Consensus::Params &consensusParams)
432
449
{
@@ -533,6 +550,15 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<con
533
550
534
551
} // namespace
535
552
553
+ // This function is used for testing the stale tip eviction logic, see
554
+ // DoS_tests.cpp
555
+ void UpdateLastBlockAnnounceTime (NodeId node, int64_t time_in_seconds)
556
+ {
557
+ LOCK (cs_main);
558
+ CNodeState *state = State (node);
559
+ if (state) state->m_last_block_announcement = time_in_seconds;
560
+ }
561
+
536
562
// Returns true for outbound peers, excluding manual connections, feelers, and
537
563
// one-shots
538
564
bool IsOutboundDisconnectionCandidate (const CNode *node)
@@ -764,9 +790,17 @@ static bool StaleBlockRequestAllowed(const CBlockIndex* pindex, const Consensus:
764
790
(GetBlockProofEquivalentTime (*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT);
765
791
}
766
792
767
- PeerLogicValidation::PeerLogicValidation (CConnman* connmanIn) : connman(connmanIn) {
793
+ PeerLogicValidation::PeerLogicValidation (CConnman* connmanIn, CScheduler &scheduler ) : connman(connmanIn), m_stale_tip_check_time( 0 ) {
768
794
// Initialize global variables that cannot be constructed at startup.
769
795
recentRejects.reset (new CRollingBloomFilter (120000 , 0.000001 ));
796
+
797
+ const Consensus::Params& consensusParams = Params ().GetConsensus ();
798
+ // Stale tip checking and peer eviction are on two different timers, but we
799
+ // don't want them to get out of sync due to drift in the scheduler, so we
800
+ // combine them in one function and schedule at the quicker (peer-eviction)
801
+ // timer.
802
+ static_assert (EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL, " peer eviction timer should be less than stale tip check timer" );
803
+ scheduler.scheduleEvery (std::bind (&PeerLogicValidation::CheckForStaleTipAndEvictPeers, this , consensusParams), EXTRA_PEER_CHECK_INTERVAL * 1000 );
770
804
}
771
805
772
806
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
797
831
}
798
832
LogPrint (BCLog::MEMPOOL, " Erased %d orphan tx included or conflicted by block\n " , nErased);
799
833
}
834
+
835
+ g_last_tip_update = GetTime ();
800
836
}
801
837
802
838
// 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
1215
1251
return true ;
1216
1252
}
1217
1253
1254
+ bool received_new_header = false ;
1218
1255
const CBlockIndex *pindexLast = nullptr ;
1219
1256
{
1220
1257
LOCK (cs_main);
@@ -1255,6 +1292,12 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
1255
1292
}
1256
1293
hashLastBlock = header.GetHash ();
1257
1294
}
1295
+
1296
+ // If we don't have the last header, then they'll have given us
1297
+ // something new (if these headers are valid).
1298
+ if (mapBlockIndex.find (hashLastBlock) == mapBlockIndex.end ()) {
1299
+ received_new_header = true ;
1300
+ }
1258
1301
}
1259
1302
1260
1303
CValidationState state;
@@ -1319,6 +1362,10 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
1319
1362
// because it is set in UpdateBlockAvailability. Some nullptr checks
1320
1363
// are still present, however, as belt-and-suspenders.
1321
1364
1365
+ if (received_new_header && pindexLast->nChainWork > chainActive.Tip ()->nChainWork ) {
1366
+ nodestate->m_last_block_announcement = GetTime ();
1367
+ }
1368
+
1322
1369
if (nCount == MAX_HEADERS_RESULTS) {
1323
1370
// Headers message had its maximum size; the peer may have more headers.
1324
1371
// 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
1403
1450
// If this is an outbound peer, check to see if we should protect
1404
1451
// it from the bad/lagging chain logic.
1405
1452
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 ) {
1453
+ LogPrint (BCLog::NET, " Protecting outbound peer=%d from eviction\n " , pfrom->GetId ());
1406
1454
nodestate->m_chain_sync .m_protect = true ;
1407
1455
++g_outbound_peers_with_protect_from_disconnect;
1408
1456
}
@@ -2219,6 +2267,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
2219
2267
CBlockHeaderAndShortTxIDs cmpctblock;
2220
2268
vRecv >> cmpctblock;
2221
2269
2270
+ bool received_new_header = false ;
2271
+
2222
2272
{
2223
2273
LOCK (cs_main);
2224
2274
@@ -2228,6 +2278,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
2228
2278
connman->PushMessage (pfrom, msgMaker.Make (NetMsgType::GETHEADERS, chainActive.GetLocator (pindexBestHeader), uint256 ()));
2229
2279
return true ;
2230
2280
}
2281
+
2282
+ if (mapBlockIndex.find (cmpctblock.header .GetHash ()) == mapBlockIndex.end ()) {
2283
+ received_new_header = true ;
2284
+ }
2231
2285
}
2232
2286
2233
2287
const CBlockIndex *pindex = nullptr ;
@@ -2266,6 +2320,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
2266
2320
assert (pindex);
2267
2321
UpdateBlockAvailability (pfrom->GetId (), pindex->GetBlockHash ());
2268
2322
2323
+ CNodeState *nodestate = State (pfrom->GetId ());
2324
+
2325
+ // If this was a new header with more work than our tip, update the
2326
+ // peer's last block announcement time
2327
+ if (received_new_header && pindex->nChainWork > chainActive.Tip ()->nChainWork ) {
2328
+ nodestate->m_last_block_announcement = GetTime ();
2329
+ }
2330
+
2269
2331
std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> >::iterator blockInFlightIt = mapBlocksInFlight.find (pindex->GetBlockHash ());
2270
2332
bool fAlreadyInFlight = blockInFlightIt != mapBlocksInFlight.end ();
2271
2333
@@ -2288,8 +2350,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
2288
2350
if (!fAlreadyInFlight && !CanDirectFetch (chainparams.GetConsensus ()))
2289
2351
return true ;
2290
2352
2291
- CNodeState *nodestate = State (pfrom->GetId ());
2292
-
2293
2353
if (IsWitnessEnabled (pindex->pprev , chainparams.GetConsensus ()) && !nodestate->fSupportsDesiredCmpctVersion ) {
2294
2354
// Don't bother trying to process compact blocks from v1 peers
2295
2355
// after segwit activates.
@@ -2967,6 +3027,83 @@ void PeerLogicValidation::ConsiderEviction(CNode *pto, int64_t time_in_seconds)
2967
3027
}
2968
3028
}
2969
3029
3030
+ void PeerLogicValidation::EvictExtraOutboundPeers (int64_t time_in_seconds)
3031
+ {
3032
+ // Check whether we have too many outbound peers
3033
+ int extra_peers = connman->GetExtraOutboundCount ();
3034
+ if (extra_peers > 0 ) {
3035
+ // If we have more outbound peers than we target, disconnect one.
3036
+ // Pick the outbound peer that least recently announced
3037
+ // us a new block, with ties broken by choosing the more recent
3038
+ // connection (higher node id)
3039
+ NodeId worst_peer = -1 ;
3040
+ int64_t oldest_block_announcement = std::numeric_limits<int64_t >::max ();
3041
+
3042
+ LOCK (cs_main);
3043
+
3044
+ connman->ForEachNode ([&](CNode* pnode) {
3045
+ // Ignore non-outbound peers, or nodes marked for disconnect already
3046
+ if (!IsOutboundDisconnectionCandidate (pnode) || pnode->fDisconnect ) return ;
3047
+ CNodeState *state = State (pnode->GetId ());
3048
+ if (state == nullptr ) return ; // shouldn't be possible, but just in case
3049
+ // Don't evict our protected peers
3050
+ if (state->m_chain_sync .m_protect ) return ;
3051
+ if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId () > worst_peer)) {
3052
+ worst_peer = pnode->GetId ();
3053
+ oldest_block_announcement = state->m_last_block_announcement ;
3054
+ }
3055
+ });
3056
+ if (worst_peer != -1 ) {
3057
+ bool disconnected = connman->ForNode (worst_peer, [&](CNode *pnode) {
3058
+ // Only disconnect a peer that has been connected to us for
3059
+ // some reasonable fraction of our check-frequency, to give
3060
+ // it time for new information to have arrived.
3061
+ // Also don't disconnect any peer we're trying to download a
3062
+ // block from.
3063
+ CNodeState &state = *State (pnode->GetId ());
3064
+ if (time_in_seconds - pnode->nTimeConnected > MINIMUM_CONNECT_TIME && state.nBlocksInFlight == 0 ) {
3065
+ LogPrint (BCLog::NET, " disconnecting extra outbound peer=%d (last block announcement received at time %d)\n " , pnode->GetId (), oldest_block_announcement);
3066
+ pnode->fDisconnect = true ;
3067
+ return true ;
3068
+ } else {
3069
+ LogPrint (BCLog::NET, " keeping outbound peer=%d chosen for eviction (connect time: %d, blocks_in_flight: %d)\n " , pnode->GetId (), pnode->nTimeConnected , state.nBlocksInFlight );
3070
+ return false ;
3071
+ }
3072
+ });
3073
+ if (disconnected) {
3074
+ // If we disconnected an extra peer, that means we successfully
3075
+ // connected to at least one peer after the last time we
3076
+ // detected a stale tip. Don't try any more extra peers until
3077
+ // we next detect a stale tip, to limit the load we put on the
3078
+ // network from these extra connections.
3079
+ connman->SetTryNewOutboundPeer (false );
3080
+ }
3081
+ }
3082
+ }
3083
+ }
3084
+
3085
+ void PeerLogicValidation::CheckForStaleTipAndEvictPeers (const Consensus::Params &consensusParams)
3086
+ {
3087
+ if (connman == nullptr ) return ;
3088
+
3089
+ int64_t time_in_seconds = GetTime ();
3090
+
3091
+ EvictExtraOutboundPeers (time_in_seconds);
3092
+
3093
+ if (time_in_seconds > m_stale_tip_check_time) {
3094
+ LOCK (cs_main);
3095
+ // Check whether our tip is stale, and if so, allow using an extra
3096
+ // outbound peer
3097
+ if (TipMayBeStale (consensusParams)) {
3098
+ 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);
3099
+ connman->SetTryNewOutboundPeer (true );
3100
+ } else if (connman->GetTryNewOutboundPeer ()) {
3101
+ connman->SetTryNewOutboundPeer (false );
3102
+ }
3103
+ m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL;
3104
+ }
3105
+ }
3106
+
2970
3107
class CompareInvMempoolOrder
2971
3108
{
2972
3109
CTxMemPool *mp;
0 commit comments