/* * 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 // @ROL helpfull dumps when debugging //#define ROL_DUMP 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() ) { set< WitnessID > removal_set; removal_set.insert(*it); remove_all( removal_set ); _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 ) { #ifdef ROL_DUMP wdump((revised_set)); #endif 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 ); #ifdef ROL_DUMP wdump((_eligible)); wdump((_schedule)); #endif return; } /** * Get the number of scheduled witnesses */ size_t size( )const { return _schedule.size(); } bool get_slot( OffsetType offset, WitnessID& wit )const { #ifdef ROL_DUMP wdump((_schedule)(_schedule.size())(offset)); #endif if (_schedule.empty()) return false; //@ROL wit is 1.6.0! if( offset >= _schedule.size() ) { wit = _schedule[ 0 ]; 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 ) { #ifdef ROL_DUMP wdump((first_witness)); #endif _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(); #ifdef ROL_DUMP wdump((_eligible)); #endif auto it = std::find( _eligible.begin(), _eligible.end(), first_witness ); //@ROL: maybe good idea, when wit is 1.6.0 if ( it == _eligible.end() ) return; 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 must be removed 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; }; } }