Skip to content

Commit 00bccad

Browse files
committed
AGENT: Add peer rotation
1 parent d198635 commit 00bccad

6 files changed

Lines changed: 265 additions & 0 deletions

File tree

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
204204
kernel/mempool_removal_reason.cpp
205205
mapport.cpp
206206
net.cpp
207+
net_ibd_peer.cpp
207208
net_processing.cpp
208209
netgroup.cpp
209210
node/abort.cpp

src/init.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
21112111
connOptions.whitelist_relay = args.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY);
21122112
connOptions.m_capture_messages = args.GetBoolArg("-capturemessages", false);
21132113

2114+
// Set up IBD check callback for preferential peer selection during IBD
2115+
connOptions.m_is_in_ibd_callback = [&node]() {
2116+
return node.chainman->IsInitialBlockDownload();
2117+
};
2118+
21142119
// Port to bind to if `-bind=addr` is provided without a `:port` suffix.
21152120
const uint16_t default_bind_port =
21162121
static_cast<uint16_t>(args.GetIntArg("-port", Params().GetDefaultPort()));

src/net.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include <net.h>
99

10+
#include <net_ibd_peer.h>
11+
1012
#include <addrdb.h>
1113
#include <addrman.h>
1214
#include <banman.h>
@@ -37,6 +39,10 @@
3739
#include <util/translation.h>
3840
#include <util/vector.h>
3941

42+
// Forward declarations for IBD peer selection
43+
std::chrono::seconds GetNextIBDEpoch();
44+
void EvaluateIBDPeers(CConnman& connman);
45+
4046
#include <algorithm>
4147
#include <array>
4248
#include <cmath>
@@ -2560,6 +2566,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, std
25602566
auto next_feeler = start + rng.rand_exp_duration(FEELER_INTERVAL);
25612567
auto next_extra_block_relay = start + rng.rand_exp_duration(EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
25622568
auto next_extra_network_peer{start + rng.rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)};
2569+
// Next IBD peer evaluation timestamp
2570+
auto next_ibd_peer_eval = start + GetNextIBDEpoch();
25632571
const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED);
25642572
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
25652573
const bool use_seednodes{!gArgs.GetArgs("-seednode").empty()};
@@ -2593,6 +2601,13 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, std
25932601

25942602
PerformReconnections();
25952603

2604+
// Evaluate IBD peers periodically to find better connections
2605+
auto now_ibd = GetTime<std::chrono::microseconds>();
2606+
if (now_ibd > next_ibd_peer_eval) {
2607+
EvaluateIBDPeers(*this);
2608+
next_ibd_peer_eval = now_ibd + GetNextIBDEpoch();
2609+
}
2610+
25962611
CountingSemaphoreGrant<> grant(*semOutbound);
25972612
if (m_interrupt_net->interrupted()) {
25982613
return;
@@ -4222,3 +4237,65 @@ std::function<void(const CAddress& addr,
42224237
std::span<const unsigned char> data,
42234238
bool is_incoming)>
42244239
CaptureMessage = CaptureMessageToFile;
4240+
4241+
// IBD Peer Selection Implementation
4242+
//
4243+
// During Initial Block Download, we want to connect to the best possible peers
4244+
// (low latency, high bandwidth) to speed up sync. This function is called
4245+
// periodically to evaluate our peers and potentially swap to better ones.
4246+
4247+
std::chrono::seconds GetNextIBDEpoch()
4248+
{
4249+
// Configurable epoch length
4250+
static const auto epoch_length = std::chrono::seconds{gArgs.GetArg("-ibd-peer-epoch", DEFAULT_IBD_PEER_EPOCH_LENGTH.count())};
4251+
return epoch_length;
4252+
}
4253+
4254+
void EvaluateIBDPeers(CConnman& connman)
4255+
{
4256+
// Only run if IBD check callback is available
4257+
if (!connman.IsInIBD()) return;
4258+
4259+
// Get all full outbound relay peers
4260+
std::vector<CNode*> outbound_peers;
4261+
connman.ForEachNode([&outbound_peers](CNode* node) {
4262+
if (node->IsFullOutboundConn() && node->fSuccessfullyConnected) {
4263+
outbound_peers.push_back(node);
4264+
}
4265+
});
4266+
4267+
// Need minimum peers to consider swapping
4268+
if (outbound_peers.size() < IBD_PEER_MIN_PEERS) {
4269+
return;
4270+
}
4271+
4272+
// Calculate scores for all peers
4273+
std::vector<IBDPeerScore> scores;
4274+
for (const CNode* node : outbound_peers) {
4275+
auto score = CalculateIBDScore(node);
4276+
if (score) {
4277+
scores.push_back(*score);
4278+
}
4279+
}
4280+
4281+
if (scores.empty()) return;
4282+
4283+
// Sort by score (highest first)
4284+
std::sort(scores.begin(), scores.end(), [](const IBDPeerScore& a, const IBDPeerScore& b) {
4285+
return a.CalculateScore() > b.CalculateScore();
4286+
});
4287+
4288+
// Log scores for debugging
4289+
for (const auto& score : scores) {
4290+
LogIBDPeerScore(score);
4291+
}
4292+
4293+
// If the lowest scoring peer is below threshold, try to find a better one
4294+
const auto& lowest = scores.back();
4295+
if (lowest.CalculateScore() < IBD_PEER_SCORE_THRESHOLD) {
4296+
// Mark this peer for potential eviction
4297+
// We'll request a new outbound connection attempt
4298+
connman.SetTryNewOutboundPeer(true);
4299+
LogDebug(BCLog::NET, "IBD: Low scoring peer detected, attempting to find better peer\n");
4300+
}
4301+
}

src/net.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,9 @@ class CConnman
10991099
bool whitelist_forcerelay = DEFAULT_WHITELISTFORCERELAY;
11001100
bool whitelist_relay = DEFAULT_WHITELISTRELAY;
11011101
bool m_capture_messages = false;
1102+
//! Callback to check if we're in Initial Block Download.
1103+
//! If set, used for preferential peer selection during IBD.
1104+
std::function<bool()> m_is_in_ibd_callback;
11021105
};
11031106

11041107
void Init(const Options& connOptions) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_total_bytes_sent_mutex)
@@ -1115,6 +1118,7 @@ class CConnman
11151118
m_client_interface = connOptions.uiInterface;
11161119
m_banman = connOptions.m_banman;
11171120
m_msgproc = connOptions.m_msgproc;
1121+
m_is_in_ibd_callback = connOptions.m_is_in_ibd_callback;
11181122
nSendBufferMaxSize = connOptions.nSendBufferMaxSize;
11191123
nReceiveFloodSize = connOptions.nReceiveFloodSize;
11201124
m_peer_connect_timeout = std::chrono::seconds{connOptions.m_peer_connect_timeout};
@@ -1165,6 +1169,7 @@ class CConnman
11651169

11661170
void Interrupt() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc);
11671171
bool GetNetworkActive() const { return fNetworkActive; };
1172+
bool IsInIBD() const { return m_is_in_ibd_callback && m_is_in_ibd_callback(); };
11681173
bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; };
11691174
void SetNetworkActive(bool active);
11701175

@@ -1684,6 +1689,8 @@ class CConnman
16841689
NetEventsInterface* m_msgproc;
16851690
/** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */
16861691
BanMan* m_banman;
1692+
/** Callback to check if we're in Initial Block Download. */
1693+
std::function<bool()> m_is_in_ibd_callback;
16871694

16881695
/**
16891696
* Addresses that were saved during the previous clean shutdown. We'll

src/net_ibd_peer.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <net_ibd_peer.h>
6+
7+
#include <logging.h>
8+
#include <net.h>
9+
10+
#include <algorithm>
11+
#include <cmath>
12+
#include <limits>
13+
14+
std::chrono::microseconds GetPeerMinPing(const CNode* node)
15+
{
16+
if (!node) return std::chrono::microseconds::max();
17+
return node->m_min_ping_time.load();
18+
}
19+
20+
uint64_t GetPeerTotalBytes(const CNode* node)
21+
{
22+
if (!node) return 0;
23+
// These are plain uint64_t members in CNode, not atomic
24+
return node->nSendBytes + node->nRecvBytes;
25+
}
26+
27+
std::optional<IBDPeerScore> CalculateIBDScore(const CNode* node)
28+
{
29+
if (!node) return std::nullopt;
30+
31+
// Only score outbound full relay peers
32+
if (!node->IsFullOutboundConn()) return std::nullopt;
33+
34+
// Need at least one successful ping
35+
auto min_ping = GetPeerMinPing(node);
36+
if (min_ping == std::chrono::microseconds::max() || min_ping.count() <= 0) {
37+
return std::nullopt;
38+
}
39+
40+
IBDPeerScore score;
41+
score.node_id = node->GetId();
42+
score.min_ping = min_ping;
43+
score.total_bytes = GetPeerTotalBytes(node);
44+
// m_connected is a const member, access it directly
45+
score.connected_time = node->m_connected;
46+
47+
return score;
48+
}
49+
50+
double IBDPeerScore::CalculateScore() const
51+
{
52+
// Normalize ping time: assume 0ms - 5000ms range, lower is better
53+
// Normalize to [0, 1] where 1 is best (lowest ping)
54+
constexpr double MAX_PING_US = 5'000'000.0; // 5 seconds
55+
double ping_val = static_cast<double>(min_ping.count());
56+
double ping_score = 1.0 - (std::min(ping_val, MAX_PING_US) / MAX_PING_US);
57+
58+
// Normalize bandwidth: assume 0 - 100MB range, higher is better
59+
constexpr double MAX_BYTES = 100'000'000.0; // 100 MB
60+
double bandwidth_score = std::min(static_cast<double>(total_bytes), MAX_BYTES) / MAX_BYTES;
61+
62+
// Connection stability: longer is better, cap at 10 minutes
63+
constexpr auto MAX_STABLE_TIME = std::chrono::minutes{10};
64+
double stability_score = 1.0;
65+
if (connected_time > std::chrono::seconds{1}) {
66+
stability_score = std::min(
67+
static_cast<double>(connected_time.count()) / static_cast<double>(MAX_STABLE_TIME.count()),
68+
1.0
69+
);
70+
}
71+
72+
// Weighted score: bandwidth 50%, latency 35%, stability 15%
73+
// During IBD, bandwidth is critical
74+
double final_score = (bandwidth_score * 0.5) + (ping_score * 0.35) + (stability_score * 0.15);
75+
76+
return final_score;
77+
}
78+
79+
bool IBDPeerScore::ShouldReplace() const
80+
{
81+
return poor_epoch_count >= IBD_PEER_EPOCHS_BEFORE_DISCONNECT;
82+
}
83+
84+
void LogIBDPeerScore(const IBDPeerScore& score)
85+
{
86+
LogDebug(BCLog::NET, "IBD Peer %lld: ping=%lluus, bytes=%llu, time=%lds, score=%.2f, poor_epochs=%d\n",
87+
static_cast<long long>(score.node_id),
88+
static_cast<unsigned long long>(score.min_ping.count()),
89+
static_cast<unsigned long long>(score.total_bytes),
90+
static_cast<long>(score.connected_time.count()),
91+
score.CalculateScore(),
92+
score.poor_epoch_count);
93+
}

src/net_ibd_peer.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_NET_IBD_PEER_H
6+
#define BITCOIN_NET_IBD_PEER_H
7+
8+
#include <chrono>
9+
#include <cstdint>
10+
#include <optional>
11+
12+
class CNode;
13+
14+
/**
15+
* IBD peer scoring constants and configuration.
16+
* These can be adjusted to tune the preferential peer behavior during Initial Block Download.
17+
*/
18+
19+
// Default epoch duration: how often we evaluate peer quality during IBD
20+
static constexpr std::chrono::seconds DEFAULT_IBD_PEER_EPOCH_LENGTH{60};
21+
22+
// Minimum number of full relay peers required before we consider swapping
23+
static constexpr int IBD_PEER_MIN_PEERS{2};
24+
25+
// Score threshold below which we try to find a better peer (lower = stricter)
26+
static constexpr double IBD_PEER_SCORE_THRESHOLD{0.5};
27+
28+
// Number of epochs a peer must perform poorly before we disconnect
29+
static constexpr int IBD_PEER_EPOCHS_BEFORE_DISCONNECT{3};
30+
31+
// Maximum attempts to find a better peer per epoch
32+
static constexpr int IBD_PEER_MAX_SWAP_ATTEMPTS{5};
33+
34+
/**
35+
* IBDPeerScore - tracks peer quality metrics during Initial Block Download
36+
*/
37+
struct IBDPeerScore {
38+
int64_t node_id{0};
39+
std::chrono::microseconds min_ping{std::chrono::microseconds::max()};
40+
uint64_t total_bytes{0};
41+
std::chrono::seconds connected_time{std::chrono::seconds::zero()};
42+
int poor_epoch_count{0}; // Number of epochs peer scored below threshold
43+
44+
/**
45+
* Calculate a normalized score for this peer.
46+
* Higher is better. Range: [0.0, 1.0]
47+
*
48+
* Score is based on:
49+
* - Latency (ping time) - lower is better
50+
* - Bandwidth (total bytes transferred) - higher is better
51+
* - Connection stability (time connected) - longer is better
52+
*/
53+
double CalculateScore() const;
54+
55+
/**
56+
* Check if this peer should be considered for replacement.
57+
*/
58+
bool ShouldReplace() const;
59+
};
60+
61+
/**
62+
* Get the minimum ping time for a peer in microseconds.
63+
*/
64+
std::chrono::microseconds GetPeerMinPing(const CNode* node);
65+
66+
/**
67+
* Get total bytes transferred (send + receive) for a peer.
68+
*/
69+
uint64_t GetPeerTotalBytes(const CNode* node);
70+
71+
/**
72+
* Calculate peer score for IBD purposes.
73+
* Returns nullopt if not enough data to score.
74+
*/
75+
std::optional<IBDPeerScore> CalculateIBDScore(const CNode* node);
76+
77+
/**
78+
* Log IBD peer scoring information.
79+
*/
80+
void LogIBDPeerScore(const IBDPeerScore& score);
81+
82+
#endif // BITCOIN_NET_IBD_PEER_H

0 commit comments

Comments
 (0)