update shuffling algorithm

This commit is contained in:
Daniel Larimer 2015-09-18 13:42:12 -04:00
parent c875c8ac11
commit 26007bb655
9 changed files with 57 additions and 142 deletions

View file

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

View file

@ -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() )

View file

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

View file

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

View file

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

View file

@ -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
///@{

View file

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

View file

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

View file

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