Issue #160 - Dynamic Undo History / Minority Fork

The blockchain now has a minimal participation requirement that can only
be overridden with checkpoints.  Any time participation falls below a
minimal level no new blocks may be added.

Currently it requires 66% participation and tolerates short periods of
time below 66% participation with a maximum of 500 consecutive blocks
missed.  For every two blocks produced 1 can be missed with a slack of
999 bias.
This commit is contained in:
Daniel Larimer 2015-07-15 14:13:24 -04:00
parent b24006cca3
commit 7f54d3d077
10 changed files with 55 additions and 9 deletions

View file

@ -25,6 +25,7 @@
#include <graphene/chain/transaction_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <graphene/chain/exceptions.hpp>
namespace graphene { namespace chain {
@ -305,7 +306,9 @@ signed_block database::_generate_block(
_pending_block.transactions.clear();
bool failed = false;
try { push_block( tmp, skip ); } catch ( const fc::exception& e ) { failed = true; }
try { push_block( tmp, skip ); }
catch ( const undo_database_exception& e ) { throw; }
catch ( const fc::exception& e ) { failed = true; }
if( failed )
{
for( const auto& trx : tmp.transactions )
@ -378,6 +381,14 @@ void database::apply_block( const signed_block& next_block, uint32_t skip )
{
// WE CAN SKIP ALMOST EVERYTHING
skip = ~0;
/** clear the recently missed count because the checkpoint indicates that
* we will never have to go back further than this.
*/
const auto& _dgp = dynamic_global_property_id_type(0)(*this);
modify( _dgp, [&]( dynamic_global_property_object& dgp ){
dgp.recently_missed_count = 0;
});
}
}

View file

@ -156,6 +156,7 @@ void database::initialize_evaluators()
void database::initialize_indexes()
{
reset_indexes();
_undo_db.set_max_size( GRAPHENE_MIN_UNDO_HISTORY );
//Protocol object indexes
add_index< primary_index<asset_index> >();

View file

@ -36,6 +36,10 @@ 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;
//
// dynamic global properties updating
//
@ -44,11 +48,27 @@ void database::update_global_dynamic_data( const signed_block& b )
fc::raw::pack( enc, dgp.random );
fc::raw::pack( enc, b.previous_secret );
dgp.random = enc.result();
if( missed_blocks )
dgp.recently_missed_count += 2*missed_blocks;
else if( dgp.recently_missed_count > 0 )
dgp.recently_missed_count--;
dgp.head_block_number = b.block_num();
dgp.head_block_id = b.id();
dgp.time = b.timestamp;
dgp.current_witness = b.witness;
});
if( !(get_node_properties().skip_flags & skip_undo_history_check) )
{
GRAPHENE_ASSERT( _dgp.recently_missed_count < GRAPHENE_MAX_UNDO_HISTORY, undo_database_exception,
"The database does not have enough undo history to support a blockchain with so many missed blocks. "
"Please add a checkpoint if you would like to continue applying blocks beyond this point.",
("recently_missed",_dgp.recently_missed_count)("max_undo",GRAPHENE_MAX_UNDO_HISTORY) );
}
_undo_db.set_max_size( _dgp.recently_missed_count + GRAPHENE_MIN_UNDO_HISTORY );
}
void database::update_signing_witness(const witness_object& signing_witness, const signed_block& new_block)
@ -88,9 +108,8 @@ void database::clear_expired_transactions()
auto& transaction_idx = static_cast<transaction_index&>(get_mutable_index(implementation_ids, impl_transaction_object_type));
const auto& dedupe_index = transaction_idx.indices().get<by_expiration>();
const auto& global_parameters = get_global_properties().parameters;
auto forking_window_time = global_parameters.maximum_undo_history * global_parameters.block_interval;
while( !dedupe_index.empty()
&& head_block_time() - dedupe_index.rbegin()->trx.expiration >= fc::seconds(forking_window_time) )
&& head_block_time() - dedupe_index.rbegin()->trx.expiration >= fc::seconds(global_parameters.maximum_expiration) )
transaction_idx.remove(*dedupe_index.rbegin());
}

View file

@ -46,7 +46,10 @@
#define GRAPHENE_DEFAULT_MAX_BLOCK_SIZE (GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE*GRAPHENE_DEFAULT_BLOCK_INTERVAL*200000)
#define GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION (60*60*24) // seconds, aka: 1 day
#define GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL (60*60*24) // seconds, aka: 1 day
#define GRAPHENE_DEFAULT_MAX_UNDO_HISTORY 1024
#define GRAPHENE_DEFAULT_MAX_EXPIRATION_SEC (60*60) // 1 hour
#define GRAPHENE_MIN_UNDO_HISTORY 10
#define GRAPHENE_MAX_UNDO_HISTORY 1000
#define GRAPHENE_MIN_BLOCK_SIZE_LIMIT (GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT*5) // 5 transactions per block
#define GRAPHENE_MIN_TRANSACTION_EXPIRATION_LIMIT (GRAPHENE_MAX_BLOCK_INTERVAL * 5) // 5 transactions per block

View file

@ -90,7 +90,8 @@ namespace graphene { namespace chain {
skip_tapos_check = 1 << 7, ///< used while reindexing -- note this skips expiration check as well
skip_authority_check = 1 << 8, ///< used while reindexing -- disables any checking of authority on transactions
skip_merkle_check = 1 << 9, ///< used while reindexing
skip_assert_evaluation = 1 << 10 ///< used while reindexing
skip_assert_evaluation = 1 << 10, ///< used while reindexing
skip_undo_history_check = 1 << 11 ///< used while reindexing
};
/**

View file

@ -68,6 +68,7 @@ namespace graphene { namespace chain {
FC_DECLARE_DERIVED_EXCEPTION( operation_validate_exception, graphene::chain::chain_exception, 3040000, "operation validation exception" )
FC_DECLARE_DERIVED_EXCEPTION( operation_evaluate_exception, graphene::chain::chain_exception, 3050000, "operation evaluation exception" )
FC_DECLARE_DERIVED_EXCEPTION( utility_exception, graphene::chain::chain_exception, 3060000, "utility method exception" )
FC_DECLARE_DERIVED_EXCEPTION( undo_database_exception, graphene::chain::chain_exception, 3070000, "undo database exception" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_active_auth, graphene::chain::transaction_exception, 3030001, "missing required active authority" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_owner_auth, graphene::chain::transaction_exception, 3030002, "missing required owner authority" )

View file

@ -77,7 +77,15 @@ namespace graphene { namespace chain {
time_point_sec next_maintenance_time;
time_point_sec last_budget_time;
share_type witness_budget;
uint32_t accounts_registered_this_interval;
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
*
* If the recently_missed_count hits 2*UNDO_HISTORY then no ew blocks may be pushed.
*/
uint32_t recently_missed_count = 0;
/** if the interval changes then how we calculate witness participation will
* also change. Normally witness participation is defined as % of blocks
* produced in the last round which is calculated by dividing the delta
@ -97,6 +105,7 @@ FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene::
(next_maintenance_time)
(witness_budget)
(accounts_registered_this_interval)
(recently_missed_count)
(first_maintenance_block_with_current_interval)
)

View file

@ -39,7 +39,7 @@ namespace graphene { namespace chain {
uint32_t committee_proposal_review_period = GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC; ///< minimum time in seconds that a proposed transaction requiring committee authority may not be signed, prior to expiration
uint32_t maximum_transaction_size = GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE; ///< maximum allowable size in bytes for a transaction
uint32_t maximum_block_size = GRAPHENE_DEFAULT_MAX_BLOCK_SIZE; ///< maximum allowable size in bytes for a block
uint32_t maximum_undo_history = GRAPHENE_DEFAULT_MAX_UNDO_HISTORY; ///< maximum number of undo states to keep in RAM
uint32_t maximum_expiration = GRAPHENE_DEFAULT_MAX_EXPIRATION_SEC; ///< maximum number of seconds in the future a transaction may expire
uint32_t maximum_time_until_expiration = GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION; ///< maximum lifetime in seconds for transactions to be valid, before expiring
uint32_t maximum_proposal_lifetime = GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC; ///< maximum lifetime in seconds for proposed transactions to be kept, before expiring
uint8_t maximum_asset_whitelist_authorities = GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES; ///< maximum number of accounts which an asset may list as authorities for its whitelist OR blacklist
@ -102,7 +102,7 @@ FC_REFLECT( graphene::chain::chain_parameters,
(committee_proposal_review_period)
(maximum_transaction_size)
(maximum_block_size)
(maximum_undo_history)
(maximum_expiration)
(maximum_time_until_expiration)
(maximum_proposal_lifetime)
(maximum_asset_whitelist_authorities)

View file

@ -28,7 +28,7 @@ undo_database::session undo_database::start_undo_session()
{
if( _disabled ) return session(*this);
if( size() == max_size() )
while( size() > max_size() )
_stack.pop_front();
_stack.emplace_back();

View file

@ -296,6 +296,7 @@ signed_block database_fixture::generate_block(uint32_t skip, const fc::ecc::priv
{
open_database();
skip |= database::skip_undo_history_check;
// skip == ~0 will skip checks specified in database::validation_steps
return db.generate_block(db.get_slot_time(miss_blocks + 1),
db.get_scheduled_witness(miss_blocks + 1).first,