diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index c3f6e062..3aba9ea5 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -550,7 +550,10 @@ 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 ); - update_witness_schedule(); + if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) + update_witness_schedule(); + if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) + update_witness_schedule(next_block); if( !_node_property_object.debug_updates.empty() ) apply_debug_updates(); diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 69bee98b..831edf12 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -783,6 +783,31 @@ void database::init_genesis(const genesis_state_type& genesis_state) } }); + // Initialize witness schedule +#ifndef NDEBUG + const witness_schedule_object& wso = +#endif + create([&](witness_schedule_object& _wso) + { + memset(_wso.rng_seed.begin(), 0, _wso.rng_seed.size()); + + witness_scheduler_rng rng(_wso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + + auto init_witnesses = get_global_properties().active_witnesses; + + _wso.scheduler = witness_scheduler(); + _wso.scheduler._min_token_count = std::max(int(init_witnesses.size()) / 2, 1); + _wso.scheduler.update(init_witnesses); + + for( size_t i=0; i #include #include +#include #include namespace graphene { namespace chain { @@ -240,6 +241,11 @@ void database::update_active_witnesses() }); }); + const witness_schedule_object& wso = witness_schedule_id_type()(*this); + modify(wso, [&](witness_schedule_object& _wso) + { + _wso.scheduler.update(gpo.active_witnesses); + }); } FC_CAPTURE_AND_RETHROW() } void database::update_active_committee_members() diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 5a1b04d9..6838468a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -83,7 +83,7 @@ void database::update_global_dynamic_data( const signed_block& b ) if( BOOST_UNLIKELY( b.block_num() == 1 ) ) dgp.recently_missed_count = 0; - else if( _checkpoints.size() && _checkpoints.rbegin()->first >= b.block_num() ) + else if( _checkpoints.size() && _checkpoints.rbegin()->first >= b.block_num() ) dgp.recently_missed_count = 0; else if( missed_blocks ) dgp.recently_missed_count += GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT*missed_blocks; diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index 66db87ee..446d34a9 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -33,10 +33,43 @@ using boost::container::flat_set; witness_id_type database::get_scheduled_witness( uint32_t slot_num )const { - const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - const witness_schedule_object& wso = witness_schedule_id_type()(*this); - uint64_t current_aslot = dpo.current_aslot + slot_num; - return wso.current_shuffled_witnesses[ current_aslot % wso.current_shuffled_witnesses.size() ]; + witness_id_type wid; + const global_property_object& gpo = get_global_properties(); + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) + { + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + const witness_schedule_object& wso = witness_schedule_id_type()(*this); + uint64_t current_aslot = dpo.current_aslot + slot_num; + return wso.current_shuffled_witnesses[ current_aslot % wso.current_shuffled_witnesses.size() ]; + } + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM && + slot_num != 0 ) + { + const witness_schedule_object& wso = witness_schedule_id_type()(*this); + // ask the near scheduler who goes in the given slot + bool slot_is_near = wso.scheduler.get_slot(slot_num-1, wid); + if(! slot_is_near) + { + // if the near scheduler doesn't know, we have to extend it to + // a far scheduler. + // n.b. instantiating it is slow, but block gaps long enough to + // need it are likely pretty rare. + + witness_scheduler_rng far_rng(wso.rng_seed.begin(), GRAPHENE_FAR_SCHEDULE_CTR_IV); + + far_future_witness_scheduler far_scheduler = + far_future_witness_scheduler(wso.scheduler, far_rng); + if(!far_scheduler.get_slot(slot_num-1, wid)) + { + // no scheduled witness -- somebody set up us the bomb + // n.b. this code path is impossible, the present + // implementation of far_future_witness_scheduler + // returns true unconditionally + assert( false ); + } + } + } + return wid; } fc::time_point_sec database::get_slot_time(uint32_t slot_num)const @@ -59,30 +92,45 @@ fc::time_point_sec database::get_slot_time(uint32_t slot_num)const const global_property_object& gpo = get_global_properties(); - if( dpo.dynamic_flags & dynamic_global_property_object::maintenance_flag ) - slot_num += gpo.parameters.maintenance_skip_slots; + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) + { + if( dpo.dynamic_flags & dynamic_global_property_object::maintenance_flag ) + slot_num += gpo.parameters.maintenance_skip_slots; - // "slot 0" is head_slot_time - // "slot 1" is head_slot_time, - // plus maint interval if head block is a maint block - // plus block interval if head block is not a maint block - return head_slot_time + (slot_num * interval); + // "slot 0" is head_slot_time + // "slot 1" is head_slot_time, + // plus maint interval if head block is a maint block + // plus block interval if head block is not a maint block + return head_slot_time + (slot_num * interval); + } + + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) + { + // "slot 0" is head_slot_time + // "slot 1" is head_slot_time, + // plus maint interval if head block is a maint block + // plus block interval if head block is not a maint block + return head_slot_time + + (slot_num + + ( + (dpo.dynamic_flags & dynamic_global_property_object::maintenance_flag) + ? gpo.parameters.maintenance_skip_slots : 0 + ) + ) * interval + ; + } + return fc::time_point_sec(); } uint32_t database::get_slot_at_time(fc::time_point_sec when)const { fc::time_point_sec first_slot_time = get_slot_time( 1 ); + std::cout << "romek " << when.to_iso_string() << " " << first_slot_time.to_iso_string() << "\n"; if( when < first_slot_time ) return 0; return (when - first_slot_time).to_seconds() / block_interval() + 1; } -uint32_t database::witness_participation_rate()const -{ - const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - return uint64_t(GRAPHENE_100_PERCENT) * dpo.recent_slots_filled.popcount() / 128; -} - void database::update_witness_schedule() { const witness_schedule_object& wso = witness_schedule_id_type()(*this); @@ -118,4 +166,99 @@ void database::update_witness_schedule() } } +vector database::get_near_witness_schedule()const +{ + const witness_schedule_object& wso = witness_schedule_id_type()(*this); + + vector result; + result.reserve(wso.scheduler.size()); + uint32_t slot_num = 0; + witness_id_type wid; + + while( wso.scheduler.get_slot(slot_num++, wid) ) + result.emplace_back(wid); + + return result; +} + +void database::update_witness_schedule(const signed_block& next_block) +{ + auto start = fc::time_point::now(); + const global_property_object& gpo = get_global_properties(); + const witness_schedule_object& wso = get(witness_schedule_id_type()); + uint32_t schedule_needs_filled = gpo.active_witnesses.size(); + uint32_t schedule_slot = get_slot_at_time(next_block.timestamp); + + // We shouldn't be able to generate _pending_block with timestamp + // in the past, and incoming blocks from the network with timestamp + // in the past shouldn't be able to make it this far without + // triggering FC_ASSERT elsewhere + + assert( schedule_slot > 0 ); + witness_id_type first_witness; + bool slot_is_near = wso.scheduler.get_slot( schedule_slot-1, first_witness ); + + witness_id_type wit; + + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + + assert( dpo.random.data_size() == witness_scheduler_rng::seed_length ); + assert( witness_scheduler_rng::seed_length == wso.rng_seed.size() ); + + modify(wso, [&](witness_schedule_object& _wso) + { + _wso.slots_since_genesis += schedule_slot; + witness_scheduler_rng rng(wso.rng_seed.data, _wso.slots_since_genesis); + + _wso.scheduler._min_token_count = std::max(int(gpo.active_witnesses.size()) / 2, 1); + + if( slot_is_near ) + { + uint32_t drain = schedule_slot; + while( drain > 0 ) + { + if( _wso.scheduler.size() == 0 ) + break; + _wso.scheduler.consume_schedule(); + --drain; + } + } + else + { + _wso.scheduler.reset_schedule( first_witness ); + } + while( !_wso.scheduler.get_slot(schedule_needs_filled, wit) ) + { + if( _wso.scheduler.produce_schedule(rng) & emit_turn ) + memcpy(_wso.rng_seed.begin(), dpo.random.data(), dpo.random.data_size()); + } + _wso.last_scheduling_block = next_block.block_num(); + _wso.recent_slots_filled = ( + (_wso.recent_slots_filled << 1) + + 1) << (schedule_slot - 1); + }); + auto end = fc::time_point::now(); + static uint64_t total_time = 0; + static uint64_t calls = 0; + total_time += (end - start).count(); + if( ++calls % 1000 == 0 ) + idump( ( double(total_time/1000000.0)/calls) ); +} + +uint32_t database::witness_participation_rate()const +{ + const global_property_object& gpo = get_global_properties(); + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) + { + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + return uint64_t(GRAPHENE_100_PERCENT) * dpo.recent_slots_filled.popcount() / 128; + } + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) + { + const witness_schedule_object& wso = get(witness_schedule_id_type()); + return uint64_t(GRAPHENE_100_PERCENT) * wso.recent_slots_filled.popcount() / 128; + } + return 0; +} + } } diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 844469ad..e589f023 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -113,6 +113,9 @@ #define GRAPHENE_MAX_URL_LENGTH 127 +#define GRAPHENE_WITNESS_SHUFFLED_ALGORITHM 0 +#define GRAPHENE_WITNESS_SCHEDULED_ALGORITHM 1 + // counter initialization values used to derive near and far future seeds for shuffling witnesses // we use the fractional bits of sqrt(2) in hex #define GRAPHENE_NEAR_SCHEDULE_CTR_IV ( (uint64_t( 0x6a09 ) << 0x30) \ diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index d42bbf2d..893a095a 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -240,7 +240,9 @@ namespace graphene { namespace chain { */ uint32_t get_slot_at_time(fc::time_point_sec when)const; + vector get_near_witness_schedule()const; void update_witness_schedule(); + void update_witness_schedule(const signed_block& next_block); //////////////////// db_getter.cpp //////////////////// diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 67bb870f..3a3b759c 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -69,6 +69,7 @@ namespace graphene { namespace chain { uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH; + uint8_t witness_schedule_algorithm = GRAPHENE_WITNESS_SHUFFLED_ALGORITHM; ///< 0 shuffled, 1 scheduled /* rps tournament parameters constraints */ uint32_t min_round_delay = TOURNAMENT_MIN_ROUND_DELAY; ///< miniaml delay between games uint32_t max_round_delay = TOURNAMENT_MAX_ROUND_DELAY; ///< maximal delay between games diff --git a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp index b5f1ea8a..f0d9e85c 100644 --- a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp +++ b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp @@ -25,24 +25,82 @@ #include #include #include +#include +#include namespace graphene { namespace chain { class witness_schedule_object; +//#ifndef GRAPHENE_SHUFFLED_WITNESSES +typedef hash_ctr_rng< + /* HashClass = */ fc::sha256, + /* SeedLength = */ GRAPHENE_RNG_SEED_LENGTH + > witness_scheduler_rng; + +typedef generic_witness_scheduler< + /* WitnessID = */ witness_id_type, + /* RNG = */ witness_scheduler_rng, + /* CountType = */ decltype( chain_parameters::maximum_witness_count ), + /* OffsetType = */ uint32_t, + /* debug = */ true + > witness_scheduler; + +typedef generic_far_future_witness_scheduler< + /* WitnessID = */ witness_id_type, + /* RNG = */ witness_scheduler_rng, + /* CountType = */ decltype( chain_parameters::maximum_witness_count ), + /* OffsetType = */ uint32_t, + /* debug = */ true + > far_future_witness_scheduler; +//#endif + class witness_schedule_object : public graphene::db::abstract_object { public: static const uint8_t space_id = implementation_ids; static const uint8_t type_id = impl_witness_schedule_object_type; +//#ifdef GRAPHENE_SHUFFLED_WITNESSES vector< witness_id_type > current_shuffled_witnesses; +//#else + witness_scheduler scheduler; + uint32_t last_scheduling_block; + uint64_t slots_since_genesis = 0; + fc::array< char, sizeof(secret_hash_type) > rng_seed; + + /** + * Not necessary for consensus, but used for figuring out the participation rate. + * The nth bit is 0 if the nth slot was unfilled, else it is 1. + */ + fc::uint128 recent_slots_filled; + //#endif + }; } } + +//#ifdef GRAPHENE_SHUFFLED_WITNESSES +FC_REFLECT( graphene::chain::witness_scheduler, + (_turns) + (_tokens) + (_min_token_count) + (_ineligible_waiting_for_token) + (_ineligible_no_turn) + (_eligible) + (_schedule) + (_lame_duck) + ) + +//#ifdef GRAPHENE_SHUFFLED_WITNESSES FC_REFLECT_DERIVED( graphene::chain::witness_schedule_object, (graphene::db::object), + (scheduler) + (last_scheduling_block) + (slots_since_genesis) + (rng_seed) + (recent_slots_filled) (current_shuffled_witnesses) ) diff --git a/libraries/chain/include/graphene/chain/witness_scheduler.hpp b/libraries/chain/include/graphene/chain/witness_scheduler.hpp new file mode 100644 index 00000000..5feef417 --- /dev/null +++ b/libraries/chain/include/graphene/chain/witness_scheduler.hpp @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once + +#include +#include +#include +#include + +#include + +namespace graphene { namespace chain { + +//using boost::container::flat_set; + +enum witness_scheduler_relax_flags +{ + emit_turn = 0x01, + emit_token = 0x02 +}; + +template< typename WitnessID, typename RNG, typename CountType, typename OffsetType, bool debug = true > +class generic_witness_scheduler +{ + public: + void check_invariant() const + { +#ifndef NDEBUG + CountType tokens = _ineligible_no_turn.size() + _eligible.size(); + CountType turns = _eligible.size(); + for( const std::pair< WitnessID, bool >& item : _ineligible_waiting_for_token ) + turns += (item.second ? 1 : 0 ); + + assert( _tokens == tokens ); + assert( _turns == turns ); +#endif + + set< WitnessID > witness_set; + // make sure each witness_id occurs only once among the three states + auto process_id = [&]( WitnessID item ) + { + assert( witness_set.find( item ) == witness_set.end() ); + witness_set.insert( item ); + } ; + + for( const std::pair< WitnessID, bool >& item : _ineligible_waiting_for_token ) + process_id( item.first ); + for( const WitnessID& item : _ineligible_no_turn ) + process_id( item ); + for( const WitnessID& item : _eligible ) + process_id( item ); + return; + } + + /** + * Deterministically evolve over time + */ + uint32_t relax() + { + uint32_t relax_flags = 0; + + if( debug ) check_invariant(); + assert( _min_token_count > 0 ); + + // turn distribution + if( _turns == 0 ) + { + relax_flags |= emit_turn; + for( const WitnessID& item : _ineligible_no_turn ) + _eligible.push_back( item ); + _turns += _ineligible_no_turn.size(); + _ineligible_no_turn.clear(); + if( debug ) check_invariant(); + + for( std::pair< WitnessID, bool >& item : _ineligible_waiting_for_token ) + { + assert( item.second == false ); + item.second = true; + } + _turns += _ineligible_waiting_for_token.size(); + if( debug ) check_invariant(); + } + + // token distribution + while( true ) + { + if( _ineligible_waiting_for_token.empty() ) + { + // eligible must be non-empty + assert( !_eligible.empty() ); + return relax_flags; + } + + if( _tokens >= _min_token_count ) + { + if( !_eligible.empty() ) + return relax_flags; + } + + const std::pair< WitnessID, bool >& item = _ineligible_waiting_for_token.front(); + if( item.second ) + _eligible.push_back( item.first ); + else + _ineligible_no_turn.push_back( item.first ); + _ineligible_waiting_for_token.pop_front(); + relax_flags |= emit_token; + _tokens++; + if( debug ) check_invariant(); + } + + return relax_flags; + } + + /** + * Add another element to _schedule + */ + uint32_t produce_schedule( RNG& rng ) + { + uint32_t relax_flags = relax(); + if( debug ) check_invariant(); + if( _eligible.empty() ) + return relax_flags; + + decltype( rng( _eligible.size() ) ) pos = rng( _eligible.size() ); + assert( (pos >= 0) && (pos < _eligible.size()) ); + auto it = _eligible.begin() + pos; + _schedule.push_back( *it ); + _ineligible_waiting_for_token.emplace_back( *it, false ); + _eligible.erase( it ); + _turns--; + _tokens--; + if( debug ) check_invariant(); + return relax_flags; + } + + /** + * Pull an element from _schedule + */ + WitnessID consume_schedule() + { + assert( _schedule.size() > 0 ); + + WitnessID result = _schedule.front(); + _schedule.pop_front(); + + auto it = _lame_duck.find( result ); + if( it != _lame_duck.end() ) + _lame_duck.erase( it ); + if( debug ) check_invariant(); + return result; + } + + /** + * Remove all witnesses in the removal_set from + * future scheduling (but not from the current schedule). + */ + template< typename T > + void remove_all( const T& removal_set ) + { + if( debug ) check_invariant(); + + _ineligible_waiting_for_token.erase( + std::remove_if( + _ineligible_waiting_for_token.begin(), + _ineligible_waiting_for_token.end(), + [&]( const std::pair< WitnessID, bool >& item ) -> bool + { + bool found = removal_set.find( item.first ) != removal_set.end(); + _turns -= (found & item.second) ? 1 : 0; + return found; + } ), + _ineligible_waiting_for_token.end() ); + if( debug ) check_invariant(); + + _ineligible_no_turn.erase( + std::remove_if( + _ineligible_no_turn.begin(), + _ineligible_no_turn.end(), + [&]( WitnessID item ) -> bool + { + bool found = (removal_set.find( item ) != removal_set.end()); + _tokens -= (found ? 1 : 0); + return found; + } ), + _ineligible_no_turn.end() ); + if( debug ) check_invariant(); + + _eligible.erase( + std::remove_if( + _eligible.begin(), + _eligible.end(), + [&]( WitnessID item ) -> bool + { + bool found = (removal_set.find( item ) != removal_set.end()); + _tokens -= (found ? 1 : 0); + _turns -= (found ? 1 : 0); + return found; + } ), + _eligible.end() ); + if( debug ) check_invariant(); + + return; + } + + /** + * Convenience function to call insert_all() and remove_all() + * as needed to update to the given revised_set. + */ + template< typename T > + void insert_all( const T& insertion_set ) + { + if( debug ) check_invariant(); + for( const WitnessID wid : insertion_set ) + { + _eligible.push_back( wid ); + } + _turns += insertion_set.size(); + _tokens += insertion_set.size(); + if( debug ) check_invariant(); + return; + } + + /** + * Convenience function to call insert_all() and remove_all() + * as needed to update to the given revised_set. + * + * This function calls find() on revised_set for all current + * witnesses. Running time is O(n*log(n)) if the revised_set + * implementation of find() is O(log(n)). + * + * TODO: Rewriting to use std::set_difference may marginally + * increase efficiency, but a benchmark is needed to justify this. + */ + template< typename T > + void update( const T& revised_set ) + { + set< WitnessID > current_set; + set< WitnessID > schedule_set; + + /* current_set.reserve( + _ineligible_waiting_for_token.size() + + _ineligible_no_turn.size() + + _eligible.size() + + _schedule.size() ); + */ + for( const auto& item : _ineligible_waiting_for_token ) + current_set.insert( item.first ); + for( const WitnessID& item : _ineligible_no_turn ) + current_set.insert( item ); + for( const WitnessID& item : _eligible ) + current_set.insert( item ); + for( const WitnessID& item : _schedule ) + { + current_set.insert( item ); + schedule_set.insert( item ); + } + + set< WitnessID > insertion_set; + //insertion_set.reserve( revised_set.size() ); + for( const WitnessID& item : revised_set ) + { + if( current_set.find( item ) == current_set.end() ) + insertion_set.insert( item ); + } + + set< WitnessID > removal_set; + //removal_set.reserve( current_set.size() ); + for( const WitnessID& item : current_set ) + { + if( revised_set.find( item ) == revised_set.end() ) + { + if( schedule_set.find( item ) == schedule_set.end() ) + removal_set.insert( item ); + else + _lame_duck.insert( item ); + } + } + + insert_all( insertion_set ); + remove_all( removal_set ); + + return; + } + + /** + * Get the number of scheduled witnesses + */ + + size_t size( )const + { + return _schedule.size(); + } + + bool get_slot( OffsetType offset, WitnessID& wit )const + { + if( offset >= _schedule.size() ) + return false; + wit = _schedule[ offset ]; + return true; + } + + /** + * Reset the schedule, then re-schedule the given witness as the + * first witness. + */ + void reset_schedule( WitnessID first_witness ) + { + _schedule.clear(); + for( const WitnessID& wid : _ineligible_no_turn ) + { + _eligible.push_back( wid ); + } + _turns += _ineligible_no_turn.size(); + _ineligible_no_turn.clear(); + for( const auto& item : _ineligible_waiting_for_token ) + { + _eligible.push_back( item.first ); + _turns += (item.second ? 0 : 1); + } + _tokens += _ineligible_waiting_for_token.size(); + _ineligible_waiting_for_token.clear(); + if( debug ) check_invariant(); + + auto it = std::find( _eligible.begin(), _eligible.end(), first_witness ); + assert( it != _eligible.end() ); + + _schedule.push_back( *it ); + _ineligible_waiting_for_token.emplace_back( *it, false ); + _eligible.erase( it ); + _turns--; + _tokens--; + if( debug ) check_invariant(); + return; + } + + // keep track of total turns / tokens in existence + CountType _turns = 0; + CountType _tokens = 0; + + // new tokens handed out when _tokens < _min_token_count + CountType _min_token_count; + + // WitnessID appears in exactly one of the following: + // has no token; second indicates whether we have a turn or not: + std::deque < std::pair< WitnessID, bool > > _ineligible_waiting_for_token; // ".." | "T." + // has token, but no turn + std::vector< WitnessID > _ineligible_no_turn; // ".t" + // has token and turn + std::vector< WitnessID > _eligible; // "Tt" + + // scheduled + std::deque < WitnessID > _schedule; + + // in _schedule, but not to be replaced + set< WitnessID > _lame_duck; +}; + +template< typename WitnessID, typename RNG, typename CountType, typename OffsetType, bool debug = true > +class generic_far_future_witness_scheduler +{ + public: + generic_far_future_witness_scheduler( + const generic_witness_scheduler< WitnessID, RNG, CountType, OffsetType, debug >& base_scheduler, + RNG rng + ) + { + generic_witness_scheduler< WitnessID, RNG, CountType, OffsetType, debug > extended_scheduler = base_scheduler; + _begin_offset = base_scheduler.size()+1; + while( (extended_scheduler.produce_schedule( rng ) & emit_turn) == 0 ) + _begin_offset++; + assert( _begin_offset == extended_scheduler.size() ); + + _end_offset = _begin_offset; + while( (extended_scheduler.produce_schedule( rng ) & emit_turn) == 0 ) + _end_offset++; + assert( _end_offset == extended_scheduler.size()-1 ); + _schedule.resize( extended_scheduler._schedule.size() ); + std::copy( extended_scheduler._schedule.begin(), + extended_scheduler._schedule.end(), + _schedule.begin() ); + return; + } + + bool get_slot( OffsetType offset, WitnessID& wit )const + { + if( offset <= _end_offset ) + wit = _schedule[ offset ]; + else + wit = _schedule[ _begin_offset + + ( + (offset - _begin_offset) % + (_end_offset + 1 - _begin_offset) + ) ]; + return true; + } + + std::vector< WitnessID > _schedule; + OffsetType _begin_offset; + OffsetType _end_offset; +}; + +} } diff --git a/libraries/chain/include/graphene/chain/witness_scheduler_rng.hpp b/libraries/chain/include/graphene/chain/witness_scheduler_rng.hpp new file mode 100644 index 00000000..e7467010 --- /dev/null +++ b/libraries/chain/include/graphene/chain/witness_scheduler_rng.hpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once + +#include + +namespace graphene { namespace chain { + +/** + * Always returns 0. Useful for testing. + */ +class nullary_rng +{ + public: + nullary_rng() {} + virtual ~nullary_rng() {} + + template< typename T > T operator()( T max ) + { return T(0); } +} ; + +/** + * The sha256_ctr_rng generates bits using SHA256 in counter (CTR) + * mode. + */ +template< class HashClass, int SeedLength=sizeof(secret_hash_type) > +class hash_ctr_rng +{ + public: + hash_ctr_rng( const char* seed, uint64_t counter = 0 ) + : _counter( counter ), _current_offset( 0 ) + { + memcpy( _seed, seed, SeedLength ); + _reset_current_value(); + return; + } + + virtual ~hash_ctr_rng() {} + + uint64_t get_bits( uint8_t count ) + { + uint64_t result = 0; + uint64_t mask = 1; + // grab the requested number of bits + while( count > 0 ) + { + result |= + ( + ( + ( + _current_value.data()[ (_current_offset >> 3) & 0x1F ] + & ( 1 << (_current_offset & 0x07) ) + ) + != 0 + ) ? mask : 0 + ); + mask += mask; + --count; + ++_current_offset; + if( _current_offset == (_current_value.data_size() << 3) ) + { + _counter++; + _current_offset = 0; + _reset_current_value(); + } + } + return result; + } + + uint64_t operator()( uint64_t bound ) + { + if( bound <= 1 ) + return 0; + uint8_t bitcount = boost::multiprecision::detail::find_msb( bound ) + 1; + + // probability of loop exiting is >= 1/2, so probability of + // running N times is bounded above by (1/2)^N + while( true ) + { + uint64_t result = get_bits( bitcount ); + if( result < bound ) + return result; + } + } + + // convenience method which does casting for types other than uint64_t + template< typename T > T operator()( T bound ) + { return (T) ( (*this)(uint64_t( bound )) ); } + + void _reset_current_value() + { + // internal implementation detail, called to update + // _current_value when _counter changes + typename HashClass::encoder enc; + enc.write( _seed , SeedLength ); + enc.write( (char *) &_counter, 8 ); + _current_value = enc.result(); + return; + } + + uint64_t _counter; + char _seed[ SeedLength ]; + HashClass _current_value; + uint16_t _current_offset; + + static const int seed_length = SeedLength; +} ; + +} } diff --git a/tests/intense/block_tests.cpp b/tests/intense/block_tests.cpp index aaabf4e6..4890b1fd 100644 --- a/tests/intense/block_tests.cpp +++ b/tests/intense/block_tests.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 661252f2..968c87dc 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 9e5e5ebc..b3f9eb7a 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -916,6 +917,45 @@ BOOST_FIXTURE_TEST_CASE( pop_block_twice, database_fixture ) } } +BOOST_FIXTURE_TEST_CASE( witness_scheduler_missed_blocks, database_fixture ) +{ try { + + uint8_t witness_schedule_algorithm = db.get_global_properties().parameters.witness_schedule_algorithm; + if (witness_schedule_algorithm != GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) + db.modify(db.get_global_properties(), [](global_property_object& p) { + p.parameters.witness_schedule_algorithm = GRAPHENE_WITNESS_SCHEDULED_ALGORITHM; + }); + + db.get_near_witness_schedule(); + generate_block(); + auto near_schedule = db.get_near_witness_schedule(); + + std::for_each(near_schedule.begin(), near_schedule.end(), [&](witness_id_type id) { + generate_block(0); + BOOST_CHECK(db.get_dynamic_global_properties().current_witness == id); + }); + + near_schedule = db.get_near_witness_schedule(); + generate_block(0, init_account_priv_key, 2); + BOOST_CHECK(db.get_dynamic_global_properties().current_witness == near_schedule[2]); + + near_schedule.erase(near_schedule.begin(), near_schedule.begin() + 3); + auto new_schedule = db.get_near_witness_schedule(); + new_schedule.erase(new_schedule.end() - 3, new_schedule.end()); + BOOST_CHECK(new_schedule == near_schedule); + + std::for_each(near_schedule.begin(), near_schedule.end(), [&](witness_id_type id) { + generate_block(0); + BOOST_CHECK(db.get_dynamic_global_properties().current_witness == id); + + if (db.get_global_properties().parameters.witness_schedule_algorithm != witness_schedule_algorithm) + db.modify(db.get_global_properties(), [&witness_schedule_algorithm](global_property_object& p) { + p.parameters.witness_schedule_algorithm = witness_schedule_algorithm; + }); + + }); +} FC_LOG_AND_RETHROW() } + BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, database_fixture ) { try @@ -924,7 +964,11 @@ BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, database_fixture ) auto rsf = [&]() -> string { - fc::uint128 rsf = db.get_dynamic_global_properties().recent_slots_filled; + fc::uint128 rsf; + if (db.get_global_properties().parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) + rsf = db.get(witness_schedule_id_type()).recent_slots_filled; + else + rsf = db.get_dynamic_global_properties().recent_slots_filled; string result = ""; result.reserve(128); for( int i=0; i<128; i++ ) diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 8cefec4e..6d27f804 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -457,18 +457,55 @@ BOOST_AUTO_TEST_CASE( witness_create ) while( ((db.get_dynamic_global_properties().current_aslot + 1) % witnesses.size()) != 0 ) generate_block(); - int produced = 0; - // Make sure we get scheduled at least once in witnesses.size()*2 blocks - // may take this many unless we measure where in the scheduling round we are - // TODO: intense_test that repeats this loop many times - for( size_t i=0, n=witnesses.size()*2; i near_witnesses = db.get_near_witness_schedule(); + BOOST_CHECK( std::find( near_witnesses.begin(), near_witnesses.end(), nathan_witness_id ) + != near_witnesses.end() ); + + struct generator_helper { + database_fixture& f; + witness_id_type nathan_id; + fc::ecc::private_key nathan_key; + bool nathan_generated_block; + + void operator()(witness_id_type id) { + if( id == nathan_id ) + { + nathan_generated_block = true; + f.generate_block(0, nathan_key); + } else + f.generate_block(0); + BOOST_CHECK_EQUAL(f.db.get_dynamic_global_properties().current_witness.instance.value, id.instance.value); + f.db.get_near_witness_schedule(); + } + }; + + generator_helper h = std::for_each(near_witnesses.begin(), near_witnesses.end(), + generator_helper{*this, nathan_witness_id, nathan_private_key, false}); + BOOST_CHECK(h.nathan_generated_block); } - BOOST_CHECK_GE( produced, 1 ); -} FC_LOG_AND_RETHROW() } + + if (db.get_global_properties().parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) + { + int produced = 0; + // Make sure we get scheduled at least once in witnesses.size()*2 blocks + // may take this many unless we measure where in the scheduling round we are + // TODO: intense_test that repeats this loop many times + for( size_t i=0, n=witnesses.size()*2; i