Skip to content

Commit

Permalink
GH-1101 Add support for p2p-listen-endpoint specification of only wan…
Browse files Browse the repository at this point in the history
…ting trx or only wanting blocks
  • Loading branch information
heifner committed Jan 22, 2025
1 parent 07793ad commit 8321858
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 22 deletions.
67 changes: 49 additions & 18 deletions plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,63 @@ namespace detail {
return block_sync_rate_limit;
}

} // namespace detail
/// @return host, port, remainder
inline std::tuple<std::string, std::string, std::string> split_host_port_remainder(const std::string& peer_add) {
using std::string;
// host:port[:trx|:blk][:<rate>]
if (peer_add.empty()) return {};

/// @return listen address and block sync rate limit (in bytes/sec) of address string
inline std::tuple<std::string, size_t> parse_listen_address( const std::string& address ) {
auto listen_addr = address;
auto limit = std::string("0");
auto last_colon_location = address.rfind(':');
if( auto right_bracket_location = address.find(']'); right_bracket_location != address.npos ) {
if( std::count(address.begin()+right_bracket_location, address.end(), ':') > 1 ) {
listen_addr = std::string(address, 0, last_colon_location);
limit = std::string(address, last_colon_location+1);
}
} else {
if( auto colon_count = std::count(address.begin(), address.end(), ':'); colon_count > 1 ) {
string::size_type p = peer_add[0] == '[' ? peer_add.find(']') : 0;
if (p == 0) {
if( auto colon_count = std::count(peer_add.begin(), peer_add.end(), ':'); colon_count >= 7 ) {
EOS_ASSERT( colon_count <= 2, chain::plugin_config_exception,
"Invalid address specification ${addr}; IPv6 addresses must be enclosed in square brackets.", ("addr", address));
listen_addr = std::string(address, 0, last_colon_location);
limit = std::string(address, last_colon_location+1);
"Invalid address specification ${a}; IPv6 addresses must be enclosed in square brackets.", ("a", peer_add));
}
}
string::size_type colon = p != string::npos ? peer_add.find(':', p) : string::npos;
if (colon == string::npos || colon == 0) {
return {};
}
string::size_type colon2 = peer_add.find(':', colon + 1);
string host = (p > 0) ? peer_add.substr( 0, p+1 ) : peer_add.substr( 0, colon );
string port = peer_add.substr( colon + 1, colon2 == string::npos ? string::npos : colon2 - (colon + 1));
string remainder = colon2 == string::npos ? "" : peer_add.substr( colon2 + 1 );
return {std::move(host), std::move(port), std::move(remainder)};
}

} // namespace detail

/// @return host, port, type
inline std::tuple<std::string, std::string, std::string> split_host_port_type(const std::string& peer_add) {
using std::string;
// host:port[:trx|:blk][:<rate>] // rate is discarded
if (peer_add.empty()) return {};

auto [host, port, remainder] = detail::split_host_port_remainder(peer_add);
if (host.empty()) return {};

string::size_type end = remainder.find_first_of( " :+=.,<>!$%^&(*)|-#@\t" ); // future proof by including most symbols without using regex
std::string type = remainder.substr(0, end);

return {std::move(host), std::move(port), std::move(type)};
}

/// @return listen address, type [trx|blk], and block sync rate limit (in bytes/sec) of address string
inline std::tuple<std::string, size_t> parse_listen_address( const std::string& address ) {

auto [host, port, remainder] = detail::split_host_port_remainder(address);
auto listen_addr = host + ":" + port;
auto limit = remainder;
auto last_colon_location = remainder.rfind(':');
if (last_colon_location != std::string::npos) {
limit = std::string(remainder, last_colon_location+1);
}
auto block_sync_rate_limit = detail::parse_connection_rate_limit(limit);

return {listen_addr, block_sync_rate_limit};
return {std::move(listen_addr), block_sync_rate_limit};
}

inline std::tuple<std::string, std::string, std::string> split_host_port_type(const std::string& peer_add) {
inline std::tuple<std::string, std::string, std::string> split_host_xport_type(const std::string& peer_add) {
using std::string;
// host:port:[<trx>|<blk>]
if (peer_add.empty()) return {};
Expand Down
39 changes: 35 additions & 4 deletions plugins/net_plugin/net_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ namespace eosio {
const string& peer_address() const { return peer_addr; } // thread safe, const

void set_connection_type( const string& peer_addr );
void set_peer_connection_type( const string& peer_addr );
bool is_transactions_only_connection()const { return connection_type == transactions_only; } // thread safe, atomic
bool is_blocks_only_connection()const { return connection_type == blocks_only; }
bool is_transactions_connection() const { return connection_type != blocks_only; } // thread safe, atomic
Expand Down Expand Up @@ -1286,6 +1287,29 @@ namespace eosio {
}
}

// called from connection strand
void connection::set_peer_connection_type( const std::string& peer_add ) {
// peer p2p-listen-endpoint received via handshake may indicate they do not want trx or blocks
auto [host, port, type] = net_utils::split_host_port_type(peer_add);
if (host.empty()) {
fc_dlog( logger, "Invalid peer address: ${a}", ("a", peer_add));
} else if( type.empty() ) {
// peer asked for both, continue with p2p-peer-address type
} else if( type == "trx" ) {
if (connection_type == both) { // only switch to trx if p2p-peer-address didn't specify a connection type
fc_dlog( logger, "Setting peer connection - ${c} type for: ${peer} to transactions only", ("c", connection_id)("peer", peer_add) );
connection_type = transactions_only;
}
} else if( type == "blk" ) {
if (connection_type == both) { // only switch to blocks if p2p-peer-address didn't specify a connection type
fc_dlog( logger, "Setting peer connection - ${c} type for: ${peer} to blocks only", ("c", connection_id)("peer", peer_add) );
connection_type = blocks_only;
}
} else {
fc_dlog( logger, "Unknown peer connection - ${c} type: ${t}, for ${peer}", ("c", connection_id)("t", type)("peer", peer_add) );
}
}

std::string connection::state_str(connection_state s) {
switch (s) {
case connection_state::connecting:
Expand Down Expand Up @@ -3445,6 +3469,9 @@ namespace eosio {
}
} else {
peer_dlog(this, "skipping duplicate check, addr == ${pa}, id = ${ni}", ("pa", peer_address())("ni", msg.node_id));

// check if peer requests no trx or no blocks
set_peer_connection_type(msg.p2p_address);
}

if( msg.chain_id != my_impl->chain_id ) {
Expand Down Expand Up @@ -4226,9 +4253,11 @@ namespace eosio {
if(hello.sig == chain::signature_type())
hello.token = sha256();
hello.p2p_address = listen_address;
if( is_transactions_only_connection() ) hello.p2p_address += ":trx";
if( is_transactions_only_connection() && hello.p2p_address.find(":trx") == std::string::npos ) hello.p2p_address += ":trx";
// if we are not accepting transactions tell peer we are blocks only
if( is_blocks_only_connection() || !my_impl->p2p_accept_transactions ) hello.p2p_address += ":blk";
if( is_blocks_only_connection() || !my_impl->p2p_accept_transactions )
if (hello.p2p_address.find(":blk") == std::string::npos)
hello.p2p_address += ":blk";
if( !is_blocks_only_connection() && !my_impl->p2p_accept_transactions ) {
peer_dlog( this, "p2p-accept-transactions=false inform peer blocks only connection ${a}", ("a", hello.p2p_address) );
}
Expand Down Expand Up @@ -4257,15 +4286,17 @@ namespace eosio {
void net_plugin::set_program_options( options_description& /*cli*/, options_description& cfg )
{
cfg.add_options()
( "p2p-listen-endpoint", bpo::value< vector<string> >()->default_value( vector<string>(1, string("0.0.0.0:9876:0")) ), "The actual host:port[:<rate-cap>] used to listen for incoming p2p connections. May be used multiple times. "
( "p2p-listen-endpoint", bpo::value< vector<string> >()->default_value( vector<string>(1, string("0.0.0.0:9876:0")) ),
"The actual host:port[:trx|:blk][:<rate-cap>] used to listen for incoming p2p connections. May be used multiple times. "
" The optional rate cap will limit per connection block sync bandwidth to the specified rate. Total "
" allowed bandwidth is the rate-cap multiplied by the connection count limit. A number alone will be "
" interpreted as bytes per second. The number may be suffixed with units. Supported units are: "
" 'B/s', 'KB/s', 'MB/s, 'GB/s', 'TB/s', 'KiB/s', 'MiB/s', 'GiB/s', 'TiB/s'."
" Transactions and blocks outside of sync mode are not throttled."
" The optional 'trx' and 'blk' indicates to peers that only transactions 'trx' or blocks 'blk' should be sent."
" Examples:\n"
" 192.168.0.100:9876:1MiB/s\n"
" node.eos.io:9876:1512KB/s\n"
" node.eos.io:9876:trx:1512KB/s\n"
" node.eos.io:9876:0.5GB/s\n"
" [2001:db8:85a3:8d3:1319:8a2e:370:7348]:9876:250KB/s")
( "p2p-server-address", bpo::value< vector<string> >(), "An externally accessible host:port for identifying this node. Defaults to p2p-listen-endpoint. May be used as many times as p2p-listen-endpoint. If provided, the first address will be used in handshakes with other nodes. Otherwise the default is used.")
Expand Down
52 changes: 52 additions & 0 deletions plugins/net_plugin/tests/rate_limit_parse_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ BOOST_AUTO_TEST_CASE(test_parse_rate_limit) {
, "[::1]:9876:-250KB/s"
, "0.0.0.0:9877:640Kb/s"
, "0.0.0.0:9877:999999999999999999999999999TiB/s"
, "0.0.0.0:9876:trx"
, "0.0.0.0:9776:blk:0"
, "0.0.0.0:9877:trx:640KB/s"
, "192.168.0.1:9878:blk:20MiB/s"
, "localhost:9879:trx:0.5KB/s"
, "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9876:trx:250KB/s"
, "[::1]:9876:trx:250KB/s"
, "2001:db8:85a3:8d3:1319:8a2e:370:7348:9876:trx:250KB/s"
, "[::1]:9876:trx:-1KB/s"
, "0.0.0.0:9877:trx:640Kb/s"
, "0.0.0.0:9877:trx:999999999999999999999999999TiB/s"
, "0.0.0.0:9876:trx - 84c470d"
, "0.0.0.0:9877:trx:640KB/s - addition info"
};
size_t which = 0;
auto [listen_addr, block_sync_rate_limit] = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
Expand Down Expand Up @@ -49,4 +62,43 @@ BOOST_AUTO_TEST_CASE(test_parse_rate_limit) {
BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception,
[](const eosio::chain::plugin_config_exception& e)
{return std::strstr(e.top_message().c_str(), "block sync rate limit specification overflowed");});
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9876");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 0u);
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9776");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 0u);
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9877");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 640000u);
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "192.168.0.1:9878");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 20971520u);
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "localhost:9879");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 500u);
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9876");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 250000u);
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "[::1]:9876");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 250000u);
BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception,
[](const eosio::chain::plugin_config_exception& e)
{return std::strstr(e.top_message().c_str(), "IPv6 addresses must be enclosed in square brackets");});
BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception,
[](const eosio::chain::plugin_config_exception& e)
{return std::strstr(e.top_message().c_str(), "block sync rate limit must not be negative");});
BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception,
[](const eosio::chain::plugin_config_exception& e)
{return std::strstr(e.top_message().c_str(), "invalid block sync rate limit specification");});
BOOST_CHECK_EXCEPTION(eosio::net_utils::parse_listen_address(p2p_addresses[which++]), eosio::chain::plugin_config_exception,
[](const eosio::chain::plugin_config_exception& e)
{return std::strstr(e.top_message().c_str(), "block sync rate limit specification overflowed");});
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9876");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 0u);
std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses[which++]);
BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9877");
BOOST_CHECK_EQUAL(block_sync_rate_limit, 640000u);
}

0 comments on commit 8321858

Please sign in to comment.