Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2021-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <netaddress.h>6#include <net.h>7#include <test/util/net.h>8#include <test/util/setup_common.h>10#include <boost/test/unit_test.hpp>12#include <algorithm>13#include <functional>14#include <optional>15#include <unordered_set>16#include <vector>18BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup)20// Create `num_peers` random nodes, apply setup function `candidate_setup_fn`,21// call ProtectEvictionCandidatesByRatio() to apply protection logic, and then22// return true if all of `protected_peer_ids` and none of `unprotected_peer_ids`23// are protected from eviction, i.e. removed from the eviction candidates.24bool IsProtected(int num_peers,25std::function<void(NodeEvictionCandidate&)> candidate_setup_fn,26const std::unordered_set<NodeId>& protected_peer_ids,27const std::unordered_set<NodeId>& unprotected_peer_ids,28FastRandomContext& random_context)29{30std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};31for (NodeEvictionCandidate& candidate : candidates) {32candidate_setup_fn(candidate);33}34std::shuffle(candidates.begin(), candidates.end(), random_context);36const size_t size{candidates.size()};37const size_t expected{size - size / 2}; // Expect half the candidates will be protected.38ProtectEvictionCandidatesByRatio(candidates);39BOOST_CHECK_EQUAL(candidates.size(), expected);41size_t unprotected_count{0};42for (const NodeEvictionCandidate& candidate : candidates) {43if (protected_peer_ids.contains(candidate.id)) {44// this peer should have been removed from the eviction candidates45BOOST_TEST_MESSAGE(strprintf("expected candidate to be protected: %d", candidate.id));46return false;47}48if (unprotected_peer_ids.contains(candidate.id)) {49// this peer remains in the eviction candidates, as expected50++unprotected_count;51}52}54const bool is_protected{unprotected_count == unprotected_peer_ids.size()};55if (!is_protected) {56BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",57unprotected_peer_ids.size(), unprotected_count));58}59return is_protected;60}62BOOST_AUTO_TEST_CASE(peer_protection_test)63{64FastRandomContext random_context{true};65int num_peers{12};67// Expect half of the peers with greatest uptime (the lowest m_connected)68// to be protected from eviction.69BOOST_CHECK(IsProtected(70num_peers, [](NodeEvictionCandidate& c) {71c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};72c.m_is_local = false;73c.m_network = NET_IPV4;74},75/*protected_peer_ids=*/{0, 1, 2, 3, 4, 5},76/*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11},77random_context));79// Verify in the opposite direction.80BOOST_CHECK(IsProtected(81num_peers, [num_peers](NodeEvictionCandidate& c) {82c.m_connected = NodeSeconds{std::chrono::seconds{num_peers - c.id}};83c.m_is_local = false;84c.m_network = NET_IPV6;85},86/*protected_peer_ids=*/{6, 7, 8, 9, 10, 11},87/*unprotected_peer_ids=*/{0, 1, 2, 3, 4, 5},88random_context));90// Test protection of onion, localhost, and I2P peers...92// Expect 1/4 onion peers to be protected from eviction,93// if no localhost, I2P, or CJDNS peers.94BOOST_CHECK(IsProtected(95num_peers, [](NodeEvictionCandidate& c) {96c.m_is_local = false;97c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4;98},99/*protected_peer_ids=*/{3, 8, 9},100/*unprotected_peer_ids=*/{},101random_context));103// Expect 1/4 onion peers and 1/4 of the other peers to be protected,104// sorted by longest uptime (lowest m_connected), if no localhost, I2P or CJDNS peers.105BOOST_CHECK(IsProtected(106num_peers, [](NodeEvictionCandidate& c) {107c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};108c.m_is_local = false;109c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6;110},111/*protected_peer_ids=*/{0, 1, 2, 3, 8, 9},112/*unprotected_peer_ids=*/{4, 5, 6, 7, 10, 11},113random_context));115// Expect 1/4 localhost peers to be protected from eviction,116// if no onion, I2P, or CJDNS peers.117BOOST_CHECK(IsProtected(118num_peers, [](NodeEvictionCandidate& c) {119c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);120c.m_network = NET_IPV4;121},122/*protected_peer_ids=*/{1, 9, 11},123/*unprotected_peer_ids=*/{},124random_context));126// Expect 1/4 localhost peers and 1/4 of the other peers to be protected,127// sorted by longest uptime (lowest m_connected), if no onion, I2P, or CJDNS peers.128BOOST_CHECK(IsProtected(129num_peers, [](NodeEvictionCandidate& c) {130c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};131c.m_is_local = (c.id > 6);132c.m_network = NET_IPV6;133},134/*protected_peer_ids=*/{0, 1, 2, 7, 8, 9},135/*unprotected_peer_ids=*/{3, 4, 5, 6, 10, 11},136random_context));138// Expect 1/4 I2P peers to be protected from eviction,139// if no onion, localhost, or CJDNS peers.140BOOST_CHECK(IsProtected(141num_peers, [](NodeEvictionCandidate& c) {142c.m_is_local = false;143c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4;144},145/*protected_peer_ids=*/{2, 7, 10},146/*unprotected_peer_ids=*/{},147random_context));149// Expect 1/4 I2P peers and 1/4 of the other peers to be protected, sorted150// by longest uptime (lowest m_connected), if no onion, localhost, or CJDNS peers.151BOOST_CHECK(IsProtected(152num_peers, [](NodeEvictionCandidate& c) {153c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};154c.m_is_local = false;155c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6;156},157/*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},158/*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},159random_context));161// Expect 1/4 CJDNS peers to be protected from eviction,162// if no onion, localhost, or I2P peers.163BOOST_CHECK(IsProtected(164num_peers, [](NodeEvictionCandidate& c) {165c.m_is_local = false;166c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4;167},168/*protected_peer_ids=*/{2, 7, 10},169/*unprotected_peer_ids=*/{},170random_context));172// Expect 1/4 CJDNS peers and 1/4 of the other peers to be protected, sorted173// by longest uptime (lowest m_connected), if no onion, localhost, or I2P peers.174BOOST_CHECK(IsProtected(175num_peers, [](NodeEvictionCandidate& c) {176c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};177c.m_is_local = false;178c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6;179},180/*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},181/*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},182random_context));184// Tests with 2 networks...186// Combined test: expect having 1 localhost and 1 onion peer out of 4 to187// protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime;188// stable sort breaks tie with array order of localhost first.189BOOST_CHECK(IsProtected(1904, [](NodeEvictionCandidate& c) {191c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};192c.m_is_local = (c.id == 4);193c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4;194},195/*protected_peer_ids=*/{0, 4},196/*unprotected_peer_ids=*/{1, 2},197random_context));199// Combined test: expect having 1 localhost and 1 onion peer out of 7 to200// protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by201// uptime; stable sort breaks tie with array order of localhost first.202BOOST_CHECK(IsProtected(2037, [](NodeEvictionCandidate& c) {204c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};205c.m_is_local = (c.id == 6);206c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;207},208/*protected_peer_ids=*/{0, 1, 6},209/*unprotected_peer_ids=*/{2, 3, 4, 5},210random_context));212// Combined test: expect having 1 localhost and 1 onion peer out of 8 to213// protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted214// by uptime; stable sort breaks tie with array order of localhost first.215BOOST_CHECK(IsProtected(2168, [](NodeEvictionCandidate& c) {217c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};218c.m_is_local = (c.id == 6);219c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;220},221/*protected_peer_ids=*/{0, 1, 5, 6},222/*unprotected_peer_ids=*/{2, 3, 4, 7},223random_context));225// Combined test: expect having 3 localhost and 3 onion peers out of 12 to226// protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest227// uptime; stable sort breaks ties with the array order of localhost first.228BOOST_CHECK(IsProtected(229num_peers, [](NodeEvictionCandidate& c) {230c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};231c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11);232c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;233},234/*protected_peer_ids=*/{0, 1, 2, 6, 7, 9},235/*unprotected_peer_ids=*/{3, 4, 5, 8, 10, 11},236random_context));238// Combined test: expect having 4 localhost and 1 onion peer out of 12 to239// protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest uptime.240BOOST_CHECK(IsProtected(241num_peers, [](NodeEvictionCandidate& c) {242c.m_connected = NodeSeconds{std::chrono::seconds{c.id}};243c.m_is_local = (c.id > 4 && c.id < 9);244c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;245},246/*protected_peer_ids=*/{0, 1, 2, 5, 6, 10},247/*unprotected_peer_ids=*/{3, 4, 7, 8, 9, 11},248random_context));250// Combined test: expect having 4 localhost and 2 onion peers out of 16 to
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.