提交 7ddef42d 编写于 作者: J Jonathan Giszczak

Authenticate peers during network handshake.

Authenticates producers using producer keys, optionally authenticates
non-producing peers using configured keys, and further optionally
allows unauthenticated connections.  Includes launcher support.

Also fix bug when a 100% producer node started up after a long (>64
second) delay and thought it's a 0% node.

Add additional information for debugging Travis builds.
上级 f5871d1c
...@@ -558,7 +558,7 @@ void check_output(const T& expected, const T& actual, const path_cons_list& path ...@@ -558,7 +558,7 @@ void check_output(const T& expected, const T& actual, const path_cons_list& path
template<typename T> template<typename T>
void check_output(const vector<T>& expected, const vector<T>& actual, const path_cons_list& path) { void check_output(const vector<T>& expected, const vector<T>& actual, const path_cons_list& path) {
check_output(expected.size(), actual.size(), path(".size()")); check_output(expected.size(), actual.size(), path(".size()"));
for(int idx=0; idx < expected.size(); idx++) { for(size_t idx=0; idx < expected.size(); idx++) {
const auto &expected_element = expected.at(idx); const auto &expected_element = expected.at(idx);
const auto &actual_element = actual.at(idx); const auto &actual_element = actual.at(idx);
check_output(expected_element, actual_element, path(idx)); check_output(expected_element, actual_element, path(idx));
...@@ -666,16 +666,16 @@ void chain_controller::_apply_block(const signed_block& next_block) ...@@ -666,16 +666,16 @@ void chain_controller::_apply_block(const signed_block& next_block)
* when building a block. * when building a block.
*/ */
auto root_path = path_cons_list("next_block.cycles"); auto root_path = path_cons_list("next_block.cycles");
for (int c_idx = 0; c_idx < next_block.cycles.size(); c_idx++) { for (size_t c_idx = 0; c_idx < next_block.cycles.size(); c_idx++) {
const auto& cycle = next_block.cycles.at(c_idx); const auto& cycle = next_block.cycles.at(c_idx);
auto c_path = path_cons_list(c_idx, root_path); auto c_path = path_cons_list(c_idx, root_path);
for (int t_idx = 0; t_idx < cycle.size(); t_idx++) { for (size_t t_idx = 0; t_idx < cycle.size(); t_idx++) {
const auto& thread = cycle.at(t_idx); const auto& thread = cycle.at(t_idx);
auto t_path = path_cons_list(t_idx, c_path); auto t_path = path_cons_list(t_idx, c_path);
auto gen_path = path_cons_list(".generated_input", t_path); auto gen_path = path_cons_list(".generated_input", t_path);
for(int p_idx = 0; p_idx < thread.generated_input.size(); p_idx++ ) { for(size_t p_idx = 0; p_idx < thread.generated_input.size(); p_idx++ ) {
const auto& ptrx = thread.generated_input.at(p_idx); const auto& ptrx = thread.generated_input.at(p_idx);
const auto& trx = get_generated_transaction(ptrx.id); const auto& trx = get_generated_transaction(ptrx.id);
auto processed = apply_transaction(trx); auto processed = apply_transaction(trx);
...@@ -683,7 +683,7 @@ void chain_controller::_apply_block(const signed_block& next_block) ...@@ -683,7 +683,7 @@ void chain_controller::_apply_block(const signed_block& next_block)
} }
auto user_path = path_cons_list(".user_input", t_path); auto user_path = path_cons_list(".user_input", t_path);
for(int p_idx = 0; p_idx < thread.user_input.size(); p_idx++ ) { for(size_t p_idx = 0; p_idx < thread.user_input.size(); p_idx++ ) {
const auto& ptrx = thread.user_input.at(p_idx); const auto& ptrx = thread.user_input.at(p_idx);
const signed_transaction& trx = ptrx; const signed_transaction& trx = ptrx;
auto processed = apply_transaction(trx); auto processed = apply_transaction(trx);
...@@ -994,7 +994,7 @@ void chain_controller::process_message(const transaction& trx, account_name code ...@@ -994,7 +994,7 @@ void chain_controller::process_message(const transaction& trx, account_name code
// propagate used_authorizations up the context chain // propagate used_authorizations up the context chain
if (parent_context != nullptr) if (parent_context != nullptr)
for (int i = 0; i < apply_ctx.used_authorizations.size(); ++i) for (size_t i = 0; i < apply_ctx.used_authorizations.size(); ++i)
if (apply_ctx.used_authorizations[i]) if (apply_ctx.used_authorizations[i])
parent_context->used_authorizations[i] = true; parent_context->used_authorizations[i] = true;
...@@ -1400,6 +1400,9 @@ void chain_controller::update_global_dynamic_data(const signed_block& b) { ...@@ -1400,6 +1400,9 @@ void chain_controller::update_global_dynamic_data(const signed_block& b) {
dgp.recent_slots_filled += 1; dgp.recent_slots_filled += 1;
dgp.recent_slots_filled <<= missed_blocks; dgp.recent_slots_filled <<= missed_blocks;
} else } else
if(config::percent100 * get_global_properties().active_producers.size() / config::blocks_per_round > config::required_producer_participation)
dgp.recent_slots_filled = uint64_t(-1);
else
dgp.recent_slots_filled = 0; dgp.recent_slots_filled = 0;
}); });
......
...@@ -39,6 +39,8 @@ const static int default_per_auth_account = 1800; ...@@ -39,6 +39,8 @@ const static int default_per_auth_account = 1800;
const static int default_per_code_account_time_frame_seconds = 18; const static int default_per_code_account_time_frame_seconds = 18;
const static int default_per_code_account = 18000; const static int default_per_code_account = 18000;
const static uint32 required_producer_participation = 33 * config::percent1;
const static uint32 default_max_block_size = 5 * 1024 * 1024; const static uint32 default_max_block_size = 5 * 1024 * 1024;
const static uint32 default_target_block_size = 128 * 1024; const static uint32 default_target_block_size = 128 * 1024;
const static uint64 default_max_storage_size = 10 * 1024; const static uint64 default_max_storage_size = 10 * 1024;
......
...@@ -3,7 +3,7 @@ add_library( net_plugin ...@@ -3,7 +3,7 @@ add_library( net_plugin
net_plugin.cpp net_plugin.cpp
${HEADERS} ) ${HEADERS} )
target_link_libraries( net_plugin chain_plugin appbase fc ) target_link_libraries( net_plugin chain_plugin producer_plugin appbase fc )
target_include_directories( net_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) target_include_directories( net_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
install( TARGETS install( TARGETS
......
...@@ -11,10 +11,17 @@ namespace eosio { ...@@ -11,10 +11,17 @@ namespace eosio {
using namespace chain; using namespace chain;
using namespace fc; using namespace fc;
static_assert(sizeof(std::chrono::system_clock::duration::rep) >= 8, "system_clock is expected to be at least 64 bits");
typedef std::chrono::system_clock::duration::rep tstamp;
struct handshake_message { struct handshake_message {
int16_t network_version = 0; ///< derived from git commit hash, not sequential int16_t network_version = 0; ///< derived from git commit hash, not sequential
chain_id_type chain_id; ///< used to identify chain chain_id_type chain_id; ///< used to identify chain
fc::sha256 node_id; ///< used to identify peers and prevent self-connect fc::sha256 node_id; ///< used to identify peers and prevent self-connect
chain::public_key_type key; ///< authentication key; may be a producer or peer key, or empty
tstamp time;
fc::sha256 token; ///< digest of time to prove we own the private key of the key above
fc::ecc::compact_signature sig; ///< signature for the digest
string p2p_address; string p2p_address;
uint32_t last_irreversible_block_num = 0; uint32_t last_irreversible_block_num = 0;
block_id_type last_irreversible_block_id; block_id_type last_irreversible_block_id;
...@@ -139,8 +146,8 @@ namespace eosio { ...@@ -139,8 +146,8 @@ namespace eosio {
FC_REFLECT( eosio::select_ids<fc::sha256>, (mode)(pending)(ids) ) FC_REFLECT( eosio::select_ids<fc::sha256>, (mode)(pending)(ids) )
FC_REFLECT( eosio::handshake_message, FC_REFLECT( eosio::handshake_message,
(network_version)(chain_id)(node_id) (network_version)(chain_id)(node_id)(key)
(p2p_address) (time)(token)(sig)(p2p_address)
(last_irreversible_block_num)(last_irreversible_block_id) (last_irreversible_block_num)(last_irreversible_block_id)
(head_num)(head_id) (head_num)(head_id)
(os)(agent)(generation) ) (os)(agent)(generation) )
......
...@@ -9,8 +9,12 @@ ...@@ -9,8 +9,12 @@
#include <eos/chain/chain_controller.hpp> #include <eos/chain/chain_controller.hpp>
#include <eos/chain/exceptions.hpp> #include <eos/chain/exceptions.hpp>
#include <eos/chain/block.hpp> #include <eos/chain/block.hpp>
#include <eos/chain/producer_object.hpp>
#include <eos/producer_plugin/producer_plugin.hpp>
#include <eos/utilities/key_conversion.hpp>
#include <fc/network/ip.hpp> #include <fc/network/ip.hpp>
#include <fc/io/json.hpp>
#include <fc/io/raw.hpp> #include <fc/io/raw.hpp>
#include <fc/log/appender.hpp> #include <fc/log/appender.hpp>
#include <fc/container/flat.hpp> #include <fc/container/flat.hpp>
...@@ -23,6 +27,10 @@ ...@@ -23,6 +27,10 @@
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <boost/intrusive/set.hpp> #include <boost/intrusive/set.hpp>
namespace fc {
extern std::unordered_map<std::string,logger>& get_logger_map();
}
namespace eosio { namespace eosio {
using std::vector; using std::vector;
...@@ -114,6 +122,17 @@ namespace eosio { ...@@ -114,6 +122,17 @@ namespace eosio {
uint32_t num_clients; uint32_t num_clients;
vector<string> supplied_peers; vector<string> supplied_peers;
vector<chain::public_key_type> allowed_peers; ///< peer keys allowed to connect
std::map<chain::public_key_type,
fc::ecc::private_key> private_keys; ///< overlapping with producer keys, also authenticating non-producing nodes
enum possible_connections : char {
None = 0,
Producers = 1 << 0,
Specified = 1 << 1,
Any = 1 << 2
};
possible_connections allowed_connections{None};
std::set< connection_ptr > connections; std::set< connection_ptr > connections;
bool done = false; bool done = false;
...@@ -127,6 +146,8 @@ namespace eosio { ...@@ -127,6 +146,8 @@ namespace eosio {
boost::asio::steady_timer::duration resp_expected_period; boost::asio::steady_timer::duration resp_expected_period;
boost::asio::steady_timer::duration keepalive_interval{std::chrono::seconds{32}}; boost::asio::steady_timer::duration keepalive_interval{std::chrono::seconds{32}};
const std::chrono::system_clock::duration peer_authentication_interval{std::chrono::seconds{1}}; ///< Peer clock may be no more than 1 second skewed from our clock, including network latency.
int16_t network_version; int16_t network_version;
chain_id_type chain_id; chain_id_type chain_id;
fc::sha256 node_id; fc::sha256 node_id;
...@@ -198,10 +219,40 @@ namespace eosio { ...@@ -198,10 +219,40 @@ namespace eosio {
*/ */
void ticker(); void ticker();
/** @} */ /** @} */
/** \brief Determine if a peer is allowed to connect.
*
* Checks current connection mode and key authentication.
*
* \return False if the peer should not connect, true otherwise.
*/
bool authenticate_peer(const handshake_message& msg) const;
/** \brief Retrieve public key used to authenticate with peers.
*
* Finds a key to use for authentication. If this node is a producer, use
* the front of the producer key map. If the node is not a producer but has
* a configured private key, use it. If the node is neither a producer nor has
* a private key, returns an empty key.
*
* \note On a node with multiple private keys configured, the key with the first
* numerically smaller byte will always be used.
*/
chain::public_key_type get_authentication_key() const;
/** \brief Returns a signature of the digest using the corresponding private key of the signer.
*
* If there are no configured private keys, returns an empty signature.
*/
fc::ecc::compact_signature sign_compact(const chain::public_key_type& signer, const fc::sha256& digest) const;
static const fc::string logger_name; static const fc::string logger_name;
static fc::logger logger; static fc::logger logger;
}; };
template<class enum_type, class=typename std::enable_if<std::is_enum<enum_type>::value>::type>
inline enum_type& operator|=(enum_type& lhs, const enum_type& rhs)
{
using T = std::underlying_type_t <enum_type>;
return lhs = static_cast<enum_type>(static_cast<T>(lhs) | static_cast<T>(rhs));
}
static net_plugin_impl *my_impl; static net_plugin_impl *my_impl;
const fc::string net_plugin_impl::logger_name("net_plugin_impl"); const fc::string net_plugin_impl::logger_name("net_plugin_impl");
fc::logger net_plugin_impl::logger(net_plugin_impl::logger_name); fc::logger net_plugin_impl::logger(net_plugin_impl::logger_name);
...@@ -750,7 +801,7 @@ namespace eosio { ...@@ -750,7 +801,7 @@ namespace eosio {
char* connection::convert_tstamp(const tstamp& t) char* connection::convert_tstamp(const tstamp& t)
{ {
const long NsecPerSec{1000000000}; const long long NsecPerSec{1000000000};
time_t seconds = t / NsecPerSec; time_t seconds = t / NsecPerSec;
strftime(ts, ts_buffer_size, "%F %T", localtime(&seconds)); strftime(ts, ts_buffer_size, "%F %T", localtime(&seconds));
snprintf(ts+19, ts_buffer_size-19, ".%lld", t % NsecPerSec); snprintf(ts+19, ts_buffer_size-19, ".%lld", t % NsecPerSec);
...@@ -1337,6 +1388,12 @@ namespace eosio { ...@@ -1337,6 +1388,12 @@ namespace eosio {
if( c->node_id != msg.node_id) { if( c->node_id != msg.node_id) {
c->node_id = msg.node_id; c->node_id = msg.node_id;
} }
if(!authenticate_peer(msg)) {
dlog("Peer not authenticated. Closing connection.");
close(c);
return;
}
} }
chain_controller& cc = chain_plug->chain(); chain_controller& cc = chain_plug->chain();
...@@ -2017,11 +2074,97 @@ namespace eosio { ...@@ -2017,11 +2074,97 @@ namespace eosio {
}); });
} }
bool net_plugin_impl::authenticate_peer(const handshake_message& msg) const {
if(allowed_connections == None)
return false;
if(allowed_connections == Any)
return true;
if(allowed_connections & (Producers | Specified)) {
auto allowed_it = std::find(allowed_peers.begin(), allowed_peers.end(), msg.key);
auto private_it = private_keys.find(msg.key);
bool found_producer_key = false;
producer_plugin* pp = app().find_plugin<producer_plugin>();
if(pp != nullptr)
found_producer_key = pp->is_producer_key(msg.key);
if( allowed_it == allowed_peers.end() && private_it == private_keys.end() && !found_producer_key) {
elog( "Peer ${peer} sent a handshake with an unauthorized key: ${key}.",
("peer", msg.p2p_address)("key", msg.key));
return false;
}
}
namespace sc = std::chrono;
sc::system_clock::duration msg_time(msg.time);
auto time = sc::system_clock::now().time_since_epoch();
if(time - msg_time > peer_authentication_interval) {
elog( "Peer ${peer} sent a handshake with a timestamp skewed by more than ${time}.",
("peer", msg.p2p_address)("time", "1 second")); // TODO Add to_variant for std::chrono::system_clock::duration
return false;
}
if(msg.sig != ecc::compact_signature() && msg.token != sha256()) {
sha256 hash = fc::sha256::hash(msg.time);
if(hash != msg.token) {
elog( "Peer ${peer} sent a handshake with an invalid token.",
("peer", msg.p2p_address));
return false;
}
types::public_key peer_key;
try {
peer_key = ecc::public_key(msg.sig, msg.token, true);
}
catch (fc::exception& /*e*/) {
elog( "Peer ${peer} sent a handshake with an unrecoverable key.",
("peer", msg.p2p_address));
return false;
}
if((allowed_connections & (Producers | Specified)) && peer_key != msg.key) {
elog( "Peer ${peer} sent a handshake with an unauthenticated key.",
("peer", msg.p2p_address));
return false;
}
}
else if(allowed_connections & (Producers | Specified)) {
dlog( "Peer sent a handshake with blank signature and token, but this node accepts only authenticated connections.");
return false;
}
return true;
}
chain::public_key_type net_plugin_impl::get_authentication_key() const {
producer_plugin* pp = app().find_plugin<producer_plugin>();
if(pp != nullptr)
return pp->first_producer_public_key();
if(!private_keys.empty())
return private_keys.begin()->first;
return chain::public_key_type();
}
fc::ecc::compact_signature net_plugin_impl::sign_compact(const chain::public_key_type& signer, const fc::sha256& digest) const
{
producer_plugin* pp = app().find_plugin<producer_plugin>();
if(pp != nullptr)
return pp->sign_compact(signer, digest);
auto private_key_itr = private_keys.find(signer);
if(private_key_itr == private_keys.end())
return ecc::compact_signature();
return private_key_itr->second.sign_compact(digest);
}
void void
handshake_initializer::populate( handshake_message &hello) { handshake_initializer::populate( handshake_message &hello) {
hello.network_version = my_impl->network_version; hello.network_version = my_impl->network_version;
hello.chain_id = my_impl->chain_id; hello.chain_id = my_impl->chain_id;
hello.node_id = my_impl->node_id; hello.node_id = my_impl->node_id;
hello.key = my_impl->get_authentication_key();
hello.time = std::chrono::system_clock::now().time_since_epoch().count();
hello.token = fc::sha256::hash(hello.time);
hello.sig = my_impl->sign_compact(hello.key, hello.token);
// If we couldn't sign, don't send a token.
if(hello.sig == ecc::compact_signature())
hello.token = sha256();
hello.p2p_address = my_impl->p2p_address; hello.p2p_address = my_impl->p2p_address;
#if defined( __APPLE__ ) #if defined( __APPLE__ )
hello.os = "osx"; hello.os = "osx";
...@@ -2067,7 +2210,7 @@ namespace eosio { ...@@ -2067,7 +2210,7 @@ namespace eosio {
net_plugin::~net_plugin() { net_plugin::~net_plugin() {
} }
void net_plugin::set_program_options( options_description& cli, options_description& cfg ) void net_plugin::set_program_options( options_description& /*cli*/, options_description& cfg )
{ {
cfg.add_options() cfg.add_options()
( "listen-endpoint", bpo::value<string>()->default_value( "0.0.0.0:9876" ), "The local IP address and port to listen for incoming connections.") ( "listen-endpoint", bpo::value<string>()->default_value( "0.0.0.0:9876" ), "The local IP address and port to listen for incoming connections.")
...@@ -2075,13 +2218,27 @@ namespace eosio { ...@@ -2075,13 +2218,27 @@ namespace eosio {
( "public-endpoint", bpo::value<string>(), "Overrides the advertised listen endpointlisten ip address.") ( "public-endpoint", bpo::value<string>(), "Overrides the advertised listen endpointlisten ip address.")
( "agent-name", bpo::value<string>()->default_value("EOS Test Agent"), "The name supplied to identify this node amongst the peers.") ( "agent-name", bpo::value<string>()->default_value("EOS Test Agent"), "The name supplied to identify this node amongst the peers.")
( "send-whole-blocks", bpo::value<bool>()->default_value(def_send_whole_blocks), "True to always send full blocks, false to send block summaries" ) ( "send-whole-blocks", bpo::value<bool>()->default_value(def_send_whole_blocks), "True to always send full blocks, false to send block summaries" )
( "allowed-connection", bpo::value<vector<string>>()->multitoken()->default_value({"none"}, "none"), "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<vector<string>>()->composing()->multitoken(), "Optional public key of peer allowed to connect. May be used multiple times.")
( "peer-private-key", boost::program_options::value<vector<string>>()->composing()->multitoken(),
"Tuple of [PublicKey, WIF private key] (may specify multiple times)")
( "log-level-net-plugin", bpo::value<string>()->default_value("info"), "Log level: one of 'all', 'debug', 'info', 'warn', 'error', or 'off'") ( "log-level-net-plugin", bpo::value<string>()->default_value("info"), "Log level: one of 'all', 'debug', 'info', 'warn', 'error', or 'off'")
; ;
} }
template<typename T>
T dejsonify(const string& s) {
return fc::json::from_string(s).as<T>();
}
void net_plugin::plugin_initialize( const variables_map& options ) { void net_plugin::plugin_initialize( const variables_map& options ) {
ilog("Initialize net plugin"); ilog("Initialize net plugin");
// Housekeeping so fc::logger::get() will work as expected
fc::get_logger_map()[connection::logger_name] = connection::logger;
fc::get_logger_map()[net_plugin_impl::logger_name] = net_plugin_impl::logger;
fc::get_logger_map()[sync_manager::logger_name] = sync_manager::logger;
// Setting a parent would in theory get us the default appenders for free but // Setting a parent would in theory get us the default appenders for free but
// a) the parent's log level overrides our own in that case; and // a) the parent's log level overrides our own in that case; and
// b) fc library's logger was never finished - the _additivity flag tested is never true. // b) fc library's logger was never finished - the _additivity flag tested is never true.
...@@ -2114,7 +2271,7 @@ namespace eosio { ...@@ -2114,7 +2271,7 @@ namespace eosio {
my->num_clients = 0; my->num_clients = 0;
my->resolver = std::make_shared<tcp::resolver>( std::ref( app().get_io_service() ) ); my->resolver = std::make_shared<tcp::resolver>( std::ref( app().get_io_service() ) );
if( options.count( "listen-endpoint" ) ) { if(options.count("listen-endpoint")) {
my->p2p_address = options.at("listen-endpoint").as< string >(); my->p2p_address = options.at("listen-endpoint").as< string >();
auto host = my->p2p_address.substr( 0, my->p2p_address.find(':') ); auto host = my->p2p_address.substr( 0, my->p2p_address.find(':') );
auto port = my->p2p_address.substr( host.size()+1, my->p2p_address.size() ); auto port = my->p2p_address.substr( host.size()+1, my->p2p_address.size() );
...@@ -2126,11 +2283,11 @@ namespace eosio { ...@@ -2126,11 +2283,11 @@ namespace eosio {
my->acceptor.reset( new tcp::acceptor( app().get_io_service() ) ); my->acceptor.reset( new tcp::acceptor( app().get_io_service() ) );
} }
if( options.count( "public-endpoint") ) { if(options.count("public-endpoint")) {
my->p2p_address = options.at("public-endpoint").as< string >(); my->p2p_address = options.at("public-endpoint").as< string >();
} }
else { else {
if( my->listen_endpoint.address().to_v4() == address_v4::any()) { if(my->listen_endpoint.address().to_v4() == address_v4::any()) {
boost::system::error_code ec; boost::system::error_code ec;
auto host = host_name(ec); auto host = host_name(ec);
if( ec.value() != boost::system::errc::success) { if( ec.value() != boost::system::errc::success) {
...@@ -2144,12 +2301,52 @@ namespace eosio { ...@@ -2144,12 +2301,52 @@ namespace eosio {
} }
} }
if( options.count( "remote-endpoint" ) ) { if(options.count("remote-endpoint")) {
my->supplied_peers = options.at( "remote-endpoint" ).as< vector<string> >(); my->supplied_peers = options.at("remote-endpoint").as<vector<string> >();
}
if(options.count("agent-name")) {
my->user_agent_name = options.at("agent-name").as<string>();
}
if(options.count("allowed-connection")) {
const std::vector<std::string> allowed_remotes = options["allowed-connection"].as<std::vector<std::string>>();
for(const std::string& allowed_remote : allowed_remotes)
{
if(allowed_remote == "any")
my->allowed_connections |= net_plugin_impl::Any;
else if(allowed_remote == "producers")
my->allowed_connections |= net_plugin_impl::Producers;
else if(allowed_remote == "specified")
my->allowed_connections |= net_plugin_impl::Specified;
else if(allowed_remote == "none")
my->allowed_connections = net_plugin_impl::None;
}
}
if(my->allowed_connections & net_plugin_impl::Specified)
FC_ASSERT(options.count("peer-key"), "At least one peer-key must accompany 'allowed-connection=specified'");
if(options.count("peer-key")) {
const std::vector<std::string> key_strings = options["peer-key"].as<std::vector<std::string>>();
for(const std::string& key_string : key_strings)
{
my->allowed_peers.push_back(dejsonify<chain::public_key_type>(key_string));
}
}
if(options.count("peer-private-key"))
{
const std::vector<std::string> key_id_to_wif_pair_strings = options["peer-private-key"].as<std::vector<std::string>>();
for(const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)
{
auto key_id_to_wif_pair = dejsonify<std::pair<chain::public_key_type, std::string>>(key_id_to_wif_pair_string);
fc::optional<fc::ecc::private_key> private_key = eosio::utilities::wif_to_key(key_id_to_wif_pair.second);
FC_ASSERT(private_key, "Invalid WIF-format private key ${key_string}",
("key_string", key_id_to_wif_pair.second));
my->private_keys[key_id_to_wif_pair.first] = *private_key;
} }
if( options.count("agent-name")) {
my->user_agent_name = options.at( "agent-name").as< string >( );
} }
if( options.count( "send-whole-blocks")) { if( options.count( "send-whole-blocks")) {
my->send_whole_blocks = options.at( "send-whole-blocks" ).as<bool>(); my->send_whole_blocks = options.at( "send-whole-blocks" ).as<bool>();
} }
......
...@@ -38,6 +38,10 @@ public: ...@@ -38,6 +38,10 @@ public:
boost::program_options::options_description &config_file_options boost::program_options::options_description &config_file_options
) override; ) override;
chain::public_key_type first_producer_public_key() const;
bool is_producer_key(const chain::public_key_type& key) const;
fc::ecc::compact_signature sign_compact(const chain::public_key_type& key, const fc::sha256& digest) const;
virtual void plugin_initialize(const boost::program_options::variables_map& options); virtual void plugin_initialize(const boost::program_options::variables_map& options);
virtual void plugin_startup(); virtual void plugin_startup();
virtual void plugin_shutdown(); virtual void plugin_shutdown();
......
...@@ -33,7 +33,7 @@ public: ...@@ -33,7 +33,7 @@ public:
boost::program_options::variables_map _options; boost::program_options::variables_map _options;
bool _production_enabled = false; bool _production_enabled = false;
uint32_t _required_producer_participation = 33 * config::percent1; uint32_t _required_producer_participation = uint32_t(config::required_producer_participation);
uint32_t _production_skip_flags = eosio::chain::chain_controller::skip_nothing; uint32_t _production_skip_flags = eosio::chain::chain_controller::skip_nothing;
eosio::chain::block_schedule::factory _production_scheduler = eosio::chain::block_schedule::in_single_thread; eosio::chain::block_schedule::factory _production_scheduler = eosio::chain::block_schedule::in_single_thread;
...@@ -86,8 +86,9 @@ void producer_plugin::set_program_options( ...@@ -86,8 +86,9 @@ void producer_plugin::set_program_options(
("ID of producer controlled by this node (e.g. inita; may specify multiple times)")) ("ID of producer controlled by this node (e.g. inita; may specify multiple times)"))
("private-key", boost::program_options::value<vector<string>>()->composing()->multitoken()->default_value({fc::json::to_string(private_key_default)}, ("private-key", boost::program_options::value<vector<string>>()->composing()->multitoken()->default_value({fc::json::to_string(private_key_default)},
fc::json::to_string(private_key_default)), fc::json::to_string(private_key_default)),
"Tuple of [public_key, WIF private key] (may specify multiple times)") "Tuple of [public key, WIF private key] (may specify multiple times)")
; ;
command_line_options.add(producer_options); command_line_options.add(producer_options);
config_file_options.add(producer_options); config_file_options.add(producer_options);
...@@ -115,6 +116,37 @@ void producer_plugin::set_program_options( ...@@ -115,6 +116,37 @@ void producer_plugin::set_program_options(
; ;
} }
chain::public_key_type producer_plugin::first_producer_public_key() const
{
chain::chain_controller& chain = app().get_plugin<chain_plugin>().chain();
try {
return chain.get_producer(*my->_producers.begin()).signing_key;
} catch(std::out_of_range) {
return chain::public_key_type();
}
}
bool producer_plugin::is_producer_key(const chain::public_key_type& key) const
{
auto private_key_itr = my->_private_keys.find(key);
if(private_key_itr != my->_private_keys.end())
return true;
return false;
}
fc::ecc::compact_signature producer_plugin::sign_compact(const chain::public_key_type& key, const fc::sha256& digest) const
{
if(key != chain::public_key_type()) {
auto private_key_itr = my->_private_keys.find(key);
FC_ASSERT(private_key_itr != my->_private_keys.end(), "Local producer has no private key in config.ini corresponding to public key ${key}", ("key", key));
return private_key_itr->second.sign_compact(digest);
}
else {
return fc::ecc::compact_signature();
}
}
template<typename T> template<typename T>
T dejsonify(const string& s) { T dejsonify(const string& s) {
return fc::json::from_string(s).as<T>(); return fc::json::from_string(s).as<T>();
...@@ -335,4 +367,4 @@ block_production_condition::block_production_condition_enum producer_plugin_impl ...@@ -335,4 +367,4 @@ block_production_condition::block_production_condition_enum producer_plugin_impl
return block_production_condition::produced; return block_production_condition::produced;
} }
} // namespace eos } // namespace eosio
...@@ -220,11 +220,19 @@ enum launch_modes { ...@@ -220,11 +220,19 @@ enum launch_modes {
LM_ALL LM_ALL
}; };
enum allowed_connection : char {
PC_NONE = 0,
PC_PRODUCERS = 1 << 0,
PC_SPECIFIED = 1 << 1,
PC_ANY = 1 << 2
};
struct launcher_def { struct launcher_def {
int producers; size_t producers;
int total_nodes; size_t total_nodes;
int prod_nodes; size_t prod_nodes;
string shape; string shape;
allowed_connection allowed_connections = PC_NONE;
bf::path genesis; bf::path genesis;
bf::path output; bf::path output;
bool skip_transaction_signatures = false; bool skip_transaction_signatures = false;
...@@ -257,36 +265,43 @@ struct launcher_def { ...@@ -257,36 +265,43 @@ struct launcher_def {
void void
launcher_def::set_options (bpo::options_description &cli) { launcher_def::set_options (bpo::options_description &cli) {
cli.add_options() cli.add_options()
("nodes,n",bpo::value<int>()->default_value(1),"total number of nodes to configure and launch") ("nodes,n",bpo::value<size_t>(&total_nodes)->default_value(1),"total number of nodes to configure and launch")
("pnodes,p",bpo::value<int>()->default_value(1),"number of nodes that are producers") ("pnodes,p",bpo::value<size_t>(&prod_nodes)->default_value(1),"number of nodes that are producers")
("shape,s",bpo::value<string>()->default_value("star"),"network topology, use \"ring\" \"star\" \"mesh\" or give a filename for custom") ("mode,m",bpo::value<vector<string>>()->multitoken()->default_value({"any"}, "any"),"connection mode, combination of \"any\", \"producers\", \"specified\", \"none\"")
("genesis,g",bpo::value<bf::path>()->default_value("./genesis.json"),"set the path to genesis.json") ("shape,s",bpo::value<string>(&shape)->default_value("star"),"network topology, use \"ring\" \"star\" \"mesh\" or give a filename for custom")
("output,o",bpo::value<bf::path>(),"save a copy of the generated topology in this file") ("genesis,g",bpo::value<bf::path>(&genesis)->default_value("./genesis.json"),"set the path to genesis.json")
("skip-signature", bpo::bool_switch()->default_value(false), "EOSD does not require transaction signatures.") ("output,o",bpo::value<bf::path>(&output),"save a copy of the generated topology in this file")
("eosd", bpo::value<string>(), "forward eosd command line argument(s) to each instance of eosd, enclose arg in quotes") ("skip-signature", bpo::bool_switch(&skip_transaction_signatures)->default_value(false), "EOSD does not require transaction signatures.")
("delay,d",bpo::value<int>()->default_value(0),"seconds delay before starting each node after the first") ("eosd", bpo::value<string>(&eosd_extra_args), "forward eosd command line argument(s) to each instance of eosd, enclose arg in quotes")
("delay,d",bpo::value<int>(&start_delay)->default_value(0),"seconds delay before starting each node after the first")
; ;
} }
template<class enum_type, class=typename std::enable_if<std::is_enum<enum_type>::value>::type>
inline enum_type& operator|=(enum_type&lhs, const enum_type& rhs)
{
using T = std::underlying_type_t <enum_type>;
return lhs = static_cast<enum_type>(static_cast<T>(lhs) | static_cast<T>(rhs));
}
void void
launcher_def::initialize (const variables_map &vmap) { launcher_def::initialize (const variables_map &vmap) {
if (vmap.count("nodes")) if (vmap.count("mode")) {
total_nodes = vmap["nodes"].as<int>(); const vector<string> modes = vmap["mode"].as<vector<string>>();
if (vmap.count("pnodes")) for(const string&m : modes)
prod_nodes = vmap["pnodes"].as<int>(); {
if (vmap.count("shape")) if (boost::iequals(m, "any"))
shape = vmap["shape"].as<string>(); allowed_connections |= PC_ANY;
if (vmap.count("genesis")) else if (boost::iequals(m, "producers"))
genesis = vmap["genesis"].as<bf::path>(); allowed_connections |= PC_PRODUCERS;
if (vmap.count("output")) else if (boost::iequals(m, "specified"))
output = vmap["output"].as<bf::path>(); allowed_connections |= PC_SPECIFIED;
if (vmap.count("skip-signature")) else {
skip_transaction_signatures = vmap["skip-signature"].as<bool>(); cerr << "unrecognized connection mode: " << m << endl;
if (vmap.count("eosd")) exit (-1);
eosd_extra_args = vmap["eosd"].as<string>(); }
if (vmap.count("delay")) }
start_delay = vmap["delay"].as<int>(); }
producers = 21; producers = 21;
data_dir_base = "tn_data_"; data_dir_base = "tn_data_";
alias_base = "testnet_"; alias_base = "testnet_";
...@@ -359,7 +374,7 @@ void ...@@ -359,7 +374,7 @@ void
launcher_def::define_nodes () { launcher_def::define_nodes () {
int per_node = producers / prod_nodes; int per_node = producers / prod_nodes;
int extra = producers % prod_nodes; int extra = producers % prod_nodes;
for (int i = 0; i < total_nodes; i++) { for (size_t i = 0; i < total_nodes; i++) {
eosd_def node; eosd_def node;
string dex = boost::lexical_cast<string,int>(i); string dex = boost::lexical_cast<string,int>(i);
string name = alias_base + dex; string name = alias_base + dex;
...@@ -432,6 +447,21 @@ launcher_def::write_config_file (eosd_def &node) { ...@@ -432,6 +447,21 @@ launcher_def::write_config_file (eosd_def &node) {
<< "http-server-endpoint = " << node.hostname << ":" << node.http_port << "\n" << "http-server-endpoint = " << node.hostname << ":" << node.http_port << "\n"
<< "listen-endpoint = 0.0.0.0:" << node.p2p_port << "\n" << "listen-endpoint = 0.0.0.0:" << node.p2p_port << "\n"
<< "public-endpoint = " << node.public_name << ":" << node.p2p_port << "\n"; << "public-endpoint = " << node.public_name << ":" << node.p2p_port << "\n";
if (allowed_connections & PC_ANY) {
cfg << "allowed-connection = any\n";
}
else
{
if (allowed_connections & PC_PRODUCERS) {
cfg << "allowed-connection = producers\n";
}
if (allowed_connections & PC_SPECIFIED) {
cfg << "allowed-connection = specified\n";
cfg << "peer-key = \"" << node.keys.begin()->public_key << "\"\n";
cfg << "peer-private-key = [\"" << node.keys.begin()->public_key
<< "\",\"" << node.keys.begin()->wif_private_key << "\"]\n";
}
}
for (const auto &p : node.peers) { for (const auto &p : node.peers) {
cfg << "remote-endpoint = " << network.nodes.find(p)->second.p2p_endpoint() << "\n"; cfg << "remote-endpoint = " << network.nodes.find(p)->second.p2p_endpoint() << "\n";
} }
...@@ -750,23 +780,19 @@ int main (int argc, char *argv[]) { ...@@ -750,23 +780,19 @@ int main (int argc, char *argv[]) {
top.set_options(opts); top.set_options(opts);
opts.add_options() opts.add_options()
("timestamp,i",bpo::value<string>(),"set the timestamp for the first block. Use \"now\" to indicate the current time") ("timestamp,i",bpo::value<string>(&gts),"set the timestamp for the first block. Use \"now\" to indicate the current time")
("launch,l",bpo::value<string>(), "select a subset of nodes to launch. Currently may be \"all\", \"none\", or \"local\". If not set, the default is to launch all unless an output file is named, in which case it starts none.") ("launch,l",bpo::value<string>(), "select a subset of nodes to launch. Currently may be \"all\", \"none\", or \"local\". If not set, the default is to launch all unless an output file is named, in which case it starts none.")
("kill,k", bpo::value<string>(),"The launcher retrieves the previously started process ids and issue a kill signal to each.") ("kill,k", bpo::value<string>(&kill_arg),"The launcher retrieves the previously started process ids and issue a kill signal to each.")
("version,v", "print version information") ("version,v", "print version information")
("help,h","print this list"); ("help,h","print this list");
try { try {
bpo::store(bpo::parse_command_line(argc, argv, opts), vmap); bpo::store(bpo::parse_command_line(argc, argv, opts), vmap);
bpo::notify(vmap);
top.initialize(vmap); top.initialize(vmap);
if (vmap.count("timestamp"))
gts = vmap["timestamp"].as<string>();
if (vmap.count("kill")) {
kill_arg = vmap["kill"].as<string>();
}
if (vmap.count("help") > 0) { if (vmap.count("help") > 0) {
opts.print(cerr); opts.print(cerr);
return 0; return 0;
......
...@@ -36,6 +36,7 @@ else ...@@ -36,6 +36,7 @@ else
fi fi
fi fi
fi fi
total_nodes=`expr $pnodes + $npnodes` total_nodes=`expr $pnodes + $npnodes`
rm -rf tn_data_* rm -rf tn_data_*
......
...@@ -414,9 +414,9 @@ BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, testing_fixture ) ...@@ -414,9 +414,9 @@ BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, testing_fixture )
chain.produce_blocks(1, 64); chain.produce_blocks(1, 64);
BOOST_CHECK_EQUAL( rsf(), BOOST_CHECK_EQUAL( rsf(),
"0000000000000000000000000000000000000000000000000000000000000000" "1111111111111111111111111111111111111111111111111111111111111111"
); ); // For very small producer nets. All zeros otherwise.
BOOST_CHECK_EQUAL( chain.producer_participation_rate(), pct(0) ); BOOST_CHECK_EQUAL( chain.producer_participation_rate(), config::percent100 );
chain.produce_blocks(1, 63); chain.produce_blocks(1, 63);
BOOST_CHECK_EQUAL( rsf(), BOOST_CHECK_EQUAL( rsf(),
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册