Remove block randomness and rewrite witness scheduling
This commit is contained in:
parent
dcc4f8076b
commit
6c052294e1
22 changed files with 123 additions and 1022 deletions
|
|
@ -781,7 +781,6 @@ namespace graphene { namespace app {
|
|||
break;
|
||||
} case impl_block_summary_object_type:{
|
||||
} case impl_account_transaction_history_object_type:{
|
||||
} case impl_witness_schedule_object_type: {
|
||||
} case impl_chain_property_object_type: {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,24 +267,6 @@ signed_block database::_generate_block(
|
|||
|
||||
_pending_block.timestamp = when;
|
||||
|
||||
// Genesis witnesses start with a default initial secret
|
||||
if( witness_obj.next_secret_hash == secret_hash_type::hash( secret_hash_type() ) )
|
||||
{
|
||||
_pending_block.previous_secret = secret_hash_type();
|
||||
}
|
||||
else
|
||||
{
|
||||
secret_hash_type::encoder last_enc;
|
||||
fc::raw::pack( last_enc, block_signing_private_key );
|
||||
fc::raw::pack( last_enc, witness_obj.previous_secret );
|
||||
_pending_block.previous_secret = last_enc.result();
|
||||
}
|
||||
|
||||
secret_hash_type::encoder next_enc;
|
||||
fc::raw::pack( next_enc, block_signing_private_key );
|
||||
fc::raw::pack( next_enc, _pending_block.previous_secret );
|
||||
_pending_block.next_secret_hash = secret_hash_type::hash(next_enc.result());
|
||||
|
||||
_pending_block.transaction_merkle_root = _pending_block.calculate_merkle_root();
|
||||
|
||||
_pending_block.witness = witness_id;
|
||||
|
|
@ -404,7 +386,6 @@ void database::_apply_block( const signed_block& next_block )
|
|||
++_current_trx_in_block;
|
||||
}
|
||||
|
||||
update_witness_schedule(next_block);
|
||||
update_global_dynamic_data(next_block);
|
||||
update_signing_witness(signing_witness, next_block);
|
||||
|
||||
|
|
@ -555,8 +536,6 @@ const witness_object& database::validate_block_header( uint32_t skip, const sign
|
|||
FC_ASSERT( _pending_block.previous == next_block.previous, "", ("pending.prev",_pending_block.previous)("next.prev",next_block.previous) );
|
||||
FC_ASSERT( _pending_block.timestamp <= next_block.timestamp, "", ("_pending_block.timestamp",_pending_block.timestamp)("next",next_block.timestamp)("blocknum",next_block.block_num()) );
|
||||
const witness_object& witness = next_block.witness(*this);
|
||||
FC_ASSERT( secret_hash_type::hash( next_block.previous_secret ) == witness.next_secret_hash, "",
|
||||
("previous_secret", next_block.previous_secret)("next_secret_hash", witness.next_secret_hash));
|
||||
|
||||
if( !(skip&skip_witness_signature) )
|
||||
FC_ASSERT( next_block.validate_signee( witness.signing_key ) );
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
#include <graphene/chain/vesting_balance_object.hpp>
|
||||
#include <graphene/chain/withdraw_permission_object.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
#include <graphene/chain/witness_schedule_object.hpp>
|
||||
|
||||
#include <graphene/chain/account_evaluator.hpp>
|
||||
#include <graphene/chain/asset_evaluator.hpp>
|
||||
|
|
@ -107,9 +106,6 @@ const uint8_t withdraw_permission_object::type_id;
|
|||
const uint8_t witness_object::space_id;
|
||||
const uint8_t witness_object::type_id;
|
||||
|
||||
const uint8_t witness_schedule_object::space_id;
|
||||
const uint8_t witness_schedule_object::type_id;
|
||||
|
||||
const uint8_t worker_object::space_id;
|
||||
const uint8_t worker_object::type_id;
|
||||
|
||||
|
|
@ -193,7 +189,6 @@ void database::initialize_indexes()
|
|||
add_index< primary_index<simple_index<account_statistics_object >> >();
|
||||
add_index< primary_index<simple_index<asset_dynamic_data_object >> >();
|
||||
add_index< primary_index<flat_index< block_summary_object >> >();
|
||||
add_index< primary_index<simple_index<witness_schedule_object >> >();
|
||||
add_index< primary_index<simple_index<chain_property_object > > >();
|
||||
}
|
||||
|
||||
|
|
@ -317,6 +312,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
|||
p.time = genesis_state.initial_timestamp;
|
||||
p.dynamic_flags = 0;
|
||||
p.witness_budget = 0;
|
||||
p.recent_slots_filled = fc::uint128::max_value();
|
||||
});
|
||||
|
||||
FC_ASSERT( (genesis_state.immutable_parameters.min_witness_count & 1) == 1, "min_witness_count must be odd" );
|
||||
|
|
@ -538,31 +534,6 @@ 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;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <boost/multiprecision/integer.hpp>
|
||||
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
#include <fc/uint128.hpp>
|
||||
|
||||
|
|
@ -27,7 +29,6 @@
|
|||
#include <graphene/chain/global_property_object.hpp>
|
||||
#include <graphene/chain/vesting_balance_object.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
#include <graphene/chain/witness_schedule_object.hpp>
|
||||
#include <graphene/chain/worker_evaluator.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
|
@ -206,11 +207,6 @@ 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()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
|
|
|
|||
|
|
@ -36,25 +36,18 @@ void database::update_global_dynamic_data( const signed_block& b )
|
|||
const dynamic_global_property_object& _dgp =
|
||||
dynamic_global_property_id_type(0)(*this);
|
||||
|
||||
const auto& global_props = get_global_properties();
|
||||
auto delta_time = b.timestamp - _dgp.time;
|
||||
auto missed_blocks = (delta_time.to_seconds() / global_props.parameters.block_interval) - 1;
|
||||
if( _dgp.head_block_number == 0 )
|
||||
missed_blocks = 0;
|
||||
uint32_t missed_blocks = get_slot_at_time( b.timestamp );
|
||||
assert( missed_blocks != 0 );
|
||||
missed_blocks--;
|
||||
|
||||
// dynamic global properties updating
|
||||
modify( _dgp, [&]( dynamic_global_property_object& dgp ){
|
||||
secret_hash_type::encoder enc;
|
||||
fc::raw::pack( enc, dgp.random );
|
||||
fc::raw::pack( enc, b.previous_secret );
|
||||
dgp.random = enc.result();
|
||||
|
||||
if( _checkpoints.size() && _checkpoints.rbegin()->first >= b.block_num() )
|
||||
dgp.recently_missed_count = 0;
|
||||
else if( missed_blocks )
|
||||
dgp.recently_missed_count += 4*missed_blocks;
|
||||
else if( dgp.recently_missed_count > 4 )
|
||||
dgp.recently_missed_count -= 3;
|
||||
dgp.recently_missed_count += GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT*missed_blocks;
|
||||
else if( dgp.recently_missed_count > GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT )
|
||||
dgp.recently_missed_count -= GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT;
|
||||
else if( dgp.recently_missed_count > 0 )
|
||||
dgp.recently_missed_count--;
|
||||
|
||||
|
|
@ -62,6 +55,10 @@ void database::update_global_dynamic_data( const signed_block& b )
|
|||
dgp.head_block_id = b.id();
|
||||
dgp.time = b.timestamp;
|
||||
dgp.current_witness = b.witness;
|
||||
dgp.recent_slots_filled = (
|
||||
(dgp.recent_slots_filled << 1)
|
||||
+ 1) << missed_blocks;
|
||||
dgp.current_aslot += missed_blocks+1;
|
||||
});
|
||||
|
||||
if( !(get_node_properties().skip_flags & skip_undo_history_check) )
|
||||
|
|
@ -80,6 +77,7 @@ void database::update_signing_witness(const witness_object& signing_witness, con
|
|||
{
|
||||
const global_property_object& gpo = get_global_properties();
|
||||
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
|
||||
uint64_t new_block_aslot = dpo.current_aslot + get_slot_at_time( new_block.timestamp );
|
||||
|
||||
share_type witness_pay = std::min( gpo.parameters.witness_pay_per_block, dpo.witness_budget );
|
||||
|
||||
|
|
@ -92,8 +90,7 @@ void database::update_signing_witness(const witness_object& signing_witness, con
|
|||
|
||||
modify( signing_witness, [&]( witness_object& _wit )
|
||||
{
|
||||
_wit.previous_secret = new_block.previous_secret;
|
||||
_wit.next_secret_hash = new_block.next_secret_hash;
|
||||
_wit.last_aslot = new_block_aslot;
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,45 +15,87 @@
|
|||
* 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 <graphene/chain/database.hpp>
|
||||
|
||||
#include <graphene/chain/global_property_object.hpp>
|
||||
#include <graphene/chain/witness_schedule_object.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
witness_id_type database::get_scheduled_witness(uint32_t slot_num)const
|
||||
using boost::container::flat_set;
|
||||
|
||||
witness_id_type database::get_scheduled_witness( uint32_t slot_num )const
|
||||
{
|
||||
if( slot_num == 0 )
|
||||
return witness_id_type();
|
||||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
|
||||
const flat_set< witness_id_type >& active_witnesses = get_global_properties().active_witnesses;
|
||||
uint32_t n = active_witnesses.size();
|
||||
uint64_t min_witness_separation = (n / 2)+1;
|
||||
uint64_t current_aslot = get_dynamic_global_properties().current_aslot + slot_num;
|
||||
|
||||
// ask the near scheduler who goes in the given slot
|
||||
witness_id_type wid;
|
||||
bool slot_is_near = wso.scheduler.get_slot(slot_num-1, wid);
|
||||
if( ! slot_is_near )
|
||||
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;
|
||||
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 )
|
||||
{
|
||||
// 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.
|
||||
const witness_object& wit = wit_id(*this);
|
||||
if( wit.last_aslot >= first_ineligible_aslot )
|
||||
continue;
|
||||
|
||||
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) )
|
||||
uint64_t k = now_hi + uint64_t(wit_id);
|
||||
k ^= (k >> 12);
|
||||
k ^= (k << 25);
|
||||
k ^= (k >> 27);
|
||||
k *= 2685821657736338717ULL;
|
||||
if( k >= best_k )
|
||||
{
|
||||
// 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 );
|
||||
best_k = k;
|
||||
best_wit = wit_id;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
return wid;
|
||||
|
||||
// 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)
|
||||
|
||||
assert( success );
|
||||
return best_wit;
|
||||
}
|
||||
|
||||
fc::time_point_sec database::get_slot_time(uint32_t slot_num)const
|
||||
|
|
@ -98,89 +140,10 @@ uint32_t database::get_slot_at_time(fc::time_point_sec when)const
|
|||
return (when - first_slot_time).to_seconds() / block_interval() + 1;
|
||||
}
|
||||
|
||||
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 witness_schedule_object& wso = get(witness_schedule_id_type());
|
||||
return uint64_t(GRAPHENE_100_PERCENT) * wso.recent_slots_filled.popcount() / 128;
|
||||
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
|
||||
return uint64_t(GRAPHENE_100_PERCENT) * dpo.recent_slots_filled.popcount() / 128;
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -139,6 +139,9 @@
|
|||
|
||||
#define GRAPHENE_MAX_INTEREST_APR uint16_t( 10000 )
|
||||
|
||||
#define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4
|
||||
#define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3
|
||||
|
||||
/**
|
||||
* Reserved Account IDs with special meaning
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -244,11 +244,6 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
uint32_t get_slot_at_time(fc::time_point_sec when)const;
|
||||
|
||||
/**
|
||||
* Get the near schedule.
|
||||
*/
|
||||
vector<witness_id_type> get_near_witness_schedule()const;
|
||||
|
||||
//////////////////// db_getter.cpp ////////////////////
|
||||
|
||||
const chain_id_type& get_chain_id()const;
|
||||
|
|
@ -451,9 +446,6 @@ namespace graphene { namespace chain {
|
|||
void update_maintenance_flag( bool new_maintenance_flag );
|
||||
void update_withdraw_permissions();
|
||||
|
||||
//////////////////// db_witness_schedule.cpp ////////////////////
|
||||
void update_witness_schedule(const signed_block& next_block); /// no-op except for scheduling blocks
|
||||
|
||||
///Steps performed only at maintenance intervals
|
||||
///@{
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace graphene { namespace chain {
|
|||
chain_parameters parameters;
|
||||
optional<chain_parameters> pending_parameters;
|
||||
|
||||
uint32_t next_available_vote_id = 0;
|
||||
uint32_t next_available_vote_id = 0;
|
||||
vector<committee_member_id_type> active_committee_members; // updated once per maintenance interval
|
||||
flat_set<witness_id_type> active_witnesses; // updated once per maintenance interval
|
||||
// n.b. witness scheduling is done by witness_schedule object
|
||||
|
|
@ -64,7 +64,6 @@ namespace graphene { namespace chain {
|
|||
static const uint8_t space_id = implementation_ids;
|
||||
static const uint8_t type_id = impl_dynamic_global_property_object_type;
|
||||
|
||||
secret_hash_type random;
|
||||
uint32_t head_block_number = 0;
|
||||
block_id_type head_block_id;
|
||||
time_point_sec time;
|
||||
|
|
@ -74,13 +73,28 @@ namespace graphene { namespace chain {
|
|||
share_type witness_budget;
|
||||
uint32_t accounts_registered_this_interval = 0;
|
||||
/**
|
||||
* Every time a block is missed this increases by 2, every time a block is found it decreases by 1 it is
|
||||
* never less than 0
|
||||
* Every time a block is missed this increases by
|
||||
* RECENTLY_MISSED_COUNT_INCREMENT,
|
||||
* every time a block is found it decreases by
|
||||
* RECENTLY_MISSED_COUNT_DECREMENT. It is
|
||||
* never less than 0.
|
||||
*
|
||||
* If the recently_missed_count hits 2*UNDO_HISTORY then no ew blocks may be pushed.
|
||||
* If the recently_missed_count hits 2*UNDO_HISTORY then no new blocks may be pushed.
|
||||
*/
|
||||
uint32_t recently_missed_count = 0;
|
||||
|
||||
/**
|
||||
* The current absolute slot number. Equal to the total
|
||||
* number of slots since genesis. Also equal to the total
|
||||
* number of missed slots plus head_block_number.
|
||||
*/
|
||||
uint64_t current_aslot = 0;
|
||||
|
||||
/**
|
||||
* used to compute witness participation.
|
||||
*/
|
||||
fc::uint128_t recent_slots_filled;
|
||||
|
||||
/**
|
||||
* dynamic_flags specifies chain state properties that can be
|
||||
* expressed in one bit.
|
||||
|
|
@ -104,7 +118,6 @@ namespace graphene { namespace chain {
|
|||
}}
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene::db::object),
|
||||
(random)
|
||||
(head_block_number)
|
||||
(head_block_id)
|
||||
(time)
|
||||
|
|
@ -113,6 +126,8 @@ FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene::
|
|||
(witness_budget)
|
||||
(accounts_registered_this_interval)
|
||||
(recently_missed_count)
|
||||
(current_aslot)
|
||||
(recent_slots_filled)
|
||||
(dynamic_flags)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ namespace graphene { namespace chain {
|
|||
uint32_t block_num()const { return num_from_id(previous) + 1; }
|
||||
fc::time_point_sec timestamp;
|
||||
witness_id_type witness;
|
||||
secret_hash_type next_secret_hash;
|
||||
secret_hash_type previous_secret;
|
||||
checksum_type transaction_merkle_root;
|
||||
extensions_type extensions;
|
||||
|
||||
|
|
@ -53,7 +51,6 @@ namespace graphene { namespace chain {
|
|||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT( graphene::chain::block_header, (previous)(timestamp)(witness)
|
||||
(next_secret_hash)(previous_secret)(transaction_merkle_root)(extensions) )
|
||||
FC_REFLECT( graphene::chain::block_header, (previous)(timestamp)(witness)(transaction_merkle_root)(extensions) )
|
||||
FC_REFLECT_DERIVED( graphene::chain::signed_block_header, (graphene::chain::block_header), (witness_signature) )
|
||||
FC_REFLECT_DERIVED( graphene::chain::signed_block, (graphene::chain::signed_block_header), (transactions) )
|
||||
|
|
|
|||
|
|
@ -140,7 +140,6 @@ namespace graphene { namespace chain {
|
|||
impl_transaction_object_type,
|
||||
impl_block_summary_object_type,
|
||||
impl_account_transaction_history_object_type,
|
||||
impl_witness_schedule_object_type,
|
||||
impl_blinded_balance_object_type,
|
||||
impl_chain_property_object_type
|
||||
};
|
||||
|
|
@ -165,7 +164,6 @@ namespace graphene { namespace chain {
|
|||
class operation_history_object;
|
||||
class withdraw_permission_object;
|
||||
class vesting_balance_object;
|
||||
class witness_schedule_object;
|
||||
class worker_object;
|
||||
class balance_object;
|
||||
|
||||
|
|
@ -209,7 +207,6 @@ namespace graphene { namespace chain {
|
|||
typedef object_id< implementation_ids,
|
||||
impl_account_transaction_history_object_type,
|
||||
account_transaction_history_object> account_transaction_history_id_type;
|
||||
typedef object_id< implementation_ids, impl_witness_schedule_object_type, witness_schedule_object > witness_schedule_id_type;
|
||||
typedef object_id< implementation_ids, impl_chain_property_object_type, chain_property_object> chain_property_id_type;
|
||||
|
||||
typedef fc::array<char, GRAPHENE_MAX_ASSET_SYMBOL_LENGTH> symbol_type;
|
||||
|
|
@ -286,7 +283,6 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
|
|||
(impl_transaction_object_type)
|
||||
(impl_block_summary_object_type)
|
||||
(impl_account_transaction_history_object_type)
|
||||
(impl_witness_schedule_object_type)
|
||||
(impl_blinded_balance_object_type)
|
||||
(impl_chain_property_object_type)
|
||||
)
|
||||
|
|
@ -317,7 +313,6 @@ FC_REFLECT_TYPENAME( graphene::chain::account_statistics_id_type )
|
|||
FC_REFLECT_TYPENAME( graphene::chain::transaction_obj_id_type )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::block_summary_id_type )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::account_transaction_history_id_type )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::witness_schedule_id_type )
|
||||
FC_REFLECT( graphene::chain::void_t, )
|
||||
|
||||
FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, (charge_market_fee)(white_list)(transfer_restricted)(override_authority)(disable_force_settle)(global_settle)(disable_confidential) )
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace graphene { namespace chain {
|
|||
static const uint8_t type_id = witness_object_type;
|
||||
|
||||
account_id_type witness_account;
|
||||
uint64_t last_aslot = 0;
|
||||
public_key_type signing_key;
|
||||
secret_hash_type next_secret_hash;
|
||||
secret_hash_type previous_secret;
|
||||
|
|
@ -64,9 +65,8 @@ namespace graphene { namespace chain {
|
|||
|
||||
FC_REFLECT_DERIVED( graphene::chain::witness_object, (graphene::db::object),
|
||||
(witness_account)
|
||||
(last_aslot)
|
||||
(signing_key)
|
||||
(next_secret_hash)
|
||||
(previous_secret)
|
||||
(pay_vb)
|
||||
(vote_id)
|
||||
(total_votes)
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// needed to serialize witness_scheduler
|
||||
#include <fc/container/deque.hpp>
|
||||
#include <fc/uint128.hpp>
|
||||
|
||||
#include <graphene/chain/protocol/types.hpp>
|
||||
#include <graphene/chain/witness_scheduler.hpp>
|
||||
#include <graphene/chain/witness_scheduler_rng.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
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;
|
||||
|
||||
class witness_schedule_object : public 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;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
FC_REFLECT( graphene::chain::witness_scheduler,
|
||||
(_turns)
|
||||
(_tokens)
|
||||
(_min_token_count)
|
||||
(_ineligible_waiting_for_token)
|
||||
(_ineligible_no_turn)
|
||||
(_eligible)
|
||||
(_schedule)
|
||||
(_lame_duck)
|
||||
)
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::witness_schedule_object, (graphene::chain::object),
|
||||
(scheduler)
|
||||
(last_scheduling_block)
|
||||
(slots_since_genesis)
|
||||
(rng_seed)
|
||||
(recent_slots_filled)
|
||||
)
|
||||
|
|
@ -1,417 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
};
|
||||
|
||||
} }
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
} ;
|
||||
|
||||
} }
|
||||
|
|
@ -478,7 +478,6 @@ public:
|
|||
result["chain_id"] = chain_props.chain_id;
|
||||
result["active_witnesses"] = global_props.active_witnesses;
|
||||
result["active_committee_members"] = global_props.active_committee_members;
|
||||
result["entropy"] = dynamic_props.random;
|
||||
return result;
|
||||
}
|
||||
chain_property_object get_chain_properties() const
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ int main( int argc, char** argv )
|
|||
std::cout << "\n";
|
||||
}
|
||||
std::cout << "]\n";
|
||||
std::cerr << "Size of block header: " << sizeof( block_header ) << " " << fc::raw::pack_size( block_header() ) << "\n";
|
||||
}
|
||||
catch ( const fc::exception& e ){ edump((e.to_detail_string())); }
|
||||
idump((sizeof(signed_block)));
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#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>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
#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>
|
||||
|
|
@ -166,7 +165,6 @@ BOOST_AUTO_TEST_CASE( price_test )
|
|||
BOOST_CHECK(dummy == dummy2);
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( memo_test )
|
||||
{ try {
|
||||
memo_data m;
|
||||
|
|
@ -186,134 +184,6 @@ BOOST_AUTO_TEST_CASE( memo_test )
|
|||
BOOST_CHECK_EQUAL(m.get_message(receiver, sender.get_public_key()), "Hello, world!");
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
BOOST_AUTO_TEST_CASE( witness_rng_test_bits )
|
||||
{
|
||||
try
|
||||
{
|
||||
const uint64_t COUNT = 131072;
|
||||
const uint64_t HASH_SIZE = 32;
|
||||
string ref_bits = "";
|
||||
ref_bits.reserve( COUNT * HASH_SIZE );
|
||||
static const char seed_data[] = "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55";
|
||||
|
||||
for( uint64_t i=0; i<COUNT; i++ )
|
||||
{
|
||||
// grab the bits
|
||||
fc::sha256::encoder enc;
|
||||
enc.write( seed_data, HASH_SIZE );
|
||||
enc.put( char((i ) & 0xFF) );
|
||||
enc.put( char((i >> 0x08) & 0xFF) );
|
||||
enc.put( char((i >> 0x10) & 0xFF) );
|
||||
enc.put( char((i >> 0x18) & 0xFF) );
|
||||
enc.put( char((i >> 0x20) & 0xFF) );
|
||||
enc.put( char((i >> 0x28) & 0xFF) );
|
||||
enc.put( char((i >> 0x30) & 0xFF) );
|
||||
enc.put( char((i >> 0x38) & 0xFF) );
|
||||
|
||||
fc::sha256 result = enc.result();
|
||||
auto result_data = result.data();
|
||||
std::copy( result_data, result_data+HASH_SIZE, std::back_inserter( ref_bits ) );
|
||||
}
|
||||
|
||||
fc::sha256 seed = fc::sha256::hash( string("") );
|
||||
// seed = sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
BOOST_CHECK( memcmp( seed.data(), seed_data, HASH_SIZE ) == 0 );
|
||||
|
||||
hash_ctr_rng< fc::sha256, 32 > test_rng(seed.data(), 0);
|
||||
// python2 -c 'import hashlib; import struct; h = lambda x : hashlib.sha256(x).digest(); i = lambda x : struct.pack("<Q", x); print( h( h("") + i(0) ) )' | hd
|
||||
string ref_bits_hex =
|
||||
"5c5d42dcf39f71c0226ca720d8d518db615b5773f038e5e491963f6f47621bbd" // h( h("") + i(0) )
|
||||
"43fd6dae047c400060be262e6d443200eacd1fafcb77828638085c2e2341fd8d" // h( h("") + i(1) )
|
||||
"d666330a7441dc7279b786e65aba32817275989cfc691b3901f000fb0f14cd05" // h( h("") + i(2) )
|
||||
"34bd93f83d7bac4a667d62fee39bd5eb1991fbadc29a5f216ea746772ca31544" // h( h("") + i(3) )
|
||||
"d3b41a093eab01cd25f987a909b2f4812b0f38475e0fe40f6f42a12c6e018aa7" // ...
|
||||
"c8db17b946c5a6bceaa7b903c93e6ccb8cc6c09b0cfd2108d930de1a79c3a68e"
|
||||
"cc1945b36c82e356b6d127057d036a150cb03b760e9c9e706c560f32a749e80d"
|
||||
"872b28fe97e289d4f6f361f3427d454113e3b513892d129398dac4daf8a0e43e"
|
||||
"8d7a5a2f3cbb245fa471e87e30a38d9c775c985c28db6e521e34cf1e88507c26"
|
||||
"c662f230eed0f10899c3a74a2d1bfb88d732909b206a2aed3ae0bda728fac8fe"
|
||||
"38eface8b1d473e45cbb40603bcef8bf2219e55669c7a2cfb5f8d52610689f14"
|
||||
"3b1d1734273b069a7de7cc6dd2e80db09d1feff200c9bdaf033cd553ea40e05d"
|
||||
"16653ca7aa7f790a95c6a8d41e5694b0c6bff806c3ce3e0e320253d408fb6f27"
|
||||
"b55df71d265de0b86a1cdf45d1d9c53da8ebf0ceec136affa12228d0d372e698"
|
||||
"37e9305ce57d386d587039b49b67104fd4d8467e87546237afc9a90cf8c677f9"
|
||||
"fc26784c94f754cf7aeacb6189e705e2f1873ea112940560f11dbbebb22a8922"
|
||||
;
|
||||
char* ref_bits_chars = new char[ ref_bits_hex.length() / 2 ];
|
||||
fc::from_hex( ref_bits_hex, ref_bits_chars, ref_bits_hex.length() / 2 );
|
||||
string ref_bits_str( ref_bits_chars, ref_bits_hex.length() / 2 );
|
||||
delete[] ref_bits_chars;
|
||||
ref_bits_chars = nullptr;
|
||||
|
||||
BOOST_CHECK( ref_bits_str.length() < ref_bits.length() );
|
||||
BOOST_CHECK( ref_bits_str == ref_bits.substr( 0, ref_bits_str.length() ) );
|
||||
//std::cout << "ref_bits_str: " << fc::to_hex( ref_bits_str.c_str(), std::min( ref_bits_str.length(), size_t(256) ) ) << "\n";
|
||||
//std::cout << "ref_bits : " << fc::to_hex( ref_bits .c_str(), std::min( ref_bits.length(), size_t(256) ) ) << "\n";
|
||||
|
||||
// when we get to this point, our code to generate the RNG byte output is working.
|
||||
// now let's test get_bits() as follows:
|
||||
|
||||
uint64_t ref_get_bits_offset = 0;
|
||||
|
||||
auto ref_get_bits = [&]( uint8_t count ) -> uint64_t
|
||||
{
|
||||
uint64_t result = 0;
|
||||
uint64_t i = ref_get_bits_offset;
|
||||
uint64_t mask = 1;
|
||||
while( count > 0 )
|
||||
{
|
||||
if( ref_bits[ i >> 3 ] & (1 << (i & 7)) )
|
||||
result |= mask;
|
||||
mask += mask;
|
||||
i++;
|
||||
count--;
|
||||
}
|
||||
ref_get_bits_offset = i;
|
||||
return result;
|
||||
};
|
||||
|
||||
// use PRNG to decide between 0-64 bits
|
||||
std::minstd_rand rng;
|
||||
rng.seed( 9999 );
|
||||
std::uniform_int_distribution< uint16_t > bit_dist( 0, 64 );
|
||||
for( int i=0; i<10000; i++ )
|
||||
{
|
||||
uint8_t bit_count = bit_dist( rng );
|
||||
uint64_t ref_bits = ref_get_bits( bit_count );
|
||||
uint64_t test_bits = test_rng.get_bits( bit_count );
|
||||
//std::cout << i << ": get(" << int(bit_count) << ") -> " << test_bits << " (expect " << ref_bits << ")\n";
|
||||
if( bit_count < 64 )
|
||||
{
|
||||
BOOST_CHECK( ref_bits < (uint64_t( 1 ) << bit_count ) );
|
||||
BOOST_CHECK( test_bits < (uint64_t( 1 ) << bit_count ) );
|
||||
}
|
||||
BOOST_CHECK( ref_bits == test_bits );
|
||||
if( ref_bits != test_bits )
|
||||
break;
|
||||
}
|
||||
|
||||
std::uniform_int_distribution< uint64_t > whole_dist(
|
||||
0, std::numeric_limits<uint64_t>::max() );
|
||||
for( int i=0; i<10000; i++ )
|
||||
{
|
||||
uint8_t bit_count = bit_dist( rng );
|
||||
uint64_t bound = whole_dist( rng ) & ((uint64_t(1) << bit_count) - 1);
|
||||
//std::cout << "bound:" << bound << "\n";
|
||||
uint64_t rnum = test_rng( bound );
|
||||
//std::cout << "rnum:" << rnum << "\n";
|
||||
if( bound > 1 )
|
||||
{
|
||||
BOOST_CHECK( rnum < bound );
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_CHECK( rnum == 0 );
|
||||
}
|
||||
}
|
||||
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( exceptions )
|
||||
{
|
||||
GRAPHENE_CHECK_THROW(FC_THROW_EXCEPTION(balance_claim_invalid_claim_amount, "Etc"), balance_claim_invalid_claim_amount);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#include <graphene/chain/committee_member_object.hpp>
|
||||
#include <graphene/chain/proposal_object.hpp>
|
||||
#include <graphene/chain/market_evaluator.hpp>
|
||||
#include <graphene/chain/witness_schedule_object.hpp>
|
||||
|
||||
#include <graphene/utilities/tempdir.hpp>
|
||||
|
||||
|
|
@ -644,7 +643,6 @@ BOOST_FIXTURE_TEST_CASE( maintenance_interval, database_fixture )
|
|||
initial_properties.parameters.maximum_transaction_size);
|
||||
BOOST_CHECK_EQUAL(db.get_dynamic_global_properties().next_maintenance_time.sec_since_epoch(),
|
||||
db.head_block_time().sec_since_epoch() + db.get_global_properties().parameters.block_interval);
|
||||
// shuffling is now handled by the witness_schedule_object.
|
||||
BOOST_CHECK(db.get_global_properties().active_witnesses == initial_properties.active_witnesses);
|
||||
BOOST_CHECK(db.get_global_properties().active_committee_members == initial_properties.active_committee_members);
|
||||
|
||||
|
|
@ -870,32 +868,6 @@ BOOST_FIXTURE_TEST_CASE( pop_block_twice, database_fixture )
|
|||
}
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE( witness_scheduler_missed_blocks, database_fixture )
|
||||
{ try {
|
||||
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);
|
||||
});
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, database_fixture )
|
||||
{
|
||||
try
|
||||
|
|
@ -904,7 +876,7 @@ BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, database_fixture )
|
|||
|
||||
auto rsf = [&]() -> string
|
||||
{
|
||||
fc::uint128 rsf = db.get( witness_schedule_id_type() ).recent_slots_filled;
|
||||
fc::uint128 rsf = db.get_dynamic_global_properties().recent_slots_filled;
|
||||
string result = "";
|
||||
result.reserve(128);
|
||||
for( int i=0; i<128; i++ )
|
||||
|
|
|
|||
|
|
@ -443,34 +443,15 @@ BOOST_AUTO_TEST_CASE( witness_create )
|
|||
auto itr = std::find(witnesses.begin(), witnesses.end(), nathan_witness_id);
|
||||
BOOST_CHECK(itr != witnesses.end());
|
||||
|
||||
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);
|
||||
int produced = 0;
|
||||
// Make sure we get scheduled exactly once in witnesses.size() blocks
|
||||
// TODO: intense_test that repeats this loop many times
|
||||
for( size_t i=0; i<witnesses.size(); i++ )
|
||||
{
|
||||
if( generate_block().witness == nathan_witness_id )
|
||||
produced++;
|
||||
}
|
||||
BOOST_CHECK_EQUAL( produced, 1 );
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue