witness.cpp: Simplify block production loop

This commit is contained in:
theoreticalbts 2015-08-28 21:57:10 -04:00
parent 5a923697ad
commit b236d46027
3 changed files with 144 additions and 102 deletions

@ -1 +1 @@
Subproject commit 71be796af50c407281a40e61e4199a87e0a19314
Subproject commit 80d967a70d21d26d27ef3a1544a177925b2a7bbe

View file

@ -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;

View file

@ -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;
}