witness.cpp: Simplify block production loop
This commit is contained in:
parent
5a923697ad
commit
b236d46027
3 changed files with 144 additions and 102 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 71be796af50c407281a40e61e4199a87e0a19314
|
||||
Subproject commit 80d967a70d21d26d27ef3a1544a177925b2a7bbe
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue