Skip to content

Commit

Permalink
net: Favor peers from addrman over fetching seednodes
Browse files Browse the repository at this point in the history
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 even 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
  • Loading branch information
sr-gi committed May 1, 2024
1 parent 842f7fd commit fa9a0de
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 7 deletions.
42 changes: 35 additions & 7 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15};
/** Number of DNS seeds to query when the number of connections is low. */
static constexpr int DNSSEEDS_TO_QUERY_AT_ONCE = 3;

/** Minimum number of outbound connections under which we will keep fetching our address seeds. */
static constexpr int SEED_OUTBOUND_CONNECTION_THRESHOLD = 2;

/** How long to delay before querying DNS seeds
*
* If we have more than THRESHOLD entries in addrman, then it's likely
Expand Down Expand Up @@ -2183,7 +2186,6 @@ void CConnman::WakeMessageHandler()

void CConnman::ThreadDNSAddressSeed()
{
constexpr int TARGET_OUTBOUND_CONNECTIONS = 2;
int outbound_connection_count = 0;

if (gArgs.IsArgSet("-seednode")) {
Expand All @@ -2202,7 +2204,7 @@ void CConnman::ThreadDNSAddressSeed()
}

outbound_connection_count = GetFullOutboundConnCount();
if (outbound_connection_count >= TARGET_OUTBOUND_CONNECTIONS) {
if (outbound_connection_count >= SEED_OUTBOUND_CONNECTION_THRESHOLD) {
LogPrintf("P2P peers available. Finished fetching data from seed nodes.\n");
break;
}
Expand All @@ -2225,7 +2227,7 @@ void CConnman::ThreadDNSAddressSeed()
}

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

if (GetFullOutboundConnCount() >= TARGET_OUTBOUND_CONNECTIONS) {
if (GetFullOutboundConnCount() >= SEED_OUTBOUND_CONNECTION_THRESHOLD) {
if (found > 0) {
LogPrintf("%d addresses found from DNS seeds\n", found);
LogPrintf("P2P peers available. Finished DNS seeding.\n");
Expand Down Expand Up @@ -2491,16 +2493,33 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
const bool use_seednodes{gArgs.IsArgSet("-seednode")};

auto seed_node_timer = NodeClock::now();
bool add_addr_fetch{addrman.Size() == 0 && !m_seed_nodes.empty()};
constexpr std::chrono::seconds ADD_NEXT_SEEDNODE = 10s;

if (!add_fixed_seeds) {
LogPrintf("Fixed seeds are disabled\n");
}

while (!interruptNet)
{
if (add_addr_fetch) {
add_addr_fetch = false;
auto seed = m_seed_nodes.back();
m_seed_nodes.pop_back();
AddAddrFetch(seed);

if (addrman.Size() == 0) {
LogPrintf("Empty addrman, adding seednode (%s) to addrfetch\n", seed);
} else {
LogPrintf("Couldn't connect to peers from addrman after %d seconds. Adding seednode (%s) to addrfetch\n", ADD_NEXT_SEEDNODE.count(), seed);
}
}

ProcessAddrFetch();

if (!interruptNet.sleep_for(std::chrono::milliseconds(500)))
return;
return;

PerformReconnections();

Expand Down Expand Up @@ -2597,6 +2616,13 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
}
}

if (!m_seed_nodes.empty() && nOutboundFullRelay < SEED_OUTBOUND_CONNECTION_THRESHOLD) {
if (NodeClock::now() > seed_node_timer + ADD_NEXT_SEEDNODE) {
seed_node_timer = NodeClock::now();
add_addr_fetch = true;
}
}

ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
auto now = GetTime<std::chrono::microseconds>();
bool anchor = false;
Expand Down Expand Up @@ -3242,8 +3268,10 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
i2p_sam, &interruptNet);
}

for (const auto& strDest : connOptions.vSeedNodes) {
AddAddrFetch(strDest);
// 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)
if (!connOptions.vSeedNodes.empty()) {
m_seed_nodes = connOptions.vSeedNodes;
Shuffle(m_seed_nodes.begin(), m_seed_nodes.end(), FastRandomContext{});
}

if (m_use_addrman_outgoing) {
Expand Down
2 changes: 2 additions & 0 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,8 @@ class CConnman
mutable RecursiveMutex m_nodes_mutex;
std::atomic<NodeId> nLastNodeId{0};
unsigned int nPrevNodeCount{0};
// Collection of seed nodes, populated by connOptions.vSeedNodes
std::vector<std::string> m_seed_nodes;

// Stores number of full-tx connections (outbound and manual) per network
std::array<unsigned int, Network::NET_MAX> m_network_conn_counts GUARDED_BY(m_nodes_mutex) = {};
Expand Down
50 changes: 50 additions & 0 deletions test/functional/p2p_seednode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
# Copyright (c) 2019-2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

"""
Test seednode interaction with the AddrMan
"""
import random

from test_framework.test_framework import BitcoinTestFramework


class P2PSeedNodes(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.disable_autoconnect = False

def test_no_seednode(self):
# Check that if no seednode is provided, the node proceeds as usual (without waiting)
with self.nodes[0].assert_debug_log(expected_msgs=[], unexpected_msgs=["Empty addrman, adding seednode", "Couldn't connect to peers from addrman after 10 seconds. Adding seednode"], timeout=10):
self.restart_node(0)

def test_seednode_empty_addrman(self):
seed_node = "0.0.0.1"
# Check that the seednode is added to m_addr_fetches on bootstrap on an empty addrman
with self.nodes[0].assert_debug_log(expected_msgs=[f"Empty addrman, adding seednode ({seed_node}) to addrfetch"], timeout=10):
self.restart_node(0, extra_args=[f'-seednode={seed_node}'])

def test_seednode_addrman_unreachable_peers(self):
seed_node = "0.0.0.2"
# Fill the addrman with unreachable nodes
for i in range(10):
ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}"
port = 8333 + i
self.nodes[0].addpeeraddress(ip, port)

# Restart the node so seednode is processed again
with self.nodes[0].assert_debug_log(expected_msgs=[f"Couldn't connect to peers from addrman after 10 seconds. Adding seednode ({seed_node}) to addrfetch"], unexpected_msgs=["Empty addrman, adding seednode"], timeout=20):
self.restart_node(0, extra_args=[f'-seednode={seed_node}'])

def run_test(self):
self.test_no_seednode()
self.test_seednode_empty_addrman()
self.test_seednode_addrman_unreachable_peers()


if __name__ == '__main__':
P2PSeedNodes().main()

1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@
'feature_shutdown.py',
'wallet_migration.py',
'p2p_ibd_txrelay.py',
'p2p_seednode.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
]
Expand Down

0 comments on commit fa9a0de

Please sign in to comment.