From b236d4602728d67ec9541078874847afe3cc4ef9 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 28 Aug 2015 21:57:10 -0400 Subject: [PATCH] witness.cpp: Simplify block production loop --- libraries/fc | 2 +- .../include/graphene/witness/witness.hpp | 21 +- libraries/plugins/witness/witness.cpp | 223 ++++++++++-------- 3 files changed, 144 insertions(+), 102 deletions(-) diff --git a/libraries/fc b/libraries/fc index 71be796a..80d967a7 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 71be796af50c407281a40e61e4199a87e0a19314 +Subproject commit 80d967a70d21d26d27ef3a1544a177925b2a7bbe diff --git a/libraries/plugins/witness/include/graphene/witness/witness.hpp b/libraries/plugins/witness/include/graphene/witness/witness.hpp index 3d2dc0bc..c82b83d8 100644 --- a/libraries/plugins/witness/include/graphene/witness/witness.hpp +++ b/libraries/plugins/witness/include/graphene/witness/witness.hpp @@ -24,6 +24,22 @@ namespace graphene { namespace witness_plugin { +namespace block_production_condition +{ + enum block_production_condition_enum + { + produced = 0, + not_synced = 1, + not_my_turn = 2, + not_time_yet = 3, + no_private_key = 4, + low_participation = 5, + lag = 6, + consecutive = 7, + exception_producing_block = 8 + }; +} + class witness_plugin : public graphene::app::plugin { public: ~witness_plugin() { @@ -51,8 +67,9 @@ public: virtual void plugin_shutdown() override; private: - void schedule_next_production(const graphene::chain::chain_parameters& global_parameters); - void block_production_loop(); + void schedule_production_loop(); + block_production_condition::block_production_condition_enum block_production_loop(); + block_production_condition::block_production_condition_enum maybe_produce_block( fc::mutable_variant_object& capture ); boost::program_options::variables_map _options; bool _production_enabled = false; diff --git a/libraries/plugins/witness/witness.cpp b/libraries/plugins/witness/witness.cpp index 2c3619fe..439a2e36 100644 --- a/libraries/plugins/witness/witness.cpp +++ b/libraries/plugins/witness/witness.cpp @@ -163,7 +163,7 @@ void witness_plugin::plugin_startup() app().set_block_production(true); if( _production_enabled && (d.head_block_num() == 0) ) new_chain_banner(d); - schedule_next_production(d.get_global_properties().parameters); + schedule_production_loop(); } else elog("No witnesses configured! Please add witness IDs and private keys to configuration."); } FC_CAPTURE_AND_RETHROW() } @@ -174,123 +174,148 @@ void witness_plugin::plugin_shutdown() return; } -void witness_plugin::schedule_next_production(const graphene::chain::chain_parameters& global_parameters) +void witness_plugin::schedule_production_loop() { - //Get next production time for *any* witness - auto block_interval = global_parameters.block_interval; - fc::time_point next_block_time = fc::time_point_sec() + - (graphene::time::now().sec_since_epoch() / block_interval + 1) * block_interval; - - if( graphene::time::ntp_time().valid() ) - next_block_time -= graphene::time::ntp_error(); - - //Sleep until the next production time for *any* witness + //Schedule for the next second's tick regardless of chain state + fc::time_point_sec next_second( graphene::time::now().sec_since_epoch() + 1 ); + wdump( (next_second) ); _block_production_task = fc::schedule([this]{block_production_loop();}, - next_block_time, "Witness Block Production"); + next_second, "Witness Block Production"); } -void witness_plugin::block_production_loop() +block_production_condition::block_production_condition_enum witness_plugin::block_production_loop() +{ + block_production_condition::block_production_condition_enum result; + fc::mutable_variant_object capture; + try + { + result = maybe_produce_block(capture); + } + catch( const fc::canceled_exception& ) + { + //We're trying to exit. Go ahead and let this one out. + throw; + } + catch( const fc::exception& e ) + { + elog("Got exception while generating block:\n${e}", ("e", e.to_detail_string())); + result = block_production_condition::exception_producing_block; + } + + switch( result ) + { + case block_production_condition::produced: + ilog("Generated block #${n} with timestamp ${t} at time ${c}", (capture)); + break; + case block_production_condition::not_synced: + ilog("Not producing block because production is disabled until we receive a recent block (see: --enable-stale-production)"); + break; + case block_production_condition::not_my_turn: + ilog("Not producing block because it isn't my turn"); + break; + case block_production_condition::not_time_yet: + ilog("Not producing block because slot has not yet arrived"); + break; + case block_production_condition::no_private_key: + ilog("Not producing block because I don't have the private key for ${scheduled_key}", (capture) ); + break; + case block_production_condition::low_participation: + elog("Not producing block because node appears to be on a minority fork with only ${pct}% witness participation", (capture) ); + break; + case block_production_condition::lag: + elog("Not producing block because node didn't wake up within 500ms of the slot time."); + break; + case block_production_condition::consecutive: + elog("Not producting block because the last block was generated by the same witness.\nThis node is probably disconnected from the network so block production has been disabled.\nDisable this check with --allow-consecutive option."); + break; + case block_production_condition::exception_producing_block: + break; + } + + schedule_production_loop(); + return result; +} + +block_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block( fc::mutable_variant_object& capture ) { chain::database& db = database(); - const auto& global_parameters = db.get_global_properties().parameters; + fc::time_point_sec now = graphene::time::now(); - // Is there a head block within a block interval of now? If so, we're synced and can begin production. - if( !_production_enabled && - llabs((db.head_block_time() - graphene::time::now()).to_seconds()) <= global_parameters.block_interval ) - _production_enabled = true; + // If the next block production opportunity is in the present or future, we're synced. + if( !_production_enabled ) + { + if( db.get_slot_time(1) >= now ) + _production_enabled = true; + else + return block_production_condition::not_synced; + } // is anyone scheduled to produce now or one second in the future? - const fc::time_point_sec now = graphene::time::now(); uint32_t slot = db.get_slot_at_time( now ); + if( slot == 0 ) + { + capture("next_time", db.get_slot_time(1)); + return block_production_condition::not_time_yet; + } + + // + // this assert should not fail, because now <= db.head_block_time() + // should have resulted in slot == 0. + // + // if this assert triggers, there is a serious bug in get_slot_at_time() + // which would result in allowing a later block to have a timestamp + // less than or equal to the previous block + // + assert( now > db.head_block_time() ); + graphene::chain::witness_id_type scheduled_witness = db.get_scheduled_witness( slot ); + // we must control the witness scheduled to produce the next block. + if( _witnesses.find( scheduled_witness ) == _witnesses.end() ) + { + capture("scheduled_witness", scheduled_witness); + return block_production_condition::not_my_turn; + } + fc::time_point_sec scheduled_time = db.get_slot_time( slot ); graphene::chain::public_key_type scheduled_key = scheduled_witness( db ).signing_key; + auto private_key_itr = _private_keys.find( scheduled_key ); - auto is_scheduled = [&]() + if( private_key_itr == _private_keys.end() ) { - // conditions needed to produce a block: + capture("scheduled_key", scheduled_key); + return block_production_condition::no_private_key; + } - // we must control the witness scheduled to produce the next block. - if( _witnesses.find( scheduled_witness ) == _witnesses.end() ) { - 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() ) { - elog("Not producing block because I don't have the private key for ${id}.", ("id", scheduled_key)); - 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. - if( slot == 0 ) { - ilog("Not producing block because next slot time is in the future (likely a maitenance block)."); - return false; - } - - // block production must be enabled (i.e. witness must be synced) - if( !_production_enabled ) - { - wlog("Not producing block because production is disabled until we receive a recent block (see: --enable-stale-production)"); - return false; - } - - uint32_t prate = db.witness_participation_rate(); - if( prate < _required_witness_participation ) - { - elog("Not producing block because node appears to be on a minority fork with only ${x}% witness participation", - ("x",uint32_t(100*uint64_t(prate) / GRAPHENE_1_PERCENT) ) ); - return false; - } - - // the local clock must be at least 1 second ahead of head_block_time. - if( (now - db.head_block_time()).to_seconds() < GRAPHENE_MIN_BLOCK_INTERVAL ) { - elog("Not producing block because head block is less than a second old."); - return false; - } - - // 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 250ms of scheduled block time."); - return false; - } - - - return true; - }; - - wdump((slot)(scheduled_witness)(scheduled_time)(now)); - if( is_scheduled() ) + uint32_t prate = db.witness_participation_rate(); + if( prate < _required_witness_participation ) { - ilog("Witness ${id} production slot has arrived; generating a block now...", ("id", scheduled_witness)); - try + capture("pct", uint32_t(100*uint64_t(prate) / GRAPHENE_1_PERCENT)); + return block_production_condition::low_participation; + } + + if( llabs((scheduled_time - now).count()) > fc::milliseconds( 500 ).count() ) + { + capture("scheduled_time", scheduled_time)("now", now); + return block_production_condition::lag; + } + + if( !_consecutive_production_enabled ) + { + if( db.get_dynamic_global_properties().current_witness == scheduled_witness ) { - 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, - _private_keys[ scheduled_key ], - graphene::chain::database::skip_nothing - ); - ilog("Generated block #${n} with timestamp ${t} at time ${c}", - ("n", block.block_num())("t", block.timestamp)("c", now)); - p2p_node().broadcast(net::block_message(block)); - } - catch( const fc::canceled_exception& ) - { - //We're trying to exit. Go ahead and let this one out. - throw; - } - catch( const fc::exception& e ) - { - elog("Got exception while generating block:\n${e}", ("e", e.to_detail_string())); + capture("scheduled_witness", scheduled_witness); + return block_production_condition::consecutive; } } - schedule_next_production(global_parameters); + auto block = db.generate_block( + scheduled_time, + scheduled_witness, + private_key_itr->second, + graphene::chain::database::skip_nothing + ); + capture("n", block.block_num())("t", block.timestamp)("c", now); + p2p_node().broadcast(net::block_message(block)); + + return block_production_condition::produced; }