integrating scheduled (old) and shuffled (current) witnesses

This commit is contained in:
Roman Olearski 2017-04-19 20:31:00 +02:00
parent 94d681181c
commit 90ec694446
15 changed files with 895 additions and 30 deletions

View file

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

View file

@ -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>([&](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<init_witnesses.size(); ++i )
_wso.scheduler.produce_schedule(rng);
_wso.last_scheduling_block = 0;
_wso.recent_slots_filled = fc::uint128::max_value();
});
assert( wso.id == witness_schedule_id_type() );
// Enable fees
modify(get_global_properties(), [&genesis_state](global_property_object& p) {
p.parameters.current_fees = genesis_state.initial_parameters.current_fees;

View file

@ -45,6 +45,7 @@
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/vote_count.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/chain/worker_object.hpp>
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()

View file

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

View file

@ -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<witness_id_type> database::get_near_witness_schedule()const
{
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
vector<witness_id_type> 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;
}
} }

View file

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

View file

@ -240,7 +240,9 @@ namespace graphene { namespace chain {
*/
uint32_t get_slot_at_time(fc::time_point_sec when)const;
vector<witness_id_type> get_near_witness_schedule()const;
void update_witness_schedule();
void update_witness_schedule(const signed_block& next_block);
//////////////////// db_getter.cpp ////////////////////

View file

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

View file

@ -25,24 +25,82 @@
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/witness_scheduler.hpp>
#include <graphene/chain/witness_scheduler_rng.hpp>
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<witness_schedule_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)
)

View file

@ -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 <algorithm>
#include <deque>
#include <memory>
#include <vector>
#include <boost/container/flat_set.hpp>
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;
};
} }

View file

@ -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 <boost/multiprecision/integer.hpp>
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;
} ;
} }

View file

@ -31,6 +31,7 @@
#include <graphene/chain/protocol/protocol.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <fc/crypto/digest.hpp>

View file

@ -29,6 +29,7 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/witness_scheduler_rng.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/db/simple_index.hpp>

View file

@ -32,6 +32,7 @@
#include <graphene/chain/committee_member_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/market_object.hpp>
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/utilities/tempdir.hpp>
@ -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++ )

View file

@ -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<n; i++ )
if (db.get_global_properties().parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
{
signed_block block = generate_block();
if( block.witness == nathan_witness_id )
produced++;
generate_blocks(witnesses.size());
// make sure we're scheduled to produce
vector<witness_id_type> 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<n; i++ )
{
signed_block block = generate_block();
if( block.witness == nathan_witness_id )
produced++;
}
BOOST_CHECK_GE( produced, 1 );
}
} FC_LOG_AND_RETHROW()
}
/**
* This test should verify that the asset_global_settle operation works as expected,