From 26007bb655557697689da612aefad31d3653aa35 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Fri, 18 Sep 2015 13:42:12 -0400 Subject: [PATCH] update shuffling algorithm --- libraries/app/application.cpp | 2 +- libraries/chain/db_block.cpp | 31 +++++ libraries/chain/db_init.cpp | 1 + libraries/chain/db_update.cpp | 25 ++-- libraries/chain/db_witness_schedule.cpp | 119 +----------------- .../chain/include/graphene/chain/database.hpp | 1 + .../graphene/chain/global_property_object.hpp | 8 +- libraries/plugins/witness/witness.cpp | 10 -- tests/tests/block_tests.cpp | 2 +- 9 files changed, 57 insertions(+), 142 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index eada9374..17d4c1c5 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -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 { diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 52d144e9..06f68059 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -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() ) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 6f7a7623..ecb79309 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -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) ); } }); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index e4d7d6af..86e14da4 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -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 diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index cc428ad8..5c56ca9a 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -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 diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index af417624..66215923 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -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 ///@{ diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index c52997fb..bb761cc7 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -44,9 +44,10 @@ namespace graphene { namespace chain { uint32_t next_available_vote_id = 0; vector active_committee_members; // updated once per maintenance interval - flat_set active_witnesses; // updated once per maintenance interval + flat_set active_witnesses; // updated once per maintenance interval // n.b. witness scheduling is done by witness_schedule object - flat_set witness_accounts; // updated once per maintenance interval + flat_set witness_accounts; // updated once per maintenance interval + vector 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) ) diff --git a/libraries/plugins/witness/witness.cpp b/libraries/plugins/witness/witness.cpp index 72fbc5c0..fc2eb4fc 100644 --- a/libraries/plugins/witness/witness.cpp +++ b/libraries/plugins/witness/witness.cpp @@ -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>()->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>()->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, diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index e64b0c11..b2c62102 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -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 );