Skip to content

Commit 5a46f8e

Browse files
committed
GH-1155 Add disconnect of connections over peer-limit
1 parent b5aca0c commit 5a46f8e

File tree

1 file changed

+97
-11
lines changed

1 file changed

+97
-11
lines changed

plugins/net_plugin/net_plugin.cpp

+97-11
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ namespace eosio {
248248
constexpr auto def_max_clients = 25; // 0 for unlimited clients
249249
constexpr auto def_max_nodes_per_host = 1;
250250
constexpr auto def_conn_retry_wait = 30;
251+
constexpr auto def_conn_retry_wait_peer_limit_multiplier = 10;
251252
constexpr auto def_txn_expire_wait = std::chrono::seconds(3);
252253
constexpr auto def_resp_expected_wait = std::chrono::seconds(5);
253254
constexpr auto def_sync_fetch_span = 1000;
@@ -310,6 +311,7 @@ namespace eosio {
310311
mutable std::shared_mutex connections_mtx;
311312
connection_details_index connections;
312313
chain::flat_set<string> supplied_peers;
314+
std::atomic<uint64_t> max_peer_ping_time_ns{std::numeric_limits<uint64_t>::max()};;
313315

314316
alignas(hardware_destructive_interference_sz)
315317
fc::mutex connector_check_timer_mtx;
@@ -322,6 +324,7 @@ namespace eosio {
322324
fc::microseconds max_cleanup_time;
323325
boost::asio::steady_timer::duration connector_period{0};
324326
uint32_t max_client_count{def_max_clients};
327+
uint16_t peer_limit{6};
325328
std::function<void(net_plugin::p2p_connections_metrics)> update_p2p_connection_metrics;
326329

327330
private: // must call with held mutex
@@ -332,13 +335,16 @@ namespace eosio {
332335

333336
public:
334337
size_t number_connections() const;
338+
size_t number_connected_peers() const;
335339
void add_supplied_peers(const vector<string>& peers );
340+
void maybe_disconnect(connection_ptr c) const;
336341

337342
// not thread safe, only call on startup
338343
void init(std::chrono::milliseconds heartbeat_timeout_ms,
339344
fc::microseconds conn_max_cleanup_time,
340345
boost::asio::steady_timer::duration conn_period,
341-
uint32_t maximum_client_count);
346+
uint32_t maximum_client_count,
347+
uint16_t peer_limit);
342348

343349
std::chrono::milliseconds get_heartbeat_timeout() const { return heartbeat_timeout; }
344350

@@ -907,6 +913,8 @@ namespace eosio {
907913
block_status_monitor block_status_monitor_;
908914
std::atomic<time_point> last_vote_received;
909915
std::atomic<uint32_t> unique_votes_rcvd_count{0};
916+
std::atomic<time_point> last_unique_block_received;
917+
std::atomic<bool> closed_by_peer_limit = false;
910918

911919
alignas(hardware_destructive_interference_sz)
912920
fc::mutex sync_response_expected_timer_mtx;
@@ -1399,6 +1407,7 @@ namespace eosio {
13991407
peer_dlog( this, "connected" );
14001408
socket_open = true;
14011409
connection_start_time = get_time();
1410+
closed_by_peer_limit = false;
14021411
start_read_message();
14031412
return true;
14041413
}
@@ -3665,6 +3674,8 @@ namespace eosio {
36653674
// make sure we also get the latency we need
36663675
if (peer_ping_time_ns == std::numeric_limits<uint64_t>::max()) {
36673676
send_time();
3677+
} else {
3678+
my_impl->connections.maybe_disconnect(shared_from_this());
36683679
}
36693680
}
36703681

@@ -4019,6 +4030,7 @@ namespace eosio {
40194030

40204031
if (fork_db_add_result == fork_db_add_t::appended_to_head || fork_db_add_result == fork_db_add_t::fork_switch) {
40214032
++c->unique_blocks_rcvd_count;
4033+
c->last_unique_block_received = fc::time_point::now();
40224034
fc_dlog(logger, "post process_incoming_block to app thread, block ${n}", ("n", ptr->block_num()));
40234035
my_impl->producer_plug->process_blocks();
40244036

@@ -4352,7 +4364,7 @@ namespace eosio {
43524364
" p2p.trx.eos.io:9876:trx\n"
43534365
" p2p.blk.eos.io:9876:blk\n")
43544366
( "p2p-peer-limit", bpo::value<uint16_t>()->default_value(6),
4355-
"Limit the number of p2p-peer-address to remain connected to. Selects the best peers of p2p-peer-address-list.")
4367+
"Soft limit on the number of p2p-peer-address to remain connected to. Selects the best peers of p2p-peer-address-list.")
43564368
( "p2p-max-nodes-per-host", bpo::value<int>()->default_value(def_max_nodes_per_host), "Maximum number of client nodes from any single IP address")
43574369
( "p2p-accept-transactions", bpo::value<bool>()->default_value(true), "Allow transactions received over p2p network to be evaluated and relayed if valid.")
43584370
( "p2p-disable-block-nack", bpo::value<bool>()->default_value(false),
@@ -4371,7 +4383,7 @@ namespace eosio {
43714383
"Tuple of [PublicKey, WIF private key] (may specify multiple times)")
43724384
( "max-clients", bpo::value<uint32_t>()->default_value(def_max_clients), "Maximum number of clients from which connections are accepted, use 0 for no limit")
43734385
( "connection-cleanup-period", bpo::value<int>()->default_value(def_conn_retry_wait), "number of seconds to wait before cleaning up dead connections")
4374-
( "max-cleanup-time-msec", bpo::value<uint32_t>()->default_value(10), "max connection cleanup time per cleanup call in milliseconds")
4386+
( "max-cleanup-time-msec", bpo::value<uint32_t>()->default_value(50), "max connection cleanup time per cleanup call in milliseconds")
43754387
( "p2p-dedup-cache-expire-time-sec", bpo::value<uint32_t>()->default_value(10), "Maximum time to track transaction for duplicate optimization")
43764388
( "net-threads", bpo::value<uint16_t>()->default_value(my->thread_pool_size),
43774389
"Number of worker threads in net_plugin thread pool" )
@@ -4418,6 +4430,16 @@ namespace eosio {
44184430
p2p_accept_transactions = options.at( "p2p-accept-transactions" ).as<bool>();
44194431
p2p_disable_block_nack = options.at( "p2p-disable-block-nack" ).as<bool>();
44204432

4433+
if (p2p_accept_transactions || chain_plug->accept_votes()) {
4434+
if (p2p_accept_transactions && chain_plug->accept_votes()) {
4435+
EOS_ASSERT( p2p_peer_limit >= 6, chain::plugin_config_exception,
4436+
"p2p-peer-limit with vote processing and trx processing should be >= 6, two connections each reserved for votes and trxs.");
4437+
} else {
4438+
EOS_ASSERT( p2p_peer_limit >= 4, chain::plugin_config_exception,
4439+
"p2p-peer-limit with vote processing or trx processing should be >= 4, two connections reserved for vote or trx processing");
4440+
}
4441+
}
4442+
44214443
use_socket_read_watermark = options.at( "use-socket-read-watermark" ).as<bool>();
44224444
keepalive_interval = std::chrono::milliseconds( options.at( "p2p-keepalive-interval-ms" ).as<int>() );
44234445
EOS_ASSERT( keepalive_interval.count() > 0, chain::plugin_config_exception,
@@ -4436,7 +4458,8 @@ namespace eosio {
44364458
connections.init( std::chrono::milliseconds( options.at("p2p-keepalive-interval-ms").as<int>() * 2 ),
44374459
fc::milliseconds( options.at("max-cleanup-time-msec").as<uint32_t>() ),
44384460
std::chrono::seconds( options.at("connection-cleanup-period").as<int>() ),
4439-
options.at("max-clients").as<uint32_t>() );
4461+
options.at("max-clients").as<uint32_t>(),
4462+
p2p_peer_limit );
44404463

44414464
if( options.count( "p2p-listen-endpoint" )) {
44424465
auto p2ps = options.at("p2p-listen-endpoint").as<vector<string>>();
@@ -4727,6 +4750,16 @@ namespace eosio {
47274750
return connections.size();
47284751
}
47294752

4753+
size_t connections_manager::number_connected_peers() const {
4754+
size_t num = 0;
4755+
for_each_block_connection([&num](const auto& cc) {
4756+
if (cc->socket_is_open() && !cc->incoming()) {
4757+
++num;
4758+
}
4759+
});
4760+
return num;
4761+
}
4762+
47304763
connection_ptr connections_manager::find_connection(uint32_t connection_id) const {
47314764
std::shared_lock g(connections_mtx);
47324765
const auto& index = connections.get<by_connection_id>();
@@ -4740,15 +4773,48 @@ namespace eosio {
47404773
supplied_peers.insert( peers.begin(), peers.end() );
47414774
}
47424775

4776+
void connections_manager::maybe_disconnect(connection_ptr c) const {
4777+
assert(c);
4778+
// if accepting trx then don't disconnect from any peers
4779+
if (my_impl->p2p_accept_transactions) return;
4780+
// if syncing then other connections are basically idle
4781+
if (my_impl->sync_master->syncing_from_peer()) return;
4782+
// verify current, not syncing from us
4783+
if (!c->current()) return;
4784+
// remain connected for a heartbeat to gather info
4785+
if (c->connection_start_time.load() > connection::get_time() - get_heartbeat_timeout()) return;
4786+
// we have received votes first from this connection
4787+
if (c->unique_votes_rcvd_count > 0) return;
4788+
// we received block first from this connection recently
4789+
if (c->last_unique_block_received.load() > fc::time_point::now() - fc::minutes(10)) return;
4790+
// quick check if more connections than limit
4791+
if (number_connections() <= peer_limit) return;
4792+
// do we have more than the limit currently
4793+
if (number_connected_peers() <= peer_limit) return;
4794+
4795+
auto peer_ping_time = c->get_peer_ping_time_ns();
4796+
if (peer_ping_time < max_peer_ping_time_ns) {
4797+
return;
4798+
}
4799+
4800+
peer_ilog(c, "Disconnecting from peer that is not one of the best of p2p-peer-address, ping ${p}ms, max ${m}ms",
4801+
("p", peer_ping_time/1000000)("m", max_peer_ping_time_ns.load()/1000000));
4802+
4803+
c->closed_by_peer_limit = true;
4804+
c->close(false);
4805+
}
4806+
47434807
// not thread safe, only call on startup
47444808
void connections_manager::init( std::chrono::milliseconds heartbeat_timeout_ms,
47454809
fc::microseconds conn_max_cleanup_time,
47464810
boost::asio::steady_timer::duration conn_period,
4747-
uint32_t maximum_client_count ) {
4811+
uint32_t maximum_client_count,
4812+
uint16_t p2p_peer_limit) {
47484813
heartbeat_timeout = heartbeat_timeout_ms;
47494814
max_cleanup_time = conn_max_cleanup_time;
47504815
connector_period = conn_period;
47514816
max_client_count = maximum_client_count;
4817+
peer_limit = p2p_peer_limit;
47524818
}
47534819

47544820
fc::microseconds connections_manager::get_connector_period() const {
@@ -4958,11 +5024,16 @@ namespace eosio {
49585024
// called from any thread
49595025
void connections_manager::connection_monitor(const std::weak_ptr<connection>& from_connection) {
49605026
size_t num_rm = 0, num_clients = 0, num_peers = 0, num_bp_peers = 0;
4961-
auto cleanup = [&num_rm, this](vector<connection_ptr>&& reconnecting, vector<connection_ptr>&& removing) {
4962-
for( auto& c : reconnecting ) {
4963-
if (!c->resolve_and_connect()) {
4964-
++num_rm;
4965-
removing.push_back(c);
5027+
std::multiset<uint64_t> ping_times;
5028+
auto cleanup = [&num_rm, num_peers, this](vector<connection_ptr>&& reconnecting, vector<connection_ptr>&& removing) {
5029+
// if num_peers less than or equal the peer_limit then attempt to connect to all configured peers
5030+
// the best of the configured peers will be chosen after they connect.
5031+
if (num_peers <= peer_limit) {
5032+
for( auto& c : reconnecting ) {
5033+
if (!c->resolve_and_connect()) {
5034+
++num_rm;
5035+
removing.push_back(c);
5036+
}
49665037
}
49675038
}
49685039
std::scoped_lock g( connections_mtx );
@@ -4971,6 +5042,14 @@ namespace eosio {
49715042
index.erase(c->connection_id);
49725043
}
49735044
};
5045+
auto record_max_peer_ping_time = [&]() {
5046+
if (ping_times.size() > peer_limit) {
5047+
auto itr = ping_times.begin();
5048+
std::advance(itr, peer_limit-1);
5049+
max_peer_ping_time_ns = *itr;
5050+
}
5051+
};
5052+
auto peer_limit_reconnect_time = connection::get_time() - (connector_period * def_conn_retry_wait_peer_limit_multiplier);
49745053
auto max_time = fc::time_point::now().safe_add(max_cleanup_time);
49755054
std::vector<connection_ptr> reconnecting, removing;
49765055
auto from = from_connection.lock();
@@ -5001,16 +5080,23 @@ namespace eosio {
50015080
if (!c->socket_is_open() && c->state() != connection::connection_state::connecting) {
50025081
if (!c->incoming()) {
50035082
--num_peers;
5004-
reconnecting.push_back(c);
5083+
if (!c->closed_by_peer_limit || (c->connection_start_time.load() < peer_limit_reconnect_time)) {
5084+
reconnecting.push_back(c);
5085+
}
50055086
} else {
50065087
--num_clients;
50075088
++num_rm;
50085089
removing.push_back(c);
50095090
}
50105091
}
5092+
auto ping_time = c->get_peer_ping_time_ns();
5093+
if (ping_time < std::numeric_limits<uint64_t>::max()) {
5094+
ping_times.insert(ping_time);
5095+
}
50115096
++it;
50125097
}
50135098
g.unlock();
5099+
record_max_peer_ping_time();
50145100
cleanup(std::move(reconnecting), std::move(removing));
50155101

50165102
if( num_clients > 0 || num_peers > 0 ) {

0 commit comments

Comments
 (0)