From 7f66aaddedeb11c2f5a5dd024124e60d06486f4d Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 12 Mar 2025 15:22:58 -0500 Subject: [PATCH 01/50] GH-1245 Add gossip_bp_peers_message --- .../include/eosio/net_plugin/protocol.hpp | 25 ++++++++++++++++++- plugins/net_plugin/net_plugin.cpp | 8 ++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index 1ddb241086..3d483ba6e6 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -144,6 +144,26 @@ namespace eosio { block_id_type id; }; + struct gossip_bp_peers_message { + struct bp_peer { + eosio::name producer_name; + std::string server_address; + std::vector proposer_peers; // size limit 2 + std::vector finalizer_peers; // size limit 2 + // sig over [producer_name, server_address, proposer_peers, finalizer_peers] + signature_type sig; + + digest_type digest() const; + bool operator==(const bp_peer&) const = default; + bool operator<(const bp_peer& rhs) const { + return std::tie(producer_name, server_address, proposer_peers, finalizer_peers) < + std::tie(rhs.producer_name, rhs.server_address, rhs.proposer_peers, rhs.finalizer_peers); + } + }; + + std::vector peers; + }; + using net_message = std::variant; + block_notice_message, + gossip_bp_peers_message>; } // namespace eosio @@ -176,6 +197,8 @@ FC_REFLECT( eosio::request_message, (req_trx)(req_blocks) ) FC_REFLECT( eosio::sync_request_message, (start_block)(end_block) ) FC_REFLECT( eosio::block_nack_message, (id) ) FC_REFLECT( eosio::block_notice_message, (previous)(id) ) +FC_REFLECT( eosio::gossip_bp_peers_message::bp_peer, (producer_name)(server_address)(proposer_peers)(finalizer_peers)(sig) ) +FC_REFLECT( eosio::gossip_bp_peers_message, (peers) ) /** * diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index bd104e07e5..ec56b89f6c 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -269,6 +269,7 @@ namespace eosio { vote_message = fc::get_index(), block_nack_message = fc::get_index(), block_notice_message = fc::get_index(), + gossip_bp_peers_message= fc::get_index(), unknown }; @@ -1083,6 +1084,7 @@ namespace eosio { void handle_message( const vote_message& msg ) = delete; // vote_message_ptr overload used instead void handle_message( const block_nack_message& msg); void handle_message( const block_notice_message& msg); + void handle_message( const gossip_bp_peers_message& msg); // returns calculated number of blocks combined latency uint32_t calc_block_latency(); @@ -1175,6 +1177,12 @@ namespace eosio { peer_dlog( c, "handle block_notice_message #${bn}:${id}", ("bn", block_header::num_from_id(msg.id))("id", msg.id) ); c->handle_message( msg ); } + + void operator()( const gossip_bp_peers_message& msg ) const { + // continue call to handle_message on connection strand + peer_dlog( c, "handle gossip_bp_peers_message size ${s}", ("s", msg.peers.size()) ); + c->handle_message( msg ); + } }; From 4a48bbaee1e0a6362783c4adb9de16eb966ac382 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 12 Mar 2025 15:39:27 -0500 Subject: [PATCH 02/50] GH-1245 Move max_p2p_address_length to net_utils and enforce in split_host_port_remainder() --- .../eosio/net_plugin/auto_bp_peering.hpp | 21 ++++++++----- .../include/eosio/net_plugin/net_utils.hpp | 13 ++++++++ .../include/eosio/net_plugin/protocol.hpp | 9 ------ plugins/net_plugin/net_plugin.cpp | 31 ++++++++++--------- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index bfe6f45228..54490bd9a4 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -1,9 +1,11 @@ #pragma once + +#include #include + #include #include - namespace eosio::auto_bp_peering { /// @@ -69,20 +71,25 @@ class bp_connection_manager { // Only called at plugin startup void set_bp_peers(const std::vector& peers) { - try { - for (auto& entry : peers) { + for (const auto& entry : peers) { + try { auto comma_pos = entry.find(','); EOS_ASSERT(comma_pos != std::string::npos, chain::plugin_config_exception, - "auto-bp-peer must consists an account name and server address separated by a comma token"); + "p2p-auto-bp-peer must consist of an account name and server address separated by a comma"); auto addr = entry.substr(comma_pos + 1); account_name account(entry.substr(0, comma_pos)); + const auto& [host, port, type] = net_utils::split_host_port_type(addr); + EOS_ASSERT( !host.empty() && !port.empty(), chain::plugin_config_exception, + "Invalid p2p-auto-bp-peer ${p}, syntax host:port:[trx|blk]", ("p", addr)); config.bp_peer_accounts[addr] = account; config.bp_peer_addresses[account] = std::move(addr); - fc_dlog(self()->get_logger(), "Setting auto-bp-peer ${a} -> ${d}", ("a", account)("d", config.bp_peer_addresses[account])); + fc_dlog(self()->get_logger(), "Setting p2p-auto-bp-peer ${a} -> ${d}", + ("a", account)("d", config.bp_peer_addresses[account])); + } catch (chain::name_type_exception&) { + EOS_ASSERT(false, chain::plugin_config_exception, + "the account ${a} supplied by --p2p-auto-bp-peer option is invalid", ("a", entry)); } - } catch (eosio::chain::name_type_exception&) { - EOS_ASSERT(false, chain::plugin_config_exception, "the account supplied by --auto-bp-peer option is invalid"); } } diff --git a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp index deb79b73eb..186e1fcf44 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp @@ -9,6 +9,15 @@ namespace eosio::net_utils { +// Longest domain name is 253 characters according to wikipedia. +// Addresses include ":port" where max port is 65535, which adds 6 chars. +// Addresses may also include ":bitrate" with suffix and separators, which adds 30 chars, +// for the maximum comma-separated value that fits in a size_t expressed in decimal plus a suffix. +// We also add our own extentions of "[:trx|:blk] - xxxxxxx", which adds 14 chars, total= 273. +// Allow for future extentions as well, hence 384. +constexpr size_t max_p2p_address_length = 253 + 6 + 30; +constexpr size_t max_handshake_str_length = 384; + namespace detail { inline static const std::map prefix_multipliers{ @@ -48,6 +57,10 @@ namespace detail { EOS_ASSERT(!should_throw, chain::plugin_config_exception, "Address specification is empty" ); return {}; } + if (peer_add.size() > max_p2p_address_length) { + EOS_ASSERT(!should_throw, chain::plugin_config_exception, "Address specification exceeds max p2p address length" ); + return {}; + } auto colon_count = std::count(peer_add.begin(), peer_add.end(), ':'); string::size_type end_bracket = 0; diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index 3d483ba6e6..8f2a7562e1 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -14,15 +14,6 @@ namespace eosio { block_id_type head_id; }; - // Longest domain name is 253 characters according to wikipedia. - // Addresses include ":port" where max port is 65535, which adds 6 chars. - // Addresses may also include ":bitrate" with suffix and separators, which adds 30 chars, - // for the maximum comma-separated value that fits in a size_t expressed in decimal plus a suffix. - // We also add our own extentions of "[:trx|:blk] - xxxxxxx", which adds 14 chars, total= 273. - // Allow for future extentions as well, hence 384. - constexpr size_t max_p2p_address_length = 253 + 6 + 30; - constexpr size_t max_handshake_str_length = 384; - struct handshake_message { uint16_t network_version = 0; ///< incremental value above a computed base chain_id_type chain_id; ///< used to identify chain diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index ec56b89f6c..30267a0b8f 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3419,23 +3419,23 @@ namespace eosio { if (msg.p2p_address.empty()) { peer_wlog( this, "Handshake message validation: p2p_address is null string" ); valid = false; - } else if( msg.p2p_address.length() > max_handshake_str_length ) { + } else if( msg.p2p_address.length() > net_utils::max_handshake_str_length ) { // see max_handshake_str_length comment in protocol.hpp peer_wlog( this, "Handshake message validation: p2p_address too large: ${p}", - ("p", msg.p2p_address.substr(0, max_handshake_str_length) + "...") ); + ("p", msg.p2p_address.substr(0, net_utils::max_handshake_str_length) + "...") ); valid = false; } if (msg.os.empty()) { peer_wlog( this, "Handshake message validation: os field is null string" ); valid = false; - } else if( msg.os.length() > max_handshake_str_length ) { + } else if( msg.os.length() > net_utils::max_handshake_str_length ) { peer_wlog( this, "Handshake message validation: os field too large: ${p}", - ("p", msg.os.substr(0, max_handshake_str_length) + "...") ); + ("p", msg.os.substr(0, net_utils::max_handshake_str_length) + "...") ); valid = false; } - if( msg.agent.length() > max_handshake_str_length ) { + if( msg.agent.length() > net_utils::max_handshake_str_length ) { peer_wlog( this, "Handshake message validation: agent field too large: ${p}", - ("p", msg.agent.substr(0, max_handshake_str_length) + "...") ); + ("p", msg.agent.substr(0, net_utils::max_handshake_str_length) + "...") ); valid = false; } if ((msg.sig != chain::signature_type() || msg.token != sha256()) && (msg.token != fc::sha256::hash(msg.time))) { @@ -4485,9 +4485,9 @@ namespace eosio { fc_wlog( logger, "Removed ${count} duplicate p2p-listen-endpoint entries", ("count", addr_diff)); } for( const auto& addr : p2p_addresses ) { - EOS_ASSERT( addr.length() <= max_p2p_address_length, chain::plugin_config_exception, + EOS_ASSERT( addr.length() <= net_utils::max_p2p_address_length, chain::plugin_config_exception, "p2p-listen-endpoint ${a} too long, must be less than ${m}", - ("a", addr)("m", max_p2p_address_length) ); + ("a", addr)("m", net_utils::max_p2p_address_length) ); } } } @@ -4496,9 +4496,9 @@ namespace eosio { EOS_ASSERT( p2p_server_addresses.size() <= p2p_addresses.size(), chain::plugin_config_exception, "p2p-server-address may not be specified more times than p2p-listen-endpoint" ); for( const auto& addr: p2p_server_addresses ) { - EOS_ASSERT( addr.length() <= max_p2p_address_length, chain::plugin_config_exception, + EOS_ASSERT( addr.length() <= net_utils::max_p2p_address_length, chain::plugin_config_exception, "p2p-server-address ${a} too long, must be less than ${m}", - ("a", addr)("m", max_p2p_address_length) ); + ("a", addr)("m", net_utils::max_p2p_address_length) ); } } p2p_server_addresses.resize(p2p_addresses.size()); // extend with empty entries as needed @@ -4513,22 +4513,23 @@ namespace eosio { for (const auto& peer : peers) { const auto& [host, port, type] = net_utils::split_host_port_type(peer); EOS_ASSERT( !host.empty() && !port.empty(), chain::plugin_config_exception, - "Invalid p2p-peer-address ${p}, syntax host:port:[trx|blk]"); + "Invalid p2p-peer-address ${p}, syntax host:port:[trx|blk]", ("p", peer)); } connections.add_supplied_peers(peers); } if( options.count( "agent-name" )) { user_agent_name = options.at( "agent-name" ).as(); - EOS_ASSERT( user_agent_name.length() <= max_handshake_str_length, chain::plugin_config_exception, - "agent-name too long, must be less than ${m}", ("m", max_handshake_str_length) ); + EOS_ASSERT( user_agent_name.length() <= net_utils::max_handshake_str_length, chain::plugin_config_exception, + "agent-name too long, must be less than ${m}", ("m", net_utils::max_handshake_str_length) ); } if ( options.count( "p2p-auto-bp-peer")) { + EOS_ASSERT(options.count("p2p-producer-peer"), chain::plugin_config_exception, + "p2p-producer-peer required for p2p-auto-bp-peer"); set_bp_peers(options.at( "p2p-auto-bp-peer" ).as>()); for_each_bp_peer_address([&peers](const auto& addr) { EOS_ASSERT(std::find(peers.begin(), peers.end(), addr) == peers.end(), chain::plugin_config_exception, - "\"${addr}\" should only appear in either p2p-peer-address or p2p-auto-bp-peer option, not both.", - ("addr",addr)); + "\"${a}\" should only appear in either p2p-peer-address or p2p-auto-bp-peer option, not both.", ("a",addr)); }); } From 73ba970d8a22868194955dd0944ad435e11e982a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 14 Mar 2025 12:43:04 -0500 Subject: [PATCH 03/50] GH-1245 Move net_logger_impl to net_logger.hpp --- .../include/eosio/net_plugin/net_logger.hpp | 54 +++++++++++++++++++ plugins/net_plugin/net_plugin.cpp | 46 +--------------- 2 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 plugins/net_plugin/include/eosio/net_plugin/net_logger.hpp diff --git a/plugins/net_plugin/include/eosio/net_plugin/net_logger.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_logger.hpp new file mode 100644 index 0000000000..bb4ab19542 --- /dev/null +++ b/plugins/net_plugin/include/eosio/net_plugin/net_logger.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include + +namespace eosio { + + const std::string logger_name("net_plugin_impl"); + inline fc::logger logger; + inline std::string peer_log_format; + + template + void verify_strand_in_this_thread(const Strand& strand, const char* func, int line) { + if( !strand.running_in_this_thread() ) { + fc_elog( logger, "wrong strand: ${f} : line ${n}, exiting", ("f", func)("n", line) ); + appbase::app().quit(); + } + } + + // peer_[x]log must be called from thread in connection strand +#define peer_dlog( PEER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( logger.is_enabled( fc::log_level::debug ) ) { \ + verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ + logger.log( FC_LOG_MESSAGE( debug, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ + } \ + FC_MULTILINE_MACRO_END + +#define peer_ilog( PEER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( logger.is_enabled( fc::log_level::info ) ) { \ + verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ + logger.log( FC_LOG_MESSAGE( info, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ + } \ + FC_MULTILINE_MACRO_END + +#define peer_wlog( PEER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( logger.is_enabled( fc::log_level::warn ) ) { \ + verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ + logger.log( FC_LOG_MESSAGE( warn, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ + } \ + FC_MULTILINE_MACRO_END + +#define peer_elog( PEER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( logger.is_enabled( fc::log_level::error ) ) { \ + verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ + logger.log( FC_LOG_MESSAGE( error, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ + } \ + FC_MULTILINE_MACRO_END + +} // namespace eosio \ No newline at end of file diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 30267a0b8f..e7cd0540b8 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -76,18 +77,6 @@ namespace eosio { static constexpr int64_t block_interval_ns = std::chrono::duration_cast(std::chrono::milliseconds(config::block_interval_ms)).count(); - const std::string logger_name("net_plugin_impl"); - fc::logger logger; - std::string peer_log_format; - - template - void verify_strand_in_this_thread(const Strand& strand, const char* func, int line) { - if( !strand.running_in_this_thread() ) { - fc_elog( logger, "wrong strand: ${f} : line ${n}, exiting", ("f", func)("n", line) ); - app().quit(); - } - } - struct node_transaction_state { transaction_id_type id; time_point_sec expires; /// time after which this may be purged. @@ -536,39 +525,6 @@ namespace eosio { std::string empty{}; }; //net_plugin_impl - // peer_[x]log must be called from thread in connection strand -#define peer_dlog( PEER, FORMAT, ... ) \ - FC_MULTILINE_MACRO_BEGIN \ - if( logger.is_enabled( fc::log_level::debug ) ) { \ - verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ - logger.log( FC_LOG_MESSAGE( debug, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ - } \ - FC_MULTILINE_MACRO_END - -#define peer_ilog( PEER, FORMAT, ... ) \ - FC_MULTILINE_MACRO_BEGIN \ - if( logger.is_enabled( fc::log_level::info ) ) { \ - verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ - logger.log( FC_LOG_MESSAGE( info, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ - } \ - FC_MULTILINE_MACRO_END - -#define peer_wlog( PEER, FORMAT, ... ) \ - FC_MULTILINE_MACRO_BEGIN \ - if( logger.is_enabled( fc::log_level::warn ) ) { \ - verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ - logger.log( FC_LOG_MESSAGE( warn, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ - } \ - FC_MULTILINE_MACRO_END - -#define peer_elog( PEER, FORMAT, ... ) \ - FC_MULTILINE_MACRO_BEGIN \ - if( logger.is_enabled( fc::log_level::error ) ) { \ - verify_strand_in_this_thread( PEER->strand, __func__, __LINE__ ); \ - logger.log( FC_LOG_MESSAGE( error, peer_log_format + FORMAT, __VA_ARGS__ (PEER->get_logger_variant()) ) ); \ - } \ - FC_MULTILINE_MACRO_END - template::value>::type> inline enum_type& operator|=(enum_type& lhs, const enum_type& rhs) From d51c6ed8c230852787cd67be86489f4f3cb51e0a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 14 Mar 2025 12:45:02 -0500 Subject: [PATCH 04/50] GH-1245 Move buffer_factory into buffer_factor.hpp --- .../eosio/net_plugin/buffer_factory.hpp | 219 ++++++++++++++++++ plugins/net_plugin/net_plugin.cpp | 157 ------------- plugins/producer_plugin/producer_plugin.cpp | 2 + 3 files changed, 221 insertions(+), 157 deletions(-) create mode 100644 plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp diff --git a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp new file mode 100644 index 0000000000..f718de2545 --- /dev/null +++ b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp @@ -0,0 +1,219 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace eosio { + + using send_buffer_type = std::shared_ptr>; + + struct buffer_factory { + + /// caches result for subsequent calls, only provide same net_message instance for each invocation + const send_buffer_type& get_send_buffer( const net_message& m ) { + if( !send_buffer ) { + send_buffer = create_send_buffer( m ); + } + return send_buffer; + } + + protected: + send_buffer_type send_buffer; + + protected: + static send_buffer_type create_send_buffer( const net_message& m ) { + const uint32_t payload_size = fc::raw::pack_size( m ); + + const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t + const size_t buffer_size = message_header_size + payload_size; + + auto send_buffer = std::make_shared>(buffer_size); + fc::datastream ds( send_buffer->data(), buffer_size); + ds.write( header, message_header_size ); + fc::raw::pack( ds, m ); + + return send_buffer; + } + + template< typename T> + static send_buffer_type create_send_buffer( uint32_t which, const T& v ) { + // match net_message static_variant pack + const uint32_t which_size = fc::raw::pack_size( unsigned_int( which ) ); + const uint32_t payload_size = which_size + fc::raw::pack_size( v ); + + const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t + const size_t buffer_size = message_header_size + payload_size; + + auto send_buffer = std::make_shared>( buffer_size ); + fc::datastream ds( send_buffer->data(), buffer_size ); + ds.write( header, message_header_size ); + fc::raw::pack( ds, unsigned_int( which ) ); + fc::raw::pack( ds, v ); + + return send_buffer; + } + + static send_buffer_type create_send_buffer_from_serialized_block( const std::vector& v ) { + constexpr uint32_t signed_block_which = to_index(msg_type_t::signed_block); + + // match net_message static_variant pack + const uint32_t which_size = fc::raw::pack_size( unsigned_int( signed_block_which ) ); + const uint32_t payload_size = which_size + v.size(); + + const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t + const size_t buffer_size = message_header_size + payload_size; + + auto send_buffer = std::make_shared>( buffer_size ); + fc::datastream ds( send_buffer->data(), buffer_size ); + ds.write( header, message_header_size ); + fc::raw::pack( ds, unsigned_int( signed_block_which ) ); + ds.write( v.data(), v.size() ); + + return send_buffer; + } + + }; + + struct block_buffer_factory : public buffer_factory { + + /// caches result for subsequent calls, only provide same signed_block_ptr instance for each invocation. + const send_buffer_type& get_send_buffer( const signed_block_ptr& sb ) { + if( !send_buffer ) { + send_buffer = create_send_buffer( sb ); + } + return send_buffer; + } + + const send_buffer_type& get_send_buffer( const std::vector& sb ) { + if( !send_buffer ) { + send_buffer = create_send_buffer( sb ); + } + return send_buffer; + } + + private: + + static std::shared_ptr> create_send_buffer( const signed_block_ptr& sb ) { + constexpr uint32_t signed_block_which = to_index(msg_type_t::signed_block); + + // this implementation is to avoid copy of signed_block to net_message + // matches which of net_message for signed_block + fc_dlog( logger, "sending block ${bn}", ("bn", sb->block_num()) ); + return buffer_factory::create_send_buffer( signed_block_which, *sb ); + } + + static std::shared_ptr> create_send_buffer( const std::vector& ssb ) { // ssb: serialized signed block + // this implementation is to avoid copy of signed_block to net_message + // matches which of net_message for signed_block + return buffer_factory::create_send_buffer_from_serialized_block( ssb ); + } + }; + + struct trx_buffer_factory : public buffer_factory { + + /// caches result for subsequent calls, only provide same packed_transaction_ptr instance for each invocation. + const send_buffer_type& get_send_buffer( const packed_transaction_ptr& trx ) { + if( !send_buffer ) { + send_buffer = create_send_buffer( trx ); + } + return send_buffer; + } + + private: + + static std::shared_ptr> create_send_buffer( const packed_transaction_ptr& trx ) { + constexpr uint32_t packed_transaction_which = to_index(msg_type_t::packed_transaction); + + // this implementation is to avoid copy of packed_transaction to net_message + // matches which of net_message for packed_transaction + return buffer_factory::create_send_buffer( packed_transaction_which, *trx ); + } + }; + + struct gossip_buffer_factory : public buffer_factory { + + /// caches result for subsequent calls + const send_buffer_type& get_send_buffer(const gossip_bp_index_t& gossip_bp_peers) { + if( !send_buffer ) { + send_buffer = create_send_buffer(gossip_bp_peers); + } + return send_buffer; + } + + private: + + static std::shared_ptr> create_send_buffer(const gossip_bp_index_t& gossip_bp_peers) { + constexpr uint32_t which = to_index(msg_type_t::gossip_bp_peers_message); + + fc::lock_guard g(gossip_bp_peers.mtx); + + // match net_message static_variant pack + const uint32_t which_size = fc::raw::pack_size( unsigned_int( which ) ); + // content size + size_t s = fc::raw::pack_size( unsigned_int((uint32_t)gossip_bp_peers.index.size()) ); // match vector pack + for (const auto& peer : gossip_bp_peers.index.get()) { + s += fc::raw::pack_size( peer ); + } + const uint32_t payload_size = which_size + s; + + const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t + const size_t buffer_size = message_header_size + payload_size; + + auto send_buffer = std::make_shared>( buffer_size ); + fc::datastream ds( send_buffer->data(), buffer_size ); + ds.write( header, message_header_size ); + fc::raw::pack( ds, unsigned_int( which ) ); + fc::raw::pack( ds, unsigned_int((uint32_t)gossip_bp_peers.index.size()) ); + for (const auto& peer : gossip_bp_peers.index.get()) { + fc::raw::pack( ds, peer ); + } + + return send_buffer; + } + }; + + struct gossip_buffer_initial_factory : public buffer_factory { + + // called on startup + void set_initial_send_buffer(const gossip_bp_peers_message::bp_peer& signed_empty) { + send_buffer = create_initial_send_buffer(signed_empty); + } + + /// requires set_initial_send_buffer to be called first + const send_buffer_type& get_initial_send_buffer() { + assert(send_buffer); + return send_buffer; + } + + private: + + static std::shared_ptr> create_initial_send_buffer(const gossip_bp_peers_message::bp_peer& signed_empty) { + constexpr uint32_t which = to_index(msg_type_t::gossip_bp_peers_message); + + // match net_message static_variant pack + const uint32_t which_size = fc::raw::pack_size( unsigned_int( which ) ); + // content size + size_t s = fc::raw::pack_size( unsigned_int((uint32_t)1) ); // match vector pack + s += fc::raw::pack_size(signed_empty); + const uint32_t payload_size = which_size + s; + + const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t + const size_t buffer_size = message_header_size + payload_size; + + auto send_buffer = std::make_shared>( buffer_size ); + fc::datastream ds( send_buffer->data(), buffer_size ); + ds.write( header, message_header_size ); + fc::raw::pack( ds, unsigned_int( which ) ); + fc::raw::pack( ds, unsigned_int((uint32_t)1) ); + fc::raw::pack( ds, signed_empty ); + + return send_buffer; + } + }; + +} // namespace eosio diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index e7cd0540b8..c4ce22bec1 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -72,7 +72,6 @@ namespace eosio { using connection_ptr = std::shared_ptr; using connection_wptr = std::weak_ptr; - using send_buffer_type = std::shared_ptr>; static constexpr int64_t block_interval_ns = std::chrono::duration_cast(std::chrono::milliseconds(config::block_interval_ms)).count(); @@ -242,37 +241,6 @@ namespace eosio { constexpr auto def_sync_fetch_span = 1000; constexpr auto def_keepalive_interval = 10000; - constexpr auto message_header_size = sizeof(uint32_t); - - // see protocol net_message - enum class msg_type_t { - handshake_message = fc::get_index(), - chain_size_message = fc::get_index(), - go_away_message = fc::get_index(), - time_message = fc::get_index(), - notice_message = fc::get_index(), - request_message = fc::get_index(), - sync_request_message = fc::get_index(), - signed_block = fc::get_index(), - packed_transaction = fc::get_index(), - vote_message = fc::get_index(), - block_nack_message = fc::get_index(), - block_notice_message = fc::get_index(), - gossip_bp_peers_message= fc::get_index(), - unknown - }; - - constexpr uint32_t to_index(msg_type_t net_msg) { - static_assert( std::variant_size_v == static_cast(msg_type_t::unknown)); - return static_cast(net_msg); - } - - constexpr msg_type_t to_msg_type_t(size_t v) { - static_assert( std::variant_size_v == static_cast(msg_type_t::unknown)); - EOS_ASSERT(v < to_index(msg_type_t::unknown), plugin_exception, "Invalid net_message index: ${v}", ("v", v)); - return static_cast(v); - } - class connections_manager { public: struct connection_detail { @@ -1793,131 +1761,6 @@ namespace eosio { //------------------------------------------------------------------------ - struct buffer_factory { - - /// caches result for subsequent calls, only provide same net_message instance for each invocation - const send_buffer_type& get_send_buffer( const net_message& m ) { - if( !send_buffer ) { - send_buffer = create_send_buffer( m ); - } - return send_buffer; - } - - protected: - send_buffer_type send_buffer; - - protected: - static send_buffer_type create_send_buffer( const net_message& m ) { - const uint32_t payload_size = fc::raw::pack_size( m ); - - const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t - const size_t buffer_size = message_header_size + payload_size; - - auto send_buffer = std::make_shared>(buffer_size); - fc::datastream ds( send_buffer->data(), buffer_size); - ds.write( header, message_header_size ); - fc::raw::pack( ds, m ); - - return send_buffer; - } - - template< typename T> - static send_buffer_type create_send_buffer( uint32_t which, const T& v ) { - // match net_message static_variant pack - const uint32_t which_size = fc::raw::pack_size( unsigned_int( which ) ); - const uint32_t payload_size = which_size + fc::raw::pack_size( v ); - - const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t - const size_t buffer_size = message_header_size + payload_size; - - auto send_buffer = std::make_shared>( buffer_size ); - fc::datastream ds( send_buffer->data(), buffer_size ); - ds.write( header, message_header_size ); - fc::raw::pack( ds, unsigned_int( which ) ); - fc::raw::pack( ds, v ); - - return send_buffer; - } - - static send_buffer_type create_send_buffer_from_serialized_block( const std::vector& v ) { - constexpr uint32_t signed_block_which = to_index(msg_type_t::signed_block); - - // match net_message static_variant pack - const uint32_t which_size = fc::raw::pack_size( unsigned_int( signed_block_which ) ); - const uint32_t payload_size = which_size + v.size(); - - const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t - const size_t buffer_size = message_header_size + payload_size; - - auto send_buffer = std::make_shared>( buffer_size ); - fc::datastream ds( send_buffer->data(), buffer_size ); - ds.write( header, message_header_size ); - fc::raw::pack( ds, unsigned_int( signed_block_which ) ); - ds.write( v.data(), v.size() ); - - return send_buffer; - } - - }; - - struct block_buffer_factory : public buffer_factory { - - /// caches result for subsequent calls, only provide same signed_block_ptr instance for each invocation. - const send_buffer_type& get_send_buffer( const signed_block_ptr& sb ) { - if( !send_buffer ) { - send_buffer = create_send_buffer( sb ); - } - return send_buffer; - } - - const send_buffer_type& get_send_buffer( const std::vector& sb ) { - if( !send_buffer ) { - send_buffer = create_send_buffer( sb ); - } - return send_buffer; - } - - private: - - static std::shared_ptr> create_send_buffer( const signed_block_ptr& sb ) { - constexpr uint32_t signed_block_which = to_index(msg_type_t::signed_block); - - // this implementation is to avoid copy of signed_block to net_message - // matches which of net_message for signed_block - fc_dlog( logger, "sending block ${bn}", ("bn", sb->block_num()) ); - return buffer_factory::create_send_buffer( signed_block_which, *sb ); - } - - static std::shared_ptr> create_send_buffer( const std::vector& ssb ) { // ssb: serialized signed block - // this implementation is to avoid copy of signed_block to net_message - // matches which of net_message for signed_block - return buffer_factory::create_send_buffer_from_serialized_block( ssb ); - } - }; - - struct trx_buffer_factory : public buffer_factory { - - /// caches result for subsequent calls, only provide same packed_transaction_ptr instance for each invocation. - const send_buffer_type& get_send_buffer( const packed_transaction_ptr& trx ) { - if( !send_buffer ) { - send_buffer = create_send_buffer( trx ); - } - return send_buffer; - } - - private: - - static std::shared_ptr> create_send_buffer( const packed_transaction_ptr& trx ) { - constexpr uint32_t packed_transaction_which = to_index(msg_type_t::packed_transaction); - - // this implementation is to avoid copy of packed_transaction to net_message - // matches which of net_message for packed_transaction - return buffer_factory::create_send_buffer( packed_transaction_which, *trx ); - } - }; - - //------------------------------------------------------------------------ - // called from connection strand void connection::enqueue( const net_message& m ) { verify_strand_in_this_thread( strand, __func__, __LINE__ ); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 5826eb1b4c..0baa28e3fc 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1146,10 +1146,12 @@ class producer_plugin_impl : public std::enable_shared_from_this= 0 && get_irreversible_block_age() >= _max_irreversible_block_age_us); } + // thread safe, not modified after plugin_initialize bool is_producer_key(const chain::public_key_type& key) const { return _signature_providers.find(key) != _signature_providers.end(); } + // thread safe, not modified after plugin_initialize chain::signature_type sign_compact(const chain::public_key_type& key, const fc::sha256& digest) const { if (key != chain::public_key_type()) { auto private_key_itr = _signature_providers.find(key); From 414c22d8f14f7df7e499c1d8434818d59a39fbce Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 14 Mar 2025 12:47:01 -0500 Subject: [PATCH 05/50] GH-1245 Add tracking and validation of gossip_bp_peers_message. --- .../eosio/net_plugin/auto_bp_peering.hpp | 73 +++++-- .../eosio/net_plugin/gossip_bps_index.hpp | 36 ++++ .../include/eosio/net_plugin/protocol.hpp | 40 +++- plugins/net_plugin/net_plugin.cpp | 184 +++++++++++++++++- 4 files changed, 309 insertions(+), 24 deletions(-) create mode 100644 plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 54490bd9a4..5ecf929eac 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -30,11 +31,15 @@ class bp_connection_manager { } config; // thread safe only because modified at plugin startup currently // the following member are only accessed from main thread + flat_set my_bp_peer_accounts; flat_set pending_configured_bps; flat_set active_configured_bps; uint32_t pending_schedule_version = 0; uint32_t active_schedule_version = 0; + fc::mutex factory_mtx; + gossip_buffer_initial_factory initial_gossip_msg_factory GUARDED_BY(factory_mtx); + Derived* self() { return static_cast(this); } const Derived* self() const { return static_cast(this); } @@ -57,7 +62,7 @@ class bp_connection_manager { } public: - bool auto_bp_peering_enabled() const { return !config.bp_peer_addresses.empty(); } + bool auto_bp_peering_enabled() const { return !config.bp_peer_addresses.empty() && !my_bp_peer_accounts.empty(); } // Only called at plugin startup void set_producer_accounts(const std::set& accounts) { @@ -93,24 +98,66 @@ class bp_connection_manager { } } + send_buffer_type get_initial_send_buffer() { + fc::lock_guard g(factory_mtx); + return initial_gossip_msg_factory.get_initial_send_buffer(); + } + // Only called at plugin startup - template - void for_each_bp_peer_address(T&& fun) const { - for (auto& [_, addr] : config.bp_peer_addresses) { fun(addr); } + void set_bp_producer_peers(const std::vector& peers) { + for (const auto& entry : peers) { + try { + my_bp_peer_accounts.emplace(chain::name(entry)); + } catch (chain::name_type_exception&) { + EOS_ASSERT(false, chain::plugin_config_exception, + "the producer ${p} supplied by --p2p-producer-peer option is invalid", ("p", entry)); + } + } } - // Only called from connection strand and the connection constructor - void mark_bp_connection(Connection* conn) const { - /// mark an connection as a bp connection if it connects to an address in the bp peer list, so that the connection - /// won't be subject to the limit of max_client_count. - auto space_pos = conn->log_p2p_address.find(' '); - // log_p2p_address always has a trailing hex like `localhost:9877 - bc3f55b` - std::string addr = conn->log_p2p_address.substr(0, space_pos); - if (config.bp_peer_accounts.count(addr)) { - conn->is_bp_connection = true; + // Called at startup and when peer key changes + // empty modified_keys means to update all + void update_bp_producer_peers(const chain::controller& cc, gossip_bp_index_t& gossip_bp_index, + const std::set& modified_keys, const std::string& server_address) + { + if (my_bp_peer_accounts.empty()) + return; + fc::lock_guard g(gossip_bp_index.mtx); + // normally only one bp peer account except in testing scenarios or test chains + bool first = true; + for (const auto& e : my_bp_peer_accounts) { // my_bp_peer_accounts not modified after plugin startup + if (modified_keys.empty() || modified_keys.contains(e)) { + //todo: auto pk = cc.get_peer_key(e); + std::optional pk; // todo + // EOS_ASSERT can only be hit on plugin startup, otherwise this method called with modified_keys that are in cc.get_peer_key() + EOS_ASSERT(pk, chain::plugin_config_exception, "No on-chain peer key found for ${n}", ("n", e)); + if (first) { + gossip_bp_peers_message::bp_peer signed_empty{.producer_name = e}; // .server_address not set for initial message + signed_empty.sig = self()->sign_compact(*pk, signed_empty.digest()); + fc::lock_guard lck(factory_mtx); + initial_gossip_msg_factory.set_initial_send_buffer(signed_empty); + } + auto& prod_idx = gossip_bp_index.index.get(); + gossip_bp_peers_message::bp_peer peer{.producer_name = e, .server_address = server_address}; + peer.sig = self()->sign_compact(*pk, peer.digest()); + if (auto i = prod_idx.find(boost::make_tuple(e, boost::cref(server_address))); i != prod_idx.end()) { + gossip_bp_index.index.modify(i, [&peer](auto& v) { + v = peer; + }); + } else { + gossip_bp_index.index.emplace(peer); + } + } + first = false; } } + // Only called at plugin startup + template + void for_each_bp_peer_address(T&& fun) const { + for (auto& [_, addr] : config.bp_peer_addresses) { fun(addr); } + } + // Only called from connection strand template static bool established_client_connection(Conn&& conn) { diff --git a/plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp b/plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp new file mode 100644 index 0000000000..1f427de590 --- /dev/null +++ b/plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace eosio { + +struct gossip_bp_index_t { + using gossip_bps_index_container_t = boost::multi_index_container< + gossip_bp_peers_message::bp_peer, + indexed_by< + ordered_unique< + tag, + composite_key< gossip_bp_peers_message::bp_peer, + member, + member + >, + composite_key_compare< std::less<>, std::less<> > + >, + ordered_unique< + tag< struct by_sig >, + member< gossip_bp_peers_message::bp_peer, chain::signature_type, &gossip_bp_peers_message::bp_peer::sig > > + > + >; + + alignas(hardware_destructive_interference_sz) + mutable fc::mutex mtx; + gossip_bps_index_container_t index GUARDED_BY(mtx);; +}; + + +} // namespace eosio \ No newline at end of file diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index 8f2a7562e1..a9b6829d34 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -7,6 +7,8 @@ namespace eosio { using namespace chain; using namespace fc; + constexpr auto message_header_size = sizeof(uint32_t); + struct chain_size_message { uint32_t last_irreversible_block_num = 0; block_id_type last_irreversible_block_id; @@ -139,16 +141,13 @@ namespace eosio { struct bp_peer { eosio::name producer_name; std::string server_address; - std::vector proposer_peers; // size limit 2 - std::vector finalizer_peers; // size limit 2 - // sig over [producer_name, server_address, proposer_peers, finalizer_peers] + // sig over [producer_name, server_address] signature_type sig; digest_type digest() const; bool operator==(const bp_peer&) const = default; bool operator<(const bp_peer& rhs) const { - return std::tie(producer_name, server_address, proposer_peers, finalizer_peers) < - std::tie(rhs.producer_name, rhs.server_address, rhs.proposer_peers, rhs.finalizer_peers); + return std::tie(producer_name, server_address) < std::tie(rhs.producer_name, rhs.server_address); } }; @@ -169,6 +168,35 @@ namespace eosio { block_notice_message, gossip_bp_peers_message>; + // see protocol net_message + enum class msg_type_t { + handshake_message = fc::get_index(), + chain_size_message = fc::get_index(), + go_away_message = fc::get_index(), + time_message = fc::get_index(), + notice_message = fc::get_index(), + request_message = fc::get_index(), + sync_request_message = fc::get_index(), + signed_block = fc::get_index(), + packed_transaction = fc::get_index(), + vote_message = fc::get_index(), + block_nack_message = fc::get_index(), + block_notice_message = fc::get_index(), + gossip_bp_peers_message= fc::get_index(), + unknown + }; + + constexpr uint32_t to_index(msg_type_t net_msg) { + static_assert( std::variant_size_v == static_cast(msg_type_t::unknown)); + return static_cast(net_msg); + } + + constexpr msg_type_t to_msg_type_t(size_t v) { + static_assert( std::variant_size_v == static_cast(msg_type_t::unknown)); + EOS_ASSERT(v < to_index(msg_type_t::unknown), plugin_exception, "Invalid net_message index: ${v}", ("v", v)); + return static_cast(v); + } + } // namespace eosio FC_REFLECT( eosio::select_ids, (mode)(pending)(ids) ) @@ -188,7 +216,7 @@ FC_REFLECT( eosio::request_message, (req_trx)(req_blocks) ) FC_REFLECT( eosio::sync_request_message, (start_block)(end_block) ) FC_REFLECT( eosio::block_nack_message, (id) ) FC_REFLECT( eosio::block_notice_message, (previous)(id) ) -FC_REFLECT( eosio::gossip_bp_peers_message::bp_peer, (producer_name)(server_address)(proposer_peers)(finalizer_peers)(sig) ) +FC_REFLECT( eosio::gossip_bp_peers_message::bp_peer, (producer_name)(server_address)(sig) ) FC_REFLECT( eosio::gossip_bp_peers_message, (peers) ) /** diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index c4ce22bec1..2204c8ff71 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -348,6 +350,7 @@ namespace eosio { unique_ptr< sync_manager > sync_master; dispatch_manager dispatcher {thread_pool.get_executor()}; connections_manager connections; + gossip_bp_index_t gossip_bps; /** * Thread safe, only updated in plugin initialize @@ -534,9 +537,10 @@ namespace eosio { constexpr uint16_t proto_block_range = 8; // include block range in notice_message constexpr uint16_t proto_savanna = 9; // savanna, adds vote_message constexpr uint16_t proto_block_nack = 10; // adds block_nack_message & block_notice_message + constexpr uint16_t proto_gossip_bp_peers = 11; // adds gossip_bp_peers_message #pragma GCC diagnostic pop - constexpr uint16_t net_version_max = proto_block_nack; + constexpr uint16_t net_version_max = proto_gossip_bp_peers; struct peer_sync_state { enum class sync_t { @@ -903,6 +907,10 @@ namespace eosio { bool process_next_trx_message(uint32_t message_length); bool process_next_vote_message(uint32_t message_length); void update_endpoints(const tcp::endpoint& endpoint = tcp::endpoint()); + + void send_gossip_bp_peers_initial_message(); + void send_gossip_bp_peers_message(); + void send_gossip_bp_peers_message_to_bp_peers(); public: bool populate_handshake( handshake_message& hello ) const; @@ -1008,7 +1016,8 @@ namespace eosio { void handle_message( const vote_message& msg ) = delete; // vote_message_ptr overload used instead void handle_message( const block_nack_message& msg); void handle_message( const block_notice_message& msg); - void handle_message( const gossip_bp_peers_message& msg); + void handle_message( gossip_bp_peers_message& msg); + void handle_message( const gossip_bp_peers_message& msg) = delete; // returns calculated number of blocks combined latency uint32_t calc_block_latency(); @@ -1102,7 +1111,7 @@ namespace eosio { c->handle_message( msg ); } - void operator()( const gossip_bp_peers_message& msg ) const { + void operator()( gossip_bp_peers_message& msg ) const { // continue call to handle_message on connection strand peer_dlog( c, "handle gossip_bp_peers_message size ${s}", ("s", msg.peers.size()) ); c->handle_message( msg ); @@ -1195,7 +1204,6 @@ namespace eosio { auto [host, port, type] = net_utils::split_host_port_type(this_address); listen_address = host + ":" + port; // do not include type in listen_address to avoid peer setting type on connection set_connection_type( peer_address() ); - my_impl->mark_bp_connection(this); fc_ilog( logger, "created connection - ${c} to ${n}", ("c", connection_id)("n", endpoint) ); } @@ -2748,6 +2756,7 @@ namespace eosio { } + // thread safe, only modified in plugin startup const string& net_plugin_impl::get_first_p2p_address() const { return p2p_addresses.size() > 0 ? *p2p_addresses.begin() : empty; } @@ -3281,7 +3290,6 @@ namespace eosio { unique_conn_node_id = msg.node_id.str().substr( 0, 7 ); g_conn.unlock(); - my_impl->mark_bp_connection(this); if (my_impl->exceeding_connection_limit(shared_from_this())) { // When auto bp peering is enabled, create_session() check doesn't have enough information to determine // if a client is a BP peer. In create_session(), it only has the peer address which a node is connecting @@ -3408,6 +3416,8 @@ namespace eosio { if( sent_handshake_count == 0 ) { send_handshake(); } + + send_gossip_bp_peers_initial_message(); } uint32_t nblk_combined_latency = calc_block_latency(); @@ -3748,6 +3758,154 @@ namespace eosio { } } + digest_type gossip_bp_peers_message::bp_peer::digest() const { + digest_type::encoder enc; + fc::raw::pack(enc, my_impl->chain_id); + fc::raw::pack(enc, *this); + return enc.result(); + } + + // thread safe + // removes invalid entries from msg + bool validate_gossip_bp_peers_message( gossip_bp_peers_message& msg ) { + if (msg.peers.empty()) + return false; + if (msg.peers.size() != 1 || !msg.peers[0].server_address.empty()) { // initial case, no server_addresses to validate + auto valid_address = [](const std::string& addr) -> bool { + const auto& [host, port, type] = net_utils::split_host_port_type(addr); + return !host.empty() && !port.empty(); + }; + std::map producers; + const gossip_bp_peers_message::bp_peer* prev = nullptr; + for (const auto& peer : msg.peers) { + if (peer.producer_name.empty()) + return false; + if (!valid_address(peer.server_address)) + return false; + if (prev != nullptr) { + if (prev->producer_name == peer.producer_name) { + if (prev->server_address == peer.server_address) + return false; // duplicate entries not allowed + } else if (prev->producer_name > peer.producer_name) { + return false; // required to be sorted + } + } + if (++producers[peer.producer_name] > 2) + return false; // only 2 entries per producer allowed + prev = &peer; + } + } + + controller& cc = my_impl->chain_plug->chain(); + + fc::lock_guard g(my_impl->gossip_bps.mtx); + auto& sig_idx = my_impl->gossip_bps.index.get(); + auto& prod_idx = my_impl->gossip_bps.index.get(); + for (auto i = msg.peers.begin(); i != msg.peers.end();) { + const auto& peer = *i; + try { + if (!sig_idx.contains(peer.sig)) { // we already have it, already verified + // peer key may have changed or been removed, so if invalid or not found skip it + // todo: when cc.get_peer_key available + // if (auto peer_key = cc.get_peer_key(peer.producer_name)) { + // public_key_type pk(peer.sig, peer.digest()); + // if (pk != *peer_key) { + // i = msg.peers.erase(i); + // continue; + // } + // } else { // unknown key + // i = msg.peers.erase(i); + // continue; + // } + } + } catch (fc::exception& e) { + // invalid key + i = msg.peers.erase(i); + continue; + } + ++i; + } + + return !msg.peers.empty(); + } + + // called from connection strand + void connection::handle_message( gossip_bp_peers_message& msg ) { + if (!my_impl->auto_bp_peering_enabled()) + return; + + if (!validate_gossip_bp_peers_message(msg)) { + peer_wlog( this, "bad gossip_bp_peers_message, closing"); + no_retry = go_away_reason::fatal_other; + enqueue( go_away_message( fatal_other ) ); + return; + } + + // valid gossip peer connection + is_bp_connection = true; + + assert(!msg.peers.empty()); // checked by validate_gossip_bp_peers_message() + if (msg.peers.size() == 1 && msg.peers[0].server_address.empty()) { + // initial message case, send back our entire collection + send_gossip_bp_peers_message(); + } else { + // providing us with full set + fc::lock_guard g(my_impl->gossip_bps.mtx); + auto& idx = my_impl->gossip_bps.index.get(); + bool diff = false; + for (const auto& peer : msg.peers) { + if (auto i = idx.find(boost::make_tuple(peer.producer_name, boost::cref(peer.server_address))); i != idx.end()) { + if (*i != peer) { + my_impl->gossip_bps.index.modify(i, [&peer](auto& m) { + m = peer; + }); + diff = true; + } + } else { + my_impl->gossip_bps.index.insert(peer); + diff = true; + } + } + if (msg.peers.size() < idx.size()) { // we have more than sent to us, send back our set + send_gossip_bp_peers_message(); + } + if (diff) { // update, let all our peers know about it + send_gossip_bp_peers_message_to_bp_peers(); + } + } + } + + // called from connection strand + void connection::send_gossip_bp_peers_initial_message() { + if (protocol_version < proto_gossip_bp_peers || !my_impl->auto_bp_peering_enabled()) + return; + auto sb = my_impl->get_initial_send_buffer(); + enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); + } + + + // called from connection strand + void connection::send_gossip_bp_peers_message() { + assert(protocol_version >= proto_gossip_bp_peers); + gossip_buffer_factory factory; + auto sb = factory.get_send_buffer(my_impl->gossip_bps); + enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); + } + + // called from connection strand, thread safe + void connection::send_gossip_bp_peers_message_to_bp_peers() { + my_impl->connections.for_each_connection([this](const connection_ptr& c) { + gossip_buffer_factory factory; + if (this != c.get() && c->is_bp_connection && c->socket_is_open()) { + assert(c->protocol_version >= proto_gossip_bp_peers); + const send_buffer_type& sb = factory.get_send_buffer(my_impl->gossip_bps); + boost::asio::post(c->strand, [sb, c]() { + c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); + }); + } + }); + } + size_t calc_trx_size( const packed_transaction_ptr& trx ) { return trx->get_estimated_size(); } @@ -4197,6 +4355,8 @@ namespace eosio { " eosproducer1,p2p.eos.io:9876\n" " eosproducer2,p2p.trx.eos.io:9876:trx\n" " eosproducer3,p2p.blk.eos.io:9876:blk\n") + ("p2p-producer-peer", boost::program_options::value>()->composing()->multitoken(), + "Producer peer name of this node used to retrieve peer key from on-chain peerkeys table. Private key of peer key should be configured via signature-provider.") ( "agent-name", bpo::value()->default_value("EOS Test Agent"), "The name supplied to identify this node amongst the peers.") ( "allowed-connection", bpo::value>()->multitoken()->default_value({"any"}, "any"), "Can be 'any' or 'producers' or 'specified' or 'none'. If 'specified', peer-key must be specified at least once. If only 'producers', peer-key is not required. 'producers' and 'specified' may be combined.") ( "peer-key", bpo::value>()->composing()->multitoken(), "Optional public key of peer allowed to connect. May be used multiple times.") @@ -4287,6 +4447,9 @@ namespace eosio { EOS_ASSERT( addr.length() <= net_utils::max_p2p_address_length, chain::plugin_config_exception, "p2p-listen-endpoint ${a} too long, must be less than ${m}", ("a", addr)("m", net_utils::max_p2p_address_length) ); + const auto& [host, port, type] = net_utils::split_host_port_type(addr); + EOS_ASSERT( !host.empty() && !port.empty(), chain::plugin_config_exception, + "Invalid p2p-listen-endpoint ${p}, syntax host:port:[trx|blk]", ("p", addr)); } } } @@ -4298,6 +4461,9 @@ namespace eosio { EOS_ASSERT( addr.length() <= net_utils::max_p2p_address_length, chain::plugin_config_exception, "p2p-server-address ${a} too long, must be less than ${m}", ("a", addr)("m", net_utils::max_p2p_address_length) ); + const auto& [host, port, type] = net_utils::split_host_port_type(addr); + EOS_ASSERT( !host.empty() && !port.empty(), chain::plugin_config_exception, + "Invalid p2p-server-address ${p}, syntax host:port:[trx|blk]", ("p", addr)); } } p2p_server_addresses.resize(p2p_addresses.size()); // extend with empty entries as needed @@ -4332,6 +4498,12 @@ namespace eosio { }); } + if ( options.count("p2p-producer-peer") ) { + EOS_ASSERT(options.count("signature-provider"), chain::plugin_config_exception, + "signature-provider of associated key required for p2p-producer-peer"); + set_bp_producer_peers(options.at("p2p-producer-peer").as>()); + } + if( options.count( "allowed-connection" )) { const std::vector allowed_remotes = options["allowed-connection"].as>(); for( const std::string& allowed_remote : allowed_remotes ) { @@ -4444,6 +4616,8 @@ namespace eosio { cc.aggregated_vote().connect( broadcast_vote ); cc.voted_block().connect( broadcast_vote ); + + update_bp_producer_peers(cc, gossip_bps, std::set{}, get_first_p2p_address()); } incoming_transaction_ack_subscription = app().get_channel().subscribe( From 1f97d9d091362132c3dbc7c416c5e04af96c3dfb Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 14 Mar 2025 13:33:54 -0500 Subject: [PATCH 06/50] GH-1245 Refactor some functionality into auto_bp_peering.hpp --- .../eosio/net_plugin/auto_bp_peering.hpp | 106 ++++++++++++++++-- plugins/net_plugin/net_plugin.cpp | 97 ++-------------- 2 files changed, 107 insertions(+), 96 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 5ecf929eac..657699ab40 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -30,6 +31,8 @@ class bp_connection_manager { flat_set my_bp_accounts; } config; // thread safe only because modified at plugin startup currently + gossip_bp_index_t gossip_bps; + // the following member are only accessed from main thread flat_set my_bp_peer_accounts; flat_set pending_configured_bps; @@ -98,11 +101,15 @@ class bp_connection_manager { } } - send_buffer_type get_initial_send_buffer() { + send_buffer_type get_gossip_bp_initial_send_buffer() { fc::lock_guard g(factory_mtx); return initial_gossip_msg_factory.get_initial_send_buffer(); } + send_buffer_type get_gossip_bp_send_buffer(gossip_buffer_factory& factory) { + return factory.get_send_buffer(gossip_bps); + } + // Only called at plugin startup void set_bp_producer_peers(const std::vector& peers) { for (const auto& entry : peers) { @@ -117,12 +124,11 @@ class bp_connection_manager { // Called at startup and when peer key changes // empty modified_keys means to update all - void update_bp_producer_peers(const chain::controller& cc, gossip_bp_index_t& gossip_bp_index, - const std::set& modified_keys, const std::string& server_address) + void update_bp_producer_peers(const chain::controller& cc, const std::set& modified_keys, const std::string& server_address) { if (my_bp_peer_accounts.empty()) return; - fc::lock_guard g(gossip_bp_index.mtx); + fc::lock_guard g(gossip_bps.mtx); // normally only one bp peer account except in testing scenarios or test chains bool first = true; for (const auto& e : my_bp_peer_accounts) { // my_bp_peer_accounts not modified after plugin startup @@ -137,15 +143,15 @@ class bp_connection_manager { fc::lock_guard lck(factory_mtx); initial_gossip_msg_factory.set_initial_send_buffer(signed_empty); } - auto& prod_idx = gossip_bp_index.index.get(); + auto& prod_idx = gossip_bps.index.get(); gossip_bp_peers_message::bp_peer peer{.producer_name = e, .server_address = server_address}; peer.sig = self()->sign_compact(*pk, peer.digest()); if (auto i = prod_idx.find(boost::make_tuple(e, boost::cref(server_address))); i != prod_idx.end()) { - gossip_bp_index.index.modify(i, [&peer](auto& v) { + gossip_bps.index.modify(i, [&peer](auto& v) { v = peer; }); } else { - gossip_bp_index.index.emplace(peer); + gossip_bps.index.emplace(peer); } } first = false; @@ -184,6 +190,92 @@ class bp_connection_manager { established_client_connection(new_connection) && num_established_clients() > self()->connections.get_max_client_count(); } + // thread safe + // removes invalid entries from msg + bool validate_gossip_bp_peers_message( gossip_bp_peers_message& msg ) { + if (msg.peers.empty()) + return false; + if (msg.peers.size() != 1 || !msg.peers[0].server_address.empty()) { // initial case, no server_addresses to validate + auto valid_address = [](const std::string& addr) -> bool { + const auto& [host, port, type] = net_utils::split_host_port_type(addr); + return !host.empty() && !port.empty(); + }; + std::map producers; + const gossip_bp_peers_message::bp_peer* prev = nullptr; + for (const auto& peer : msg.peers) { + if (peer.producer_name.empty()) + return false; + if (!valid_address(peer.server_address)) + return false; + if (prev != nullptr) { + if (prev->producer_name == peer.producer_name) { + if (prev->server_address == peer.server_address) + return false; // duplicate entries not allowed + } else if (prev->producer_name > peer.producer_name) { + return false; // required to be sorted + } + } + if (++producers[peer.producer_name] > 4) + return false; // only 4 entries per producer allowed + prev = &peer; + } + } + + controller& cc = self()->chain_plug->chain(); + + fc::lock_guard g(gossip_bps.mtx); + auto& sig_idx = gossip_bps.index.get(); + auto& prod_idx = gossip_bps.index.get(); + for (auto i = msg.peers.begin(); i != msg.peers.end();) { + const auto& peer = *i; + try { + if (!sig_idx.contains(peer.sig)) { // we already have it, already verified + // peer key may have changed or been removed, so if invalid or not found skip it + // todo: when cc.get_peer_key available + // if (auto peer_key = cc.get_peer_key(peer.producer_name)) { + // public_key_type pk(peer.sig, peer.digest()); + // if (pk != *peer_key) { + // i = msg.peers.erase(i); + // continue; + // } + // } else { // unknown key + // i = msg.peers.erase(i); + // continue; + // } + } + } catch (fc::exception& e) { + // invalid key + i = msg.peers.erase(i); + continue; + } + ++i; + } + + return !msg.peers.empty(); + } + + // thread-safe + std::tuple update_gossip_bps(const gossip_bp_peers_message& msg) { + // providing us with full set + fc::lock_guard g(gossip_bps.mtx); + auto& idx = gossip_bps.index.get(); + bool diff = false; + for (const auto& peer : msg.peers) { + if (auto i = idx.find(boost::make_tuple(peer.producer_name, boost::cref(peer.server_address))); i != idx.end()) { + if (*i != peer) { + gossip_bps.index.modify(i, [&peer](auto& m) { + m = peer; + }); + diff = true; + } + } else { + gossip_bps.index.insert(peer); + diff = true; + } + } + return std::make_tuple(gossip_bps.index.size(), diff); + } + // Only called from main thread void on_pending_schedule(const chain::producer_authority_schedule& schedule) { if (auto_bp_peering_enabled() && !self()->is_lib_catchup()) { diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 2204c8ff71..ea20779e33 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -350,7 +350,6 @@ namespace eosio { unique_ptr< sync_manager > sync_master; dispatch_manager dispatcher {thread_pool.get_executor()}; connections_manager connections; - gossip_bp_index_t gossip_bps; /** * Thread safe, only updated in plugin initialize @@ -1798,7 +1797,7 @@ namespace eosio { void connection::enqueue_buffer( msg_type_t net_msg, std::optional block_num, // only valid for net_msg == signed_block variant which queued_buffer::queue_t queue, - const std::shared_ptr>& send_buffer, + const send_buffer_type& send_buffer, go_away_reason close_after_send) { connection_ptr self = shared_from_this(); @@ -3765,76 +3764,12 @@ namespace eosio { return enc.result(); } - // thread safe - // removes invalid entries from msg - bool validate_gossip_bp_peers_message( gossip_bp_peers_message& msg ) { - if (msg.peers.empty()) - return false; - if (msg.peers.size() != 1 || !msg.peers[0].server_address.empty()) { // initial case, no server_addresses to validate - auto valid_address = [](const std::string& addr) -> bool { - const auto& [host, port, type] = net_utils::split_host_port_type(addr); - return !host.empty() && !port.empty(); - }; - std::map producers; - const gossip_bp_peers_message::bp_peer* prev = nullptr; - for (const auto& peer : msg.peers) { - if (peer.producer_name.empty()) - return false; - if (!valid_address(peer.server_address)) - return false; - if (prev != nullptr) { - if (prev->producer_name == peer.producer_name) { - if (prev->server_address == peer.server_address) - return false; // duplicate entries not allowed - } else if (prev->producer_name > peer.producer_name) { - return false; // required to be sorted - } - } - if (++producers[peer.producer_name] > 2) - return false; // only 2 entries per producer allowed - prev = &peer; - } - } - - controller& cc = my_impl->chain_plug->chain(); - - fc::lock_guard g(my_impl->gossip_bps.mtx); - auto& sig_idx = my_impl->gossip_bps.index.get(); - auto& prod_idx = my_impl->gossip_bps.index.get(); - for (auto i = msg.peers.begin(); i != msg.peers.end();) { - const auto& peer = *i; - try { - if (!sig_idx.contains(peer.sig)) { // we already have it, already verified - // peer key may have changed or been removed, so if invalid or not found skip it - // todo: when cc.get_peer_key available - // if (auto peer_key = cc.get_peer_key(peer.producer_name)) { - // public_key_type pk(peer.sig, peer.digest()); - // if (pk != *peer_key) { - // i = msg.peers.erase(i); - // continue; - // } - // } else { // unknown key - // i = msg.peers.erase(i); - // continue; - // } - } - } catch (fc::exception& e) { - // invalid key - i = msg.peers.erase(i); - continue; - } - ++i; - } - - return !msg.peers.empty(); - } - // called from connection strand void connection::handle_message( gossip_bp_peers_message& msg ) { if (!my_impl->auto_bp_peering_enabled()) return; - if (!validate_gossip_bp_peers_message(msg)) { + if (!my_impl->validate_gossip_bp_peers_message(msg)) { peer_wlog( this, "bad gossip_bp_peers_message, closing"); no_retry = go_away_reason::fatal_other; enqueue( go_away_message( fatal_other ) ); @@ -3849,24 +3784,8 @@ namespace eosio { // initial message case, send back our entire collection send_gossip_bp_peers_message(); } else { - // providing us with full set - fc::lock_guard g(my_impl->gossip_bps.mtx); - auto& idx = my_impl->gossip_bps.index.get(); - bool diff = false; - for (const auto& peer : msg.peers) { - if (auto i = idx.find(boost::make_tuple(peer.producer_name, boost::cref(peer.server_address))); i != idx.end()) { - if (*i != peer) { - my_impl->gossip_bps.index.modify(i, [&peer](auto& m) { - m = peer; - }); - diff = true; - } - } else { - my_impl->gossip_bps.index.insert(peer); - diff = true; - } - } - if (msg.peers.size() < idx.size()) { // we have more than sent to us, send back our set + auto [diff, size] = my_impl->update_gossip_bps(msg); + if (msg.peers.size() < size) { // we have more than sent to us, send back our set send_gossip_bp_peers_message(); } if (diff) { // update, let all our peers know about it @@ -3879,7 +3798,7 @@ namespace eosio { void connection::send_gossip_bp_peers_initial_message() { if (protocol_version < proto_gossip_bp_peers || !my_impl->auto_bp_peering_enabled()) return; - auto sb = my_impl->get_initial_send_buffer(); + auto sb = my_impl->get_gossip_bp_initial_send_buffer(); enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); } @@ -3888,7 +3807,7 @@ namespace eosio { void connection::send_gossip_bp_peers_message() { assert(protocol_version >= proto_gossip_bp_peers); gossip_buffer_factory factory; - auto sb = factory.get_send_buffer(my_impl->gossip_bps); + const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); } @@ -3898,7 +3817,7 @@ namespace eosio { gossip_buffer_factory factory; if (this != c.get() && c->is_bp_connection && c->socket_is_open()) { assert(c->protocol_version >= proto_gossip_bp_peers); - const send_buffer_type& sb = factory.get_send_buffer(my_impl->gossip_bps); + const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); boost::asio::post(c->strand, [sb, c]() { c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); }); @@ -4617,7 +4536,7 @@ namespace eosio { cc.aggregated_vote().connect( broadcast_vote ); cc.voted_block().connect( broadcast_vote ); - update_bp_producer_peers(cc, gossip_bps, std::set{}, get_first_p2p_address()); + update_bp_producer_peers(cc, std::set{}, get_first_p2p_address()); } incoming_transaction_ack_subscription = app().get_channel().subscribe( From b337c1ac646eacf42694391b9b406ee3d4a46e40 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 17 Mar 2025 10:06:16 -0500 Subject: [PATCH 07/50] GH-1245 The gethostname() seems to be testing this python script rather than anything in net_plugin. Empty p2p-server-address should not be allowed. --- tests/auto_bp_peering_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/auto_bp_peering_test.py b/tests/auto_bp_peering_test.py index 4821df4a01..2208b6255b 100755 --- a/tests/auto_bp_peering_test.py +++ b/tests/auto_bp_peering_test.py @@ -49,8 +49,6 @@ port = cluster.p2pBasePort + nodeId if producer_name == 'defproducerf': hostname = 'ext-ip0:9999' - elif producer_name == 'defproducerk': - hostname = socket.gethostname() + ':9886' else: hostname = "localhost:" + str(port) peer_names[hostname] = producer_name @@ -66,7 +64,6 @@ specificNodeosArgs[nodeId] = auto_bp_peer_args specificNodeosArgs[5] = specificNodeosArgs[5] + ' --p2p-server-address ext-ip0:9999' - specificNodeosArgs[10] = specificNodeosArgs[10] + ' --p2p-server-address ""' TestHelper.printSystemInfo("BEGIN") cluster.launch( From b4abdc73f1c44718cb33bab1d06351404bf4af28 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 17 Mar 2025 10:07:27 -0500 Subject: [PATCH 08/50] GH-1245 Support both manually configured bp peers and gossip bp peers. --- .../eosio/net_plugin/auto_bp_peering.hpp | 117 ++++++++++++------ .../eosio/net_plugin/gossip_bps_index.hpp | 2 +- plugins/net_plugin/net_plugin.cpp | 25 ++-- .../tests/auto_bp_peering_unittest.cpp | 32 ++--- 4 files changed, 109 insertions(+), 67 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 657699ab40..1e8886454a 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -25,18 +25,20 @@ class bp_connection_manager { using flat_map = chain::flat_map; template using flat_set = chain::flat_set; + + gossip_bp_index_t gossip_bps; + + // the following members are thread-safe, only modified during plugin startup struct config_t { flat_map bp_peer_addresses; flat_map bp_peer_accounts; - flat_set my_bp_accounts; + flat_set my_bp_accounts; // block producer --producer-name + flat_set my_bp_peer_accounts; // peer key account --p2p-producer-peer } config; // thread safe only because modified at plugin startup currently - gossip_bp_index_t gossip_bps; - - // the following member are only accessed from main thread - flat_set my_bp_peer_accounts; - flat_set pending_configured_bps; - flat_set active_configured_bps; + // the following members are only accessed from main thread + flat_set pending_bps; + flat_set active_bps; uint32_t pending_schedule_version = 0; uint32_t active_schedule_version = 0; @@ -52,20 +54,22 @@ class bp_connection_manager { } // Only called from main thread - chain::flat_set configured_bp_accounts(const config_t& config, - const std::vector& schedule) const + chain::flat_set bp_accounts(const std::vector& schedule) const { + + fc::lock_guard g(gossip_bps.mtx); + auto& prod_idx = gossip_bps.index.get(); chain::flat_set result; for (const auto& auth : schedule) { - if (config.bp_peer_addresses.contains(auth.producer_name)) { + if (config.bp_peer_addresses.contains(auth.producer_name) || prod_idx.contains(auth.producer_name)) result.insert(auth.producer_name); - } } return result; } - public: - bool auto_bp_peering_enabled() const { return !config.bp_peer_addresses.empty() && !my_bp_peer_accounts.empty(); } +public: + bool auto_bp_peering_enabled() const { return !config.bp_peer_addresses.empty() || !config.my_bp_peer_accounts.empty(); } + bool bp_gossip_enabled() const { return !config.my_bp_peer_accounts.empty(); } // Only called at plugin startup void set_producer_accounts(const std::set& accounts) { @@ -78,7 +82,8 @@ class bp_connection_manager { } // Only called at plugin startup - void set_bp_peers(const std::vector& peers) { + void set_configured_bp_peers(const std::vector& peers) { + fc::lock_guard g(gossip_bps.mtx); for (const auto& entry : peers) { try { auto comma_pos = entry.find(','); @@ -90,10 +95,9 @@ class bp_connection_manager { EOS_ASSERT( !host.empty() && !port.empty(), chain::plugin_config_exception, "Invalid p2p-auto-bp-peer ${p}, syntax host:port:[trx|blk]", ("p", addr)); + fc_dlog(self()->get_logger(), "Setting p2p-auto-bp-peer ${a} -> ${d}", ("a", account)("d", addr)); config.bp_peer_accounts[addr] = account; config.bp_peer_addresses[account] = std::move(addr); - fc_dlog(self()->get_logger(), "Setting p2p-auto-bp-peer ${a} -> ${d}", - ("a", account)("d", config.bp_peer_addresses[account])); } catch (chain::name_type_exception&) { EOS_ASSERT(false, chain::plugin_config_exception, "the account ${a} supplied by --p2p-auto-bp-peer option is invalid", ("a", entry)); @@ -101,20 +105,20 @@ class bp_connection_manager { } } - send_buffer_type get_gossip_bp_initial_send_buffer() { - fc::lock_guard g(factory_mtx); - return initial_gossip_msg_factory.get_initial_send_buffer(); - } - - send_buffer_type get_gossip_bp_send_buffer(gossip_buffer_factory& factory) { - return factory.get_send_buffer(gossip_bps); + // Only called at plugin startup + template + void for_each_bp_peer_address(T&& fun) const { + fc::lock_guard g(gossip_bps.mtx); + for (const auto& bp_peer : gossip_bps.index) { + fun(bp_peer.server_address); + } } // Only called at plugin startup void set_bp_producer_peers(const std::vector& peers) { for (const auto& entry : peers) { try { - my_bp_peer_accounts.emplace(chain::name(entry)); + config.my_bp_peer_accounts.emplace(chain::name(entry)); } catch (chain::name_type_exception&) { EOS_ASSERT(false, chain::plugin_config_exception, "the producer ${p} supplied by --p2p-producer-peer option is invalid", ("p", entry)); @@ -126,12 +130,12 @@ class bp_connection_manager { // empty modified_keys means to update all void update_bp_producer_peers(const chain::controller& cc, const std::set& modified_keys, const std::string& server_address) { - if (my_bp_peer_accounts.empty()) + if (config.my_bp_peer_accounts.empty()) return; fc::lock_guard g(gossip_bps.mtx); // normally only one bp peer account except in testing scenarios or test chains bool first = true; - for (const auto& e : my_bp_peer_accounts) { // my_bp_peer_accounts not modified after plugin startup + for (const auto& e : config.my_bp_peer_accounts) { // my_bp_peer_accounts not modified after plugin startup if (modified_keys.empty() || modified_keys.contains(e)) { //todo: auto pk = cc.get_peer_key(e); std::optional pk; // todo @@ -158,16 +162,31 @@ class bp_connection_manager { } } - // Only called at plugin startup - template - void for_each_bp_peer_address(T&& fun) const { - for (auto& [_, addr] : config.bp_peer_addresses) { fun(addr); } + // Only called from connection strand and the connection constructor + void mark_configured_bp_connection(Connection* conn) const { + /// mark an connection as a configured bp connection if it connects to an address in the bp peer list, + /// so that the connection won't be subject to the limit of max_client_count. + auto space_pos = conn->log_p2p_address.find(' '); + // log_p2p_address always has a trailing hex like `localhost:9877 - bc3f55b` + std::string addr = conn->log_p2p_address.substr(0, space_pos); + if (config.bp_peer_accounts.count(addr)) { + conn->is_configured_bp_connection = true; + } } // Only called from connection strand template static bool established_client_connection(Conn&& conn) { - return !conn->is_bp_connection && conn->socket_is_open() && conn->incoming_and_handshake_received(); + return !conn->is_gossip_bp_connection && !conn->is_configured_bp_connection && conn->socket_is_open() && conn->incoming_and_handshake_received(); + } + + send_buffer_type get_gossip_bp_initial_send_buffer() { + fc::lock_guard g(factory_mtx); + return initial_gossip_msg_factory.get_initial_send_buffer(); + } + + send_buffer_type get_gossip_bp_send_buffer(gossip_buffer_factory& factory) { + return factory.get_send_buffer(gossip_bps); } // Only called from connection strand @@ -286,20 +305,32 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "pending producer schedule switches from version ${old} to ${new}", ("old", pending_schedule_version)("new", schedule.version)); - auto pending_connections = configured_bp_accounts(config, schedule.producers); + auto pending_connections = bp_accounts(schedule.producers); fc_dlog(self()->get_logger(), "pending_connections: ${c}", ("c", to_string(pending_connections))); + for (const auto& i : pending_connections) { self()->connections.resolve_and_connect(config.bp_peer_addresses[i], self()->get_first_p2p_address() ); } + fc::lock_guard g(gossip_bps.mtx); + auto& prod_idx = gossip_bps.index.get(); + for (const auto& account : pending_connections) { + if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { + self()->connections.resolve_and_connect(i->second, self()->get_first_p2p_address() ); + } + auto r = prod_idx.equal_range(account); + for (auto i = r.first; i != r.second; ++i) { + self()->connections.resolve_and_connect(i->server_address, self()->get_first_p2p_address() ); + } + } - pending_configured_bps = std::move(pending_connections); + pending_bps = std::move(pending_connections); pending_schedule_version = schedule.version; } } else { fc_dlog(self()->get_logger(), "pending producer schedule version ${v} is being cleared", ("v", schedule.version)); - pending_configured_bps.clear(); + pending_bps.clear(); } } } @@ -311,13 +342,13 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "active producer schedule switches from version ${old} to ${new}", ("old", active_schedule_version)("new", schedule.version)); - auto old_bps = std::move(active_configured_bps); - active_configured_bps = configured_bp_accounts(config, schedule.producers); + auto old_bps = std::move(active_bps); + active_bps = bp_accounts(schedule.producers); - fc_dlog(self()->get_logger(), "active_configured_bps: ${a}", ("a", to_string(active_configured_bps))); + fc_dlog(self()->get_logger(), "active_bps: ${a}", ("a", to_string(active_bps))); flat_set peers_to_stay; - std::set_union(active_configured_bps.begin(), active_configured_bps.end(), pending_configured_bps.begin(), pending_configured_bps.end(), + std::set_union(active_bps.begin(), active_bps.end(), pending_bps.begin(), pending_bps.end(), std::inserter(peers_to_stay, peers_to_stay.begin())); fc_dlog(self()->get_logger(), "peers_to_stay: ${p}", ("p", to_string(peers_to_stay))); @@ -327,8 +358,16 @@ class bp_connection_manager { std::back_inserter(peers_to_drop)); fc_dlog(self()->get_logger(), "peers to drop: ${p}", ("p", to_string(peers_to_drop))); + fc::lock_guard g(gossip_bps.mtx); + auto& prod_idx = gossip_bps.index.get(); for (const auto& account : peers_to_drop) { - self()->connections.disconnect(config.bp_peer_addresses[account]); + if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { + self()->connections.disconnect(i->second); + } + auto r = prod_idx.equal_range(account); + for (auto i = r.first; i != r.second; ++i) { + self()->connections.disconnect(i->server_address); + } } active_schedule_version = schedule.version; } diff --git a/plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp b/plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp index 1f427de590..ac1399c567 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/gossip_bps_index.hpp @@ -29,7 +29,7 @@ struct gossip_bp_index_t { alignas(hardware_destructive_interference_sz) mutable fc::mutex mtx; - gossip_bps_index_container_t index GUARDED_BY(mtx);; + gossip_bps_index_container_t index GUARDED_BY(mtx); }; diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index ea20779e33..9bb4691bfb 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -843,7 +843,8 @@ namespace eosio { std::atomic protocol_version = 0; uint16_t net_version = net_version_max; std::atomic consecutive_immediate_connection_close = 0; - std::atomic is_bp_connection = false; + std::atomic is_gossip_bp_connection = false; + std::atomic is_configured_bp_connection = false; block_status_monitor block_status_monitor_; std::atomic last_vote_received; @@ -1203,6 +1204,7 @@ namespace eosio { auto [host, port, type] = net_utils::split_host_port_type(this_address); listen_address = host + ":" + port; // do not include type in listen_address to avoid peer setting type on connection set_connection_type( peer_address() ); + my_impl->mark_configured_bp_connection(this); fc_ilog( logger, "created connection - ${c} to ${n}", ("c", connection_id)("n", endpoint) ); } @@ -1318,7 +1320,7 @@ namespace eosio { connection_status stat; stat.connecting = state() == connection_state::connecting; stat.syncing = peer_syncing_from_us; - stat.is_bp_peer = is_bp_connection; + stat.is_bp_peer = is_gossip_bp_connection || is_configured_bp_connection; stat.is_socket_open = socket_is_open(); stat.is_blocks_only = is_blocks_only_connection(); stat.is_transactions_only = is_transactions_only_connection(); @@ -3289,6 +3291,7 @@ namespace eosio { unique_conn_node_id = msg.node_id.str().substr( 0, 7 ); g_conn.unlock(); + my_impl->mark_configured_bp_connection(this); if (my_impl->exceeding_connection_limit(shared_from_this())) { // When auto bp peering is enabled, create_session() check doesn't have enough information to determine // if a client is a BP peer. In create_session(), it only has the peer address which a node is connecting @@ -3766,7 +3769,7 @@ namespace eosio { // called from connection strand void connection::handle_message( gossip_bp_peers_message& msg ) { - if (!my_impl->auto_bp_peering_enabled()) + if (!my_impl->bp_gossip_enabled()) return; if (!my_impl->validate_gossip_bp_peers_message(msg)) { @@ -3777,7 +3780,7 @@ namespace eosio { } // valid gossip peer connection - is_bp_connection = true; + is_gossip_bp_connection = true; assert(!msg.peers.empty()); // checked by validate_gossip_bp_peers_message() if (msg.peers.size() == 1 && msg.peers[0].server_address.empty()) { @@ -3796,7 +3799,7 @@ namespace eosio { // called from connection strand void connection::send_gossip_bp_peers_initial_message() { - if (protocol_version < proto_gossip_bp_peers || !my_impl->auto_bp_peering_enabled()) + if (protocol_version < proto_gossip_bp_peers || !my_impl->bp_gossip_enabled()) return; auto sb = my_impl->get_gossip_bp_initial_send_buffer(); enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); @@ -3806,6 +3809,7 @@ namespace eosio { // called from connection strand void connection::send_gossip_bp_peers_message() { assert(protocol_version >= proto_gossip_bp_peers); + assert(my_impl->bp_gossip_enabled()); gossip_buffer_factory factory; const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); @@ -3813,9 +3817,10 @@ namespace eosio { // called from connection strand, thread safe void connection::send_gossip_bp_peers_message_to_bp_peers() { + assert(my_impl->bp_gossip_enabled()); my_impl->connections.for_each_connection([this](const connection_ptr& c) { gossip_buffer_factory factory; - if (this != c.get() && c->is_bp_connection && c->socket_is_open()) { + if (this != c.get() && c->is_gossip_bp_connection && c->socket_is_open()) { assert(c->protocol_version >= proto_gossip_bp_peers); const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); boost::asio::post(c->strand, [sb, c]() { @@ -4408,9 +4413,7 @@ namespace eosio { } if ( options.count( "p2p-auto-bp-peer")) { - EOS_ASSERT(options.count("p2p-producer-peer"), chain::plugin_config_exception, - "p2p-producer-peer required for p2p-auto-bp-peer"); - set_bp_peers(options.at( "p2p-auto-bp-peer" ).as>()); + set_configured_bp_peers(options.at( "p2p-auto-bp-peer" ).as>()); for_each_bp_peer_address([&peers](const auto& addr) { EOS_ASSERT(std::find(peers.begin(), peers.end(), addr) == peers.end(), chain::plugin_config_exception, "\"${a}\" should only appear in either p2p-peer-address or p2p-auto-bp-peer option, not both.", ("a",addr)); @@ -4910,7 +4913,7 @@ namespace eosio { return; } const connection_ptr& c = it->c; - if (c->is_bp_connection) { + if (c->is_gossip_bp_connection || c->is_configured_bp_connection) { ++num_bp_peers; } else if (c->incoming()) { ++num_clients; @@ -4951,7 +4954,7 @@ namespace eosio { net_plugin::p2p_per_connection_metrics per_connection(index.size()); for (auto it = index.begin(); it != index.end(); ++it) { const connection_ptr& c = it->c; - if(c->is_bp_connection) { + if(c->is_gossip_bp_connection || c->is_configured_bp_connection) { ++num_bp_peers; } else if(c->incoming()) { ++num_clients; diff --git a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp index ce0d53df5e..30b5cf9453 100644 --- a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp +++ b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp @@ -2,11 +2,12 @@ #include struct mock_connection { - bool is_bp_connection = false; + bool is_configured_bp_connection = false; + bool is_gossip_bp_connection = false; bool is_open = false; bool handshake_received = false; mock_connection(bool bp_connection, bool open, bool received) - : is_bp_connection(bp_connection) + : is_configured_bp_connection(bp_connection) , is_open(open) , handshake_received(received) {} @@ -46,7 +47,7 @@ struct mock_net_plugin : eosio::auto_bp_peering::bp_connection_manager peer_addresses{ BOOST_AUTO_TEST_CASE(test_set_bp_peers) { mock_net_plugin plugin; - BOOST_CHECK_THROW(plugin.set_bp_peers({ "producer17,127.0.0.1:8888"s }), eosio::chain::plugin_config_exception); - BOOST_CHECK_THROW(plugin.set_bp_peers({ "producer1"s }), eosio::chain::plugin_config_exception); + BOOST_CHECK_THROW(plugin.set_configured_bp_peers({ "producer17,127.0.0.1:8888"s }), eosio::chain::plugin_config_exception); + BOOST_CHECK_THROW(plugin.set_configured_bp_peers({ "producer1"s }), eosio::chain::plugin_config_exception); - plugin.set_bp_peers({ + plugin.set_configured_bp_peers({ "producer1,127.0.0.1:8888:blk"s, "producer2,127.0.0.1:8889:trx"s, "producer3,127.0.0.1:8890"s, "producer4,127.0.0.1:8891"s }); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer1"_n], "127.0.0.1:8888:blk"s); BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer2"_n], "127.0.0.1:8889:trx"s); BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer3"_n], "127.0.0.1:8890"s); @@ -161,7 +161,7 @@ BOOST_AUTO_TEST_CASE(test_on_pending_schedule) { mock_net_plugin plugin; plugin.setup_test_peers(); - plugin.pending_configured_bps = { "prodj"_n, "prodm"_n }; + plugin.pending_bps = { "prodj"_n, "prodm"_n }; std::vector connected_hosts; @@ -172,7 +172,7 @@ BOOST_AUTO_TEST_CASE(test_on_pending_schedule) { plugin.on_pending_schedule(test_schedule1); BOOST_CHECK_EQUAL(connected_hosts, (std::vector{})); - BOOST_CHECK_EQUAL(plugin.pending_configured_bps, (fc::flat_set{ "prodj"_n, "prodm"_n })); + BOOST_CHECK_EQUAL(plugin.pending_bps, (fc::flat_set{ "prodj"_n, "prodm"_n })); BOOST_CHECK_EQUAL(plugin.pending_schedule_version, 0u); // when it is in sync and on_pending_schedule is called @@ -180,7 +180,7 @@ BOOST_AUTO_TEST_CASE(test_on_pending_schedule) { plugin.on_pending_schedule(test_schedule1); // the pending are connected to - BOOST_CHECK_EQUAL(plugin.pending_configured_bps, producers_minus_prodkt); + BOOST_CHECK_EQUAL(plugin.pending_bps, producers_minus_prodkt); // all connect to bp peers should be invoked BOOST_CHECK_EQUAL(connected_hosts, peer_addresses); @@ -196,7 +196,7 @@ BOOST_AUTO_TEST_CASE(test_on_pending_schedule) { BOOST_CHECK_EQUAL(connected_hosts, (std::vector{})); plugin.on_pending_schedule(reset_schedule1); - BOOST_CHECK_EQUAL(plugin.pending_configured_bps, (fc::flat_set{})); + BOOST_CHECK_EQUAL(plugin.pending_bps, (fc::flat_set{})); } BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { mock_net_plugin plugin; plugin.setup_test_peers(); - plugin.active_configured_bps = { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n }; + plugin.active_bps = { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n }; plugin.connections.resolve_and_connect = [](std::string host, std::string p2p_address) {}; std::vector disconnected_hosts; @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { plugin.on_active_schedule(test_schedule1); BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{})); - BOOST_CHECK_EQUAL(plugin.active_configured_bps, + BOOST_CHECK_EQUAL(plugin.active_bps, (fc::flat_set{ "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n })); BOOST_CHECK_EQUAL(plugin.active_schedule_version, 0u); @@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { // then disconnect to prodt BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{ "127.0.0.1:8020"s })); - BOOST_CHECK_EQUAL(plugin.active_configured_bps, producers_minus_prodkt); + BOOST_CHECK_EQUAL(plugin.active_bps, producers_minus_prodkt); // make sure we change the active_schedule_version BOOST_CHECK_EQUAL(plugin.active_schedule_version, 1u); @@ -237,7 +237,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule2) { mock_net_plugin plugin; plugin.setup_test_peers(); - plugin.active_configured_bps = { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n }; + plugin.active_bps = { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n }; plugin.connections.resolve_and_connect = [](std::string host, std::string p2p_address) {}; std::vector disconnected_hosts; plugin.connections.disconnect = [&disconnected_hosts](std::string host) { disconnected_hosts.push_back(host); }; @@ -249,7 +249,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule2) { // then disconnect prodt BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{ "127.0.0.1:8020"s })); - BOOST_CHECK_EQUAL(plugin.active_configured_bps, producers_minus_prodkt); + BOOST_CHECK_EQUAL(plugin.active_bps, producers_minus_prodkt); // make sure we change the active_schedule_version BOOST_CHECK_EQUAL(plugin.active_schedule_version, 1u); From a18b4fc3e5a3ff951c941a48831968b86a7f4a2a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 17 Mar 2025 10:44:59 -0500 Subject: [PATCH 09/50] GH-1245 Remove extra loop --- .../net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 1e8886454a..cd0783330f 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -309,9 +309,6 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "pending_connections: ${c}", ("c", to_string(pending_connections))); - for (const auto& i : pending_connections) { - self()->connections.resolve_and_connect(config.bp_peer_addresses[i], self()->get_first_p2p_address() ); - } fc::lock_guard g(gossip_bps.mtx); auto& prod_idx = gossip_bps.index.get(); for (const auto& account : pending_connections) { From 8c98362216db1395b2bf8e946a63523e5ce52536 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 19 Mar 2025 10:55:07 -0500 Subject: [PATCH 10/50] GH-1245 Add is_bp_gossip_peer to connection_status --- plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp | 3 ++- plugins/net_plugin/net_plugin.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp index b6eb399387..683004cecc 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp @@ -15,6 +15,7 @@ namespace eosio { bool connecting = false; bool syncing = false; bool is_bp_peer = false; + bool is_bp_gossip_peer = false; bool is_socket_open = false; bool is_blocks_only = false; bool is_transactions_only = false; @@ -104,5 +105,5 @@ namespace eosio { } FC_REFLECT( eosio::connection_status, (peer)(remote_ip)(remote_port)(connecting)(syncing) - (is_bp_peer)(is_socket_open)(is_blocks_only)(is_transactions_only) + (is_bp_peer)(is_bp_gossip_peer)(is_socket_open)(is_blocks_only)(is_transactions_only) (last_vote_received)(last_handshake) ) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 9bb4691bfb..457d787d13 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1321,6 +1321,7 @@ namespace eosio { stat.connecting = state() == connection_state::connecting; stat.syncing = peer_syncing_from_us; stat.is_bp_peer = is_gossip_bp_connection || is_configured_bp_connection; + stat.is_bp_gossip_peer = is_gossip_bp_connection; stat.is_socket_open = socket_is_open(); stat.is_blocks_only = is_blocks_only_connection(); stat.is_transactions_only = is_transactions_only_connection(); From 1a788ea4d1d18bfa8d8e7d0c7c207aade4ecf0dc Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 19 Mar 2025 10:55:53 -0500 Subject: [PATCH 11/50] GH-1245 Integrate peer_keys_db --- libraries/chain/controller.cpp | 7 ++-- .../chain/include/eosio/chain/controller.hpp | 2 ++ .../include/eosio/chain/peer_keys_db.hpp | 4 +-- libraries/chain/peer_keys_db.cpp | 12 +++---- .../eosio/net_plugin/auto_bp_peering.hpp | 34 +++++++------------ plugins/net_plugin/net_plugin.cpp | 13 +++++-- tests/auto_bp_peering_test.py | 1 + 7 files changed, 39 insertions(+), 34 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 4391291aa6..1a311c3ae3 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1322,9 +1322,6 @@ struct controller_impl { const auto& [ block, id] = t; wasmif.current_lib(block->block_num()); vote_processor.notify_lib(block->block_num()); - - // update peer public keys from chainbase db - peer_keys_db.update_peer_keys(self, block->block_num()); }); #define SET_APP_HANDLER( receiver, contract, action) \ @@ -5787,6 +5784,10 @@ void controller::set_peer_keys_retrieval_active(bool active) { my->peer_keys_db.set_active(active); } +flat_set controller::update_peer_keys(uint32_t lib_number) { + return my->peer_keys_db.update_peer_keys(*this, lib_number); +} + std::optional controller::get_peer_key(name n) const { return my->peer_keys_db.get_peer_key(n); } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 69c84372bb..b9b5c949ca 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -428,6 +428,8 @@ namespace eosio::chain { chain_id_type get_chain_id()const; void set_peer_keys_retrieval_active(bool active); + // call on main thread with irreversible block + flat_set update_peer_keys(uint32_t lib_number); std::optional get_peer_key(name n) const; // thread safe // thread safe diff --git a/libraries/chain/include/eosio/chain/peer_keys_db.hpp b/libraries/chain/include/eosio/chain/peer_keys_db.hpp index 0bb6fa1b55..a3965672fc 100644 --- a/libraries/chain/include/eosio/chain/peer_keys_db.hpp +++ b/libraries/chain/include/eosio/chain/peer_keys_db.hpp @@ -19,8 +19,8 @@ class peer_keys_db_t { void set_active(bool b) { _active = b; } - // must be called from main thread - size_t update_peer_keys(const controller& chain, uint32_t lib_number); + // must be called from main thread, returns any new or updated producers + flat_set update_peer_keys(const controller& chain, uint32_t lib_number); // safe to be called from any thread std::optional get_peer_key(name n) const; diff --git a/libraries/chain/peer_keys_db.cpp b/libraries/chain/peer_keys_db.cpp index 0d2aaf58af..e57e8a35af 100644 --- a/libraries/chain/peer_keys_db.cpp +++ b/libraries/chain/peer_keys_db.cpp @@ -19,10 +19,10 @@ size_t peer_keys_db_t::size() const { // we update the keys that were registered up to lib_number (inclusive) // -------------------------------------------------------------------- -size_t peer_keys_db_t::update_peer_keys(const controller& chain, uint32_t lib_number) { - size_t num_updated = 0; +flat_set peer_keys_db_t::update_peer_keys(const controller& chain, uint32_t lib_number) { + flat_set result; if (!_active || lib_number <= _block_num) - return num_updated; // nothing to do + return result; // nothing to do try { const auto& db = chain.db(); @@ -38,7 +38,7 @@ size_t peer_keys_db_t::update_peer_keys(const controller& chain, uint32_t lib_nu if (upper == lower) { // no new keys registered _block_num = lib_number; - return num_updated; + return result; } fc::lock_guard g(_m); // we only need to protect access to _peer_key_map @@ -67,14 +67,14 @@ size_t peer_keys_db_t::update_peer_keys(const controller& chain, uint32_t lib_nu EOS_ASSERT(row_key.valid(), misc_exception, "deserialized invalid public key from `peerkeys`"); _peer_key_map[row_name] = row_key; - ++num_updated; + result.insert(row_name); } FC_LOG_AND_DROP(("skipping invalid record deserialized from `peerkeys`")); } _block_num = lib_number; // mark that we have updated up to lib_number } FC_LOG_AND_DROP(("Error when updating peer_keys_db")); - return num_updated; + return result; } } // namespace eosio::chain \ No newline at end of file diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index cd0783330f..3e2758faa5 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -20,12 +21,6 @@ class bp_connection_manager { public: #endif - using account_name = chain::account_name; - template - using flat_map = chain::flat_map; - template - using flat_set = chain::flat_set; - gossip_bp_index_t gossip_bps; // the following members are thread-safe, only modified during plugin startup @@ -128,7 +123,7 @@ class bp_connection_manager { // Called at startup and when peer key changes // empty modified_keys means to update all - void update_bp_producer_peers(const chain::controller& cc, const std::set& modified_keys, const std::string& server_address) + void update_bp_producer_peers(const chain::controller& cc, const flat_set& modified_keys, const std::string& server_address) { if (config.my_bp_peer_accounts.empty()) return; @@ -137,8 +132,7 @@ class bp_connection_manager { bool first = true; for (const auto& e : config.my_bp_peer_accounts) { // my_bp_peer_accounts not modified after plugin startup if (modified_keys.empty() || modified_keys.contains(e)) { - //todo: auto pk = cc.get_peer_key(e); - std::optional pk; // todo + std::optional pk = cc.get_peer_key(e); // EOS_ASSERT can only be hit on plugin startup, otherwise this method called with modified_keys that are in cc.get_peer_key() EOS_ASSERT(pk, chain::plugin_config_exception, "No on-chain peer key found for ${n}", ("n", e)); if (first) { @@ -244,23 +238,21 @@ class bp_connection_manager { fc::lock_guard g(gossip_bps.mtx); auto& sig_idx = gossip_bps.index.get(); - auto& prod_idx = gossip_bps.index.get(); for (auto i = msg.peers.begin(); i != msg.peers.end();) { const auto& peer = *i; try { if (!sig_idx.contains(peer.sig)) { // we already have it, already verified // peer key may have changed or been removed, so if invalid or not found skip it - // todo: when cc.get_peer_key available - // if (auto peer_key = cc.get_peer_key(peer.producer_name)) { - // public_key_type pk(peer.sig, peer.digest()); - // if (pk != *peer_key) { - // i = msg.peers.erase(i); - // continue; - // } - // } else { // unknown key - // i = msg.peers.erase(i); - // continue; - // } + if (std::optional peer_key = cc.get_peer_key(peer.producer_name)) { + public_key_type pk(peer.sig, peer.digest()); + if (pk != *peer_key) { + i = msg.peers.erase(i); + continue; + } + } else { // unknown key + i = msg.peers.erase(i); + continue; + } } } catch (fc::exception& e) { // invalid key diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 457d787d13..1335b4d322 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -4030,13 +4030,19 @@ namespace eosio { fc_dlog( logger, "on_irreversible_block, blk num = ${num}, id = ${id}", ("num", block->block_num())("id", id) ); update_chain_info(id); - if (chain_plug->chain().get_read_mode() == db_read_mode::IRREVERSIBLE) { + chain::controller& cc = chain_plug->chain(); + if (cc.get_read_mode() == db_read_mode::IRREVERSIBLE) { // irreversible notifies sync_manager when added to fork_db, non-irreversible notifies when applied dispatcher.strand.post([sync_master = sync_master.get(), block, id]() { const fc::microseconds age(fc::time_point::now() - block->timestamp); sync_master->sync_recv_block(connection_ptr{}, id, block->block_num(), age); }); } + + // update peer public keys from chainbase db + flat_set modified = cc.update_peer_keys(block->block_num()); + if (!modified.empty()) + update_bp_producer_peers(cc, modified, get_first_p2p_address()); } // called from other threads including net threads @@ -4540,7 +4546,10 @@ namespace eosio { cc.aggregated_vote().connect( broadcast_vote ); cc.voted_block().connect( broadcast_vote ); - update_bp_producer_peers(cc, std::set{}, get_first_p2p_address()); + if (bp_gossip_enabled()) { + cc.set_peer_keys_retrieval_active(true); + update_bp_producer_peers(cc, flat_set{}, get_first_p2p_address()); + } } incoming_transaction_ack_subscription = app().get_channel().subscribe( diff --git a/tests/auto_bp_peering_test.py b/tests/auto_bp_peering_test.py index 2208b6255b..ef7474c28b 100755 --- a/tests/auto_bp_peering_test.py +++ b/tests/auto_bp_peering_test.py @@ -93,6 +93,7 @@ for nodeId in range(0, producerNodes): # retrieve the connections in each node and check if each connects to the other bps in the schedule connections = cluster.nodes[nodeId].processUrllibRequest("net", "connections") + if Utils.Debug: Utils.Print(f"v1/net/connections: {connections}") peers = [] for conn in connections["payload"]: peer_addr = conn["peer"] From dec942db138e9c313a0fd3307f8b6bafc687336a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 21 Mar 2025 11:35:57 -0500 Subject: [PATCH 12/50] GH-1245 Add integration test for bp gossip peering --- tests/CMakeLists.txt | 2 + tests/auto_bp_gossip_peering_test.py | 186 +++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 tests/auto_bp_gossip_peering_test.py diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 898075a523..0d981099d8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -98,6 +98,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trace_plugin_test.py ${CMAKE_CURRENT_ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nested_container_multi_index_test.py ${CMAKE_CURRENT_BINARY_DIR}/nested_container_multi_index_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/auto_bp_peering_test.py ${CMAKE_CURRENT_BINARY_DIR}/auto_bp_peering_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/auto_bp_peering_test_shape.json ${CMAKE_CURRENT_BINARY_DIR}/auto_bp_peering_test_shape.json COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/auto_bp_gossip_peering_test.py ${CMAKE_CURRENT_BINARY_DIR}/auto_bp_gossip_peering_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gelf_test.py ${CMAKE_CURRENT_BINARY_DIR}/gelf_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/split_blocklog_replay_test.py ${CMAKE_CURRENT_BINARY_DIR}/split_blocklog_replay_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/PerformanceHarnessScenarioRunner.py ${CMAKE_CURRENT_BINARY_DIR}/PerformanceHarnessScenarioRunner.py COPYONLY) @@ -306,6 +307,7 @@ add_np_test(NAME interrupt_trx_test COMMAND tests/interrupt_trx_test.py -v ${UNS add_lr_test(NAME auto_bp_peering_test COMMAND tests/auto_bp_peering_test.py -v ${UNSHARE}) add_lr_test(NAME auto_bp_peering_if_test COMMAND tests/auto_bp_peering_test.py -v --activate-if ${UNSHARE}) +add_lr_test(NAME auto_bp_gossip_peering_test COMMAND tests/auto_bp_gossip_peering_test.py -v ${UNSHARE}) add_np_test(NAME gelf_test COMMAND tests/gelf_test.py ${UNSHARE}) diff --git a/tests/auto_bp_gossip_peering_test.py b/tests/auto_bp_gossip_peering_test.py new file mode 100755 index 0000000000..9739228eaf --- /dev/null +++ b/tests/auto_bp_gossip_peering_test.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 + +import copy +import signal + +from TestHarness import Cluster, TestHelper, Utils, WalletMgr, createAccountKeys + +############################################################### +# auto_bp_gossip_peering_test +# +# This test sets up a cluster with 21 producers nodeos, each nodeos is configured with only one producer and only +# connects to the bios node. Moreover, each producer nodeos is also configured with a p2p-producer-peer so that each +# one can automatically establish p2p connections to other bps. Test verifies connections are established when +# producer schedule is active. +# +############################################################### + +Print = Utils.Print +errorExit = Utils.errorExit +cmdError = Utils.cmdError +producerNodes = 21 +producerCountInEachNode = 1 +totalNodes = producerNodes + +# Parse command line arguments +args = TestHelper.parse_args({ + "-v", + "--dump-error-details", + "--leave-running", + "--keep-logs", + "--unshared" +}) + +Utils.Debug = args.v +dumpErrorDetails = args.dump_error_details +keepLogs = args.keep_logs + +# Setup cluster and its wallet manager +walletMgr = WalletMgr(True) +cluster = Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +cluster.setWalletMgr(walletMgr) + +def getHostName(nodeId): + port = cluster.p2pBasePort + nodeId + if producer_name == 'defproducerf': + hostname = 'ext-ip0:9999' + else: + hostname = "localhost:" + str(port) + return hostname + +peer_names = {} +for nodeId in range(0, producerNodes): + producer_name = "defproducer" + chr(ord('a') + nodeId) + port = cluster.p2pBasePort + nodeId + hostname = getHostName(nodeId) + peer_names[hostname] = producer_name + +auto_bp_peer_arg = f" --p2p-auto-bp-peer defproducera,localhost:{cluster.p2pBasePort}" + +peer_names["localhost:9776"] = "bios" + +testSuccessful = False +try: + accounts=createAccountKeys(21) + if accounts is None: + Utils.errorExit("FAILURE - create keys") + + if walletMgr.launch() is False: + errorExit("Failed to stand up keosd.") + + specificNodeosArgs = {} + for nodeId in range(0, producerNodes): + specificNodeosArgs[nodeId] = auto_bp_peer_arg + producer_name = "defproducer" + chr(ord('a') + nodeId) + specificNodeosArgs[nodeId] += (f" --signature-provider {accounts[nodeId].activePublicKey}=KEY:{accounts[nodeId].activePrivateKey}") + + specificNodeosArgs[5] = specificNodeosArgs[5] + ' --p2p-server-address ext-ip0:9999' + + # restarting all producers can trigger production pause, so disable + # net_api_plugin for /v1/net/connections + extraNodeosArgs = " --production-pause-vote-timeout-ms 0 --plugin eosio::net_api_plugin " + + TestHelper.printSystemInfo("BEGIN") + cluster.launch( + prodCount=producerCountInEachNode, + totalNodes=totalNodes, + pnodes=producerNodes, + totalProducers=producerNodes, + activateIF=True, + topo="./tests/auto_bp_peering_test_shape.json", + extraNodeosArgs=extraNodeosArgs, + specificExtraNodeosArgs=specificNodeosArgs, + ) + + testWalletName="test" + Print("Creating wallet \"%s\"" % (testWalletName)) + walletAccounts=copy.deepcopy(cluster.defProducerAccounts) + testWallet = walletMgr.create(testWalletName, walletAccounts.values()) + all_acc = accounts + list( cluster.defProducerAccounts.values() ) + for account in all_acc: + Print("Importing keys for account %s into wallet %s." % (account.name, testWallet.name)) + if not walletMgr.importKey(account, testWallet): + errorExit("Failed to import key for account %s" % (account.name)) + + for nodeId in range(0, producerNodes): + producer_name = "defproducer" + chr(ord('a') + nodeId) + a = accounts[nodeId] + node = cluster.getNode(nodeId) + + results = cluster.biosNode.pushMessage('eosio', 'regpeerkey', f'{{"proposer_finalizer_name":"{producer_name}","key":"{a.activePublicKey}"}}', f'-p {producer_name}@active') + assert(results[0]) + + # wait until produceru is seen by every node + for nodeId in range(0, producerNodes): + Utils.Print("Wait for defproduceru on node ", nodeId) + cluster.getNode(nodeId).waitForProducer("defproduceru", exitOnError=True, timeout=300) + + # relaunch with p2p-producer-peer + for nodeId in range(0, producerNodes): + Utils.Print(f"Relaunch node {nodeId} with p2p-producer-peer") + node = cluster.getNode(nodeId) + node.kill(signal.SIGTERM) + producer_name = "defproducer" + chr(ord('a') + nodeId) + if not node.relaunch(chainArg=" --p2p-producer-peer " + producer_name): + errorExit(f"Failed to relaunch node {nodeId}") + + # wait until producert is seen by every node + for nodeId in range(0, producerNodes): + Utils.Print("Wait for defproducert on node ", nodeId) + cluster.getNode(nodeId).waitForProducer("defproducert", exitOnError=True, timeout=300) + + # retrieve the producer stable producer schedule + scheduled_producers = [] + schedule = cluster.nodes[0].processUrllibRequest("chain", "get_producer_schedule") + for prod in schedule["payload"]["active"]["producers"]: + scheduled_producers.append(prod["producer_name"]) + scheduled_producers.sort() + + connection_failure = False + for nodeId in range(0, producerNodes): + # retrieve the connections in each node and check if each connects to the other bps in the schedule + connections = cluster.nodes[nodeId].processUrllibRequest("net", "connections") + if Utils.Debug: Utils.Print(f"v1/net/connections: {connections}") + peers = [] + for conn in connections["payload"]: + if conn["is_socket_open"] is False: + continue + peer_addr = conn["peer"] + if len(peer_addr) == 0: + if len(conn["last_handshake"]["p2p_address"]) == 0: + continue + peer_addr = conn["last_handshake"]["p2p_address"].split()[0] + if peer_names[peer_addr] != "bios" and peer_addr != getHostName(nodeId): + peers.append(peer_names[peer_addr]) + if not conn["is_bp_peer"]: + Utils.Print(f"Error: expected connection to {peer_addr} with is_bp_peer as true") + connection_failure = True + break + + if connection_failure: + break + if not peers: + Utils.Print(f"ERROR: found no connected peers for node {nodeId}") + connection_failure = True + break + name = "defproducer" + chr(ord('a') + nodeId) + peers.append(name) # add ourselves so matches schedule_producers + peers = list(set(peers)) + peers.sort() + if peers != scheduled_producers: + Utils.Print(f"ERROR: expect {name} has connections to {scheduled_producers}, got connections to {peers}") + connection_failure = True + break + + testSuccessful = not connection_failure + +finally: + TestHelper.shutdown( + cluster, + walletMgr, + testSuccessful, + dumpErrorDetails + ) + +exitCode = 0 if testSuccessful else 1 +exit(exitCode) From b4cf089c7f363121afb17ceb94eebdb3c8fd6413 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 21 Mar 2025 11:37:10 -0500 Subject: [PATCH 13/50] GH-1245 Do not include sig in digest --- plugins/net_plugin/net_plugin.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 1335b4d322..a419c69722 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3764,7 +3764,9 @@ namespace eosio { digest_type gossip_bp_peers_message::bp_peer::digest() const { digest_type::encoder enc; fc::raw::pack(enc, my_impl->chain_id); - fc::raw::pack(enc, *this); + fc::raw::pack(enc, producer_name); + fc::raw::pack(enc, server_address); + return enc.result(); } From e8cbb4994861a1c116d07c4ade54d40e64e4975c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 21 Mar 2025 11:38:08 -0500 Subject: [PATCH 14/50] GH-1245 Add peerkeys table and actions to system contract --- .../contracts/eosio.system/eosio.system.abi | 67 ++++++++++++++++++ .../contracts/eosio.system/eosio.system.cpp | 32 ++++++++- .../contracts/eosio.system/eosio.system.hpp | 41 +++++++++++ .../contracts/eosio.system/eosio.system.wasm | Bin 201591 -> 209828 bytes 4 files changed, 139 insertions(+), 1 deletion(-) mode change 100644 => 100755 unittests/contracts/eosio.system/eosio.system.wasm diff --git a/unittests/contracts/eosio.system/eosio.system.abi b/unittests/contracts/eosio.system/eosio.system.abi index 35bd49c352..12f8cfc606 100644 --- a/unittests/contracts/eosio.system/eosio.system.abi +++ b/unittests/contracts/eosio.system/eosio.system.abi @@ -418,6 +418,20 @@ } ] }, + { + "name": "delpeerkey", + "base": "", + "fields": [ + { + "name": "proposer_finalizer_name", + "type": "name" + }, + { + "name": "key", + "type": "public_key" + } + ] + }, { "name": "deposit", "base": "", @@ -716,6 +730,28 @@ } ] }, + { + "name": "peer_key", + "base": "", + "fields": [ + { + "name": "proposer_finalizer_name", + "type": "name" + }, + { + "name": "block_num", + "type": "uint32" + }, + { + "name": "version", + "type": "uint8" + }, + { + "name": "key", + "type": "public_key?" + } + ] + }, { "name": "permission_level", "base": "", @@ -860,6 +896,20 @@ } ] }, + { + "name": "regpeerkey", + "base": "", + "fields": [ + { + "name": "proposer_finalizer_name", + "type": "name" + }, + { + "name": "key", + "type": "public_key" + } + ] + }, { "name": "regproducer", "base": "", @@ -1552,6 +1602,11 @@ "type": "deleteauth", "ricardian_contract": "" }, + { + "name": "delpeerkey", + "type": "delpeerkey", + "ricardian_contract": "" + }, { "name": "deposit", "type": "deposit", @@ -1597,6 +1652,11 @@ "type": "refund", "ricardian_contract": "" }, + { + "name": "regpeerkey", + "type": "regpeerkey", + "ricardian_contract": "" + }, { "name": "regproducer", "type": "regproducer", @@ -1782,6 +1842,13 @@ "key_names": [], "key_types": [] }, + { + "name": "peerkeys", + "type": "peer_key", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, { "name": "producers", "type": "producer_info", diff --git a/unittests/contracts/eosio.system/eosio.system.cpp b/unittests/contracts/eosio.system/eosio.system.cpp index ceed74ea74..a09a596b82 100644 --- a/unittests/contracts/eosio.system/eosio.system.cpp +++ b/unittests/contracts/eosio.system/eosio.system.cpp @@ -22,7 +22,8 @@ system_contract::system_contract( name s, name code, datastream ds _rexpool(_self, _self.value), _rexfunds(_self, _self.value), _rexbalance(_self, _self.value), - _rexorders(_self, _self.value) + _rexorders(_self, _self.value), + _peer_keys(_self, _self.value) { //print( "construct system\n" ); _gstate = _global.exists() ? _global.get() : get_default_parameters(); @@ -298,6 +299,35 @@ void system_contract::init( unsigned_int version, symbol core ) { { rex_account, core, _self } ); } +void system_contract::regpeerkey( const name& proposer_finalizer_name, const public_key& key ) { + require_auth(proposer_finalizer_name); + check(!std::holds_alternative(key), "webauthn keys not allowed in regpeerkey action"); + + auto peers_itr = _peer_keys.find(proposer_finalizer_name.value); + if (peers_itr == _peer_keys.end()) { + _peer_keys.emplace(proposer_finalizer_name, [&](auto& row) { + row = peer_key::make_default_row(proposer_finalizer_name); + row.key = key; + }); + } else { + check(peers_itr->key != key, "Provided key is the same as currently stored one"); + _peer_keys.modify(peers_itr, same_payer, [&](auto& row) { + row.block_num = eosio::current_block_number(); + row.key = key; + }); + } +} + +void system_contract::delpeerkey( const name& proposer_finalizer_name, const public_key& key ) { + require_auth(proposer_finalizer_name); + + // not updating the version here. deleted keys will persist in the memory hashmap + auto peers_itr = _peer_keys.find(proposer_finalizer_name.value); + check(peers_itr != _peer_keys.end(), "Key not present for name: " + proposer_finalizer_name.to_string()); + check(peers_itr->key == key, "Current key does not match the provided one"); + _peer_keys.erase(peers_itr); +} + } /// eosio.system diff --git a/unittests/contracts/eosio.system/eosio.system.hpp b/unittests/contracts/eosio.system/eosio.system.hpp index ac3b5b04ce..59863ef5c9 100644 --- a/unittests/contracts/eosio.system/eosio.system.hpp +++ b/unittests/contracts/eosio.system/eosio.system.hpp @@ -269,6 +269,24 @@ struct rex_order_outcome { asset stake_change; }; +struct [[eosio::table("peerkeys"), eosio::contract("eosio.system")]] peer_key { + name proposer_finalizer_name; + uint32_t block_num; // block number where this row was emplaced or modified + uint8_t version; // version 0 and above must have the `key` optional + std::optional key; // used to verify peer gossip + + uint64_t primary_key() const { return proposer_finalizer_name.value; } + uint64_t by_block_num() const { return block_num; } + + static constexpr uint8_t current_version = 0; // increment when optional members are added and update `make_default_row` + static peer_key make_default_row(name n) { return peer_key{n, eosio::current_block_number(), current_version, {}}; } +}; + +typedef eosio::multi_index<"peerkeys"_n, peer_key, + indexed_by<"byblocknum"_n, const_mem_fun> + > peer_keys_table; + + class [[eosio::contract("eosio.system")]] system_contract : public native { private: @@ -286,6 +304,7 @@ class [[eosio::contract("eosio.system")]] system_contract : public native { rex_fund_table _rexfunds; rex_balance_table _rexbalance; rex_order_table _rexorders; + peer_keys_table _peer_keys; public: static constexpr eosio::name active_permission{"active"_n}; @@ -486,6 +505,28 @@ class [[eosio::contract("eosio.system")]] system_contract : public native { [[eosio::action]] void unregprod( const name producer ); + /** + * Action to register a public key for a proposer or finalizer name. + * This key will be used to validate a network peer's identity. + * A proposer or finalizer can only have have one public key registered at a time. + * If a key is already registered for `proposer_finalizer_name`, and `regpeerkey` is + * called with a different key, the new key replaces the previous one in `peer_keys_table` + */ + [[eosio::action]] + void regpeerkey( const name& proposer_finalizer_name, const public_key& key ); + + + /** + * Action to delete a public key for a proposer or finalizer name. + * + * The intent of this action is only for the account to reclaim the RAM, as + * the node software may remember the key after it was deleted using `delpeerkey`. + * + * An existing public key for a given account can be changed by calling `regpeerkey` again. + */ + [[eosio::action]] + void delpeerkey( const name& proposer_finalizer_name, const public_key& key ); + [[eosio::action]] void setram( uint64_t max_ram_size ); [[eosio::action]] diff --git a/unittests/contracts/eosio.system/eosio.system.wasm b/unittests/contracts/eosio.system/eosio.system.wasm old mode 100644 new mode 100755 index a714dc5b1400de0c9d2c55b09f4604bf4547ca4d..dd3e589e26885e601e52a3510f41f71787ab4092 GIT binary patch delta 42405 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 z%0A>?Akcgo5o?^1Y&k>@Q{Qb7EL*6)J; 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 Y`o24~Ad-91G$+2-pxx{2P}|}7f3VYky#N3J From 77bf31d2d317f7c6292a53961fd7ab9777520343 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 21 Mar 2025 11:38:30 -0500 Subject: [PATCH 15/50] GH-1245 Update to match system contract --- libraries/chain/peer_keys_db.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/chain/peer_keys_db.cpp b/libraries/chain/peer_keys_db.cpp index e57e8a35af..edb9a6ab54 100644 --- a/libraries/chain/peer_keys_db.cpp +++ b/libraries/chain/peer_keys_db.cpp @@ -50,7 +50,8 @@ flat_set peer_keys_db_t::update_peer_keys(const controller& chain, uint32_ name row_name; uint32_t row_block_num; - public_key_type row_key; + uint8_t version; + std::optional row_key; const auto& obj = *itr2; fc::datastream ds(obj.value.data(), obj.value.size()); @@ -63,10 +64,12 @@ flat_set peer_keys_db_t::update_peer_keys(const controller& chain, uint32_ EOS_ASSERT(row_block_num > static_cast(_block_num), misc_exception, "deserialized invalid version from `peerkeys`"); + fc::raw::unpack(ds, version); fc::raw::unpack(ds, row_key); - EOS_ASSERT(row_key.valid(), misc_exception, "deserialized invalid public key from `peerkeys`"); + EOS_ASSERT(row_key.has_value(), misc_exception, "deserialized empty optional public key from `peerkeys`"); + EOS_ASSERT(row_key->valid(), misc_exception, "deserialized invalid public key from `peerkeys`"); - _peer_key_map[row_name] = row_key; + _peer_key_map[row_name] = *row_key; result.insert(row_name); } FC_LOG_AND_DROP(("skipping invalid record deserialized from `peerkeys`")); From ac93b632495ff7448ac46a4d1461f7fa0e5c2c24 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 21 Mar 2025 11:43:48 -0500 Subject: [PATCH 16/50] GH-1245 Track active_schedule so when new peers are received in the schedule they can be connected to. --- .../eosio/net_plugin/auto_bp_peering.hpp | 106 +++++++++++++++--- plugins/net_plugin/net_plugin.cpp | 12 +- .../tests/auto_bp_peering_unittest.cpp | 10 +- 3 files changed, 102 insertions(+), 26 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 3e2758faa5..b089ac1815 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -33,12 +33,13 @@ class bp_connection_manager { // the following members are only accessed from main thread flat_set pending_bps; - flat_set active_bps; uint32_t pending_schedule_version = 0; uint32_t active_schedule_version = 0; - fc::mutex factory_mtx; - gossip_buffer_initial_factory initial_gossip_msg_factory GUARDED_BY(factory_mtx); + fc::mutex mtx; + gossip_buffer_initial_factory initial_gossip_msg_factory GUARDED_BY(mtx); + flat_set active_bps GUARDED_BY(mtx); + flat_set active_schedule GUARDED_BY(mtx); Derived* self() { return static_cast(this); } const Derived* self() const { return static_cast(this); } @@ -49,9 +50,7 @@ class bp_connection_manager { } // Only called from main thread - chain::flat_set bp_accounts(const std::vector& schedule) const - { - + chain::flat_set active_bp_accounts(const std::vector& schedule) const { fc::lock_guard g(gossip_bps.mtx); auto& prod_idx = gossip_bps.index.get(); chain::flat_set result; @@ -62,6 +61,37 @@ class bp_connection_manager { return result; } + // called from net threads + chain::flat_set active_bp_accounts(const flat_set& active_schedule) const REQUIRES(mtx) { + fc::lock_guard g(gossip_bps.mtx); + auto& prod_idx = gossip_bps.index.get(); + chain::flat_set result; + for (const auto& a : active_schedule) { + if (config.bp_peer_addresses.contains(a) || prod_idx.contains(a)) + result.insert(a); + } + return result; + } + + // thread-safe + void set_active_schedule(const std::vector& schedule) { + fc::lock_guard g(mtx); + active_schedule.clear(); + for (const auto& auth : schedule) + active_schedule.insert(auth.producer_name); + } + + // for testing + flat_set get_active_bps() { + fc::lock_guard g(mtx); + return active_bps; + } + // for testing + void set_active_bps(flat_set bps) { + fc::lock_guard g(mtx); + active_bps = std::move(bps); + } + public: bool auto_bp_peering_enabled() const { return !config.bp_peer_addresses.empty() || !config.my_bp_peer_accounts.empty(); } bool bp_gossip_enabled() const { return !config.my_bp_peer_accounts.empty(); } @@ -78,7 +108,6 @@ class bp_connection_manager { // Only called at plugin startup void set_configured_bp_peers(const std::vector& peers) { - fc::lock_guard g(gossip_bps.mtx); for (const auto& entry : peers) { try { auto comma_pos = entry.find(','); @@ -127,6 +156,7 @@ class bp_connection_manager { { if (config.my_bp_peer_accounts.empty()) return; + fc::lock_guard gm(mtx); fc::lock_guard g(gossip_bps.mtx); // normally only one bp peer account except in testing scenarios or test chains bool first = true; @@ -135,10 +165,10 @@ class bp_connection_manager { std::optional pk = cc.get_peer_key(e); // EOS_ASSERT can only be hit on plugin startup, otherwise this method called with modified_keys that are in cc.get_peer_key() EOS_ASSERT(pk, chain::plugin_config_exception, "No on-chain peer key found for ${n}", ("n", e)); + fc_dlog(self()->get_logger(), "Signing with producer_name ${p} key ${k}", ("p", e)("k", *pk)); if (first) { gossip_bp_peers_message::bp_peer signed_empty{.producer_name = e}; // .server_address not set for initial message signed_empty.sig = self()->sign_compact(*pk, signed_empty.digest()); - fc::lock_guard lck(factory_mtx); initial_gossip_msg_factory.set_initial_send_buffer(signed_empty); } auto& prod_idx = gossip_bps.index.get(); @@ -175,7 +205,7 @@ class bp_connection_manager { } send_buffer_type get_gossip_bp_initial_send_buffer() { - fc::lock_guard g(factory_mtx); + fc::lock_guard g(mtx); return initial_gossip_msg_factory.get_initial_send_buffer(); } @@ -246,15 +276,19 @@ class bp_connection_manager { if (std::optional peer_key = cc.get_peer_key(peer.producer_name)) { public_key_type pk(peer.sig, peer.digest()); if (pk != *peer_key) { + fc_dlog(self()->get_logger(), "Recovered peer key did not match on-chain ${p}, recovered: ${pk} != expected: ${k}", + ("p", peer.producer_name)("pk", pk)("k", *peer_key)); i = msg.peers.erase(i); continue; } } else { // unknown key + fc_dlog(self()->get_logger(), "Failed to find peer key ${p}", ("p", peer.producer_name)); i = msg.peers.erase(i); continue; } } } catch (fc::exception& e) { + fc_dlog(self()->get_logger(), "Exception recovering peer key ${p}, error: ${e}", ("p", peer.producer_name)("e", e.to_detail_string())); // invalid key i = msg.peers.erase(i); continue; @@ -266,9 +300,9 @@ class bp_connection_manager { } // thread-safe - std::tuple update_gossip_bps(const gossip_bp_peers_message& msg) { + bool update_gossip_bps(const gossip_bp_peers_message& msg) { // providing us with full set - fc::lock_guard g(gossip_bps.mtx); + fc::unique_lock g(gossip_bps.mtx); auto& idx = gossip_bps.index.get(); bool diff = false; for (const auto& peer : msg.peers) { @@ -284,7 +318,40 @@ class bp_connection_manager { diff = true; } } - return std::make_tuple(gossip_bps.index.size(), diff); + g.unlock(); + if (diff) { + connect_to_active(); + } + return diff; + } + + // thread-safe + void connect_to_active() { + // do not hold mutexes when calling resolve_and_connect which acquires connections mutex since other threads + // can be holding connections mutex when trying to acquire these mutexes + flat_set addresses; + { + fc::lock_guard gm(mtx); + active_bps = active_bp_accounts(active_schedule); + + fc::lock_guard g(gossip_bps.mtx); + auto& prod_idx = gossip_bps.index.get(); + for (const auto& account : active_bps) { + if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { + fc_dlog(self()->get_logger(), "connect to manual bp peer ${p}", ("p", i->second)); + addresses.insert(i->second); + } + auto r = prod_idx.equal_range(account); + for (auto i = r.first; i != r.second; ++i) { + fc_dlog(self()->get_logger(), "connect to gossip bp peer ${p}", ("p", i->server_address)); + addresses.insert(i->server_address); + } + } + } + + for (const auto& add : addresses) { + self()->connections.resolve_and_connect(add, self()->get_first_p2p_address()); + } } // Only called from main thread @@ -297,7 +364,7 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "pending producer schedule switches from version ${old} to ${new}", ("old", pending_schedule_version)("new", schedule.version)); - auto pending_connections = bp_accounts(schedule.producers); + auto pending_connections = active_bp_accounts(schedule.producers); fc_dlog(self()->get_logger(), "pending_connections: ${c}", ("c", to_string(pending_connections))); @@ -305,10 +372,12 @@ class bp_connection_manager { auto& prod_idx = gossip_bps.index.get(); for (const auto& account : pending_connections) { if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { + fc_dlog(self()->get_logger(), "connect to manual bp peer ${p}", ("p", i->second)); self()->connections.resolve_and_connect(i->second, self()->get_first_p2p_address() ); } auto r = prod_idx.equal_range(account); for (auto i = r.first; i != r.second; ++i) { + fc_dlog(self()->get_logger(), "connect to gossip bp peer ${p}", ("p", i->server_address)); self()->connections.resolve_and_connect(i->server_address, self()->get_first_p2p_address() ); } } @@ -331,14 +400,21 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "active producer schedule switches from version ${old} to ${new}", ("old", active_schedule_version)("new", schedule.version)); + set_active_schedule(schedule.producers); + if (active_schedule_version == 0) { // first call since node was launched, connect to active + connect_to_active(); + } + + fc::unique_lock gm(mtx); auto old_bps = std::move(active_bps); - active_bps = bp_accounts(schedule.producers); + active_bps = active_bp_accounts(schedule.producers); fc_dlog(self()->get_logger(), "active_bps: ${a}", ("a", to_string(active_bps))); flat_set peers_to_stay; std::set_union(active_bps.begin(), active_bps.end(), pending_bps.begin(), pending_bps.end(), std::inserter(peers_to_stay, peers_to_stay.begin())); + gm.unlock(); fc_dlog(self()->get_logger(), "peers_to_stay: ${p}", ("p", to_string(peers_to_stay))); @@ -351,10 +427,12 @@ class bp_connection_manager { auto& prod_idx = gossip_bps.index.get(); for (const auto& account : peers_to_drop) { if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { + fc_dlog(self()->get_logger(), "disconnect to manual bp peer ${p}", ("p", i->second)); self()->connections.disconnect(i->second); } auto r = prod_idx.equal_range(account); for (auto i = r.first; i != r.second; ++i) { + fc_dlog(self()->get_logger(), "disconnect to gossip bp peer ${p}", ("p", i->server_address)); self()->connections.disconnect(i->server_address); } } diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index a419c69722..42a9c47822 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -1113,7 +1113,7 @@ namespace eosio { void operator()( gossip_bp_peers_message& msg ) const { // continue call to handle_message on connection strand - peer_dlog( c, "handle gossip_bp_peers_message size ${s}", ("s", msg.peers.size()) ); + peer_dlog( c, "handle gossip_bp_peers_message ${m}", ("m", msg) ); c->handle_message( msg ); } }; @@ -3790,10 +3790,7 @@ namespace eosio { // initial message case, send back our entire collection send_gossip_bp_peers_message(); } else { - auto [diff, size] = my_impl->update_gossip_bps(msg); - if (msg.peers.size() < size) { // we have more than sent to us, send back our set - send_gossip_bp_peers_message(); - } + bool diff = my_impl->update_gossip_bps(msg); if (diff) { // update, let all our peers know about it send_gossip_bp_peers_message_to_bp_peers(); } @@ -3811,7 +3808,6 @@ namespace eosio { // called from connection strand void connection::send_gossip_bp_peers_message() { - assert(protocol_version >= proto_gossip_bp_peers); assert(my_impl->bp_gossip_enabled()); gossip_buffer_factory factory; const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); @@ -3824,7 +3820,6 @@ namespace eosio { my_impl->connections.for_each_connection([this](const connection_ptr& c) { gossip_buffer_factory factory; if (this != c.get() && c->is_gossip_bp_connection && c->socket_is_open()) { - assert(c->protocol_version >= proto_gossip_bp_peers); const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); boost::asio::post(c->strand, [sb, c]() { c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); @@ -4550,6 +4545,9 @@ namespace eosio { if (bp_gossip_enabled()) { cc.set_peer_keys_retrieval_active(true); + // update peer public keys from chainbase db + cc.update_peer_keys(cc.head().irreversible_blocknum()); + // pass in empty set so validation that peer key exists is enforced update_bp_producer_peers(cc, flat_set{}, get_first_p2p_address()); } } diff --git a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp index 30b5cf9453..3eb72285ac 100644 --- a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp +++ b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { mock_net_plugin plugin; plugin.setup_test_peers(); - plugin.active_bps = { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n }; + plugin.set_active_bps( { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n } ); plugin.connections.resolve_and_connect = [](std::string host, std::string p2p_address) {}; std::vector disconnected_hosts; @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { plugin.on_active_schedule(test_schedule1); BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{})); - BOOST_CHECK_EQUAL(plugin.active_bps, + BOOST_CHECK_EQUAL(plugin.get_active_bps(), (fc::flat_set{ "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n })); BOOST_CHECK_EQUAL(plugin.active_schedule_version, 0u); @@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { // then disconnect to prodt BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{ "127.0.0.1:8020"s })); - BOOST_CHECK_EQUAL(plugin.active_bps, producers_minus_prodkt); + BOOST_CHECK_EQUAL(plugin.get_active_bps(), producers_minus_prodkt); // make sure we change the active_schedule_version BOOST_CHECK_EQUAL(plugin.active_schedule_version, 1u); @@ -237,7 +237,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule2) { mock_net_plugin plugin; plugin.setup_test_peers(); - plugin.active_bps = { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n }; + plugin.set_active_bps( { "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n } ); plugin.connections.resolve_and_connect = [](std::string host, std::string p2p_address) {}; std::vector disconnected_hosts; plugin.connections.disconnect = [&disconnected_hosts](std::string host) { disconnected_hosts.push_back(host); }; @@ -249,7 +249,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule2) { // then disconnect prodt BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{ "127.0.0.1:8020"s })); - BOOST_CHECK_EQUAL(plugin.active_bps, producers_minus_prodkt); + BOOST_CHECK_EQUAL(plugin.get_active_bps(), producers_minus_prodkt); // make sure we change the active_schedule_version BOOST_CHECK_EQUAL(plugin.active_schedule_version, 1u); From 8e7e6bd6e2a854495bf689e9052a0d1575c1a791 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 21 Mar 2025 12:45:40 -0500 Subject: [PATCH 17/50] GH-1245 Fix startup missing disconnect --- .../include/eosio/net_plugin/auto_bp_peering.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index b089ac1815..16498008a4 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -73,9 +73,7 @@ class bp_connection_manager { return result; } - // thread-safe - void set_active_schedule(const std::vector& schedule) { - fc::lock_guard g(mtx); + void set_active_schedule(const std::vector& schedule) REQUIRES(mtx) { active_schedule.clear(); for (const auto& auth : schedule) active_schedule.insert(auth.producer_name); @@ -400,14 +398,17 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "active producer schedule switches from version ${old} to ${new}", ("old", active_schedule_version)("new", schedule.version)); + fc::unique_lock gm(mtx); + auto old_bps = std::move(active_bps); + set_active_schedule(schedule.producers); if (active_schedule_version == 0) { // first call since node was launched, connect to active + gm.unlock(); connect_to_active(); + gm.lock(); } - fc::unique_lock gm(mtx); - auto old_bps = std::move(active_bps); - active_bps = active_bp_accounts(schedule.producers); + active_bps = active_bp_accounts(schedule.producers); fc_dlog(self()->get_logger(), "active_bps: ${a}", ("a", to_string(active_bps))); From 292e4c2ec02aa8380461d465866ed6caf0e91799 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 21 Mar 2025 13:44:39 -0500 Subject: [PATCH 18/50] GH-1245 Reduce test runtime --- tests/auto_bp_gossip_peering_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/auto_bp_gossip_peering_test.py b/tests/auto_bp_gossip_peering_test.py index 9739228eaf..6ee9a0fbb9 100755 --- a/tests/auto_bp_gossip_peering_test.py +++ b/tests/auto_bp_gossip_peering_test.py @@ -124,10 +124,10 @@ def getHostName(nodeId): if not node.relaunch(chainArg=" --p2p-producer-peer " + producer_name): errorExit(f"Failed to relaunch node {nodeId}") - # wait until producert is seen by every node + # give time for messages to be gossiped around for nodeId in range(0, producerNodes): Utils.Print("Wait for defproducert on node ", nodeId) - cluster.getNode(nodeId).waitForProducer("defproducert", exitOnError=True, timeout=300) + cluster.getNode(nodeId).waitForHeadToAdvance(5) # retrieve the producer stable producer schedule scheduled_producers = [] From b17234d7d9126d063c8de1d30c70b3241146ea75 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 25 Mar 2025 07:51:39 -0500 Subject: [PATCH 19/50] GH-1245 Simplify and add comment --- .../net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 16498008a4..becd0c6dce 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -157,14 +157,13 @@ class bp_connection_manager { fc::lock_guard gm(mtx); fc::lock_guard g(gossip_bps.mtx); // normally only one bp peer account except in testing scenarios or test chains - bool first = true; for (const auto& e : config.my_bp_peer_accounts) { // my_bp_peer_accounts not modified after plugin startup if (modified_keys.empty() || modified_keys.contains(e)) { std::optional pk = cc.get_peer_key(e); // EOS_ASSERT can only be hit on plugin startup, otherwise this method called with modified_keys that are in cc.get_peer_key() EOS_ASSERT(pk, chain::plugin_config_exception, "No on-chain peer key found for ${n}", ("n", e)); fc_dlog(self()->get_logger(), "Signing with producer_name ${p} key ${k}", ("p", e)("k", *pk)); - if (first) { + if (e == *config.my_bp_peer_accounts.begin()) { // use the first one of the set, doesn't matter which one is used, just need one gossip_bp_peers_message::bp_peer signed_empty{.producer_name = e}; // .server_address not set for initial message signed_empty.sig = self()->sign_compact(*pk, signed_empty.digest()); initial_gossip_msg_factory.set_initial_send_buffer(signed_empty); @@ -180,7 +179,6 @@ class bp_connection_manager { gossip_bps.index.emplace(peer); } } - first = false; } } From b3fac2fb2ae52a9ec03eb9164f61f94b16455dd6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 25 Mar 2025 08:12:06 -0500 Subject: [PATCH 20/50] GH-1245 Add some additional logging --- plugins/net_plugin/net_plugin.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 42a9c47822..8d8667beb3 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -4736,14 +4736,16 @@ namespace eosio { { std::shared_lock g( connections_mtx ); - if( find_connection_i( peer_address ) ) + if( find_connection_i( peer_address ) ) { + fc_dlog( logger, "Already connected to ${p}", ("p", peer_address)); return "already connected"; + } } connection_ptr c = std::make_shared( peer_address, listen_address ); if (c->resolve_and_connect()) { add(std::move(c)); - + fc_dlog( logger, "Adding connection to ${p}", ("p", peer_address)); return "added connection"; } @@ -4764,8 +4766,10 @@ namespace eosio { } auto [host, port, type] = net_utils::split_host_port_type(peer_address()); - if (host.empty()) + if (host.empty()) { + fc_elog( logger, "Unexpected invalid peer address ${p}", ("p", peer_address())); return false; + } connection_ptr c = shared_from_this(); From 7cb4faade4eaa4960bae652ee8953c6714b3f3f6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 26 Mar 2025 09:24:19 -0500 Subject: [PATCH 21/50] GH-1245 Clarify code with extra comments and simplier if logic --- .../include/eosio/net_plugin/auto_bp_peering.hpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index becd0c6dce..0a1cfd6c54 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -230,11 +230,14 @@ class bp_connection_manager { } // thread safe - // removes invalid entries from msg + // removes outdated signed bp_peers from msg bool validate_gossip_bp_peers_message( gossip_bp_peers_message& msg ) { if (msg.peers.empty()) return false; - if (msg.peers.size() != 1 || !msg.peers[0].server_address.empty()) { // initial case, no server_addresses to validate + // initial case, no server_addresses to validate + bool initial_msg = msg.peers.size() == 1 && msg.peers[0].server_address.empty(); + if (!initial_msg) { + // validate structure and data of msg auto valid_address = [](const std::string& addr) -> bool { const auto& [host, port, type] = net_utils::split_host_port_type(addr); return !host.empty() && !port.empty(); @@ -243,9 +246,9 @@ class bp_connection_manager { const gossip_bp_peers_message::bp_peer* prev = nullptr; for (const auto& peer : msg.peers) { if (peer.producer_name.empty()) - return false; + return false; // invalid bp_peer data if (!valid_address(peer.server_address)) - return false; + return false; // invalid address if (prev != nullptr) { if (prev->producer_name == peer.producer_name) { if (prev->server_address == peer.server_address) @@ -268,7 +271,7 @@ class bp_connection_manager { const auto& peer = *i; try { if (!sig_idx.contains(peer.sig)) { // we already have it, already verified - // peer key may have changed or been removed, so if invalid or not found skip it + // peer key may have changed or been removed on-chain, do not consider that a fatal error, just remove it if (std::optional peer_key = cc.get_peer_key(peer.producer_name)) { public_key_type pk(peer.sig, peer.digest()); if (pk != *peer_key) { From fe41bf2bf3daa54f90f8495c598069af1d180964 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 26 Mar 2025 10:01:09 -0500 Subject: [PATCH 22/50] GH-1245 Simplify logic and make sure no more 4 bp peers is maintained per producer --- .../eosio/net_plugin/auto_bp_peering.hpp | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 0a1cfd6c54..0811370d8e 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -21,6 +21,7 @@ class bp_connection_manager { public: #endif + static constexpr size_t max_bp_peers_per_producer = 4; gossip_bp_index_t gossip_bps; // the following members are thread-safe, only modified during plugin startup @@ -242,8 +243,8 @@ class bp_connection_manager { const auto& [host, port, type] = net_utils::split_host_port_type(addr); return !host.empty() && !port.empty(); }; - std::map producers; const gossip_bp_peers_message::bp_peer* prev = nullptr; + size_t num_per_producer = 0; for (const auto& peer : msg.peers) { if (peer.producer_name.empty()) return false; // invalid bp_peer data @@ -251,48 +252,53 @@ class bp_connection_manager { return false; // invalid address if (prev != nullptr) { if (prev->producer_name == peer.producer_name) { + ++num_per_producer; + if (num_per_producer > max_bp_peers_per_producer) + return false; // more than allowed per producer if (prev->server_address == peer.server_address) return false; // duplicate entries not allowed } else if (prev->producer_name > peer.producer_name) { return false; // required to be sorted + } else { + num_per_producer = 0; } } - if (++producers[peer.producer_name] > 4) - return false; // only 4 entries per producer allowed prev = &peer; } } - controller& cc = self()->chain_plug->chain(); - - fc::lock_guard g(gossip_bps.mtx); - auto& sig_idx = gossip_bps.index.get(); - for (auto i = msg.peers.begin(); i != msg.peers.end();) { - const auto& peer = *i; + const controller& cc = self()->chain_plug->chain(); + auto is_peer_key_valid = [&](const gossip_bp_peers_message::bp_peer& peer) -> bool { try { - if (!sig_idx.contains(peer.sig)) { // we already have it, already verified - // peer key may have changed or been removed on-chain, do not consider that a fatal error, just remove it - if (std::optional peer_key = cc.get_peer_key(peer.producer_name)) { - public_key_type pk(peer.sig, peer.digest()); - if (pk != *peer_key) { - fc_dlog(self()->get_logger(), "Recovered peer key did not match on-chain ${p}, recovered: ${pk} != expected: ${k}", - ("p", peer.producer_name)("pk", pk)("k", *peer_key)); - i = msg.peers.erase(i); - continue; - } - } else { // unknown key - fc_dlog(self()->get_logger(), "Failed to find peer key ${p}", ("p", peer.producer_name)); - i = msg.peers.erase(i); - continue; + if (std::optional peer_key = cc.get_peer_key(peer.producer_name)) { + public_key_type pk(peer.sig, peer.digest()); + if (pk != *peer_key) { + fc_dlog(self()->get_logger(), "Recovered peer key did not match on-chain ${p}, recovered: ${pk} != expected: ${k}", + ("p", peer.producer_name)("pk", pk)("k", *peer_key)); + return false; } + } else { // unknown key + fc_dlog(self()->get_logger(), "Failed to find peer key ${p}", ("p", peer.producer_name)); + return false; } } catch (fc::exception& e) { fc_dlog(self()->get_logger(), "Exception recovering peer key ${p}, error: ${e}", ("p", peer.producer_name)("e", e.to_detail_string())); - // invalid key + return false; // invalid key + } + return true; + }; + + fc::lock_guard g(gossip_bps.mtx); + auto& sig_idx = gossip_bps.index.get(); + for (auto i = msg.peers.begin(); i != msg.peers.end();) { + const auto& peer = *i; + bool have_sig = sig_idx.contains(peer.sig); // we already have it, already verified + if (!have_sig && !is_peer_key_valid(peer)) { + // peer key may have changed or been removed on-chain, do not consider that a fatal error, just remove it i = msg.peers.erase(i); - continue; + } else { + ++i; } - ++i; } return !msg.peers.empty(); @@ -313,6 +319,10 @@ class bp_connection_manager { diff = true; } } else { + if (idx.count(peer.producer_name) >= max_bp_peers_per_producer) { + // only allow max_bp_peers_per_producer, choose one to remove + gossip_bps.index.erase(idx.find(peer.producer_name)); + } gossip_bps.index.insert(peer); diff = true; } From c47600c44a485bdc96da7cb3b39cfd1e7598bbd2 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 26 Mar 2025 10:05:45 -0500 Subject: [PATCH 23/50] GH-1245 Make sig update faster and simpler --- .../net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 0811370d8e..4240aa7632 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -312,9 +312,9 @@ class bp_connection_manager { bool diff = false; for (const auto& peer : msg.peers) { if (auto i = idx.find(boost::make_tuple(peer.producer_name, boost::cref(peer.server_address))); i != idx.end()) { - if (*i != peer) { + if (i->sig != peer.sig) { // signature has changed, producer_name and server_address has not changed gossip_bps.index.modify(i, [&peer](auto& m) { - m = peer; + m.sig = peer.sig; // update the signature, producer_name and server_address has not changed }); diff = true; } From 1e8c4d2bfbe6c6a57c65b5e8e398d83f0d6bb66c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 26 Mar 2025 10:13:56 -0500 Subject: [PATCH 24/50] GH-1245 Move call to connect_to_active_bp_peers() to net_plugin and add some const --- .../eosio/net_plugin/auto_bp_peering.hpp | 20 ++++++++----------- plugins/net_plugin/net_plugin.cpp | 1 + 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 4240aa7632..7d85402fc5 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -53,7 +53,7 @@ class bp_connection_manager { // Only called from main thread chain::flat_set active_bp_accounts(const std::vector& schedule) const { fc::lock_guard g(gossip_bps.mtx); - auto& prod_idx = gossip_bps.index.get(); + const auto& prod_idx = gossip_bps.index.get(); chain::flat_set result; for (const auto& auth : schedule) { if (config.bp_peer_addresses.contains(auth.producer_name) || prod_idx.contains(auth.producer_name)) @@ -65,7 +65,7 @@ class bp_connection_manager { // called from net threads chain::flat_set active_bp_accounts(const flat_set& active_schedule) const REQUIRES(mtx) { fc::lock_guard g(gossip_bps.mtx); - auto& prod_idx = gossip_bps.index.get(); + const auto& prod_idx = gossip_bps.index.get(); chain::flat_set result; for (const auto& a : active_schedule) { if (config.bp_peer_addresses.contains(a) || prod_idx.contains(a)) @@ -307,7 +307,7 @@ class bp_connection_manager { // thread-safe bool update_gossip_bps(const gossip_bp_peers_message& msg) { // providing us with full set - fc::unique_lock g(gossip_bps.mtx); + fc::lock_guard g(gossip_bps.mtx); auto& idx = gossip_bps.index.get(); bool diff = false; for (const auto& peer : msg.peers) { @@ -327,15 +327,11 @@ class bp_connection_manager { diff = true; } } - g.unlock(); - if (diff) { - connect_to_active(); - } return diff; } // thread-safe - void connect_to_active() { + void connect_to_active_bp_peers() { // do not hold mutexes when calling resolve_and_connect which acquires connections mutex since other threads // can be holding connections mutex when trying to acquire these mutexes flat_set addresses; @@ -344,7 +340,7 @@ class bp_connection_manager { active_bps = active_bp_accounts(active_schedule); fc::lock_guard g(gossip_bps.mtx); - auto& prod_idx = gossip_bps.index.get(); + const auto& prod_idx = gossip_bps.index.get(); for (const auto& account : active_bps) { if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { fc_dlog(self()->get_logger(), "connect to manual bp peer ${p}", ("p", i->second)); @@ -378,7 +374,7 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "pending_connections: ${c}", ("c", to_string(pending_connections))); fc::lock_guard g(gossip_bps.mtx); - auto& prod_idx = gossip_bps.index.get(); + const auto& prod_idx = gossip_bps.index.get(); for (const auto& account : pending_connections) { if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { fc_dlog(self()->get_logger(), "connect to manual bp peer ${p}", ("p", i->second)); @@ -415,7 +411,7 @@ class bp_connection_manager { set_active_schedule(schedule.producers); if (active_schedule_version == 0) { // first call since node was launched, connect to active gm.unlock(); - connect_to_active(); + connect_to_active_bp_peers(); gm.lock(); } @@ -436,7 +432,7 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "peers to drop: ${p}", ("p", to_string(peers_to_drop))); fc::lock_guard g(gossip_bps.mtx); - auto& prod_idx = gossip_bps.index.get(); + const auto& prod_idx = gossip_bps.index.get(); for (const auto& account : peers_to_drop) { if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { fc_dlog(self()->get_logger(), "disconnect to manual bp peer ${p}", ("p", i->second)); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 8d8667beb3..c2a9adb699 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3792,6 +3792,7 @@ namespace eosio { } else { bool diff = my_impl->update_gossip_bps(msg); if (diff) { // update, let all our peers know about it + my_impl->connect_to_active_bp_peers(); send_gossip_bp_peers_message_to_bp_peers(); } } From 52d8878118ad945a5b2d4029186a4acb97495f03 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 26 Mar 2025 10:37:05 -0500 Subject: [PATCH 25/50] GH-1245 Remove duplicate code and avoid calling back into net_plugin methods that take a lock --- .../eosio/net_plugin/auto_bp_peering.hpp | 70 ++++++++----------- plugins/net_plugin/net_plugin.cpp | 2 +- .../tests/auto_bp_peering_unittest.cpp | 2 +- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 7d85402fc5..6eef554d28 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -330,6 +330,24 @@ class bp_connection_manager { return diff; } + flat_set find_gossip_bp_addresses(const flat_set& accounts, const char* desc) const { + flat_set addresses; + fc::lock_guard g(gossip_bps.mtx); + const auto& prod_idx = gossip_bps.index.get(); + for (const auto& account : accounts) { + if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { + fc_dlog(self()->get_logger(), "${d} manual bp peer ${p}", ("d", desc)("p", i->second)); + addresses.insert(i->second); + } + auto r = prod_idx.equal_range(account); + for (auto i = r.first; i != r.second; ++i) { + fc_dlog(self()->get_logger(), "${d} gossip bp peer ${p}", ("d", desc)("p", i->server_address)); + addresses.insert(i->server_address); + } + } + return addresses; + } + // thread-safe void connect_to_active_bp_peers() { // do not hold mutexes when calling resolve_and_connect which acquires connections mutex since other threads @@ -338,20 +356,7 @@ class bp_connection_manager { { fc::lock_guard gm(mtx); active_bps = active_bp_accounts(active_schedule); - - fc::lock_guard g(gossip_bps.mtx); - const auto& prod_idx = gossip_bps.index.get(); - for (const auto& account : active_bps) { - if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { - fc_dlog(self()->get_logger(), "connect to manual bp peer ${p}", ("p", i->second)); - addresses.insert(i->second); - } - auto r = prod_idx.equal_range(account); - for (auto i = r.first; i != r.second; ++i) { - fc_dlog(self()->get_logger(), "connect to gossip bp peer ${p}", ("p", i->server_address)); - addresses.insert(i->server_address); - } - } + addresses = find_gossip_bp_addresses(active_bps, "connect"); } for (const auto& add : addresses) { @@ -373,18 +378,11 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "pending_connections: ${c}", ("c", to_string(pending_connections))); - fc::lock_guard g(gossip_bps.mtx); - const auto& prod_idx = gossip_bps.index.get(); - for (const auto& account : pending_connections) { - if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { - fc_dlog(self()->get_logger(), "connect to manual bp peer ${p}", ("p", i->second)); - self()->connections.resolve_and_connect(i->second, self()->get_first_p2p_address() ); - } - auto r = prod_idx.equal_range(account); - for (auto i = r.first; i != r.second; ++i) { - fc_dlog(self()->get_logger(), "connect to gossip bp peer ${p}", ("p", i->server_address)); - self()->connections.resolve_and_connect(i->server_address, self()->get_first_p2p_address() ); - } + // do not hold mutexes when calling resolve_and_connect which acquires connections mutex since other threads + // can be holding connections mutex when trying to acquire these mutexes + flat_set addresses = find_gossip_bp_addresses(pending_connections, "connect"); + for (const auto& add : addresses) { + self()->connections.resolve_and_connect(add, self()->get_first_p2p_address()); } pending_bps = std::move(pending_connections); @@ -426,24 +424,16 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "peers_to_stay: ${p}", ("p", to_string(peers_to_stay))); - std::vector peers_to_drop; + flat_set peers_to_drop; std::set_difference(old_bps.begin(), old_bps.end(), peers_to_stay.begin(), peers_to_stay.end(), - std::back_inserter(peers_to_drop)); + std::inserter(peers_to_drop, peers_to_drop.end())); fc_dlog(self()->get_logger(), "peers to drop: ${p}", ("p", to_string(peers_to_drop))); - fc::lock_guard g(gossip_bps.mtx); - const auto& prod_idx = gossip_bps.index.get(); - for (const auto& account : peers_to_drop) { - if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { - fc_dlog(self()->get_logger(), "disconnect to manual bp peer ${p}", ("p", i->second)); - self()->connections.disconnect(i->second); - } - auto r = prod_idx.equal_range(account); - for (auto i = r.first; i != r.second; ++i) { - fc_dlog(self()->get_logger(), "disconnect to gossip bp peer ${p}", ("p", i->server_address)); - self()->connections.disconnect(i->server_address); - } + flat_set addresses = find_gossip_bp_addresses(peers_to_drop, "disconnect"); + for (const auto& add : addresses) { + self()->connections.disconnect(add); } + active_schedule_version = schedule.version; } } diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index c2a9adb699..6ca918c3d4 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -488,7 +488,7 @@ namespace eosio { // switch to head_catchup on delayed blocks. Better to check not in lib_catchup. bool is_lib_catchup() const; - fc::logger& get_logger() { return logger; } + fc::logger& get_logger() const { return logger; } void create_session(tcp::socket&& socket, const string listen_address, size_t limit); diff --git a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp index 3eb72285ac..826f944eef 100644 --- a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp +++ b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp @@ -56,7 +56,7 @@ struct mock_net_plugin : eosio::auto_bp_peering::bp_connection_manager peer_addresses{ From d4e2a9ed7f53943ddf1e064b6bfb3d4cc72ade92 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 26 Mar 2025 10:56:33 -0500 Subject: [PATCH 26/50] GH-1245 Verify private key found --- .../include/eosio/net_plugin/auto_bp_peering.hpp | 6 +++++- plugins/net_plugin/net_plugin.cpp | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 6eef554d28..879ef6e6be 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -167,14 +167,18 @@ class bp_connection_manager { if (e == *config.my_bp_peer_accounts.begin()) { // use the first one of the set, doesn't matter which one is used, just need one gossip_bp_peers_message::bp_peer signed_empty{.producer_name = e}; // .server_address not set for initial message signed_empty.sig = self()->sign_compact(*pk, signed_empty.digest()); + EOS_ASSERT(signed_empty.sig != signature_type{}, chain::plugin_config_exception, + "Unable to sign empty gossip bp peer, private key not found for ${k}", ("k", pk->to_string({}))); initial_gossip_msg_factory.set_initial_send_buffer(signed_empty); } auto& prod_idx = gossip_bps.index.get(); gossip_bp_peers_message::bp_peer peer{.producer_name = e, .server_address = server_address}; peer.sig = self()->sign_compact(*pk, peer.digest()); + EOS_ASSERT(peer.sig != signature_type{}, chain::plugin_config_exception, + "Unable to sign bp peer ${p}, private key not found for ${k}", ("p", peer.producer_name)("k", pk->to_string({}))); if (auto i = prod_idx.find(boost::make_tuple(e, boost::cref(server_address))); i != prod_idx.end()) { gossip_bps.index.modify(i, [&peer](auto& v) { - v = peer; + v.sig = peer.sig; }); } else { gossip_bps.index.emplace(peer); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 6ca918c3d4..ff6ac7e861 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -4039,8 +4039,13 @@ namespace eosio { // update peer public keys from chainbase db flat_set modified = cc.update_peer_keys(block->block_num()); - if (!modified.empty()) - update_bp_producer_peers(cc, modified, get_first_p2p_address()); + if (!modified.empty()) { + try { + update_bp_producer_peers(cc, modified, get_first_p2p_address()); + } catch (fc::exception& e) { + fc_elog( logger, "Unable to update bp producer peers, error: ${e}", ("e", e.to_detail_string())); + } + } } // called from other threads including net threads From d30b57b05db09591090702ccc3d93c3e6e462b24 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 26 Mar 2025 11:46:20 -0500 Subject: [PATCH 27/50] GH-1245 Replace is_gossip_bp_connection and is_configured_bp_connection with enum bp_connection_type --- .../include/eosio/net_plugin/auto_bp_peering.hpp | 4 ++-- plugins/net_plugin/net_plugin.cpp | 16 ++++++++-------- .../tests/auto_bp_peering_unittest.cpp | 7 ++++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 879ef6e6be..9e83627d46 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -195,14 +195,14 @@ class bp_connection_manager { // log_p2p_address always has a trailing hex like `localhost:9877 - bc3f55b` std::string addr = conn->log_p2p_address.substr(0, space_pos); if (config.bp_peer_accounts.count(addr)) { - conn->is_configured_bp_connection = true; + conn->bp_connection = Connection::bp_connection_type::bp_config; } } // Only called from connection strand template static bool established_client_connection(Conn&& conn) { - return !conn->is_gossip_bp_connection && !conn->is_configured_bp_connection && conn->socket_is_open() && conn->incoming_and_handshake_received(); + return conn->bp_connection == Connection::bp_connection_type::non_bp && conn->socket_is_open() && conn->incoming_and_handshake_received(); } send_buffer_type get_gossip_bp_initial_send_buffer() { diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index ff6ac7e861..8875e06a55 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -843,8 +843,8 @@ namespace eosio { std::atomic protocol_version = 0; uint16_t net_version = net_version_max; std::atomic consecutive_immediate_connection_close = 0; - std::atomic is_gossip_bp_connection = false; - std::atomic is_configured_bp_connection = false; + enum class bp_connection_type { non_bp, bp_config, bp_gossip }; + std::atomic bp_connection = bp_connection_type::non_bp; block_status_monitor block_status_monitor_; std::atomic last_vote_received; @@ -1320,8 +1320,8 @@ namespace eosio { connection_status stat; stat.connecting = state() == connection_state::connecting; stat.syncing = peer_syncing_from_us; - stat.is_bp_peer = is_gossip_bp_connection || is_configured_bp_connection; - stat.is_bp_gossip_peer = is_gossip_bp_connection; + stat.is_bp_peer = bp_connection != bp_connection_type::non_bp; + stat.is_bp_gossip_peer = bp_connection == bp_connection_type::bp_gossip; stat.is_socket_open = socket_is_open(); stat.is_blocks_only = is_blocks_only_connection(); stat.is_transactions_only = is_transactions_only_connection(); @@ -3783,7 +3783,7 @@ namespace eosio { } // valid gossip peer connection - is_gossip_bp_connection = true; + bp_connection = bp_connection_type::bp_gossip; assert(!msg.peers.empty()); // checked by validate_gossip_bp_peers_message() if (msg.peers.size() == 1 && msg.peers[0].server_address.empty()) { @@ -3820,7 +3820,7 @@ namespace eosio { assert(my_impl->bp_gossip_enabled()); my_impl->connections.for_each_connection([this](const connection_ptr& c) { gossip_buffer_factory factory; - if (this != c.get() && c->is_gossip_bp_connection && c->socket_is_open()) { + if (this != c.get() && c->bp_connection == bp_connection_type::bp_gossip && c->socket_is_open()) { const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); boost::asio::post(c->strand, [sb, c]() { c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); @@ -4933,7 +4933,7 @@ namespace eosio { return; } const connection_ptr& c = it->c; - if (c->is_gossip_bp_connection || c->is_configured_bp_connection) { + if (c->bp_connection != connection::bp_connection_type::non_bp) { ++num_bp_peers; } else if (c->incoming()) { ++num_clients; @@ -4974,7 +4974,7 @@ namespace eosio { net_plugin::p2p_per_connection_metrics per_connection(index.size()); for (auto it = index.begin(); it != index.end(); ++it) { const connection_ptr& c = it->c; - if(c->is_gossip_bp_connection || c->is_configured_bp_connection) { + if(c->bp_connection != connection::bp_connection_type::non_bp) { ++num_bp_peers; } else if(c->incoming()) { ++num_clients; diff --git a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp index 826f944eef..e3c099de0b 100644 --- a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp +++ b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp @@ -2,12 +2,13 @@ #include struct mock_connection { - bool is_configured_bp_connection = false; - bool is_gossip_bp_connection = false; + enum class bp_connection_type { non_bp, bp_config, bp_gossip }; + bp_connection_type bp_connection = bp_connection_type::non_bp; + bool is_open = false; bool handshake_received = false; mock_connection(bool bp_connection, bool open, bool received) - : is_configured_bp_connection(bp_connection) + : bp_connection(bp_connection ? bp_connection_type::bp_config : bp_connection_type::non_bp) , is_open(open) , handshake_received(received) {} From d8f320486a7fda1b15a7fdae4fe726281201af06 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Thu, 27 Mar 2025 14:10:50 -0400 Subject: [PATCH 28/50] Use `unique_ptr` in buffer factory. use `send_buffer_type` consistently. Avoid `shared_ptr` copies. --- libraries/libfc/libraries/bn256 | 2 +- .../eosio/net_plugin/auto_bp_peering.hpp | 4 +- .../eosio/net_plugin/buffer_factory.hpp | 22 +++++----- plugins/net_plugin/net_plugin.cpp | 40 +++++++++---------- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/libraries/libfc/libraries/bn256 b/libraries/libfc/libraries/bn256 index b5adbb76d4..161ca72af4 160000 --- a/libraries/libfc/libraries/bn256 +++ b/libraries/libfc/libraries/bn256 @@ -1 +1 @@ -Subproject commit b5adbb76d456e85385296a1388ba18f6f07d9d9e +Subproject commit 161ca72af4c647ad0b23ccd97d2dd84201fc2b37 diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 9e83627d46..a487fb9b8c 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -205,12 +205,12 @@ class bp_connection_manager { return conn->bp_connection == Connection::bp_connection_type::non_bp && conn->socket_is_open() && conn->incoming_and_handshake_received(); } - send_buffer_type get_gossip_bp_initial_send_buffer() { + const send_buffer_type& get_gossip_bp_initial_send_buffer() { fc::lock_guard g(mtx); return initial_gossip_msg_factory.get_initial_send_buffer(); } - send_buffer_type get_gossip_bp_send_buffer(gossip_buffer_factory& factory) { + const send_buffer_type& get_gossip_bp_send_buffer(gossip_buffer_factory& factory) { return factory.get_send_buffer(gossip_bps); } diff --git a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp index f718de2545..3ab5f3a30c 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp @@ -10,7 +10,7 @@ namespace eosio { - using send_buffer_type = std::shared_ptr>; + using send_buffer_type = std::unique_ptr>; struct buffer_factory { @@ -32,7 +32,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_shared>(buffer_size); + auto send_buffer = std::make_unique>(buffer_size); fc::datastream ds( send_buffer->data(), buffer_size); ds.write( header, message_header_size ); fc::raw::pack( ds, m ); @@ -49,7 +49,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_shared>( buffer_size ); + auto send_buffer = std::make_unique>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( which ) ); @@ -68,7 +68,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_shared>( buffer_size ); + auto send_buffer = std::make_unique>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( signed_block_which ) ); @@ -98,7 +98,7 @@ namespace eosio { private: - static std::shared_ptr> create_send_buffer( const signed_block_ptr& sb ) { + static send_buffer_type create_send_buffer( const signed_block_ptr& sb ) { constexpr uint32_t signed_block_which = to_index(msg_type_t::signed_block); // this implementation is to avoid copy of signed_block to net_message @@ -107,7 +107,7 @@ namespace eosio { return buffer_factory::create_send_buffer( signed_block_which, *sb ); } - static std::shared_ptr> create_send_buffer( const std::vector& ssb ) { // ssb: serialized signed block + static send_buffer_type create_send_buffer( const std::vector& ssb ) { // ssb: serialized signed block // this implementation is to avoid copy of signed_block to net_message // matches which of net_message for signed_block return buffer_factory::create_send_buffer_from_serialized_block( ssb ); @@ -126,7 +126,7 @@ namespace eosio { private: - static std::shared_ptr> create_send_buffer( const packed_transaction_ptr& trx ) { + static send_buffer_type create_send_buffer( const packed_transaction_ptr& trx ) { constexpr uint32_t packed_transaction_which = to_index(msg_type_t::packed_transaction); // this implementation is to avoid copy of packed_transaction to net_message @@ -147,7 +147,7 @@ namespace eosio { private: - static std::shared_ptr> create_send_buffer(const gossip_bp_index_t& gossip_bp_peers) { + static send_buffer_type create_send_buffer(const gossip_bp_index_t& gossip_bp_peers) { constexpr uint32_t which = to_index(msg_type_t::gossip_bp_peers_message); fc::lock_guard g(gossip_bp_peers.mtx); @@ -164,7 +164,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_shared>( buffer_size ); + auto send_buffer = std::make_unique>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( which ) ); @@ -192,7 +192,7 @@ namespace eosio { private: - static std::shared_ptr> create_initial_send_buffer(const gossip_bp_peers_message::bp_peer& signed_empty) { + static send_buffer_type create_initial_send_buffer(const gossip_bp_peers_message::bp_peer& signed_empty) { constexpr uint32_t which = to_index(msg_type_t::gossip_bp_peers_message); // match net_message static_variant pack @@ -205,7 +205,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_shared>( buffer_size ); + auto send_buffer = std::make_unique>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( which ) ); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 8875e06a55..649dc1d20b 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -224,7 +224,7 @@ namespace eosio { bool have_txn( const transaction_id_type& tid ) const; void expire_txns(); - void bcast_vote_msg( uint32_t exclude_peer, send_buffer_type msg ); + void bcast_vote_msg( uint32_t exclude_peer, const send_buffer_type& msg ); }; /** @@ -616,7 +616,7 @@ namespace eosio { // @param callback must not callback into queued_buffer bool add_write_queue(msg_type_t net_msg, queue_t queue, - const std::shared_ptr>& buff, + const send_buffer_type& buff, std::function callback) { fc::lock_guard g( _mtx ); if( net_msg == msg_type_t::packed_transaction ) { @@ -666,7 +666,7 @@ namespace eosio { private: struct queued_write { - std::shared_ptr> buff; + const send_buffer_type& buff; std::function callback; }; @@ -968,7 +968,7 @@ namespace eosio { void enqueue_buffer( msg_type_t net_msg, std::optional block_num, queued_buffer::queue_t queue, - const std::shared_ptr>& send_buffer, + const send_buffer_type& send_buffer, go_away_reason close_after_send); void cancel_sync(); void flush_queues(); @@ -981,7 +981,7 @@ namespace eosio { void queue_write(msg_type_t net_msg, std::optional block_num, queued_buffer::queue_t queue, - const std::shared_ptr>& buff, + const send_buffer_type& buff, std::function callback); void do_queue_write(std::optional block_num); @@ -1616,7 +1616,7 @@ namespace eosio { void connection::queue_write(msg_type_t net_msg, std::optional block_num, queued_buffer::queue_t queue, - const std::shared_ptr>& buff, + const send_buffer_type& buff, std::function callback) { if( !buffer_queue.add_write_queue( net_msg, queue, buff, std::move(callback) )) { peer_wlog( this, "write_queue full ${s} bytes, giving up on connection", ("s", buffer_queue.write_queue_size()) ); @@ -2652,8 +2652,8 @@ namespace eosio { if (cp->consecutive_blocks_nacks > connection::consecutive_block_nacks_threshold) { // only send block_notice if we didn't produce the block, otherwise broadcast the block below if (!my_impl->is_producer(b->producer)) { - auto send_buffer = block_notice_buff_factory.get_send_buffer( block_notice_message{b->previous, id} ); - boost::asio::post(cp->strand, [cp, send_buffer{std::move(send_buffer)}, bnum]() { + const auto& send_buffer = block_notice_buff_factory.get_send_buffer( block_notice_message{b->previous, id} ); + boost::asio::post(cp->strand, [cp, &send_buffer, bnum]() { cp->latest_blk_time = std::chrono::steady_clock::now(); peer_dlog( cp, "bcast block_notice ${b}", ("b", bnum) ); cp->enqueue_buffer( msg_type_t::block_notice_message, std::nullopt, queued_buffer::queue_t::general, send_buffer, no_reason ); @@ -2663,9 +2663,9 @@ namespace eosio { } } - send_buffer_type sb = buff_factory.get_send_buffer( b ); + const send_buffer_type& sb = buff_factory.get_send_buffer( b ); - boost::asio::post(cp->strand, [cp, bnum, sb{std::move(sb)}]() { + boost::asio::post(cp->strand, [cp, bnum, &sb]() { cp->latest_blk_time = std::chrono::steady_clock::now(); bool has_block = cp->peer_fork_db_root_num >= bnum; if( !has_block ) { @@ -2676,12 +2676,12 @@ namespace eosio { } ); } - void dispatch_manager::bcast_vote_msg( uint32_t exclude_peer, send_buffer_type msg ) { - my_impl->connections.for_each_block_connection( [exclude_peer, msg{std::move(msg)}]( auto& cp ) { + void dispatch_manager::bcast_vote_msg( uint32_t exclude_peer, const send_buffer_type& msg ) { + my_impl->connections.for_each_block_connection( [exclude_peer, &msg]( auto& cp ) { if( !cp->current() ) return true; if( cp->connection_id == exclude_peer ) return true; if (cp->protocol_version < proto_savanna) return true; - boost::asio::post(cp->strand, [cp, msg]() { + boost::asio::post(cp->strand, [cp, &msg]() { if (vote_logger.is_enabled(fc::log_level::debug)) peer_dlog(cp, "sending vote msg"); cp->enqueue_buffer( msg_type_t::vote_message, std::nullopt, queued_buffer::queue_t::general, msg, no_reason ); @@ -2702,9 +2702,9 @@ namespace eosio { return; } - send_buffer_type sb = buff_factory.get_send_buffer( trx ); + const send_buffer_type& sb = buff_factory.get_send_buffer( trx ); fc_dlog( logger, "sending trx: ${id}, to connection - ${cid}", ("id", trx->id())("cid", cp->connection_id) ); - boost::asio::post(cp->strand, [cp, sb{std::move(sb)}]() { + boost::asio::post(cp->strand, [cp, &sb]() { cp->enqueue_buffer( msg_type_t::packed_transaction, std::nullopt, queued_buffer::queue_t::general, sb, no_reason ); } ); } ); @@ -3154,7 +3154,7 @@ namespace eosio { peer_dlog(this, "Sending nack ${n}", ("n", block_header::num_from_id(block_id))); buffer_factory buff_factory; - auto send_buffer = buff_factory.get_send_buffer( block_nack_message{block_id} ); + auto& send_buffer = buff_factory.get_send_buffer( block_nack_message{block_id} ); enqueue_buffer( msg_type_t::block_nack_message, std::nullopt, queued_buffer::queue_t::general, send_buffer, no_reason ); } @@ -3802,7 +3802,7 @@ namespace eosio { void connection::send_gossip_bp_peers_initial_message() { if (protocol_version < proto_gossip_bp_peers || !my_impl->bp_gossip_enabled()) return; - auto sb = my_impl->get_gossip_bp_initial_send_buffer(); + const auto& sb = my_impl->get_gossip_bp_initial_send_buffer(); enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); } @@ -3822,7 +3822,7 @@ namespace eosio { gossip_buffer_factory factory; if (this != c.get() && c->bp_connection == bp_connection_type::bp_gossip && c->socket_is_open()) { const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); - boost::asio::post(c->strand, [sb, c]() { + boost::asio::post(c->strand, [&sb, c]() { c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); }); } @@ -4112,9 +4112,9 @@ namespace eosio { boost::asio::post( thread_pool.get_executor(), [exclude_peer, msg, this]() mutable { buffer_factory buff_factory; - auto send_buffer = buff_factory.get_send_buffer( *msg ); + const auto& send_buffer = buff_factory.get_send_buffer( *msg ); - dispatcher.bcast_vote_msg( exclude_peer, std::move(send_buffer) ); + dispatcher.bcast_vote_msg( exclude_peer, send_buffer ); }); } From 8dff9eba5468acd36e4aafa0827e203900861d4f Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Thu, 27 Mar 2025 14:40:42 -0400 Subject: [PATCH 29/50] Make copies of buffers when posting them. --- .../include/eosio/net_plugin/buffer_factory.hpp | 12 ++++++------ plugins/net_plugin/net_plugin.cpp | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp index 3ab5f3a30c..c13ec25fc6 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp @@ -10,7 +10,7 @@ namespace eosio { - using send_buffer_type = std::unique_ptr>; + using send_buffer_type = std::shared_ptr>; struct buffer_factory { @@ -32,7 +32,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_unique>(buffer_size); + auto send_buffer = std::make_shared>(buffer_size); fc::datastream ds( send_buffer->data(), buffer_size); ds.write( header, message_header_size ); fc::raw::pack( ds, m ); @@ -49,7 +49,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_unique>( buffer_size ); + auto send_buffer = std::make_shared>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( which ) ); @@ -68,7 +68,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_unique>( buffer_size ); + auto send_buffer = std::make_shared>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( signed_block_which ) ); @@ -164,7 +164,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_unique>( buffer_size ); + auto send_buffer = std::make_shared>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( which ) ); @@ -205,7 +205,7 @@ namespace eosio { const char* const header = reinterpret_cast(&payload_size); // avoid variable size encoding of uint32_t const size_t buffer_size = message_header_size + payload_size; - auto send_buffer = std::make_unique>( buffer_size ); + auto send_buffer = std::make_shared>( buffer_size ); fc::datastream ds( send_buffer->data(), buffer_size ); ds.write( header, message_header_size ); fc::raw::pack( ds, unsigned_int( which ) ); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 649dc1d20b..29ef84beec 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -666,7 +666,7 @@ namespace eosio { private: struct queued_write { - const send_buffer_type& buff; + send_buffer_type buff; std::function callback; }; @@ -2653,7 +2653,7 @@ namespace eosio { // only send block_notice if we didn't produce the block, otherwise broadcast the block below if (!my_impl->is_producer(b->producer)) { const auto& send_buffer = block_notice_buff_factory.get_send_buffer( block_notice_message{b->previous, id} ); - boost::asio::post(cp->strand, [cp, &send_buffer, bnum]() { + boost::asio::post(cp->strand, [cp, send_buffer, bnum]() { cp->latest_blk_time = std::chrono::steady_clock::now(); peer_dlog( cp, "bcast block_notice ${b}", ("b", bnum) ); cp->enqueue_buffer( msg_type_t::block_notice_message, std::nullopt, queued_buffer::queue_t::general, send_buffer, no_reason ); @@ -2665,7 +2665,7 @@ namespace eosio { const send_buffer_type& sb = buff_factory.get_send_buffer( b ); - boost::asio::post(cp->strand, [cp, bnum, &sb]() { + boost::asio::post(cp->strand, [cp, bnum, sb]() { cp->latest_blk_time = std::chrono::steady_clock::now(); bool has_block = cp->peer_fork_db_root_num >= bnum; if( !has_block ) { @@ -2677,11 +2677,11 @@ namespace eosio { } void dispatch_manager::bcast_vote_msg( uint32_t exclude_peer, const send_buffer_type& msg ) { - my_impl->connections.for_each_block_connection( [exclude_peer, &msg]( auto& cp ) { + my_impl->connections.for_each_block_connection( [exclude_peer, msg]( auto& cp ) { if( !cp->current() ) return true; if( cp->connection_id == exclude_peer ) return true; if (cp->protocol_version < proto_savanna) return true; - boost::asio::post(cp->strand, [cp, &msg]() { + boost::asio::post(cp->strand, [cp, msg]() { if (vote_logger.is_enabled(fc::log_level::debug)) peer_dlog(cp, "sending vote msg"); cp->enqueue_buffer( msg_type_t::vote_message, std::nullopt, queued_buffer::queue_t::general, msg, no_reason ); @@ -2704,7 +2704,7 @@ namespace eosio { const send_buffer_type& sb = buff_factory.get_send_buffer( trx ); fc_dlog( logger, "sending trx: ${id}, to connection - ${cid}", ("id", trx->id())("cid", cp->connection_id) ); - boost::asio::post(cp->strand, [cp, &sb]() { + boost::asio::post(cp->strand, [cp, sb]() { cp->enqueue_buffer( msg_type_t::packed_transaction, std::nullopt, queued_buffer::queue_t::general, sb, no_reason ); } ); } ); @@ -3822,7 +3822,7 @@ namespace eosio { gossip_buffer_factory factory; if (this != c.get() && c->bp_connection == bp_connection_type::bp_gossip && c->socket_is_open()) { const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); - boost::asio::post(c->strand, [&sb, c]() { + boost::asio::post(c->strand, [sb, c]() { c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); }); } From 3f12cb21350395d4829501551f37f0453c00f4d6 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Thu, 27 Mar 2025 14:58:10 -0400 Subject: [PATCH 30/50] Add a `const` --- plugins/net_plugin/net_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 29ef84beec..3d369ce9ff 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3154,7 +3154,7 @@ namespace eosio { peer_dlog(this, "Sending nack ${n}", ("n", block_header::num_from_id(block_id))); buffer_factory buff_factory; - auto& send_buffer = buff_factory.get_send_buffer( block_nack_message{block_id} ); + const auto& send_buffer = buff_factory.get_send_buffer( block_nack_message{block_id} ); enqueue_buffer( msg_type_t::block_nack_message, std::nullopt, queued_buffer::queue_t::general, send_buffer, no_reason ); } From 724edc0d9c50163c51c03b32cf6a3907093b0a34 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 28 Mar 2025 15:41:13 -0400 Subject: [PATCH 31/50] Return a value as `send_buffer` can be modified at any time. --- plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp index c13ec25fc6..6f7025a6fd 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp @@ -185,7 +185,7 @@ namespace eosio { } /// requires set_initial_send_buffer to be called first - const send_buffer_type& get_initial_send_buffer() { + send_buffer_type get_initial_send_buffer() { assert(send_buffer); return send_buffer; } From de8699ee021c00e909697eb3322bdcc57aad2311 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 28 Mar 2025 16:03:19 -0400 Subject: [PATCH 32/50] Also update return value of `get_gossip_bp_initial_send_buffer` --- plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index a487fb9b8c..2d42796c76 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -205,7 +205,7 @@ class bp_connection_manager { return conn->bp_connection == Connection::bp_connection_type::non_bp && conn->socket_is_open() && conn->incoming_and_handshake_received(); } - const send_buffer_type& get_gossip_bp_initial_send_buffer() { + send_buffer_type get_gossip_bp_initial_send_buffer() { fc::lock_guard g(mtx); return initial_gossip_msg_factory.get_initial_send_buffer(); } From 313dbfd425b55d5749c229ac01b437ae4e1bfec6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 31 Mar 2025 09:25:58 -0500 Subject: [PATCH 33/50] GH-1245 Verify regpeerkey is final before shutdown --- tests/auto_bp_gossip_peering_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/auto_bp_gossip_peering_test.py b/tests/auto_bp_gossip_peering_test.py index 6ee9a0fbb9..d33bcbdf3c 100755 --- a/tests/auto_bp_gossip_peering_test.py +++ b/tests/auto_bp_gossip_peering_test.py @@ -107,13 +107,13 @@ def getHostName(nodeId): a = accounts[nodeId] node = cluster.getNode(nodeId) - results = cluster.biosNode.pushMessage('eosio', 'regpeerkey', f'{{"proposer_finalizer_name":"{producer_name}","key":"{a.activePublicKey}"}}', f'-p {producer_name}@active') - assert(results[0]) + success, trans = cluster.biosNode.pushMessage('eosio', 'regpeerkey', f'{{"proposer_finalizer_name":"{producer_name}","key":"{a.activePublicKey}"}}', f'-p {producer_name}@active') + assert(success) - # wait until produceru is seen by every node + # wait for regpeerkey to be final for nodeId in range(0, producerNodes): - Utils.Print("Wait for defproduceru on node ", nodeId) - cluster.getNode(nodeId).waitForProducer("defproduceru", exitOnError=True, timeout=300) + Utils.Print("Wait for last regpeerkey to be final on ", nodeId) + cluster.getNode(nodeId).waitForTransFinalization(trans['transaction_id']) # relaunch with p2p-producer-peer for nodeId in range(0, producerNodes): From 818da30e023fe1af7f06ffd022489be6c3ae1716 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 9 Apr 2025 13:00:19 -0500 Subject: [PATCH 34/50] GH-1245 go_away_reason now an enum class --- plugins/net_plugin/net_plugin.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 5ed61924f2..20674a2f64 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3755,7 +3755,7 @@ namespace eosio { if (!my_impl->validate_gossip_bp_peers_message(msg)) { peer_wlog( this, "bad gossip_bp_peers_message, closing"); no_retry = go_away_reason::fatal_other; - enqueue( go_away_message( fatal_other ) ); + enqueue( go_away_message( go_away_reason::fatal_other ) ); return; } @@ -3780,7 +3780,7 @@ namespace eosio { if (protocol_version < proto_gossip_bp_peers || !my_impl->bp_gossip_enabled()) return; const auto& sb = my_impl->get_gossip_bp_initial_send_buffer(); - enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); + enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, go_away_reason::no_reason); } @@ -3789,7 +3789,7 @@ namespace eosio { assert(my_impl->bp_gossip_enabled()); gossip_buffer_factory factory; const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); - enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); + enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, go_away_reason::no_reason); } // called from connection strand, thread safe @@ -3800,7 +3800,7 @@ namespace eosio { if (this != c.get() && c->bp_connection == bp_connection_type::bp_gossip && c->socket_is_open()) { const send_buffer_type& sb = my_impl->get_gossip_bp_send_buffer(factory); boost::asio::post(c->strand, [sb, c]() { - c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, no_reason); + c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, go_away_reason::no_reason); }); } }); From f87f2b33cdb621a8b59f21ded5f86173c2ca3d2f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 15 Apr 2025 16:52:03 -0500 Subject: [PATCH 35/50] GH-1245 Fix merge issue --- .../contracts/eosio.system/eosio.system.abi | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/unittests/contracts/eosio.system/eosio.system.abi b/unittests/contracts/eosio.system/eosio.system.abi index 96b7088df5..08d7d76e42 100644 --- a/unittests/contracts/eosio.system/eosio.system.abi +++ b/unittests/contracts/eosio.system/eosio.system.abi @@ -881,20 +881,6 @@ } ] }, - { - "name": "regpeerkey", - "base": "", - "fields": [ - { - "name": "proposer_finalizer_name", - "type": "name" - }, - { - "name": "key", - "type": "public_key" - } - ] - }, { "name": "regproducer", "base": "", @@ -1411,16 +1397,6 @@ } ] }, - { - "name": "v0_data", - "base": "", - "fields": [ - { - "name": "pubkey", - "type": "public_key?" - } - ] - }, { "name": "voteproducer", "base": "", From 89ead3ad677751ca1cd6ca491d7d8af1603b1695 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 16 Apr 2025 10:44:37 -0500 Subject: [PATCH 36/50] GH-1245 peerkeys table moved to peer_keys.?pp --- .../contracts/eosio.system/eosio.system.cpp | 4 +- .../contracts/eosio.system/eosio.system.hpp | 50 ------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/unittests/contracts/eosio.system/eosio.system.cpp b/unittests/contracts/eosio.system/eosio.system.cpp index 98e6c70659..ceed74ea74 100644 --- a/unittests/contracts/eosio.system/eosio.system.cpp +++ b/unittests/contracts/eosio.system/eosio.system.cpp @@ -7,7 +7,6 @@ #include "voting.cpp" #include "exchange_state.cpp" #include "rex.cpp" -#include "peer_keys.hpp" namespace eosiosystem { @@ -23,8 +22,7 @@ system_contract::system_contract( name s, name code, datastream ds _rexpool(_self, _self.value), _rexfunds(_self, _self.value), _rexbalance(_self, _self.value), - _rexorders(_self, _self.value), - _peer_keys(_self, _self.value) + _rexorders(_self, _self.value) { //print( "construct system\n" ); _gstate = _global.exists() ? _global.get() : get_default_parameters(); diff --git a/unittests/contracts/eosio.system/eosio.system.hpp b/unittests/contracts/eosio.system/eosio.system.hpp index 310d939e37..ac3b5b04ce 100644 --- a/unittests/contracts/eosio.system/eosio.system.hpp +++ b/unittests/contracts/eosio.system/eosio.system.hpp @@ -269,33 +269,6 @@ struct rex_order_outcome { asset stake_change; }; -struct [[eosio::table("peerkeys"), eosio::contract("eosio.system")]] peer_key { - - struct v0_data { - std::optional pubkey; // peer key for network message authentication - EOSLIB_SERIALIZE( v0_data, (pubkey) ) - }; - - name proposer_finalizer_name; - uint32_t block_num; // block number where this row was emplaced or modified - uint8_t version; // version 0 and above must have the `key` optional - std::variant data; - - uint64_t primary_key() const { return proposer_finalizer_name.value; } - uint64_t by_block_num() const { return block_num; } - - void set_public_key(const public_key& key) { data = v0_data{key}; } - const std::optional& get_public_key() const { - return std::visit([](auto& v) -> const std::optional& { return v.pubkey; }, data); - } - void update_row() { block_num = eosio::current_block_number(); } - void init_row(name n) { *this = peer_key{n, eosio::current_block_number(), 0, v0_data{}}; }}; - -typedef eosio::multi_index<"peerkeys"_n, peer_key, - indexed_by<"byblocknum"_n, const_mem_fun> - > peer_keys_table; - - class [[eosio::contract("eosio.system")]] system_contract : public native { private: @@ -313,7 +286,6 @@ class [[eosio::contract("eosio.system")]] system_contract : public native { rex_fund_table _rexfunds; rex_balance_table _rexbalance; rex_order_table _rexorders; - peer_keys_table _peer_keys; public: static constexpr eosio::name active_permission{"active"_n}; @@ -514,28 +486,6 @@ class [[eosio::contract("eosio.system")]] system_contract : public native { [[eosio::action]] void unregprod( const name producer ); - /** - * Action to register a public key for a proposer or finalizer name. - * This key will be used to validate a network peer's identity. - * A proposer or finalizer can only have have one public key registered at a time. - * If a key is already registered for `proposer_finalizer_name`, and `regpeerkey` is - * called with a different key, the new key replaces the previous one in `peer_keys_table` - */ - [[eosio::action]] - void regpeerkey( const name& proposer_finalizer_name, const public_key& key ); - - - /** - * Action to delete a public key for a proposer or finalizer name. - * - * The intent of this action is only for the account to reclaim the RAM, as - * the node software may remember the key after it was deleted using `delpeerkey`. - * - * An existing public key for a given account can be changed by calling `regpeerkey` again. - */ - [[eosio::action]] - void delpeerkey( const name& proposer_finalizer_name, const public_key& key ); - [[eosio::action]] void setram( uint64_t max_ram_size ); [[eosio::action]] From 2235077163bd1739893687646803e15532cc9753 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 16 Apr 2025 10:45:33 -0500 Subject: [PATCH 37/50] GH-1245 Simplify implementation since protoalvote not populated for most integration tests. --- .../contracts/eosio.system/eosio.system.wasm | Bin 215817 -> 214688 bytes .../contracts/eosio.system/peer_keys.cpp | 16 ++++------------ 2 files changed, 4 insertions(+), 12 deletions(-) mode change 100644 => 100755 unittests/contracts/eosio.system/eosio.system.wasm diff --git a/unittests/contracts/eosio.system/eosio.system.wasm b/unittests/contracts/eosio.system/eosio.system.wasm old mode 100644 new mode 100755 index 904abd0c8356febef8ea1b888ab7757e8cdbbb75..c81cd11f946478fa033cc0281050a4d072cb1bc9 GIT binary patch delta 3867 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 Fe*qbV1hW7D diff --git a/unittests/contracts/eosio.system/peer_keys.cpp b/unittests/contracts/eosio.system/peer_keys.cpp index 695bc65654..25436d2092 100644 --- a/unittests/contracts/eosio.system/peer_keys.cpp +++ b/unittests/contracts/eosio.system/peer_keys.cpp @@ -42,24 +42,16 @@ void peer_keys::delpeerkey(const name& proposer_finalizer_name, const public_key peer_keys::getpeerkeys_res_t peer_keys::getpeerkeys() { peer_keys_table peer_keys_table(get_self(), get_self().value); - producers_table producers(get_self(), get_self().value); - constexpr size_t max_return = 50; getpeerkeys_res_t resp; - resp.reserve(max_return); - - auto idx = producers.get_index<"prototalvote"_n>(); // this is a simpler implementation than the one in `eos-system-contracts`. // the one in `eos-system-contracts` iterates over both ends of the "prototalvote"_n index // (to take into account non-active producers) - // ---------------------------------------------------------------------------------------- - for( auto it = idx.cbegin(); it != idx.cend() && resp.size() < max_return; ++it ) { - auto peers_itr = peer_keys_table.find(it->owner.value); - if (peers_itr == peer_keys_table.end()) - resp.push_back(peerkeys_t{it->owner, {}}); - else - resp.push_back(peerkeys_t{it->owner, peers_itr->get_public_key()}); + // Most integration tests use bios contract to setprods, just return complete list for tests + // since prototalvote will not be populated. + for (auto it = peer_keys_table.begin(); it != peer_keys_table.end(); ++it) { + resp.push_back(peerkeys_t{it->account, it->get_public_key()}); } return resp; } From 65f3c9ea7a242377af75b6b8b31111ec0f676f5f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 16 Apr 2025 10:54:14 -0500 Subject: [PATCH 38/50] GH-1245 enable-stale-production so production restarts on node restart --- tests/auto_bp_gossip_peering_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto_bp_gossip_peering_test.py b/tests/auto_bp_gossip_peering_test.py index d33bcbdf3c..c2d68fb9b0 100755 --- a/tests/auto_bp_gossip_peering_test.py +++ b/tests/auto_bp_gossip_peering_test.py @@ -121,7 +121,7 @@ def getHostName(nodeId): node = cluster.getNode(nodeId) node.kill(signal.SIGTERM) producer_name = "defproducer" + chr(ord('a') + nodeId) - if not node.relaunch(chainArg=" --p2p-producer-peer " + producer_name): + if not node.relaunch(chainArg=" --enable-stale-production --p2p-producer-peer " + producer_name): errorExit(f"Failed to relaunch node {nodeId}") # give time for messages to be gossiped around From dff3da98277bce43a276cdee58f535f6f524a00a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 16 Apr 2025 10:56:33 -0500 Subject: [PATCH 39/50] GH-1245 Integrate peer key retrieval by read-only trx. Required waiting on receive of a block before read-only trx could be executed. --- libraries/chain/controller.cpp | 41 +++++++++++------- .../chain/include/eosio/chain/controller.hpp | 9 ++-- .../include/eosio/chain/peer_keys_db.hpp | 39 ++++++++++------- libraries/chain/peer_keys_db.cpp | 34 +++++++++------ .../eosio/net_plugin/auto_bp_peering.hpp | 43 +++++++++---------- .../eosio/net_plugin/buffer_factory.hpp | 3 +- plugins/net_plugin/net_plugin.cpp | 36 ++++++++++++---- plugins/producer_plugin/producer_plugin.cpp | 6 --- unittests/getpeerkeys_tests.cpp | 2 +- 9 files changed, 124 insertions(+), 89 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index a09a29407a..f126e9ecbe 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1286,7 +1286,7 @@ struct controller_impl { } } - getpeerkeys_res_t get_top_producer_keys(fc::time_point deadline) { + getpeerkeys_res_t get_top_producer_keys() { try { auto get_getpeerkeys_transaction = [&]() { auto perms = vector{}; @@ -1304,7 +1304,7 @@ struct controller_impl { transaction_metadata::trx_type::read_only); // allow a max of 20ms for getpeerkeys - auto trace = push_transaction(metadata, deadline, fc::milliseconds(20), 0, false, 0); + auto trace = push_transaction(metadata, fc::time_point::maximum(), fc::milliseconds(20), 0, false, 0); if( trace->except_ptr ) std::rethrow_exception(trace->except_ptr); @@ -3238,6 +3238,11 @@ struct controller_impl { auto& bb = std::get(pending->_block_stage); + // limit to complete type to avoid multiple calls per block number due to speculative blocks + if (pending->_block_status == controller::block_status::complete) { + update_peer_keys(); + } + transaction_trace_ptr onblock_trace; // block status is either ephemeral or incomplete. Modify state of speculative block only if we are building a @@ -3385,12 +3390,16 @@ struct controller_impl { return onblock_trace; } /// start_block - void update_peer_keys(fc::time_point deadline) { + void update_peer_keys() { + // if syncing or replaying old blocks don't bother updating peer keys + if (!peer_keys_db.is_active() || fc::time_point::now() - chain_head.timestamp() > fc::minutes(5)) + return; + try { - // update peer public keys from chainbase db using a readonly trx - auto block_num = chain_head.block_num(); - if (block_num % 120 == 0) { // update once/minute - peer_keys_db.update_peer_keys(get_top_producer_keys(deadline)); + auto block_num = chain_head.block_num() + 1; + if (peer_keys_db.should_update(block_num)) { // update once/minute + // update peer public keys from chainbase db using a readonly trx + peer_keys_db.update_peer_keys(block_num, get_top_producer_keys()); } } FC_LOG_AND_DROP() } @@ -5339,10 +5348,6 @@ transaction_trace_ptr controller::start_block( block_timestamp_type when, bs, std::optional(), deadline ); } -void controller::update_peer_keys(fc::time_point deadline) { - my->update_peer_keys(deadline); -} - void controller::assemble_and_complete_block( const signer_callback_type& signer_callback ) { validate_db_available_size(); @@ -5839,16 +5844,20 @@ chain_id_type controller::get_chain_id()const { return my->chain_id; } -void controller::set_peer_keys_retrieval_active(bool active) { - my->peer_keys_db.set_active(active); +void controller::set_peer_keys_retrieval_active(flat_set configured_bp_peers) { + my->peer_keys_db.set_active(std::move(configured_bp_peers)); } -peer_info_t controller::get_peer_info(name n) const { +std::optional controller::get_peer_info(name n) const { return my->peer_keys_db.get_peer_info(n); } -getpeerkeys_res_t controller::get_top_producer_keys(fc::time_point deadline) { - return my->get_top_producer_keys(deadline); +bool controller::configured_peer_keys_updated() { + return my->peer_keys_db.configured_peer_keys_updated(); +} + +getpeerkeys_res_t controller::get_top_producer_keys() { + return my->get_top_producer_keys(); } db_read_mode controller::get_read_mode()const { diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 76818c8dcb..4f27c504cb 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -246,7 +246,6 @@ namespace eosio::chain { void assemble_and_complete_block( const signer_callback_type& signer_callback ); void sign_block( const signer_callback_type& signer_callback ); void commit_block(); - void update_peer_keys(fc::time_point deadline); void testing_allow_voting(bool val); bool get_testing_allow_voting_flag(); void set_async_voting(async_t val); @@ -444,9 +443,11 @@ namespace eosio::chain { chain_id_type get_chain_id()const; - void set_peer_keys_retrieval_active(bool active); - peer_info_t get_peer_info(name n) const; // thread safe - getpeerkeys_res_t get_top_producer_keys(fc::time_point deadline); // must be called from main thread + void set_peer_keys_retrieval_active(flat_set configured_bp_peers); + std::optional get_peer_info(name n) const; // thread safe + bool configured_peer_keys_updated(); // thread safe + // used for testing, only call with an active pending block from main thread + getpeerkeys_res_t get_top_producer_keys(); // thread safe db_read_mode get_read_mode()const; diff --git a/libraries/chain/include/eosio/chain/peer_keys_db.hpp b/libraries/chain/include/eosio/chain/peer_keys_db.hpp index a567ed0296..45cf980815 100644 --- a/libraries/chain/include/eosio/chain/peer_keys_db.hpp +++ b/libraries/chain/include/eosio/chain/peer_keys_db.hpp @@ -16,28 +16,37 @@ class peer_keys_db_t { using peer_key_map_t = boost::unordered_flat_map>; using new_peers_t = flat_set; - peer_keys_db_t(); + peer_keys_db_t() = default; - void set_active(bool b) { _active = b; } + // called on startup with configured bp peers of the node + void set_active(new_peers_t configured_bp_peers) { + _configured_bp_peers = std::move(configured_bp_peers); + _active = true; + } - // must be called from main thread - // return the new peers either: - // - added to the top selected producers (according to "getpeerkeys"_n in system contracts) - // - removed from the top selected producers - // - whose key changed - // since the last call to update_peer_keys - // --------------------------------------- - new_peers_t update_peer_keys(const getpeerkeys_res_t& v); + // safe to be called from any thread, _active only modified on startup + bool is_active() const { return _active; } + + // must be called from the main thread + // return true if update_peer_keys should be called with new map of peer keys + bool should_update(block_num_type block_num) { return _active && (_last_block_num == 0 || block_num % 120 == 0); } + + // must be called from main thread, only call if should_update() returns true + void update_peer_keys(block_num_type block_num, const getpeerkeys_res_t& v); // safe to be called from any thread - // peers no longer in top selected producers will have a rank of std::numeric_limits::max() - // ---------------------------------------------------------------------------------- - peer_info_t get_peer_info(name n) const; + std::optional get_peer_info(name n) const; -private: - std::optional _get_version(const chainbase::database& db); + // safe to be called from any thread + // returns true if configured bp peers modified, also resets flag so next call returns false until updated again + bool configured_peer_keys_updated(); +private: bool _active = false; // if not active (the default), no update occurs + block_num_type _last_block_num = 0; + new_peers_t _configured_bp_peers; // no updates occurs + std::atomic _configured_bp_peers_updated{false}; + mutable fc::mutex _m; peer_key_map_t _peer_info_map GUARDED_BY(_m); }; diff --git a/libraries/chain/peer_keys_db.cpp b/libraries/chain/peer_keys_db.cpp index 1b2e9acc7d..6fa9ad9733 100644 --- a/libraries/chain/peer_keys_db.cpp +++ b/libraries/chain/peer_keys_db.cpp @@ -3,19 +3,24 @@ namespace eosio::chain { -peer_keys_db_t::peer_keys_db_t() : _active(false) {} - -peer_info_t peer_keys_db_t::get_peer_info(name n) const { +std::optional peer_keys_db_t::get_peer_info(name n) const { fc::lock_guard g(_m); assert(_active); if (auto it = _peer_info_map.find(n); it != _peer_info_map.end()) - return it->second; - return peer_info_t{}; + return std::optional(it->second); + return std::optional{}; +} + +bool peer_keys_db_t::configured_peer_keys_updated() { + return _configured_bp_peers_updated.exchange(false); } -peer_keys_db_t::new_peers_t peer_keys_db_t::update_peer_keys(const getpeerkeys_res_t& v) { - if (!_active || v.empty()) - return {}; +void peer_keys_db_t::update_peer_keys(block_num_type block_num, const getpeerkeys_res_t& v) { + assert(_active); + _last_block_num = block_num; + + if (v.empty()) + return; // create hash_map of current top selected producers (according to "getpeerkeys"_n in system contracts) // ---------------------------------------------------------------------------------------------------- @@ -23,14 +28,15 @@ peer_keys_db_t::new_peers_t peer_keys_db_t::update_peer_keys(const getpeerkeys_r for (size_t i=0; i(i), v[i].peer_key}; + bool configured_bp_peers_updated = false; fc::lock_guard g(_m); - new_peers_t res; // remove those that aren't among the top producers anymore // -------------------------------------------------------- for (auto it = _peer_info_map.begin(); it != _peer_info_map.end(); ) { if (!current.contains(it->first)) { - res.insert(it->first); + if (_configured_bp_peers.contains(it->first)) + configured_bp_peers_updated = true; it = _peer_info_map.erase(it); } else { ++it; @@ -42,11 +48,13 @@ peer_keys_db_t::new_peers_t peer_keys_db_t::update_peer_keys(const getpeerkeys_r for (auto& pi : current) { if (!_peer_info_map.contains(pi.first) || _peer_info_map[pi.first] != pi.second) { _peer_info_map[pi.first] = pi.second; - res.insert(pi.first); + if (_configured_bp_peers.contains(pi.first)) + configured_bp_peers_updated = true; } } - - return res; + + if (configured_bp_peers_updated) + _configured_bp_peers_updated = true; } } // namespace eosio::chain \ No newline at end of file diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 2d42796c76..5160ddf73b 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -94,6 +94,8 @@ class bp_connection_manager { public: bool auto_bp_peering_enabled() const { return !config.bp_peer_addresses.empty() || !config.my_bp_peer_accounts.empty(); } bool bp_gossip_enabled() const { return !config.my_bp_peer_accounts.empty(); } + flat_set configured_bp_peer_accounts() const { return config.my_bp_peer_accounts; } + bool bp_gossip_initialized() { return !!get_gossip_bp_initial_send_buffer(); } // Only called at plugin startup void set_producer_accounts(const std::set& accounts) { @@ -149,33 +151,27 @@ class bp_connection_manager { } } - // Called at startup and when peer key changes - // empty modified_keys means to update all - void update_bp_producer_peers(const chain::controller& cc, const flat_set& modified_keys, const std::string& server_address) - { - if (config.my_bp_peer_accounts.empty()) - return; + // Called when configured bp peer key changes + void update_bp_producer_peers(const chain::controller& cc, const std::string& server_address) { + assert(!config.my_bp_peer_accounts.empty()); fc::lock_guard gm(mtx); fc::lock_guard g(gossip_bps.mtx); // normally only one bp peer account except in testing scenarios or test chains for (const auto& e : config.my_bp_peer_accounts) { // my_bp_peer_accounts not modified after plugin startup - if (modified_keys.empty() || modified_keys.contains(e)) { - std::optional pk = cc.get_peer_key(e); - // EOS_ASSERT can only be hit on plugin startup, otherwise this method called with modified_keys that are in cc.get_peer_key() - EOS_ASSERT(pk, chain::plugin_config_exception, "No on-chain peer key found for ${n}", ("n", e)); - fc_dlog(self()->get_logger(), "Signing with producer_name ${p} key ${k}", ("p", e)("k", *pk)); - if (e == *config.my_bp_peer_accounts.begin()) { // use the first one of the set, doesn't matter which one is used, just need one - gossip_bp_peers_message::bp_peer signed_empty{.producer_name = e}; // .server_address not set for initial message - signed_empty.sig = self()->sign_compact(*pk, signed_empty.digest()); - EOS_ASSERT(signed_empty.sig != signature_type{}, chain::plugin_config_exception, - "Unable to sign empty gossip bp peer, private key not found for ${k}", ("k", pk->to_string({}))); - initial_gossip_msg_factory.set_initial_send_buffer(signed_empty); - } + std::optional peer_info = cc.get_peer_info(e); + if (peer_info && peer_info->key) { + // update initial so always an active one + gossip_bp_peers_message::bp_peer signed_empty{.producer_name = e}; // .server_address not set for initial message + signed_empty.sig = self()->sign_compact(*peer_info->key, signed_empty.digest()); + EOS_ASSERT(signed_empty.sig != signature_type{}, chain::plugin_config_exception, + "Unable to sign empty gossip bp peer, private key not found for ${k}", ("k", peer_info->key->to_string({}))); + initial_gossip_msg_factory.set_initial_send_buffer(signed_empty); + // update gossip_bps auto& prod_idx = gossip_bps.index.get(); gossip_bp_peers_message::bp_peer peer{.producer_name = e, .server_address = server_address}; - peer.sig = self()->sign_compact(*pk, peer.digest()); + peer.sig = self()->sign_compact(*peer_info->key, peer.digest()); EOS_ASSERT(peer.sig != signature_type{}, chain::plugin_config_exception, - "Unable to sign bp peer ${p}, private key not found for ${k}", ("p", peer.producer_name)("k", pk->to_string({}))); + "Unable to sign bp peer ${p}, private key not found for ${k}", ("p", peer.producer_name)("k", peer_info->key->to_string({}))); if (auto i = prod_idx.find(boost::make_tuple(e, boost::cref(server_address))); i != prod_idx.end()) { gossip_bps.index.modify(i, [&peer](auto& v) { v.sig = peer.sig; @@ -274,11 +270,12 @@ class bp_connection_manager { const controller& cc = self()->chain_plug->chain(); auto is_peer_key_valid = [&](const gossip_bp_peers_message::bp_peer& peer) -> bool { try { - if (std::optional peer_key = cc.get_peer_key(peer.producer_name)) { + std::optional peer_info = cc.get_peer_info(peer.producer_name); + if (peer_info && peer_info->key) { public_key_type pk(peer.sig, peer.digest()); - if (pk != *peer_key) { + if (pk != *peer_info->key) { fc_dlog(self()->get_logger(), "Recovered peer key did not match on-chain ${p}, recovered: ${pk} != expected: ${k}", - ("p", peer.producer_name)("pk", pk)("k", *peer_key)); + ("p", peer.producer_name)("pk", pk)("k", *peer_info->key)); return false; } } else { // unknown key diff --git a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp index 6f7025a6fd..2b9b53ac23 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/buffer_factory.hpp @@ -184,9 +184,8 @@ namespace eosio { send_buffer = create_initial_send_buffer(signed_empty); } - /// requires set_initial_send_buffer to be called first + /// returns nullptr if set_initial_send_buffer not called yet send_buffer_type get_initial_send_buffer() { - assert(send_buffer); return send_buffer; } diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 20674a2f64..7d0c746f9a 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -912,6 +912,7 @@ namespace eosio { void send_gossip_bp_peers_message(); void send_gossip_bp_peers_message_to_bp_peers(); public: + static void send_gossip_bp_peers_initial_message_to_peers(); bool populate_handshake( handshake_message& hello ) const; @@ -3752,6 +3753,11 @@ namespace eosio { if (!my_impl->bp_gossip_enabled()) return; + if (!my_impl->bp_gossip_initialized()) { + peer_dlog(this, "received gossip_bp_peers_message before bp gossip initialized"); + return; + } + if (!my_impl->validate_gossip_bp_peers_message(msg)) { peer_wlog( this, "bad gossip_bp_peers_message, closing"); no_retry = go_away_reason::fatal_other; @@ -3780,9 +3786,25 @@ namespace eosio { if (protocol_version < proto_gossip_bp_peers || !my_impl->bp_gossip_enabled()) return; const auto& sb = my_impl->get_gossip_bp_initial_send_buffer(); - enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, go_away_reason::no_reason); + if (sb) + enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, go_away_reason::no_reason); } + // thread safe, called from main thread + void connection::send_gossip_bp_peers_initial_message_to_peers() { + assert(my_impl->bp_gossip_enabled()); + const send_buffer_type& sb = my_impl->get_gossip_bp_initial_send_buffer(); + if (!sb) + return; + my_impl->connections.for_each_connection([sb](const connection_ptr& c) { + gossip_buffer_factory factory; + if (c->bp_connection != bp_connection_type::bp_gossip && c->socket_is_open()) { + boost::asio::post(c->strand, [sb, c]() { + c->enqueue_buffer(msg_type_t::gossip_bp_peers_message, {}, queued_buffer::queue_t::general, sb, go_away_reason::no_reason); + }); + } + }); + } // called from connection strand void connection::send_gossip_bp_peers_message() { @@ -4015,10 +4037,10 @@ namespace eosio { } // update peer public keys from chainbase db - flat_set modified = cc.update_peer_keys(block->block_num()); - if (!modified.empty()) { + if (cc.configured_peer_keys_updated()) { try { - update_bp_producer_peers(cc, modified, get_first_p2p_address()); + update_bp_producer_peers(cc, get_first_p2p_address()); + connection::send_gossip_bp_peers_initial_message_to_peers(); } catch (fc::exception& e) { fc_elog( logger, "Unable to update bp producer peers, error: ${e}", ("e", e.to_detail_string())); } @@ -4527,11 +4549,7 @@ namespace eosio { cc.voted_block().connect( broadcast_vote ); if (bp_gossip_enabled()) { - cc.set_peer_keys_retrieval_active(true); - // update peer public keys from chainbase db - cc.update_peer_keys(cc.head().irreversible_blocknum()); - // pass in empty set so validation that peer key exists is enforced - update_bp_producer_peers(cc, flat_set{}, get_first_p2p_address()); + cc.set_peer_keys_retrieval_active(configured_bp_peer_accounts()); } } diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 55c90e7120..64a6298c39 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -2353,12 +2353,6 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { return start_block_result::exhausted; } - // Here we use readonly transactions to update our internal data structures from chainbase data - // (typically every minute or so). - // Currently the only update is the peer public_keys db (updated via "getpeerkeys"_n trx) - // --------------------------------------------------------------------------------------------- - chain.update_peer_keys(preprocess_deadline); - if (!process_incoming_trxs(preprocess_deadline, incoming_itr)) return start_block_result::exhausted; diff --git a/unittests/getpeerkeys_tests.cpp b/unittests/getpeerkeys_tests.cpp index ff67516845..8e263f6e35 100644 --- a/unittests/getpeerkeys_tests.cpp +++ b/unittests/getpeerkeys_tests.cpp @@ -33,7 +33,7 @@ BOOST_FIXTURE_TEST_CASE( getpeerkeys_test, getpeerkeys_tester ) { try { BOOST_REQUIRE_EQUAL(success(), regpeerkey(n, get_public_key(n))); } - auto peerkeys = control->get_top_producer_keys(fc::time_point::maximum()); // call readonly action from controller + auto peerkeys = control->get_top_producer_keys(); // call readonly action from controller BOOST_REQUIRE_EQUAL(peerkeys.size(), 21); size_t num_found = 0; From 9e487c0b86382ad9db3133ec1e99ff17c3d5b38c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 16 Apr 2025 11:13:20 -0500 Subject: [PATCH 40/50] GH-1245 Update test for simplified system contract --- unittests/getpeerkeys_tests.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/unittests/getpeerkeys_tests.cpp b/unittests/getpeerkeys_tests.cpp index 8e263f6e35..26b6538461 100644 --- a/unittests/getpeerkeys_tests.cpp +++ b/unittests/getpeerkeys_tests.cpp @@ -34,7 +34,7 @@ BOOST_FIXTURE_TEST_CASE( getpeerkeys_test, getpeerkeys_tester ) { try { } auto peerkeys = control->get_top_producer_keys(); // call readonly action from controller - BOOST_REQUIRE_EQUAL(peerkeys.size(), 21); + BOOST_REQUIRE_EQUAL(peerkeys.size(), 11); size_t num_found = 0; for (size_t i=0; i Date: Wed, 16 Apr 2025 12:14:36 -0500 Subject: [PATCH 41/50] 1245 Improve test robustness in face of temp duplicate connections --- tests/auto_bp_gossip_peering_test.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/auto_bp_gossip_peering_test.py b/tests/auto_bp_gossip_peering_test.py index c2d68fb9b0..4d544f3529 100755 --- a/tests/auto_bp_gossip_peering_test.py +++ b/tests/auto_bp_gossip_peering_test.py @@ -151,14 +151,9 @@ def getHostName(nodeId): continue peer_addr = conn["last_handshake"]["p2p_address"].split()[0] if peer_names[peer_addr] != "bios" and peer_addr != getHostName(nodeId): - peers.append(peer_names[peer_addr]) - if not conn["is_bp_peer"]: - Utils.Print(f"Error: expected connection to {peer_addr} with is_bp_peer as true") - connection_failure = True - break + if conn["is_bp_peer"]: + peers.append(peer_names[peer_addr]) - if connection_failure: - break if not peers: Utils.Print(f"ERROR: found no connected peers for node {nodeId}") connection_failure = True From 596135dc238ffb98c07dc31fab353011b536500e Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 19 Apr 2025 08:22:25 -0500 Subject: [PATCH 42/50] GH-1245 Use peer_name_set_t throughout --- libraries/chain/controller.cpp | 2 +- .../chain/include/eosio/chain/controller.hpp | 20 ++--------- .../include/eosio/chain/peer_keys_db.hpp | 34 +++++++++++++++---- .../eosio/net_plugin/auto_bp_peering.hpp | 30 ++++++++-------- .../tests/auto_bp_peering_unittest.cpp | 19 +++++------ 5 files changed, 54 insertions(+), 51 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 8ff34beb31..162105e6cf 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -5853,7 +5853,7 @@ chain_id_type controller::get_chain_id()const { return my->chain_id; } -void controller::set_peer_keys_retrieval_active(flat_set configured_bp_peers) { +void controller::set_peer_keys_retrieval_active(peer_name_set_t configured_bp_peers) { my->peer_keys_db.set_active(std::move(configured_bp_peers)); } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 4877e8d3fd..d86b594ff7 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -80,23 +81,6 @@ namespace eosio::chain { class resource_limits_manager; }; - // vector, sorted by rank, of the top-50 producers by `total_votes` (whether - // active or not) and their peer key if populated on-chain. - // ------------------------------------------------------------------------- - struct peerkeys_t { - name producer_name; - std::optional peer_key; - }; - using getpeerkeys_res_t = std::vector; - - struct peer_info_t { - // rank by `total_votes` of all producers, active or not, may not match schedule rank - uint32_t rank{std::numeric_limits::max()}; - std::optional key; - - bool operator==(const peer_info_t&) const = default; - }; - struct controller_impl; using chainbase::database; using chainbase::pinnable_mapped_file; @@ -445,7 +429,7 @@ namespace eosio::chain { chain_id_type get_chain_id()const; - void set_peer_keys_retrieval_active(flat_set configured_bp_peers); + void set_peer_keys_retrieval_active(peer_name_set_t configured_bp_peers); std::optional get_peer_info(name n) const; // thread safe bool configured_peer_keys_updated(); // thread safe // used for testing, only call with an active pending block from main thread diff --git a/libraries/chain/include/eosio/chain/peer_keys_db.hpp b/libraries/chain/include/eosio/chain/peer_keys_db.hpp index 45cf980815..5df0e5a2c1 100644 --- a/libraries/chain/include/eosio/chain/peer_keys_db.hpp +++ b/libraries/chain/include/eosio/chain/peer_keys_db.hpp @@ -1,11 +1,34 @@ #pragma once -#include -#include +#include + #include +#include +#include + namespace eosio::chain { +// vector, sorted by rank, of the top-50 producers by `total_votes` (whether +// active or not) and their peer key if populated on-chain. +// ------------------------------------------------------------------------- +struct peerkeys_t { + name producer_name; + std::optional peer_key; +}; +using getpeerkeys_res_t = std::vector; + +struct peer_info_t { + // rank by `total_votes` of all producers, active or not, may not match schedule rank + uint32_t rank{std::numeric_limits::max()}; + std::optional key; + + bool operator==(const peer_info_t&) const = default; +}; + +using peer_key_map_t = boost::unordered_flat_map>; +using peer_name_set_t = flat_set; + /** * This class caches the on-chain public keys that BP use to sign the `gossip_bp_peers` * network message. These public keys are populated using the actions regpeerkey and @@ -13,13 +36,10 @@ namespace eosio::chain { */ class peer_keys_db_t { public: - using peer_key_map_t = boost::unordered_flat_map>; - using new_peers_t = flat_set; - peer_keys_db_t() = default; // called on startup with configured bp peers of the node - void set_active(new_peers_t configured_bp_peers) { + void set_active(peer_name_set_t configured_bp_peers) { _configured_bp_peers = std::move(configured_bp_peers); _active = true; } @@ -44,7 +64,7 @@ class peer_keys_db_t { private: bool _active = false; // if not active (the default), no update occurs block_num_type _last_block_num = 0; - new_peers_t _configured_bp_peers; // no updates occurs + peer_name_set_t _configured_bp_peers; // no updates occurs std::atomic _configured_bp_peers_updated{false}; mutable fc::mutex _m; diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 5160ddf73b..ea16bdab26 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -28,19 +28,19 @@ class bp_connection_manager { struct config_t { flat_map bp_peer_addresses; flat_map bp_peer_accounts; - flat_set my_bp_accounts; // block producer --producer-name - flat_set my_bp_peer_accounts; // peer key account --p2p-producer-peer + peer_name_set_t my_bp_accounts; // block producer --producer-name + peer_name_set_t my_bp_peer_accounts; // peer key account --p2p-producer-peer } config; // thread safe only because modified at plugin startup currently // the following members are only accessed from main thread - flat_set pending_bps; + peer_name_set_t pending_bps; uint32_t pending_schedule_version = 0; uint32_t active_schedule_version = 0; fc::mutex mtx; gossip_buffer_initial_factory initial_gossip_msg_factory GUARDED_BY(mtx); - flat_set active_bps GUARDED_BY(mtx); - flat_set active_schedule GUARDED_BY(mtx); + peer_name_set_t active_bps GUARDED_BY(mtx); + peer_name_set_t active_schedule GUARDED_BY(mtx); Derived* self() { return static_cast(this); } const Derived* self() const { return static_cast(this); } @@ -51,10 +51,10 @@ class bp_connection_manager { } // Only called from main thread - chain::flat_set active_bp_accounts(const std::vector& schedule) const { + peer_name_set_t active_bp_accounts(const std::vector& schedule) const { fc::lock_guard g(gossip_bps.mtx); const auto& prod_idx = gossip_bps.index.get(); - chain::flat_set result; + peer_name_set_t result; for (const auto& auth : schedule) { if (config.bp_peer_addresses.contains(auth.producer_name) || prod_idx.contains(auth.producer_name)) result.insert(auth.producer_name); @@ -63,10 +63,10 @@ class bp_connection_manager { } // called from net threads - chain::flat_set active_bp_accounts(const flat_set& active_schedule) const REQUIRES(mtx) { + peer_name_set_t active_bp_accounts(const peer_name_set_t& active_schedule) const REQUIRES(mtx) { fc::lock_guard g(gossip_bps.mtx); const auto& prod_idx = gossip_bps.index.get(); - chain::flat_set result; + peer_name_set_t result; for (const auto& a : active_schedule) { if (config.bp_peer_addresses.contains(a) || prod_idx.contains(a)) result.insert(a); @@ -81,12 +81,12 @@ class bp_connection_manager { } // for testing - flat_set get_active_bps() { + peer_name_set_t get_active_bps() { fc::lock_guard g(mtx); return active_bps; } // for testing - void set_active_bps(flat_set bps) { + void set_active_bps(peer_name_set_t bps) { fc::lock_guard g(mtx); active_bps = std::move(bps); } @@ -94,7 +94,7 @@ class bp_connection_manager { public: bool auto_bp_peering_enabled() const { return !config.bp_peer_addresses.empty() || !config.my_bp_peer_accounts.empty(); } bool bp_gossip_enabled() const { return !config.my_bp_peer_accounts.empty(); } - flat_set configured_bp_peer_accounts() const { return config.my_bp_peer_accounts; } + peer_name_set_t configured_bp_peer_accounts() const { return config.my_bp_peer_accounts; } bool bp_gossip_initialized() { return !!get_gossip_bp_initial_send_buffer(); } // Only called at plugin startup @@ -331,7 +331,7 @@ class bp_connection_manager { return diff; } - flat_set find_gossip_bp_addresses(const flat_set& accounts, const char* desc) const { + flat_set find_gossip_bp_addresses(const peer_name_set_t& accounts, const char* desc) const { flat_set addresses; fc::lock_guard g(gossip_bps.mtx); const auto& prod_idx = gossip_bps.index.get(); @@ -418,14 +418,14 @@ class bp_connection_manager { fc_dlog(self()->get_logger(), "active_bps: ${a}", ("a", to_string(active_bps))); - flat_set peers_to_stay; + peer_name_set_t peers_to_stay; std::set_union(active_bps.begin(), active_bps.end(), pending_bps.begin(), pending_bps.end(), std::inserter(peers_to_stay, peers_to_stay.begin())); gm.unlock(); fc_dlog(self()->get_logger(), "peers_to_stay: ${p}", ("p", to_string(peers_to_stay))); - flat_set peers_to_drop; + peer_name_set_t peers_to_drop; std::set_difference(old_bps.begin(), old_bps.end(), peers_to_stay.begin(), peers_to_stay.end(), std::inserter(peers_to_drop, peers_to_drop.end())); fc_dlog(self()->get_logger(), "peers to drop: ${p}", ("p", to_string(peers_to_drop))); diff --git a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp index e3c099de0b..97edb400c4 100644 --- a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp +++ b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp @@ -93,7 +93,7 @@ BOOST_AUTO_TEST_CASE(test_set_bp_peers) { BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts["127.0.0.1:8891"], "producer4"_n); } -bool operator==(const fc::flat_set& a, const fc::flat_set& b) { +bool operator==(const eosio::chain::peer_name_set_t& a, const eosio::chain::peer_name_set_t& b) { return std::equal(a.begin(), a.end(), b.begin(), b.end()); } @@ -102,7 +102,7 @@ bool operator==(const std::vector& a, const std::vector& accounts) { +std::ostream& boost_test_print_type(std::ostream& os, const eosio::chain::peer_name_set_t& accounts) { os << "{"; const char* sep = ""; for (auto e : accounts) { @@ -146,7 +146,7 @@ const eosio::chain::producer_authority_schedule test_schedule2{ { "prodd"_n, {} }, { "prodh"_n, {} }, { "prodl"_n, {} } } }; -const fc::flat_set producers_minus_prodkt{ +const eosio::chain::peer_name_set_t producers_minus_prodkt{ "proda"_n, "prodb"_n, "prodc"_n, "prodd"_n, "prode"_n, "prodf"_n, "prodg"_n, "prodh"_n, "prodi"_n, "prodj"_n, // "prodk"_n, not part of the peer addresses @@ -173,7 +173,7 @@ BOOST_AUTO_TEST_CASE(test_on_pending_schedule) { plugin.on_pending_schedule(test_schedule1); BOOST_CHECK_EQUAL(connected_hosts, (std::vector{})); - BOOST_CHECK_EQUAL(plugin.pending_bps, (fc::flat_set{ "prodj"_n, "prodm"_n })); + BOOST_TEST(plugin.pending_bps == (eosio::chain::peer_name_set_t{ "prodj"_n, "prodm"_n })); BOOST_CHECK_EQUAL(plugin.pending_schedule_version, 0u); // when it is in sync and on_pending_schedule is called @@ -181,7 +181,7 @@ BOOST_AUTO_TEST_CASE(test_on_pending_schedule) { plugin.on_pending_schedule(test_schedule1); // the pending are connected to - BOOST_CHECK_EQUAL(plugin.pending_bps, producers_minus_prodkt); + BOOST_TEST(plugin.pending_bps == producers_minus_prodkt); // all connect to bp peers should be invoked BOOST_CHECK_EQUAL(connected_hosts, peer_addresses); @@ -197,7 +197,7 @@ BOOST_AUTO_TEST_CASE(test_on_pending_schedule) { BOOST_CHECK_EQUAL(connected_hosts, (std::vector{})); plugin.on_pending_schedule(reset_schedule1); - BOOST_CHECK_EQUAL(plugin.pending_bps, (fc::flat_set{})); + BOOST_TEST(plugin.pending_bps == eosio::chain::peer_name_set_t{}); } BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { @@ -216,8 +216,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { plugin.on_active_schedule(test_schedule1); BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{})); - BOOST_CHECK_EQUAL(plugin.get_active_bps(), - (fc::flat_set{ "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n })); + BOOST_TEST(plugin.get_active_bps() == (eosio::chain::peer_name_set_t{ "proda"_n, "prodh"_n, "prodn"_n, "prodt"_n })); BOOST_CHECK_EQUAL(plugin.active_schedule_version, 0u); // when it is in sync and on_active_schedule is called @@ -227,7 +226,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule1) { // then disconnect to prodt BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{ "127.0.0.1:8020"s })); - BOOST_CHECK_EQUAL(plugin.get_active_bps(), producers_minus_prodkt); + BOOST_TEST(plugin.get_active_bps() == producers_minus_prodkt); // make sure we change the active_schedule_version BOOST_CHECK_EQUAL(plugin.active_schedule_version, 1u); @@ -250,7 +249,7 @@ BOOST_AUTO_TEST_CASE(test_on_active_schedule2) { // then disconnect prodt BOOST_CHECK_EQUAL(disconnected_hosts, (std::vector{ "127.0.0.1:8020"s })); - BOOST_CHECK_EQUAL(plugin.get_active_bps(), producers_minus_prodkt); + BOOST_TEST(plugin.get_active_bps() == producers_minus_prodkt); // make sure we change the active_schedule_version BOOST_CHECK_EQUAL(plugin.active_schedule_version, 1u); From f8166430a538187d3fd8085b0e7730b0efc6679f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 19 Apr 2025 08:48:51 -0500 Subject: [PATCH 43/50] GH-1245 Verify not webauthn sig and don't check for canonical --- libraries/libfc/include/fc/crypto/signature.hpp | 2 ++ .../include/eosio/net_plugin/auto_bp_peering.hpp | 15 +++++++++++++-- plugins/net_plugin/net_plugin.cpp | 7 ++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/libraries/libfc/include/fc/crypto/signature.hpp b/libraries/libfc/include/fc/crypto/signature.hpp index a1484c3553..19d6c4e51c 100644 --- a/libraries/libfc/include/fc/crypto/signature.hpp +++ b/libraries/libfc/include/fc/crypto/signature.hpp @@ -30,6 +30,8 @@ namespace fc { namespace crypto { explicit signature(const std::string& base58str); std::string to_string(const fc::yield_function_t& yield = fc::yield_function_t()) const; + constexpr bool is_webauthn() const { return _storage.index() == fc::get_index(); } + size_t which() const; size_t variable_size() const; diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index ea16bdab26..573c9c4d8b 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -268,22 +268,31 @@ class bp_connection_manager { } const controller& cc = self()->chain_plug->chain(); + bool fatal_error = false; auto is_peer_key_valid = [&](const gossip_bp_peers_message::bp_peer& peer) -> bool { try { + if (peer.sig.is_webauthn()) { + fc_dlog(self()->get_logger(), "Peer ${p} signature is webauthn, not allowed.", ("p", peer.producer_name)); + fatal_error = true; + return false; + } std::optional peer_info = cc.get_peer_info(peer.producer_name); if (peer_info && peer_info->key) { - public_key_type pk(peer.sig, peer.digest()); + constexpr bool check_canonical = false; + public_key_type pk(peer.sig, peer.digest(), check_canonical); if (pk != *peer_info->key) { fc_dlog(self()->get_logger(), "Recovered peer key did not match on-chain ${p}, recovered: ${pk} != expected: ${k}", ("p", peer.producer_name)("pk", pk)("k", *peer_info->key)); return false; } } else { // unknown key + // ok, might have just been deleted or dropped out of top ranking fc_dlog(self()->get_logger(), "Failed to find peer key ${p}", ("p", peer.producer_name)); return false; } } catch (fc::exception& e) { fc_dlog(self()->get_logger(), "Exception recovering peer key ${p}, error: ${e}", ("p", peer.producer_name)("e", e.to_detail_string())); + fatal_error = true; return false; // invalid key } return true; @@ -291,7 +300,7 @@ class bp_connection_manager { fc::lock_guard g(gossip_bps.mtx); auto& sig_idx = gossip_bps.index.get(); - for (auto i = msg.peers.begin(); i != msg.peers.end();) { + for (auto i = msg.peers.begin(); i != msg.peers.end() && !fatal_error;) { const auto& peer = *i; bool have_sig = sig_idx.contains(peer.sig); // we already have it, already verified if (!have_sig && !is_peer_key_valid(peer)) { @@ -302,6 +311,8 @@ class bp_connection_manager { } } + if (fatal_error) + return false; return !msg.peers.empty(); } diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 0fdb28ed50..a0569bea67 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3057,16 +3057,13 @@ namespace eosio { shared_ptr ptr = std::make_shared(); fc::raw::unpack( ds, *ptr ); - auto is_webauthn_sig = []( const fc::crypto::signature& s ) { - return s.which() == fc::get_index(); - }; - bool has_webauthn_sig = is_webauthn_sig( ptr->producer_signature ); + bool has_webauthn_sig = ptr->producer_signature.is_webauthn(); constexpr auto additional_sigs_eid = additional_block_signatures_extension::extension_id(); auto exts = ptr->validate_and_extract_extensions(); if( exts.count( additional_sigs_eid ) ) { const auto &additional_sigs = std::get(exts.lower_bound( additional_sigs_eid )->second).signatures; - has_webauthn_sig |= std::any_of( additional_sigs.begin(), additional_sigs.end(), is_webauthn_sig ); + has_webauthn_sig |= std::ranges::any_of(additional_sigs, [](const auto& sig) { return sig.is_webauthn(); }); } if( has_webauthn_sig ) { From 3b8145f94d14b9ea06e3b7696ed1b84e910b7ffe Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 21 Apr 2025 09:50:18 -0500 Subject: [PATCH 44/50] GH-1245 Add and use a new endpoint type instead of using string --- .../eosio/net_plugin/auto_bp_peering.hpp | 24 ++++++++++--------- .../include/eosio/net_plugin/net_utils.hpp | 17 ++++++++++++- plugins/net_plugin/net_plugin.cpp | 9 +++---- .../tests/auto_bp_peering_unittest.cpp | 22 +++++++++-------- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 573c9c4d8b..96351a203f 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -26,10 +26,10 @@ class bp_connection_manager { // the following members are thread-safe, only modified during plugin startup struct config_t { - flat_map bp_peer_addresses; - flat_map bp_peer_accounts; - peer_name_set_t my_bp_accounts; // block producer --producer-name - peer_name_set_t my_bp_peer_accounts; // peer key account --p2p-producer-peer + flat_map bp_peer_addresses; + flat_map bp_peer_accounts; + peer_name_set_t my_bp_accounts; // block producer --producer-name + peer_name_set_t my_bp_peer_accounts; // peer key account --p2p-producer-peer } config; // thread safe only because modified at plugin startup currently // the following members are only accessed from main thread @@ -119,10 +119,11 @@ class bp_connection_manager { const auto& [host, port, type] = net_utils::split_host_port_type(addr); EOS_ASSERT( !host.empty() && !port.empty(), chain::plugin_config_exception, "Invalid p2p-auto-bp-peer ${p}, syntax host:port:[trx|blk]", ("p", addr)); + net_utils::endpoint e{host, port}; fc_dlog(self()->get_logger(), "Setting p2p-auto-bp-peer ${a} -> ${d}", ("a", account)("d", addr)); - config.bp_peer_accounts[addr] = account; - config.bp_peer_addresses[account] = std::move(addr); + config.bp_peer_accounts[e] = account; + config.bp_peer_addresses[account] = std::move(e); } catch (chain::name_type_exception&) { EOS_ASSERT(false, chain::plugin_config_exception, "the account ${a} supplied by --p2p-auto-bp-peer option is invalid", ("a", entry)); @@ -187,10 +188,11 @@ class bp_connection_manager { void mark_configured_bp_connection(Connection* conn) const { /// mark an connection as a configured bp connection if it connects to an address in the bp peer list, /// so that the connection won't be subject to the limit of max_client_count. - auto space_pos = conn->log_p2p_address.find(' '); - // log_p2p_address always has a trailing hex like `localhost:9877 - bc3f55b` - std::string addr = conn->log_p2p_address.substr(0, space_pos); - if (config.bp_peer_accounts.count(addr)) { + net_utils::endpoint e; + std::string type; + std::tie(e.host, e.port, type) = eosio::net_utils::split_host_port_type(conn->log_p2p_address); + + if (config.bp_peer_accounts.count(e)) { conn->bp_connection = Connection::bp_connection_type::bp_config; } } @@ -349,7 +351,7 @@ class bp_connection_manager { for (const auto& account : accounts) { if (auto i = config.bp_peer_addresses.find(account); i != config.bp_peer_addresses.end()) { fc_dlog(self()->get_logger(), "${d} manual bp peer ${p}", ("d", desc)("p", i->second)); - addresses.insert(i->second); + addresses.insert(i->second.address()); } auto r = prod_idx.equal_range(account); for (auto i = r.first; i != r.second; ++i) { diff --git a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp index 186e1fcf44..bf4a1469dc 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -101,6 +102,18 @@ namespace detail { } // namespace detail + struct endpoint { + std::string host; + std::string port; + + std::string address() const { return host + ":" + port; } + + friend std::ostream& operator<<(std::ostream& os, const endpoint& e) { return os << e.host << ":" << e.port; } + + bool operator==(const endpoint& lhs) const = default; + auto operator<=>(const endpoint& lhs) const = default; + }; + /// @return host, port, type. returns empty on invalid peer_add, does not throw inline std::tuple split_host_port_type(const std::string& peer_add) { @@ -138,4 +151,6 @@ namespace detail { return {std::move(listen_addr), block_sync_rate_limit}; } -} // namespace eosio::net_utils \ No newline at end of file +} // namespace eosio::net_utils + +FC_REFLECT(eosio::net_utils::endpoint, (host)(port)) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index a0569bea67..adb888db2a 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2793,8 +2793,7 @@ namespace eosio { fc_ilog(logger, "Accepted new connection: " + paddr_str); connections.any_of_supplied_peers([&listen_address, &paddr_str, &paddr_desc, &limit](const string& peer_addr) { - auto [host, port, type] = net_utils::split_host_port_type(peer_addr); - if (host == paddr_str) { + if (auto [host, port, type] = net_utils::split_host_port_type(peer_addr); host == paddr_str) { if (limit > 0) { fc_dlog(logger, "Connection inbound to ${la} from ${a} is a configured p2p-peer-address and will not be throttled", ("la", listen_address)("a", paddr_desc)); } @@ -3302,8 +3301,7 @@ namespace eosio { } if( incoming() ) { - auto [host, port, type] = net_utils::split_host_port_type(msg.p2p_address); - if (host.size()) + if (auto [host, port, type] = net_utils::split_host_port_type(msg.p2p_address); !host.empty()) set_connection_type( msg.p2p_address); else peer_dlog(this, "Invalid handshake p2p_address ${p}", ("p", msg.p2p_address)); @@ -4729,8 +4727,7 @@ namespace eosio { } string connections_manager::resolve_and_connect( const string& peer_address, const string& listen_address ) { - auto [host, port, type] = net_utils::split_host_port_type(peer_address); - if (host.empty()) { + if (auto [host, port, type] = net_utils::split_host_port_type(peer_address); host.empty()) { return "invalid peer address"; } diff --git a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp index 97edb400c4..a9e8ed808f 100644 --- a/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp +++ b/plugins/net_plugin/tests/auto_bp_peering_unittest.cpp @@ -1,6 +1,8 @@ #include #include +using namespace eosio::net_utils; + struct mock_connection { enum class bp_connection_type { non_bp, bp_config, bp_gossip }; bp_connection_type bp_connection = bp_connection_type::non_bp; @@ -61,7 +63,7 @@ struct mock_net_plugin : eosio::auto_bp_peering::bp_connection_manager peer_addresses{ - "127.0.0.1:8001:blk"s, "127.0.0.1:8002:trx"s, "127.0.0.1:8003"s, + "127.0.0.1:8001"s, "127.0.0.1:8002"s, "127.0.0.1:8003"s, "127.0.0.1:8004"s, "127.0.0.1:8005"s, "127.0.0.1:8006"s, "127.0.0.1:8007"s, "127.0.0.1:8008"s, "127.0.0.1:8009"s, "127.0.0.1:8010"s, // prodk is intentionally skipped @@ -82,15 +84,15 @@ BOOST_AUTO_TEST_CASE(test_set_bp_peers) { "producer3,127.0.0.1:8890"s, "producer4,127.0.0.1:8891"s }); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer1"_n], "127.0.0.1:8888:blk"s); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer2"_n], "127.0.0.1:8889:trx"s); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer3"_n], "127.0.0.1:8890"s); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer4"_n], "127.0.0.1:8891"s); - - BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts["127.0.0.1:8888:blk"], "producer1"_n); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts["127.0.0.1:8889:trx"], "producer2"_n); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts["127.0.0.1:8890"], "producer3"_n); - BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts["127.0.0.1:8891"], "producer4"_n); + BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer1"_n], endpoint("127.0.0.1", "8888")); + BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer2"_n], endpoint("127.0.0.1", "8889")); + BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer3"_n], endpoint("127.0.0.1", "8890")); + BOOST_CHECK_EQUAL(plugin.config.bp_peer_addresses["producer4"_n], endpoint("127.0.0.1", "8891")); + + BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts[endpoint("127.0.0.1", "8888")], "producer1"_n); + BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts[endpoint("127.0.0.1", "8889")], "producer2"_n); + BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts[endpoint("127.0.0.1", "8890")], "producer3"_n); + BOOST_CHECK_EQUAL(plugin.config.bp_peer_accounts[endpoint("127.0.0.1", "8891")], "producer4"_n); } bool operator==(const eosio::chain::peer_name_set_t& a, const eosio::chain::peer_name_set_t& b) { From e70d33b09f59d1c0d0a4c186c369b1f98e52cadb Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 21 Apr 2025 12:04:39 -0500 Subject: [PATCH 45/50] GH-1245 Fix host/port parsing when there is only one colon --- .../net_plugin/include/eosio/net_plugin/net_utils.hpp | 11 ++++++++++- .../net_plugin/tests/rate_limit_parse_unittest.cpp | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp index bf4a1469dc..ba2c7c1326 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/net_utils.hpp @@ -96,7 +96,16 @@ namespace detail { string::size_type colon2 = peer_add.find(':', colon + 1); string host = 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 ); + string remainder; + if (colon2 == string::npos) { + auto port_end = port.find_first_not_of("0123456789"); + if (port_end != string::npos) { + port = port.substr(0, port_end); + remainder = port.substr( port_end ); + } + } else { + remainder = peer_add.substr( colon2 + 1 ); + } return {std::move(host), std::move(port), std::move(remainder)}; } diff --git a/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp b/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp index c75fefab65..c0b91d4dd7 100644 --- a/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp +++ b/plugins/net_plugin/tests/rate_limit_parse_unittest.cpp @@ -25,6 +25,7 @@ BOOST_AUTO_TEST_CASE(test_parse_rate_limit) { , "[::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:9875 - 84c470d" , "0.0.0.0:9876:trx - 84c470d" , "0.0.0.0:9877:trx:640KB/s - additional info" , "[2001:db8:85a3:8d3:1319:8a2e:370:7348]additional info:trx:640KB/s" @@ -100,6 +101,9 @@ BOOST_AUTO_TEST_CASE(test_parse_rate_limit) { [](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.at(which++)); + BOOST_CHECK_EQUAL(listen_addr, "0.0.0.0:9875"); + BOOST_CHECK_EQUAL(block_sync_rate_limit, 0u); + std::tie(listen_addr, block_sync_rate_limit) = eosio::net_utils::parse_listen_address(p2p_addresses.at(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.at(which++)); @@ -143,6 +147,7 @@ BOOST_AUTO_TEST_CASE(test_split_host_port_type) { , "[::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 - 84c470d" , "0.0.0.0:9876:trx - 84c470d" , "0.0.0.0:9877:trx:640KB/s - additional info" , "[2001:db8:85a3:8d3:1319:8a2e:370:7348]additional info:trx:640KB/s" @@ -242,6 +247,10 @@ BOOST_AUTO_TEST_CASE(test_split_host_port_type) { std::tie(host, port, type) = eosio::net_utils::split_host_port_type(p2p_addresses.at(which++)); BOOST_CHECK_EQUAL(host, "0.0.0.0"); BOOST_CHECK_EQUAL(port, "9876"); + BOOST_CHECK_EQUAL(type, ""); + std::tie(host, port, type) = eosio::net_utils::split_host_port_type(p2p_addresses.at(which++)); + BOOST_CHECK_EQUAL(host, "0.0.0.0"); + BOOST_CHECK_EQUAL(port, "9876"); BOOST_CHECK_EQUAL(type, "trx"); std::tie(host, port, type) = eosio::net_utils::split_host_port_type(p2p_addresses.at(which++)); BOOST_CHECK_EQUAL(host, "0.0.0.0"); From 436e188524168814fab5cb524c6f0c00d859a9f5 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 21 Apr 2025 12:05:15 -0500 Subject: [PATCH 46/50] GH-1245 Fix auto_bp_peering_test.py to check for correct host name --- tests/auto_bp_peering_test.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/auto_bp_peering_test.py b/tests/auto_bp_peering_test.py index 66eb732d1f..d9037df37c 100755 --- a/tests/auto_bp_peering_test.py +++ b/tests/auto_bp_peering_test.py @@ -40,6 +40,13 @@ cluster = Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) cluster.setWalletMgr(walletMgr) +def getHostName(nodeId): + port = cluster.p2pBasePort + nodeId + if producer_name == 'defproducerf': + hostname = 'ext-ip0:9999' + else: + hostname = "localhost:" + str(port) + return hostname peer_names = {} @@ -47,10 +54,7 @@ for nodeId in range(0, producerNodes): producer_name = "defproducer" + chr(ord('a') + nodeId) port = cluster.p2pBasePort + nodeId - if producer_name == 'defproducerf': - hostname = 'ext-ip0:9999' - else: - hostname = "localhost:" + str(port) + hostname = getHostName(nodeId) peer_names[hostname] = producer_name auto_bp_peer_args += (" --p2p-auto-bp-peer " + producer_name + "," + hostname) @@ -96,20 +100,17 @@ if Utils.Debug: Utils.Print(f"Node {nodeId} connections {connections}") peers = [] for conn in connections["payload"]: + if conn["is_socket_open"] is False: + continue peer_addr = conn["peer"] if len(peer_addr) == 0: if len(conn["last_handshake"]["p2p_address"]) == 0: continue peer_addr = conn["last_handshake"]["p2p_address"].split()[0] - if peer_names[peer_addr] != "bios": - peers.append(peer_names[peer_addr]) - if not conn["is_bp_peer"]: - Utils.Print(f"Error: expected connection to {peer_addr} with is_bp_peer as true") - connection_failure = True - break - - if connection_failure: - break + if peer_names[peer_addr] != "bios" and peer_addr != getHostName(nodeId): + if conn["is_bp_peer"]: + peers.append(peer_names[peer_addr]) + if not peers: Utils.Print(f"ERROR: found no connected peers for node {nodeId}") connection_failure = True From f778d714a3202f9647b37188c35ac8ea13af90db Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 22 Apr 2025 13:39:04 -0500 Subject: [PATCH 47/50] GH-1245 Reduce logging on expected already connected calls to resolve_and_connect. --- plugins/net_plugin/net_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index adb888db2a..05e30acd89 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -4723,6 +4723,7 @@ namespace eosio { std::unique_lock g( connections_mtx ); supplied_peers.insert(host); g.unlock(); + fc_dlog(logger, "API connect ${h}", ("h", host)); return resolve_and_connect( host, p2p_address ); } @@ -4734,7 +4735,6 @@ namespace eosio { { std::shared_lock g( connections_mtx ); if( find_connection_i( peer_address ) ) { - fc_dlog( logger, "Already connected to ${p}", ("p", peer_address)); return "already connected"; } } From 75c63e39f526c5ffe5d2de44e95fe98f3c521905 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 22 Apr 2025 13:52:32 -0500 Subject: [PATCH 48/50] GH-1245 Rename fatal_error to invalid_message --- .../include/eosio/net_plugin/auto_bp_peering.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 96351a203f..95b8d181d2 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -270,12 +270,12 @@ class bp_connection_manager { } const controller& cc = self()->chain_plug->chain(); - bool fatal_error = false; + bool invalid_message = false; auto is_peer_key_valid = [&](const gossip_bp_peers_message::bp_peer& peer) -> bool { try { if (peer.sig.is_webauthn()) { fc_dlog(self()->get_logger(), "Peer ${p} signature is webauthn, not allowed.", ("p", peer.producer_name)); - fatal_error = true; + invalid_message = true; return false; } std::optional peer_info = cc.get_peer_info(peer.producer_name); @@ -294,7 +294,7 @@ class bp_connection_manager { } } catch (fc::exception& e) { fc_dlog(self()->get_logger(), "Exception recovering peer key ${p}, error: ${e}", ("p", peer.producer_name)("e", e.to_detail_string())); - fatal_error = true; + invalid_message = true; return false; // invalid key } return true; @@ -302,7 +302,7 @@ class bp_connection_manager { fc::lock_guard g(gossip_bps.mtx); auto& sig_idx = gossip_bps.index.get(); - for (auto i = msg.peers.begin(); i != msg.peers.end() && !fatal_error;) { + for (auto i = msg.peers.begin(); i != msg.peers.end() && !invalid_message;) { const auto& peer = *i; bool have_sig = sig_idx.contains(peer.sig); // we already have it, already verified if (!have_sig && !is_peer_key_valid(peer)) { @@ -313,7 +313,7 @@ class bp_connection_manager { } } - if (fatal_error) + if (invalid_message) return false; return !msg.peers.empty(); } From 4fe408f2243a52cd17806eb1c6ba3bf864da0877 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 22 Apr 2025 17:06:22 -0500 Subject: [PATCH 49/50] GH-1245 Don't consider a peer outside of top producers a fatal error --- .../include/eosio/net_plugin/auto_bp_peering.hpp | 3 ++- plugins/net_plugin/net_plugin.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp index 95b8d181d2..78651038dd 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/auto_bp_peering.hpp @@ -315,7 +315,8 @@ class bp_connection_manager { if (invalid_message) return false; - return !msg.peers.empty(); + + return true; } // thread-safe diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 05e30acd89..b1f5fc6fa8 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3753,6 +3753,7 @@ namespace eosio { return; } + const bool first_msg = msg.peers.size() == 1 && msg.peers[0].server_address.empty(); if (!my_impl->validate_gossip_bp_peers_message(msg)) { peer_wlog( this, "bad gossip_bp_peers_message, closing"); no_retry = go_away_reason::fatal_other; @@ -3760,11 +3761,13 @@ namespace eosio { return; } + if (msg.peers.empty()) + return; // no current top producers in msg + // valid gossip peer connection bp_connection = bp_connection_type::bp_gossip; - assert(!msg.peers.empty()); // checked by validate_gossip_bp_peers_message() - if (msg.peers.size() == 1 && msg.peers[0].server_address.empty()) { + if (first_msg) { // initial message case, send back our entire collection send_gossip_bp_peers_message(); } else { From 107647b74d0a383ce54de71a89c997fa82b7e407 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 22 Apr 2025 17:09:53 -0500 Subject: [PATCH 50/50] GH-1245 Add debug log statement when connection first becoming bp gossip connection --- plugins/net_plugin/net_plugin.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index b1f5fc6fa8..4ce382cd9b 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3765,7 +3765,10 @@ namespace eosio { return; // no current top producers in msg // valid gossip peer connection - bp_connection = bp_connection_type::bp_gossip; + if (bp_connection != bp_connection_type::bp_gossip) { + peer_dlog(this, "bp gossip connection"); + bp_connection = bp_connection_type::bp_gossip; + } if (first_msg) { // initial message case, send back our entire collection