|
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,7 +128,6 @@ 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 |
|
130 |
| - |
131 | 131 | /** When our tip was last updated. */
|
132 | 132 | int64_t g_last_tip_update = 0;
|
133 | 133 |
|
@@ -435,6 +435,15 @@ void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) {
|
435 | 435 | }
|
436 | 436 | }
|
437 | 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 | + |
438 | 447 | // Requires cs_main
|
439 | 448 | bool CanDirectFetch(const Consensus::Params &consensusParams)
|
440 | 449 | {
|
@@ -772,9 +781,17 @@ static bool StaleBlockRequestAllowed(const CBlockIndex* pindex, const Consensus:
|
772 | 781 | (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT);
|
773 | 782 | }
|
774 | 783 |
|
775 |
| -PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn) : connman(connmanIn) { |
| 784 | +PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler) : connman(connmanIn), m_stale_tip_check_time(0) { |
776 | 785 | // Initialize global variables that cannot be constructed at startup.
|
777 | 786 | recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
|
| 787 | + |
| 788 | + const Consensus::Params& consensusParams = Params().GetConsensus(); |
| 789 | + // Stale tip checking and peer eviction are on two different timers, but we |
| 790 | + // don't want them to get out of sync due to drift in the scheduler, so we |
| 791 | + // combine them in one function and schedule at the quicker (peer-eviction) |
| 792 | + // timer. |
| 793 | + static_assert(EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL, "peer eviction timer should be less than stale tip check timer"); |
| 794 | + scheduler.scheduleEvery(std::bind(&PeerLogicValidation::CheckForStaleTipAndEvictPeers, this, consensusParams), EXTRA_PEER_CHECK_INTERVAL * 1000); |
778 | 795 | }
|
779 | 796 |
|
780 | 797 | void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted) {
|
@@ -1424,6 +1441,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
|
1424 | 1441 | // If this is an outbound peer, check to see if we should protect
|
1425 | 1442 | // it from the bad/lagging chain logic.
|
1426 | 1443 | 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) {
|
| 1444 | + LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId()); |
1427 | 1445 | nodestate->m_chain_sync.m_protect = true;
|
1428 | 1446 | ++g_outbound_peers_with_protect_from_disconnect;
|
1429 | 1447 | }
|
@@ -3004,6 +3022,83 @@ void PeerLogicValidation::ConsiderEviction(CNode *pto, int64_t time_in_seconds)
|
3004 | 3022 | }
|
3005 | 3023 | }
|
3006 | 3024 |
|
| 3025 | +void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds) |
| 3026 | +{ |
| 3027 | + // Check whether we have too many outbound peers |
| 3028 | + int extra_peers = connman->GetExtraOutboundCount(); |
| 3029 | + if (extra_peers > 0) { |
| 3030 | + // If we have more outbound peers than we target, disconnect one. |
| 3031 | + // Pick the outbound peer that least recently announced |
| 3032 | + // us a new block, with ties broken by choosing the more recent |
| 3033 | + // connection (higher node id) |
| 3034 | + NodeId worst_peer = -1; |
| 3035 | + int64_t oldest_block_announcement = std::numeric_limits<int64_t>::max(); |
| 3036 | + |
| 3037 | + LOCK(cs_main); |
| 3038 | + |
| 3039 | + connman->ForEachNode([&](CNode* pnode) { |
| 3040 | + // Ignore non-outbound peers, or nodes marked for disconnect already |
| 3041 | + if (!IsOutboundDisconnectionCandidate(pnode) || pnode->fDisconnect) return; |
| 3042 | + CNodeState *state = State(pnode->GetId()); |
| 3043 | + if (state == nullptr) return; // shouldn't be possible, but just in case |
| 3044 | + // Don't evict our protected peers |
| 3045 | + if (state->m_chain_sync.m_protect) return; |
| 3046 | + if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) { |
| 3047 | + worst_peer = pnode->GetId(); |
| 3048 | + oldest_block_announcement = state->m_last_block_announcement; |
| 3049 | + } |
| 3050 | + }); |
| 3051 | + if (worst_peer != -1) { |
| 3052 | + bool disconnected = connman->ForNode(worst_peer, [&](CNode *pnode) { |
| 3053 | + // Only disconnect a peer that has been connected to us for |
| 3054 | + // some reasonable fraction of our check-frequency, to give |
| 3055 | + // it time for new information to have arrived. |
| 3056 | + // Also don't disconnect any peer we're trying to download a |
| 3057 | + // block from. |
| 3058 | + CNodeState &state = *State(pnode->GetId()); |
| 3059 | + if (time_in_seconds - pnode->nTimeConnected > MINIMUM_CONNECT_TIME && state.nBlocksInFlight == 0) { |
| 3060 | + LogPrint(BCLog::NET, "disconnecting extra outbound peer=%d (last block announcement received at time %d)\n", pnode->GetId(), oldest_block_announcement); |
| 3061 | + pnode->fDisconnect = true; |
| 3062 | + return true; |
| 3063 | + } else { |
| 3064 | + LogPrint(BCLog::NET, "keeping outbound peer=%d chosen for eviction (connect time: %d, blocks_in_flight: %d)\n", pnode->GetId(), pnode->nTimeConnected, state.nBlocksInFlight); |
| 3065 | + return false; |
| 3066 | + } |
| 3067 | + }); |
| 3068 | + if (disconnected) { |
| 3069 | + // If we disconnected an extra peer, that means we successfully |
| 3070 | + // connected to at least one peer after the last time we |
| 3071 | + // detected a stale tip. Don't try any more extra peers until |
| 3072 | + // we next detect a stale tip, to limit the load we put on the |
| 3073 | + // network from these extra connections. |
| 3074 | + connman->SetTryNewOutboundPeer(false); |
| 3075 | + } |
| 3076 | + } |
| 3077 | + } |
| 3078 | +} |
| 3079 | + |
| 3080 | +void PeerLogicValidation::CheckForStaleTipAndEvictPeers(const Consensus::Params &consensusParams) |
| 3081 | +{ |
| 3082 | + if (connman == nullptr) return; |
| 3083 | + |
| 3084 | + int64_t time_in_seconds = GetTime(); |
| 3085 | + |
| 3086 | + EvictExtraOutboundPeers(time_in_seconds); |
| 3087 | + |
| 3088 | + if (time_in_seconds > m_stale_tip_check_time) { |
| 3089 | + LOCK(cs_main); |
| 3090 | + // Check whether our tip is stale, and if so, allow using an extra |
| 3091 | + // outbound peer |
| 3092 | + if (TipMayBeStale(consensusParams)) { |
| 3093 | + 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); |
| 3094 | + connman->SetTryNewOutboundPeer(true); |
| 3095 | + } else if (connman->GetTryNewOutboundPeer()) { |
| 3096 | + connman->SetTryNewOutboundPeer(false); |
| 3097 | + } |
| 3098 | + m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL; |
| 3099 | + } |
| 3100 | +} |
| 3101 | + |
3007 | 3102 | class CompareInvMempoolOrder
|
3008 | 3103 | {
|
3009 | 3104 | CTxMemPool *mp;
|
|
0 commit comments