diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 28e15602..3e87beae 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -73,4 +73,14 @@ node_property_object& database::node_properties() return _node_property_object; } +double database::witness_participation_rate()const +{ + if( head_block_num() < 10 ) return 1; // sample size is too small + uint32_t produced = std::min( head_block_num()-1, 100 ); + auto prior = fetch_block_by_number( head_block_num() - produced ); + auto delta_time = head_block_time() - prior->timestamp; + auto expected_slots = delta_time.to_seconds() / get_global_properties().parameters.block_interval; + return double(produced) / expected_slots; +} + } } diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index d17bab6e..ceeb4359 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -136,6 +136,12 @@ namespace graphene { namespace chain { optional fetch_block_by_number( uint32_t num )const; const signed_transaction& get_recent_transaction( const transaction_id_type& trx_id )const; + /** + * Calculate the percent of block production slots that were missed in the + * past 100 blocks. + */ + double witness_participation_rate()const; + void add_checkpoints( const flat_map& checkpts ); const flat_map get_checkpoints()const { return _checkpoints; } @@ -246,9 +252,10 @@ namespace graphene { namespace chain { const node_property_object& get_node_properties()const; const fee_schedule& current_fee_schedule()const; - time_point_sec head_block_time()const; - uint32_t head_block_num()const; - block_id_type head_block_id()const; + time_point_sec head_block_time()const; + uint32_t head_block_num()const; + block_id_type head_block_id()const; + witness_id_type head_block_witness()const; decltype( chain_parameters::block_interval ) block_interval( )const; diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index 5fc30259..763c5c15 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -76,6 +76,13 @@ namespace graphene { namespace chain { time_point_sec last_budget_time; share_type witness_budget; uint32_t accounts_registered_this_interval; + /** if the interval changes then how we calculate witness participation will + * also change. Normally witness participation is defined as % of blocks + * produced in the last round which is calculated by dividing the delta + * time between block N and N-NUM_WITNESSES by the block interval to calculate + * the number of blocks produced. + */ + uint32_t first_maintenance_block_with_current_interval = 0; }; }} @@ -88,6 +95,7 @@ FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene:: (next_maintenance_time) (witness_budget) (accounts_registered_this_interval) + (first_maintenance_block_with_current_interval) ) FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::object), diff --git a/libraries/plugins/witness/include/graphene/witness/witness.hpp b/libraries/plugins/witness/include/graphene/witness/witness.hpp index a364d57b..213b791d 100644 --- a/libraries/plugins/witness/include/graphene/witness/witness.hpp +++ b/libraries/plugins/witness/include/graphene/witness/witness.hpp @@ -56,6 +56,9 @@ private: boost::program_options::variables_map _options; bool _production_enabled = false; + bool _consecutive_production_enabled = false; + int _required_witness_participation = 33; + std::map _private_keys; std::set _witnesses; fc::future _block_production_task; diff --git a/libraries/plugins/witness/witness.cpp b/libraries/plugins/witness/witness.cpp index 34f53d44..a304a723 100644 --- a/libraries/plugins/witness/witness.cpp +++ b/libraries/plugins/witness/witness.cpp @@ -38,7 +38,9 @@ void witness_plugin::plugin_set_program_options( auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan"))); string witness_id_example = fc::json::to_string(chain::witness_id_type()); command_line_options.add_options() - ("enable-stale-production", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}), "Enable block production, even if the chain is stale") + ("enable-stale-production", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}), "Enable block production, even if the chain is stale.") + ("required-participation", bpo::bool_switch()->notifier([this](int e){_required_witness_participation = e;}), "Percent of witnesses (0-99) that must be participating in order to produce blocks") + ("allow-consecutive", bpo::bool_switch()->notifier([this](bool e){_consecutive_production_enabled = e;}), "Allow block production, even if the last block was produced by the same witness.") ("witness-id,w", bpo::value>()->composing()->multitoken(), ("ID of witness controlled by this node (e.g. " + witness_id_example + ", quotes are required, may specify multiple times)").c_str()) ("private-key", bpo::value>()->composing()->multitoken()-> @@ -189,6 +191,14 @@ void witness_plugin::block_production_loop() return false; } + double prate = db.witness_participation_rate(); + if( int(100*prate) < _required_witness_participation ) + { + elog("Not producing block because node appers to be on a minority fork with only ${x}% witness participation", + ("x",uint32_t(100*prate) ) ); + return false; + } + // the next block must be scheduled after the head block. // if this check fails, the local clock has not advanced far // enough from the head block. @@ -211,11 +221,12 @@ void witness_plugin::block_production_loop() // the local clock must be within 500 milliseconds of // the scheduled production time. - if( llabs((scheduled_time - now).count()) > fc::milliseconds(500).count() ) { - elog("Not producing block because network time is not within 500ms of scheduled block time."); + if( llabs((scheduled_time - now).count()) > fc::milliseconds(250).count() ) { + elog("Not producing block because network time is not within 250ms of scheduled block time."); return false; } + // we must know the private key corresponding to the witness's // published block production key. if( _private_keys.find( scheduled_key ) == _private_keys.end() ) { @@ -232,6 +243,9 @@ void witness_plugin::block_production_loop() ilog("Witness ${id} production slot has arrived; generating a block now...", ("id", scheduled_witness)); try { + FC_ASSERT( _consecutive_production_enabled || db.get_dynamic_global_properties().current_witness != scheduled_witness, + "Last block was generated by the same witness, this node is probably disconnected from the network so block production" + " has been disabled. Disable this check with --allow-consecutive option." ); auto block = db.generate_block( scheduled_time, scheduled_witness,