Skip to content

Commit cb65ac4

Browse files
committed
Merge bitcoin/bitcoin#29605: net: Favor peers from addrman over fetching seednodes
6eeb188 test: adds seednode functional tests (Sergi Delgado Segura) 3270f0a net: Favor peers from addrman over fetching seednodes (Sergi Delgado Segura) Pull request description: This is a follow-up of #28016 motivated by bitcoin/bitcoin#28016 (review) and bitcoin/bitcoin#28016 (comment). The current behavior of seednode fetching is pretty eager: we do it as the first step under `ThreadOpenNetworkConnections` even if some peers may be queryable from our addrman. This poses two potential issues: - First, if permanently set (e.g. running with seednode in a config file) we'd be signaling such seed every time we restart our node - Second, we will be giving the seed node way too much influence over our addrman, populating the latter with data from the former even when unnecessary This changes the behavior to only add seednodes to `m_addr_fetch` if our addrman is empty, or little by little after we've spent some time trying addresses from our addrman. Also, seednodes are added to `m_addr_fetch` in random order, to avoid signaling the same node in case more than one seed is added and we happen to try them over multiple restarts ACKs for top commit: achow101: ACK 6eeb188 cbergqvist: ACK 6eeb188 itornaza: Tested ACK 6eeb188 tdb3: ACK 6eeb188 Tree-SHA512: b04445412f22018852d6bef4d3f1e88425ee6ddb434f61dcffa9e0c41b8e31f8c56f83858d5c7686289c86dc4c9476c437df15ea61a47082e2bb2e073cc62f15
2 parents b8d2f58 + 6eeb188 commit cb65ac4

File tree

4 files changed

+92
-9
lines changed

4 files changed

+92
-9
lines changed

src/net.cpp

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15};
6464
/** Number of DNS seeds to query when the number of connections is low. */
6565
static constexpr int DNSSEEDS_TO_QUERY_AT_ONCE = 3;
6666

67+
/** Minimum number of outbound connections under which we will keep fetching our address seeds. */
68+
static constexpr int SEED_OUTBOUND_CONNECTION_THRESHOLD = 2;
69+
6770
/** How long to delay before querying DNS seeds
6871
*
6972
* If we have more than THRESHOLD entries in addrman, then it's likely
@@ -2179,7 +2182,6 @@ void CConnman::WakeMessageHandler()
21792182

21802183
void CConnman::ThreadDNSAddressSeed()
21812184
{
2182-
constexpr int TARGET_OUTBOUND_CONNECTIONS = 2;
21832185
int outbound_connection_count = 0;
21842186

21852187
if (gArgs.IsArgSet("-seednode")) {
@@ -2198,7 +2200,7 @@ void CConnman::ThreadDNSAddressSeed()
21982200
}
21992201

22002202
outbound_connection_count = GetFullOutboundConnCount();
2201-
if (outbound_connection_count >= TARGET_OUTBOUND_CONNECTIONS) {
2203+
if (outbound_connection_count >= SEED_OUTBOUND_CONNECTION_THRESHOLD) {
22022204
LogPrintf("P2P peers available. Finished fetching data from seed nodes.\n");
22032205
break;
22042206
}
@@ -2221,7 +2223,7 @@ void CConnman::ThreadDNSAddressSeed()
22212223
}
22222224

22232225
// Proceed with dnsseeds if seednodes hasn't reached the target or if forcednsseed is set
2224-
if (outbound_connection_count < TARGET_OUTBOUND_CONNECTIONS || seeds_right_now) {
2226+
if (outbound_connection_count < SEED_OUTBOUND_CONNECTION_THRESHOLD || seeds_right_now) {
22252227
// goal: only query DNS seed if address need is acute
22262228
// * If we have a reasonable number of peers in addrman, spend
22272229
// some time trying them first. This improves user privacy by
@@ -2252,7 +2254,7 @@ void CConnman::ThreadDNSAddressSeed()
22522254
if (!interruptNet.sleep_for(w)) return;
22532255
to_wait -= w;
22542256

2255-
if (GetFullOutboundConnCount() >= TARGET_OUTBOUND_CONNECTIONS) {
2257+
if (GetFullOutboundConnCount() >= SEED_OUTBOUND_CONNECTION_THRESHOLD) {
22562258
if (found > 0) {
22572259
LogPrintf("%d addresses found from DNS seeds\n", found);
22582260
LogPrintf("P2P peers available. Finished DNS seeding.\n");
@@ -2447,7 +2449,7 @@ bool CConnman::MaybePickPreferredNetwork(std::optional<Network>& network)
24472449
return false;
24482450
}
24492451

2450-
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
2452+
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, Span<const std::string> seed_nodes)
24512453
{
24522454
AssertLockNotHeld(m_unused_i2p_sessions_mutex);
24532455
AssertLockNotHeld(m_reconnections_mutex);
@@ -2487,12 +2489,28 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
24872489
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
24882490
const bool use_seednodes{gArgs.IsArgSet("-seednode")};
24892491

2492+
auto seed_node_timer = NodeClock::now();
2493+
bool add_addr_fetch{addrman.Size() == 0 && !seed_nodes.empty()};
2494+
constexpr std::chrono::seconds ADD_NEXT_SEEDNODE = 10s;
2495+
24902496
if (!add_fixed_seeds) {
24912497
LogPrintf("Fixed seeds are disabled\n");
24922498
}
24932499

24942500
while (!interruptNet)
24952501
{
2502+
if (add_addr_fetch) {
2503+
add_addr_fetch = false;
2504+
const auto& seed{SpanPopBack(seed_nodes)};
2505+
AddAddrFetch(seed);
2506+
2507+
if (addrman.Size() == 0) {
2508+
LogInfo("Empty addrman, adding seednode (%s) to addrfetch\n", seed);
2509+
} else {
2510+
LogInfo("Couldn't connect to peers from addrman after %d seconds. Adding seednode (%s) to addrfetch\n", ADD_NEXT_SEEDNODE.count(), seed);
2511+
}
2512+
}
2513+
24962514
ProcessAddrFetch();
24972515

24982516
if (!interruptNet.sleep_for(std::chrono::milliseconds(500)))
@@ -2593,6 +2611,13 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
25932611
}
25942612
}
25952613

2614+
if (!seed_nodes.empty() && nOutboundFullRelay < SEED_OUTBOUND_CONNECTION_THRESHOLD) {
2615+
if (NodeClock::now() > seed_node_timer + ADD_NEXT_SEEDNODE) {
2616+
seed_node_timer = NodeClock::now();
2617+
add_addr_fetch = true;
2618+
}
2619+
}
2620+
25962621
ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
25972622
auto now = GetTime<std::chrono::microseconds>();
25982623
bool anchor = false;
@@ -3249,8 +3274,10 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
32493274
i2p_sam, &interruptNet);
32503275
}
32513276

3252-
for (const auto& strDest : connOptions.vSeedNodes) {
3253-
AddAddrFetch(strDest);
3277+
// Randomize the order in which we may query seednode to potentially prevent connecting to the same one every restart (and signal that we have restarted)
3278+
std::vector<std::string> seed_nodes = connOptions.vSeedNodes;
3279+
if (!seed_nodes.empty()) {
3280+
std::shuffle(seed_nodes.begin(), seed_nodes.end(), FastRandomContext{});
32543281
}
32553282

32563283
if (m_use_addrman_outgoing) {
@@ -3311,7 +3338,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
33113338
if (connOptions.m_use_addrman_outgoing || !connOptions.m_specified_outgoing.empty()) {
33123339
threadOpenConnections = std::thread(
33133340
&util::TraceThread, "opencon",
3314-
[this, connect = connOptions.m_specified_outgoing] { ThreadOpenConnections(connect); });
3341+
[this, connect = connOptions.m_specified_outgoing, seed_nodes = std::move(seed_nodes)] { ThreadOpenConnections(connect, seed_nodes); });
33153342
}
33163343

33173344
// Process messages

src/net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,7 @@ class CConnman
12731273
void ThreadOpenAddedConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_unused_i2p_sessions_mutex, !m_reconnections_mutex);
12741274
void AddAddrFetch(const std::string& strDest) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex);
12751275
void ProcessAddrFetch() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_unused_i2p_sessions_mutex);
1276-
void ThreadOpenConnections(std::vector<std::string> connect) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex, !m_unused_i2p_sessions_mutex, !m_reconnections_mutex);
1276+
void ThreadOpenConnections(std::vector<std::string> connect, Span<const std::string> seed_nodes) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex, !m_unused_i2p_sessions_mutex, !m_reconnections_mutex);
12771277
void ThreadMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc);
12781278
void ThreadI2PAcceptIncoming();
12791279
void AcceptConnection(const ListenSocket& hListenSocket);

test/functional/p2p_seednode.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2019-2021 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
"""
7+
Test seednode interaction with the AddrMan
8+
"""
9+
import random
10+
import time
11+
12+
from test_framework.test_framework import BitcoinTestFramework
13+
14+
ADD_NEXT_SEEDNODE = 10
15+
16+
17+
class P2PSeedNodes(BitcoinTestFramework):
18+
def set_test_params(self):
19+
self.num_nodes = 1
20+
self.disable_autoconnect = False
21+
22+
def test_no_seednode(self):
23+
# Check that if no seednode is provided, the node proceeds as usual (without waiting)
24+
with self.nodes[0].assert_debug_log(expected_msgs=[], unexpected_msgs=["Empty addrman, adding seednode", f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode"], timeout=ADD_NEXT_SEEDNODE):
25+
self.restart_node(0)
26+
27+
def test_seednode_empty_addrman(self):
28+
seed_node = "0.0.0.1"
29+
# Check that the seednode is added to m_addr_fetches on bootstrap on an empty addrman
30+
with self.nodes[0].assert_debug_log(expected_msgs=[f"Empty addrman, adding seednode ({seed_node}) to addrfetch"], timeout=ADD_NEXT_SEEDNODE):
31+
self.restart_node(0, extra_args=[f'-seednode={seed_node}'])
32+
33+
def test_seednode_addrman_unreachable_peers(self):
34+
seed_node = "0.0.0.2"
35+
node = self.nodes[0]
36+
# Fill the addrman with unreachable nodes
37+
for i in range(10):
38+
ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}"
39+
port = 8333 + i
40+
node.addpeeraddress(ip, port)
41+
42+
# Restart the node so seednode is processed again
43+
with node.assert_debug_log(expected_msgs=[f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode ({seed_node}) to addrfetch"], unexpected_msgs=["Empty addrman, adding seednode"], timeout=ADD_NEXT_SEEDNODE * 1.5):
44+
self.restart_node(0, extra_args=[f'-seednode={seed_node}'])
45+
node.setmocktime(int(time.time()) + ADD_NEXT_SEEDNODE + 1)
46+
47+
def run_test(self):
48+
self.test_no_seednode()
49+
self.test_seednode_empty_addrman()
50+
self.test_seednode_addrman_unreachable_peers()
51+
52+
53+
if __name__ == '__main__':
54+
P2PSeedNodes(__file__).main()
55+

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@
406406
'feature_shutdown.py',
407407
'wallet_migration.py',
408408
'p2p_ibd_txrelay.py',
409+
'p2p_seednode.py',
409410
# Don't append tests at the end to avoid merge conflicts
410411
# Put them in a random line within the section that fits their approximate run-time
411412
]

0 commit comments

Comments
 (0)