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:
parent
b24006cca3
commit
7f54d3d077
10 changed files with 55 additions and 9 deletions
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> >();
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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" )
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue