update shuffling algorithm
This commit is contained in:
parent
c875c8ac11
commit
26007bb655
9 changed files with 57 additions and 142 deletions
|
|
@ -385,7 +385,7 @@ namespace detail {
|
|||
{
|
||||
const auto& witness = blk_msg.block.witness(*_chain_db);
|
||||
const auto& witness_account = witness.witness_account(*_chain_db);
|
||||
ilog("Got block #${n} from network with latency of ${l} ms from ${w}", ("n", blk_msg.block.block_num())("l", (latency.count()/1000))("w",witness_account.name) );
|
||||
ilog("Got block #${n} with time ${t} from network with latency of ${l} ms from ${w}", ("t",blk_msg.block.timestamp)("n", blk_msg.block.block_num())("l", (latency.count()/1000))("w",witness_account.name) );
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -485,6 +485,7 @@ void database::_apply_block( const signed_block& next_block )
|
|||
// update_global_dynamic_data() as perhaps these methods only need
|
||||
// to be called for header validation?
|
||||
update_maintenance_flag( maint_needed );
|
||||
shuffle_witnesses();
|
||||
|
||||
// notify observers that the block has been applied
|
||||
applied_block( next_block ); //emit
|
||||
|
|
@ -493,6 +494,36 @@ void database::_apply_block( const signed_block& next_block )
|
|||
notify_changed_objects();
|
||||
} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) }
|
||||
|
||||
void database::shuffle_witnesses() {
|
||||
const auto& dgp = get_global_properties();
|
||||
if( head_block_num() % dgp.active_witnesses.size() == 0 ) {
|
||||
modify( dgp, [&]( global_property_object& props ) {
|
||||
props.current_shuffled_witnesses.clear();
|
||||
props.current_shuffled_witnesses.reserve( props.active_witnesses.size() );
|
||||
|
||||
for( auto w : props.active_witnesses )
|
||||
props.current_shuffled_witnesses.push_back(w);
|
||||
|
||||
auto now_hi = uint64_t(head_block_time().sec_since_epoch()) << 32;
|
||||
for( uint32_t i = 0; i < props.current_shuffled_witnesses.size(); ++i )
|
||||
{
|
||||
/// High performance random generator
|
||||
/// http://xorshift.di.unimi.it/
|
||||
uint64_t k = now_hi + uint64_t(i)*2685821657736338717ULL;
|
||||
k ^= (k >> 12);
|
||||
k ^= (k << 25);
|
||||
k ^= (k >> 27);
|
||||
k *= 2685821657736338717ULL;
|
||||
|
||||
uint32_t jmax = props.current_shuffled_witnesses.size() - i;
|
||||
uint32_t j = i + k%jmax;
|
||||
std::swap( props.current_shuffled_witnesses[i],
|
||||
props.current_shuffled_witnesses[j] );
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void database::notify_changed_objects()
|
||||
{ try {
|
||||
if( _undo_db.enabled() )
|
||||
|
|
|
|||
|
|
@ -592,6 +592,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
|||
{
|
||||
p.active_witnesses.insert(i);
|
||||
p.witness_accounts.insert(get(witness_id_type(i)).witness_account);
|
||||
p.current_shuffled_witnesses.push_back( witness_id_type(i) );
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -40,18 +40,19 @@ void database::update_global_dynamic_data( const signed_block& b )
|
|||
uint32_t missed_blocks = get_slot_at_time( b.timestamp );
|
||||
assert( missed_blocks != 0 );
|
||||
missed_blocks--;
|
||||
if( missed_blocks < 20 ) {
|
||||
for( uint32_t i = 0; i < missed_blocks; ++i ) {
|
||||
const auto& witness_missed = get_scheduled_witness( i+1 )(*this);
|
||||
if( witness_missed.id != b.witness ) {
|
||||
const auto& witness_account = witness_missed.witness_account(*this);
|
||||
if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) )
|
||||
wlog( "Witness ${name} missed block ${n} around ${t}", ("name",witness_account.name)("n",b.block_num())("t",b.timestamp) );
|
||||
modify( witness_missed, [&]( witness_object& w ) {
|
||||
w.total_missed++;
|
||||
});
|
||||
}
|
||||
}
|
||||
for( uint32_t i = 0; i < missed_blocks; ++i ) {
|
||||
const auto& witness_missed = get_scheduled_witness( i+1 )(*this);
|
||||
if( witness_missed.id != b.witness ) {
|
||||
const auto& witness_account = witness_missed.witness_account(*this);
|
||||
/*
|
||||
if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) )
|
||||
wlog( "Witness ${name} missed block ${n} around ${t}", ("name",witness_account.name)("n",b.block_num())("t",b.timestamp) );
|
||||
*/
|
||||
|
||||
modify( witness_missed, [&]( witness_object& w ) {
|
||||
w.total_missed++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dynamic global properties updating
|
||||
|
|
|
|||
|
|
@ -27,121 +27,10 @@ using boost::container::flat_set;
|
|||
|
||||
witness_id_type database::get_scheduled_witness( uint32_t slot_num )const
|
||||
{
|
||||
//
|
||||
// Each witness gets an arbitration key H(time, witness_id).
|
||||
// The witness with the smallest key is selected to go first.
|
||||
//
|
||||
// As opposed to just using H(time) to determine an index into
|
||||
// an array of eligible witnesses, this has the following desirable
|
||||
// properties:
|
||||
//
|
||||
// - Avoid dynamic memory allocation
|
||||
// - Decreases (but does not eliminate) the probability that a
|
||||
// missed block will change the witness assigned to a future slot
|
||||
//
|
||||
// The hash function is xorshift* as given in
|
||||
// [1] https://en.wikipedia.org/wiki/Xorshift#Xorshift.2A
|
||||
//
|
||||
|
||||
if( slot_num == 0 )
|
||||
return GRAPHENE_NULL_WITNESS;
|
||||
|
||||
const flat_set< witness_id_type >& active_witnesses = get_global_properties().active_witnesses;
|
||||
uint32_t n = active_witnesses.size();
|
||||
uint64_t min_witness_separation;
|
||||
if( GRAPHENE_DEFAULT_MIN_WITNESS_COUNT < 5 && BOOST_UNLIKELY( n < 5 ) )
|
||||
{
|
||||
// special-case 0 and 1.
|
||||
// for 2 give a value which results in witnesses alternating slots
|
||||
// when there is no missed block
|
||||
// for 3-4 give values which don't lock in a single permutation
|
||||
switch( n )
|
||||
{
|
||||
case 0:
|
||||
assert(false);
|
||||
case 1:
|
||||
return *active_witnesses.begin();
|
||||
case 2:
|
||||
case 3:
|
||||
min_witness_separation = 1;
|
||||
break;
|
||||
case 4:
|
||||
min_witness_separation = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
min_witness_separation = (n/2)+1;
|
||||
|
||||
uint64_t current_aslot = get_dynamic_global_properties().current_aslot + slot_num;
|
||||
|
||||
uint64_t start_of_current_round_aslot = current_aslot - (current_aslot % n);
|
||||
uint64_t first_ineligible_aslot = std::min( start_of_current_round_aslot, current_aslot - min_witness_separation );
|
||||
//
|
||||
// overflow analysis of above subtraction:
|
||||
//
|
||||
// we always have min_witness_separation <= n, so
|
||||
// if current_aslot < min_witness_separation it follows that
|
||||
// start_of_current_round_aslot == 0
|
||||
//
|
||||
// therefore result of above min() is 0 when subtraction overflows
|
||||
//
|
||||
|
||||
first_ineligible_aslot = std::max( first_ineligible_aslot, uint64_t( 1 ) );
|
||||
|
||||
uint64_t best_k = 0;
|
||||
witness_id_type best_wit = GRAPHENE_NULL_WITNESS;
|
||||
bool success = false;
|
||||
|
||||
uint64_t now_hi = get_slot_time( slot_num ).sec_since_epoch();
|
||||
now_hi <<= 32;
|
||||
|
||||
for( const witness_id_type& wit_id : active_witnesses )
|
||||
{
|
||||
const witness_object& wit = wit_id(*this);
|
||||
if( wit.last_aslot >= first_ineligible_aslot )
|
||||
continue;
|
||||
|
||||
/// High performance random generator
|
||||
/// http://xorshift.di.unimi.it/
|
||||
uint64_t k = now_hi + uint64_t(wit_id)*2685821657736338717ULL;
|
||||
k ^= (k >> 12);
|
||||
k ^= (k << 25);
|
||||
k ^= (k >> 27);
|
||||
k *= 2685821657736338717ULL;
|
||||
if( k >= best_k )
|
||||
{
|
||||
best_k = k;
|
||||
best_wit = wit_id;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
// the above loop should choose at least 1 because
|
||||
// at most K elements are susceptible to the filter,
|
||||
// otherwise we have an inconsistent database (such as
|
||||
// wit.last_aslot values that are non-unique or in the future)
|
||||
if( !success ) {
|
||||
edump((best_k)(slot_num)(first_ineligible_aslot)(current_aslot)(start_of_current_round_aslot)(min_witness_separation)(active_witnesses.size()));
|
||||
|
||||
for( const witness_id_type& wit_id : active_witnesses )
|
||||
{
|
||||
const witness_object& wit = wit_id(*this);
|
||||
if( wit.last_aslot >= first_ineligible_aslot )
|
||||
idump((wit_id)(wit.last_aslot));
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: Comment out assert( success ) for production network.
|
||||
// This code should never be reached, but it has been observed
|
||||
// to rarely happen under conditions we have been unable to
|
||||
// reproduce. If this point is reached, we want deterministic,
|
||||
// non-crashing behavior. See #274.
|
||||
//
|
||||
assert( success );
|
||||
}
|
||||
|
||||
return best_wit;
|
||||
const auto& dgp = get_dynamic_global_properties();
|
||||
const auto& gp = get_global_properties();
|
||||
auto current_aslot = dgp.current_aslot + slot_num;
|
||||
return gp.current_shuffled_witnesses[current_aslot%gp.current_shuffled_witnesses.size()];
|
||||
}
|
||||
|
||||
fc::time_point_sec database::get_slot_time(uint32_t slot_num)const
|
||||
|
|
|
|||
|
|
@ -424,6 +424,7 @@ namespace graphene { namespace chain {
|
|||
void update_expired_feeds();
|
||||
void update_maintenance_flag( bool new_maintenance_flag );
|
||||
void update_withdraw_permissions();
|
||||
void shuffle_witnesses();
|
||||
|
||||
///Steps performed only at maintenance intervals
|
||||
///@{
|
||||
|
|
|
|||
|
|
@ -44,9 +44,10 @@ namespace graphene { namespace chain {
|
|||
|
||||
uint32_t next_available_vote_id = 0;
|
||||
vector<committee_member_id_type> active_committee_members; // updated once per maintenance interval
|
||||
flat_set<witness_id_type> active_witnesses; // updated once per maintenance interval
|
||||
flat_set<witness_id_type> active_witnesses; // updated once per maintenance interval
|
||||
// n.b. witness scheduling is done by witness_schedule object
|
||||
flat_set<account_id_type> witness_accounts; // updated once per maintenance interval
|
||||
flat_set<account_id_type> witness_accounts; // updated once per maintenance interval
|
||||
vector<witness_id_type> current_shuffled_witnesses;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -88,7 +89,7 @@ namespace graphene { namespace chain {
|
|||
* number of slots since genesis. Also equal to the total
|
||||
* number of missed slots plus head_block_number.
|
||||
*/
|
||||
uint64_t current_aslot = 0;
|
||||
uint64_t current_aslot = 0;
|
||||
|
||||
/**
|
||||
* used to compute witness participation.
|
||||
|
|
@ -137,4 +138,5 @@ FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::obje
|
|||
(next_available_vote_id)
|
||||
(active_committee_members)
|
||||
(active_witnesses)
|
||||
(current_shuffled_witnesses)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ void witness_plugin::plugin_set_program_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.")
|
||||
("required-participation", bpo::bool_switch()->notifier([this](int e){_required_witness_participation = uint32_t(e*GRAPHENE_1_PERCENT);}), "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(),
|
||||
("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()->
|
||||
|
|
@ -269,15 +268,6 @@ block_production_condition::block_production_condition_enum witness_plugin::mayb
|
|||
return block_production_condition::lag;
|
||||
}
|
||||
|
||||
if( !_consecutive_production_enabled )
|
||||
{
|
||||
if( db.get_dynamic_global_properties().current_witness == scheduled_witness )
|
||||
{
|
||||
capture("scheduled_witness", scheduled_witness);
|
||||
return block_production_condition::consecutive;
|
||||
}
|
||||
}
|
||||
|
||||
auto block = db.generate_block(
|
||||
scheduled_time,
|
||||
scheduled_witness,
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ BOOST_AUTO_TEST_CASE( generate_empty_blocks )
|
|||
BOOST_CHECK( db.head_block_id() == b.id() );
|
||||
witness_id_type prev_witness = b.witness;
|
||||
witness_id_type cur_witness = db.get_scheduled_witness(1);
|
||||
BOOST_CHECK( cur_witness != prev_witness );
|
||||
//BOOST_CHECK( cur_witness != prev_witness );
|
||||
b = db.generate_block(db.get_slot_time(1), cur_witness, init_account_priv_key, database::skip_nothing);
|
||||
}
|
||||
BOOST_CHECK_EQUAL( db.head_block_num(), 400 );
|
||||
|
|
|
|||
Loading…
Reference in a new issue