Skip to content

Commit ac7b37c

Browse files
committed
Connect to an extra outbound peer if our tip is stale
If our tip hasn't updated in a while, that may be because our peers are not relaying blocks to us that we would consider valid. Allow connection to an additional outbound peer in that circumstance. Also, periodically check to see if we are exceeding our target number of outbound peers, and disconnect the one which has least recently announced a new block to us (choosing the newest such peer in the case of tie).
1 parent db32a65 commit ac7b37c

File tree

5 files changed

+114
-6
lines changed

5 files changed

+114
-6
lines changed

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
12701270
g_connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max())));
12711271
CConnman& connman = *g_connman;
12721272

1273-
peerLogic.reset(new PeerLogicValidation(&connman));
1273+
peerLogic.reset(new PeerLogicValidation(&connman, scheduler));
12741274
RegisterValidationInterface(peerLogic.get());
12751275

12761276
// sanitize comments per BIP-0014, format user agent and check total size

src/net.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,7 @@ bool CConnman::GetTryNewOutboundPeer()
17011701
void CConnman::SetTryNewOutboundPeer(bool flag)
17021702
{
17031703
m_try_another_outbound_peer = flag;
1704+
LogPrint(BCLog::NET, "net: setting try another outbound peer=%s\n", flag ? "true" : "false");
17041705
}
17051706

17061707
// Return the number of peers we have over our outbound connection limit

src/net_processing.cpp

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "primitives/transaction.h"
2424
#include "random.h"
2525
#include "reverse_iterator.h"
26+
#include "scheduler.h"
2627
#include "tinyformat.h"
2728
#include "txmempool.h"
2829
#include "ui_interface.h"
@@ -127,7 +128,6 @@ namespace {
127128
/** Number of outbound peers with m_chain_sync.m_protect. */
128129
int g_outbound_peers_with_protect_from_disconnect = 0;
129130

130-
131131
/** When our tip was last updated. */
132132
int64_t g_last_tip_update = 0;
133133

@@ -435,6 +435,15 @@ void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) {
435435
}
436436
}
437437

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+
438447
// Requires cs_main
439448
bool CanDirectFetch(const Consensus::Params &consensusParams)
440449
{
@@ -772,9 +781,17 @@ static bool StaleBlockRequestAllowed(const CBlockIndex* pindex, const Consensus:
772781
(GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT);
773782
}
774783

775-
PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn) : connman(connmanIn) {
784+
PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler) : connman(connmanIn), m_stale_tip_check_time(0) {
776785
// Initialize global variables that cannot be constructed at startup.
777786
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);
778795
}
779796

780797
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
14241441
// If this is an outbound peer, check to see if we should protect
14251442
// it from the bad/lagging chain logic.
14261443
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());
14271445
nodestate->m_chain_sync.m_protect = true;
14281446
++g_outbound_peers_with_protect_from_disconnect;
14291447
}
@@ -3004,6 +3022,83 @@ void PeerLogicValidation::ConsiderEviction(CNode *pto, int64_t time_in_seconds)
30043022
}
30053023
}
30063024

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+
30073102
class CompareInvMempoolOrder
30083103
{
30093104
CTxMemPool *mp;

src/net_processing.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "net.h"
1010
#include "validationinterface.h"
11+
#include "consensus/params.h"
1112

1213
/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */
1314
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;
@@ -27,13 +28,19 @@ static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1000; // 1ms/head
2728
static constexpr int32_t MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT = 4;
2829
/** Timeout for (unprotected) outbound peers to sync to our chainwork, in seconds */
2930
static constexpr int64_t CHAIN_SYNC_TIMEOUT = 20 * 60; // 20 minutes
31+
/** How frequently to check for stale tips, in seconds */
32+
static constexpr int64_t STALE_CHECK_INTERVAL = 10 * 60; // 10 minutes
33+
/** How frequently to check for extra outbound peers and disconnect, in seconds */
34+
static constexpr int64_t EXTRA_PEER_CHECK_INTERVAL = 45;
35+
/** Minimum time an outbound-peer-eviction candidate must be connected for, in order to evict, in seconds */
36+
static constexpr int64_t MINIMUM_CONNECT_TIME = 30;
3037

3138
class PeerLogicValidation : public CValidationInterface, public NetEventsInterface {
3239
private:
33-
CConnman* connman;
40+
CConnman* const connman;
3441

3542
public:
36-
explicit PeerLogicValidation(CConnman* connman);
43+
explicit PeerLogicValidation(CConnman* connman, CScheduler &scheduler);
3744

3845
void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) override;
3946
void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override;
@@ -55,6 +62,11 @@ class PeerLogicValidation : public CValidationInterface, public NetEventsInterfa
5562
bool SendMessages(CNode* pto, std::atomic<bool>& interrupt) override;
5663

5764
void ConsiderEviction(CNode *pto, int64_t time_in_seconds);
65+
void CheckForStaleTipAndEvictPeers(const Consensus::Params &consensusParams);
66+
void EvictExtraOutboundPeers(int64_t time_in_seconds);
67+
68+
private:
69+
int64_t m_stale_tip_check_time; //! Next time to check for stale tip
5870
};
5971

6072
struct CNodeStateStats {

src/test/test_bitcoin.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
8686
threadGroup.create_thread(&ThreadScriptCheck);
8787
g_connman = std::unique_ptr<CConnman>(new CConnman(0x1337, 0x1337)); // Deterministic randomness for tests.
8888
connman = g_connman.get();
89-
peerLogic.reset(new PeerLogicValidation(connman));
89+
peerLogic.reset(new PeerLogicValidation(connman, scheduler));
9090
}
9191

9292
TestingSetup::~TestingSetup()

0 commit comments

Comments
 (0)