Fix #151 - Witnesses do not produce consecutively
- added an option (on by default) to prevent a witness from signing two
blocks in a row because this most likely indicates they have been cut
off from the network.
- added an option where a witness will not produce a block if the
witness participation rate is below a configurable threshold (default
to 33%)
This commit is contained in:
parent
f19ed0c697
commit
a05d88b044
5 changed files with 48 additions and 6 deletions
|
|
@ -73,4 +73,14 @@ node_property_object& database::node_properties()
|
||||||
return _node_property_object;
|
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<uint32_t>( 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;
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,12 @@ namespace graphene { namespace chain {
|
||||||
optional<signed_block> fetch_block_by_number( uint32_t num )const;
|
optional<signed_block> fetch_block_by_number( uint32_t num )const;
|
||||||
const signed_transaction& get_recent_transaction( const transaction_id_type& trx_id )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<uint32_t,block_id_type>& checkpts );
|
void add_checkpoints( const flat_map<uint32_t,block_id_type>& checkpts );
|
||||||
const flat_map<uint32_t,block_id_type> get_checkpoints()const { return _checkpoints; }
|
const flat_map<uint32_t,block_id_type> get_checkpoints()const { return _checkpoints; }
|
||||||
|
|
||||||
|
|
@ -246,9 +252,10 @@ namespace graphene { namespace chain {
|
||||||
const node_property_object& get_node_properties()const;
|
const node_property_object& get_node_properties()const;
|
||||||
const fee_schedule& current_fee_schedule()const;
|
const fee_schedule& current_fee_schedule()const;
|
||||||
|
|
||||||
time_point_sec head_block_time()const;
|
time_point_sec head_block_time()const;
|
||||||
uint32_t head_block_num()const;
|
uint32_t head_block_num()const;
|
||||||
block_id_type head_block_id()const;
|
block_id_type head_block_id()const;
|
||||||
|
witness_id_type head_block_witness()const;
|
||||||
|
|
||||||
decltype( chain_parameters::block_interval ) block_interval( )const;
|
decltype( chain_parameters::block_interval ) block_interval( )const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,13 @@ namespace graphene { namespace chain {
|
||||||
time_point_sec last_budget_time;
|
time_point_sec last_budget_time;
|
||||||
share_type witness_budget;
|
share_type witness_budget;
|
||||||
uint32_t accounts_registered_this_interval;
|
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)
|
(next_maintenance_time)
|
||||||
(witness_budget)
|
(witness_budget)
|
||||||
(accounts_registered_this_interval)
|
(accounts_registered_this_interval)
|
||||||
|
(first_maintenance_block_with_current_interval)
|
||||||
)
|
)
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::object),
|
FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::object),
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ private:
|
||||||
|
|
||||||
boost::program_options::variables_map _options;
|
boost::program_options::variables_map _options;
|
||||||
bool _production_enabled = false;
|
bool _production_enabled = false;
|
||||||
|
bool _consecutive_production_enabled = false;
|
||||||
|
int _required_witness_participation = 33;
|
||||||
|
|
||||||
std::map<chain::public_key_type, fc::ecc::private_key> _private_keys;
|
std::map<chain::public_key_type, fc::ecc::private_key> _private_keys;
|
||||||
std::set<chain::witness_id_type> _witnesses;
|
std::set<chain::witness_id_type> _witnesses;
|
||||||
fc::future<void> _block_production_task;
|
fc::future<void> _block_production_task;
|
||||||
|
|
|
||||||
|
|
@ -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")));
|
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());
|
string witness_id_example = fc::json::to_string(chain::witness_id_type());
|
||||||
command_line_options.add_options()
|
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<vector<string>>()->composing()->multitoken(),
|
("witness-id,w", bpo::value<vector<string>>()->composing()->multitoken(),
|
||||||
("ID of witness controlled by this node (e.g. " + witness_id_example + ", quotes are required, may specify multiple times)").c_str())
|
("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<vector<string>>()->composing()->multitoken()->
|
("private-key", bpo::value<vector<string>>()->composing()->multitoken()->
|
||||||
|
|
@ -189,6 +191,14 @@ void witness_plugin::block_production_loop()
|
||||||
return false;
|
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.
|
// the next block must be scheduled after the head block.
|
||||||
// if this check fails, the local clock has not advanced far
|
// if this check fails, the local clock has not advanced far
|
||||||
// enough from the head block.
|
// 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 local clock must be within 500 milliseconds of
|
||||||
// the scheduled production time.
|
// the scheduled production time.
|
||||||
if( llabs((scheduled_time - now).count()) > fc::milliseconds(500).count() ) {
|
if( llabs((scheduled_time - now).count()) > fc::milliseconds(250).count() ) {
|
||||||
elog("Not producing block because network time is not within 500ms of scheduled block time.");
|
elog("Not producing block because network time is not within 250ms of scheduled block time.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// we must know the private key corresponding to the witness's
|
// we must know the private key corresponding to the witness's
|
||||||
// published block production key.
|
// published block production key.
|
||||||
if( _private_keys.find( scheduled_key ) == _private_keys.end() ) {
|
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));
|
ilog("Witness ${id} production slot has arrived; generating a block now...", ("id", scheduled_witness));
|
||||||
try
|
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(
|
auto block = db.generate_block(
|
||||||
scheduled_time,
|
scheduled_time,
|
||||||
scheduled_witness,
|
scheduled_witness,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue