2015-06-08 15:50:35 +00:00
/*
2015-10-12 17:48:40 +00:00
* Copyright ( c ) 2015 Cryptonomex , Inc . , and contributors .
*
2016-01-06 09:51:18 +00:00
* The MIT License
2015-10-12 17:48:40 +00:00
*
2016-01-06 09:51:18 +00:00
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
2015-10-12 17:48:40 +00:00
*
2016-01-06 09:51:18 +00:00
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
2015-10-12 17:02:59 +00:00
*
2016-01-06 09:51:18 +00:00
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
2015-06-08 15:50:35 +00:00
*/
# include <graphene/chain/database.hpp>
2015-09-09 15:11:58 +00:00
# include <graphene/chain/db_with.hpp>
2015-12-16 16:23:44 +00:00
# include <graphene/chain/hardfork.hpp>
2015-06-08 15:50:35 +00:00
# include <graphene/chain/block_summary_object.hpp>
# include <graphene/chain/global_property_object.hpp>
# include <graphene/chain/operation_history_object.hpp>
2017-03-22 22:03:45 +00:00
2015-06-08 15:50:35 +00:00
# include <graphene/chain/proposal_object.hpp>
# include <graphene/chain/transaction_object.hpp>
# include <graphene/chain/witness_object.hpp>
2015-07-08 20:39:23 +00:00
# include <graphene/chain/protocol/fee_schedule.hpp>
2015-07-15 18:13:24 +00:00
# include <graphene/chain/exceptions.hpp>
2016-02-17 14:17:00 +00:00
# include <graphene/chain/evaluator.hpp>
2015-06-08 15:50:35 +00:00
2016-03-15 05:17:07 +00:00
# include <fc/smart_ref_impl.hpp>
2015-06-08 15:50:35 +00:00
namespace graphene { namespace chain {
bool database : : is_known_block ( const block_id_type & id ) const
{
2015-06-16 19:56:13 +00:00
return _fork_db . is_known_block ( id ) | | _block_id_to_block . contains ( id ) ;
2015-06-08 15:50:35 +00:00
}
/**
* Only return true * if * the transaction has not expired or been invalidated . If this
* method is called with a VERY old transaction we will return false , they should
* query things by blocks if they are that old .
*/
bool database : : is_known_transaction ( const transaction_id_type & id ) const
{
const auto & trx_idx = get_index_type < transaction_index > ( ) . indices ( ) . get < by_trx_id > ( ) ;
return trx_idx . find ( id ) ! = trx_idx . end ( ) ;
}
block_id_type database : : get_block_id_for_num ( uint32_t block_num ) const
{ try {
2015-06-16 19:56:13 +00:00
return _block_id_to_block . fetch_block_id ( block_num ) ;
2015-06-08 15:50:35 +00:00
} FC_CAPTURE_AND_RETHROW ( ( block_num ) ) }
optional < signed_block > database : : fetch_block_by_id ( const block_id_type & id ) const
{
auto b = _fork_db . fetch_block ( id ) ;
if ( ! b )
return _block_id_to_block . fetch_optional ( id ) ;
return b - > data ;
}
optional < signed_block > database : : fetch_block_by_number ( uint32_t num ) const
{
auto results = _fork_db . fetch_block_by_number ( num ) ;
if ( results . size ( ) = = 1 )
return results [ 0 ] - > data ;
else
2015-06-16 19:56:13 +00:00
return _block_id_to_block . fetch_by_number ( num ) ;
2015-06-08 15:50:35 +00:00
return optional < signed_block > ( ) ;
}
const signed_transaction & database : : get_recent_transaction ( const transaction_id_type & trx_id ) const
{
auto & index = get_index_type < transaction_index > ( ) . indices ( ) . get < by_trx_id > ( ) ;
auto itr = index . find ( trx_id ) ;
FC_ASSERT ( itr ! = index . end ( ) ) ;
return itr - > trx ;
}
2015-09-10 12:56:29 +00:00
std : : vector < block_id_type > database : : get_block_ids_on_fork ( block_id_type head_of_fork ) const
{
pair < fork_database : : branch_type , fork_database : : branch_type > branches = _fork_db . fetch_branch_from ( head_block_id ( ) , head_of_fork ) ;
2015-09-15 12:38:27 +00:00
if ( ! ( ( branches . first . back ( ) - > previous_id ( ) = = branches . second . back ( ) - > previous_id ( ) ) ) )
{
edump ( ( head_of_fork )
( head_block_id ( ) )
( branches . first . size ( ) )
( branches . second . size ( ) ) ) ;
assert ( branches . first . back ( ) - > previous_id ( ) = = branches . second . back ( ) - > previous_id ( ) ) ;
}
2015-09-10 12:56:29 +00:00
std : : vector < block_id_type > result ;
for ( const item_ptr & fork_block : branches . second )
result . emplace_back ( fork_block - > id ) ;
2015-09-14 19:24:06 +00:00
result . emplace_back ( branches . first . back ( ) - > previous_id ( ) ) ;
2015-09-10 12:56:29 +00:00
return result ;
}
2015-06-08 15:50:35 +00:00
/**
* Push block " may fail " in which case every partial change is unwound . After
* push block is successful the block is appended to the chain database on disk .
*
* @ return true if we switched forks as a result of this push .
*/
2015-06-22 19:03:18 +00:00
bool database : : push_block ( const signed_block & new_block , uint32_t skip )
2015-06-18 02:22:54 +00:00
{
2016-01-04 03:50:07 +00:00
// idump((new_block.block_num())(new_block.id())(new_block.timestamp)(new_block.previous));
2015-06-18 02:22:54 +00:00
bool result ;
2015-09-09 15:11:58 +00:00
detail : : with_skip_flags ( * this , skip , [ & ] ( )
2015-06-18 02:22:54 +00:00
{
2015-09-16 19:50:09 +00:00
detail : : without_pending_transactions ( * this , std : : move ( _pending_tx ) ,
2015-09-09 15:11:58 +00:00
[ & ] ( )
{
result = _push_block ( new_block ) ;
} ) ;
2015-08-17 15:44:11 +00:00
} ) ;
2015-06-18 02:22:54 +00:00
return result ;
}
2015-06-22 19:03:18 +00:00
bool database : : _push_block ( const signed_block & new_block )
2015-06-08 15:50:35 +00:00
{ try {
2015-06-18 02:22:54 +00:00
uint32_t skip = get_node_properties ( ) . skip_flags ;
2015-06-08 15:50:35 +00:00
if ( ! ( skip & skip_fork_db ) )
{
2015-08-19 13:47:10 +00:00
/// TODO: if the block is greater than the head block and before the next maitenance interval
// verify that the block signer is in the current set of active witnesses.
2015-07-16 19:30:05 +00:00
shared_ptr < fork_item > new_head = _fork_db . push_block ( new_block ) ;
2015-06-08 15:50:35 +00:00
//If the head block from the longest chain does not build off of the current head, we need to switch forks.
if ( new_head - > data . previous ! = head_block_id ( ) )
{
//If the newly pushed block is the same height as head, we get head back in new_head
//Only switch forks if new_head is actually higher than head
if ( new_head - > data . block_num ( ) > head_block_num ( ) )
{
2015-09-17 15:23:55 +00:00
wlog ( " Switching to fork: ${id} " , ( " id " , new_head - > data . id ( ) ) ) ;
2015-09-16 19:50:09 +00:00
auto branches = _fork_db . fetch_branch_from ( new_head - > data . id ( ) , head_block_id ( ) ) ;
2015-06-08 15:50:35 +00:00
// pop blocks until we hit the forked block
while ( head_block_id ( ) ! = branches . second . back ( ) - > data . previous )
pop_block ( ) ;
// push all blocks on the new fork
for ( auto ritr = branches . first . rbegin ( ) ; ritr ! = branches . first . rend ( ) ; + + ritr )
{
2015-10-07 15:09:28 +00:00
ilog ( " pushing blocks from fork ${n} ${id} " , ( " n " , ( * ritr ) - > data . block_num ( ) ) ( " id " , ( * ritr ) - > data . id ( ) ) ) ;
2015-06-08 15:50:35 +00:00
optional < fc : : exception > except ;
try {
2015-07-16 19:30:05 +00:00
undo_database : : session session = _undo_db . start_undo_session ( ) ;
2015-06-08 15:50:35 +00:00
apply_block ( ( * ritr ) - > data , skip ) ;
2015-06-16 22:45:33 +00:00
_block_id_to_block . store ( ( * ritr ) - > id , ( * ritr ) - > data ) ;
2015-06-08 15:50:35 +00:00
session . commit ( ) ;
}
catch ( const fc : : exception & e ) { except = e ; }
if ( except )
{
2015-10-07 15:09:28 +00:00
wlog ( " exception thrown while switching forks ${e} " , ( " e " , except - > to_detail_string ( ) ) ) ;
2015-06-08 15:50:35 +00:00
// remove the rest of branches.first from the fork_db, those blocks are invalid
while ( ritr ! = branches . first . rend ( ) )
{
_fork_db . remove ( ( * ritr ) - > data . id ( ) ) ;
+ + ritr ;
}
_fork_db . set_head ( branches . second . front ( ) ) ;
// pop all blocks from the bad fork
while ( head_block_id ( ) ! = branches . second . back ( ) - > data . previous )
pop_block ( ) ;
// restore all blocks from the good fork
for ( auto ritr = branches . second . rbegin ( ) ; ritr ! = branches . second . rend ( ) ; + + ritr )
{
auto session = _undo_db . start_undo_session ( ) ;
apply_block ( ( * ritr ) - > data , skip ) ;
_block_id_to_block . store ( new_block . id ( ) , ( * ritr ) - > data ) ;
session . commit ( ) ;
}
throw * except ;
}
}
return true ;
}
else return false ;
}
}
try {
auto session = _undo_db . start_undo_session ( ) ;
2015-06-22 19:03:18 +00:00
apply_block ( new_block , skip ) ;
_block_id_to_block . store ( new_block . id ( ) , new_block ) ;
2015-06-08 15:50:35 +00:00
session . commit ( ) ;
} catch ( const fc : : exception & e ) {
elog ( " Failed to push new block: \n ${e} " , ( " e " , e . to_detail_string ( ) ) ) ;
_fork_db . remove ( new_block . id ( ) ) ;
throw ;
}
return false ;
} FC_CAPTURE_AND_RETHROW ( ( new_block ) ) }
/**
* Attempts to push the transaction into the pending queue
*
* When called to push a locally generated transaction , set the skip_block_size_check bit on the skip argument . This
* will allow the transaction to be pushed even if it causes the pending block size to exceed the maximum block size .
* Although the transaction will probably not propagate further now , as the peers are likely to have their pending
* queues full as well , it will be kept in the queue to be propagated later when a new block flushes out the pending
* queues .
*/
processed_transaction database : : push_transaction ( const signed_transaction & trx , uint32_t skip )
2015-06-25 16:07:39 +00:00
{ try {
2015-06-18 02:22:54 +00:00
processed_transaction result ;
2015-09-09 15:11:58 +00:00
detail : : with_skip_flags ( * this , skip , [ & ] ( )
2015-06-18 02:22:54 +00:00
{
result = _push_transaction ( trx ) ;
} ) ;
return result ;
2015-06-25 16:07:39 +00:00
} FC_CAPTURE_AND_RETHROW ( ( trx ) ) }
2015-06-18 02:22:54 +00:00
processed_transaction database : : _push_transaction ( const signed_transaction & trx )
{
2015-06-08 15:50:35 +00:00
// If this is the first transaction pushed after applying a block, start a new undo session.
// This allows us to quickly rewind to the clean state of the head block, in case a new block arrives.
2015-09-16 19:50:09 +00:00
if ( ! _pending_tx_session . valid ( ) )
_pending_tx_session = _undo_db . start_undo_session ( ) ;
// Create a temporary undo session as a child of _pending_tx_session.
// The temporary session will be discarded by the destructor if
// _apply_transaction fails. If we make it to merge(), we
// apply the changes.
2015-06-08 15:50:35 +00:00
2015-09-16 19:50:09 +00:00
auto temp_session = _undo_db . start_undo_session ( ) ;
auto processed_trx = _apply_transaction ( trx ) ;
_pending_tx . push_back ( processed_trx ) ;
2015-06-08 15:50:35 +00:00
2017-02-10 08:18:23 +00:00
// notify_changed_objects();
2015-06-08 15:50:35 +00:00
// The transaction applied successfully. Merge its changes into the pending block session.
2015-09-16 19:50:09 +00:00
temp_session . merge ( ) ;
2015-09-07 22:05:43 +00:00
// notify anyone listening to pending transactions
on_pending_transaction ( trx ) ;
2015-06-08 15:50:35 +00:00
return processed_trx ;
}
2015-09-07 21:46:47 +00:00
processed_transaction database : : validate_transaction ( const signed_transaction & trx )
{
auto session = _undo_db . start_undo_session ( ) ;
return _apply_transaction ( trx ) ;
}
2015-06-08 15:50:35 +00:00
processed_transaction database : : push_proposal ( const proposal_object & proposal )
2015-09-29 14:49:12 +00:00
{ try {
2015-06-08 15:50:35 +00:00
transaction_evaluation_state eval_state ( this ) ;
eval_state . _is_proposed_trx = true ;
eval_state . operation_results . reserve ( proposal . proposed_transaction . operations . size ( ) ) ;
processed_transaction ptrx ( proposal . proposed_transaction ) ;
eval_state . _trx = & ptrx ;
2015-12-15 19:12:53 +00:00
size_t old_applied_ops_size = _applied_ops . size ( ) ;
2015-06-08 15:50:35 +00:00
2015-09-29 14:49:12 +00:00
try {
auto session = _undo_db . start_undo_session ( true ) ;
for ( auto & op : proposal . proposed_transaction . operations )
eval_state . operation_results . emplace_back ( apply_operation ( eval_state , op ) ) ;
remove ( proposal ) ;
session . merge ( ) ;
} catch ( const fc : : exception & e ) {
2015-12-16 16:23:44 +00:00
if ( head_block_time ( ) < = HARDFORK_483_TIME )
{
for ( size_t i = old_applied_ops_size , n = _applied_ops . size ( ) ; i < n ; i + + )
{
ilog ( " removing failed operation from applied_ops: ${op} " , ( " op " , * ( _applied_ops [ i ] ) ) ) ;
_applied_ops [ i ] . reset ( ) ;
}
}
else
{
_applied_ops . resize ( old_applied_ops_size ) ;
}
2017-08-30 21:15:50 +00:00
edump ( ( e ) ) ;
2015-09-29 14:49:12 +00:00
throw ;
}
2015-06-08 15:50:35 +00:00
ptrx . operation_results = std : : move ( eval_state . operation_results ) ;
return ptrx ;
2017-08-30 21:15:50 +00:00
} FC_CAPTURE_AND_RETHROW ( ) }
2015-06-08 15:50:35 +00:00
signed_block database : : generate_block (
fc : : time_point_sec when ,
witness_id_type witness_id ,
const fc : : ecc : : private_key & block_signing_private_key ,
uint32_t skip /* = 0 */
)
2016-01-29 01:02:37 +00:00
{ try {
2015-06-18 02:22:54 +00:00
signed_block result ;
2015-09-09 15:11:58 +00:00
detail : : with_skip_flags ( * this , skip , [ & ] ( )
2015-06-18 02:22:54 +00:00
{
2015-09-24 22:28:22 +00:00
result = _generate_block ( when , witness_id , block_signing_private_key ) ;
2015-06-18 02:22:54 +00:00
} ) ;
return result ;
2016-01-29 01:02:37 +00:00
} FC_CAPTURE_AND_RETHROW ( ) }
2015-06-18 02:22:54 +00:00
signed_block database : : _generate_block (
fc : : time_point_sec when ,
witness_id_type witness_id ,
2015-09-24 22:28:22 +00:00
const fc : : ecc : : private_key & block_signing_private_key
2015-06-18 02:22:54 +00:00
)
2015-06-08 15:50:35 +00:00
{
try {
2015-06-18 02:22:54 +00:00
uint32_t skip = get_node_properties ( ) . skip_flags ;
2015-06-08 15:50:35 +00:00
uint32_t slot_num = get_slot_at_time ( when ) ;
2015-07-28 14:59:00 +00:00
FC_ASSERT ( slot_num > 0 ) ;
2015-08-25 18:46:56 +00:00
witness_id_type scheduled_witness = get_scheduled_witness ( slot_num ) ;
2015-06-08 15:50:35 +00:00
FC_ASSERT ( scheduled_witness = = witness_id ) ;
const auto & witness_obj = witness_id ( * this ) ;
2015-07-13 19:41:50 +00:00
if ( ! ( skip & skip_witness_signature ) )
2015-07-02 05:52:45 +00:00
FC_ASSERT ( witness_obj . signing_key = = block_signing_private_key . get_public_key ( ) ) ;
2015-06-08 15:50:35 +00:00
2015-09-16 19:50:09 +00:00
static const size_t max_block_header_size = fc : : raw : : pack_size ( signed_block_header ( ) ) + 4 ;
auto maximum_block_size = get_global_properties ( ) . parameters . maximum_block_size ;
size_t total_block_size = max_block_header_size ;
signed_block pending_block ;
//
// The following code throws away existing pending_tx_session and
// rebuilds it by re-applying pending transactions.
//
// This rebuild is necessary because pending transactions' validity
// and semantics may have changed since they were received, because
// time-based semantics are evaluated based on the current block
// time. These changes can only be reflected in the database when
// the value of the "when" variable is known, which means we need to
// re-apply pending transactions in this method.
//
_pending_tx_session . reset ( ) ;
_pending_tx_session = _undo_db . start_undo_session ( ) ;
uint64_t postponed_tx_count = 0 ;
// pop pending state (reset to head block state)
for ( const processed_transaction & tx : _pending_tx )
2015-09-15 14:54:04 +00:00
{
2015-09-16 19:50:09 +00:00
size_t new_total_size = total_block_size + fc : : raw : : pack_size ( tx ) ;
2015-06-08 15:50:35 +00:00
2015-09-16 19:50:09 +00:00
// postpone transaction if it would make block too big
if ( new_total_size > = maximum_block_size )
{
postponed_tx_count + + ;
continue ;
}
2015-06-25 14:03:18 +00:00
2015-09-16 19:50:09 +00:00
try
2015-08-26 22:13:58 +00:00
{
2015-09-16 19:50:09 +00:00
auto temp_session = _undo_db . start_undo_session ( ) ;
processed_transaction ptx = _apply_transaction ( tx ) ;
temp_session . merge ( ) ;
// We have to recompute pack_size(ptx) because it may be different
// than pack_size(tx) (i.e. if one or more results increased
// their size)
total_block_size + = fc : : raw : : pack_size ( ptx ) ;
pending_block . transactions . push_back ( ptx ) ;
2015-08-26 22:13:58 +00:00
}
2015-09-16 19:50:09 +00:00
catch ( const fc : : exception & e )
2015-08-26 22:13:58 +00:00
{
2015-09-16 19:50:09 +00:00
// Do nothing, transaction will not be re-applied
wlog ( " Transaction was not processed while generating block due to ${e} " , ( " e " , e ) ) ;
wlog ( " The transaction was ${t} " , ( " t " , tx ) ) ;
2015-08-26 22:13:58 +00:00
}
}
2015-09-16 19:50:09 +00:00
if ( postponed_tx_count > 0 )
2015-06-25 14:03:18 +00:00
{
2015-09-16 19:50:09 +00:00
wlog ( " Postponed ${n} transactions due to block size limit " , ( " n " , postponed_tx_count ) ) ;
2015-06-25 14:03:18 +00:00
}
2015-09-24 21:07:35 +00:00
2015-09-16 19:50:09 +00:00
_pending_tx_session . reset ( ) ;
2015-09-24 21:07:35 +00:00
// We have temporarily broken the invariant that
// _pending_tx_session is the result of applying _pending_tx, as
// _pending_tx now consists of the set of postponed transactions.
// However, the push_block() call below will re-create the
// _pending_tx_session.
2015-09-16 19:50:09 +00:00
pending_block . previous = head_block_id ( ) ;
pending_block . timestamp = when ;
pending_block . transaction_merkle_root = pending_block . calculate_merkle_root ( ) ;
pending_block . witness = witness_id ;
2016-09-10 19:28:09 +00:00
// 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 ( ) ) ;
2015-09-16 19:50:09 +00:00
if ( ! ( skip & skip_witness_signature ) )
pending_block . sign ( block_signing_private_key ) ;
2015-09-24 21:07:35 +00:00
// TODO: Move this to _push_block() so session is restored.
2015-10-08 19:00:18 +00:00
if ( ! ( skip & skip_block_size_check ) )
{
FC_ASSERT ( fc : : raw : : pack_size ( pending_block ) < = get_global_properties ( ) . parameters . maximum_block_size ) ;
}
2015-09-16 19:50:09 +00:00
push_block ( pending_block , skip ) ;
return pending_block ;
2015-06-08 15:50:35 +00:00
} FC_CAPTURE_AND_RETHROW ( ( witness_id ) ) }
/**
* Removes the most recent block from the database and
* undoes any changes it made .
*/
void database : : pop_block ( )
{ try {
2015-09-16 19:50:09 +00:00
_pending_tx_session . reset ( ) ;
auto head_id = head_block_id ( ) ;
optional < signed_block > head_block = fetch_block_by_id ( head_id ) ;
GRAPHENE_ASSERT ( head_block . valid ( ) , pop_empty_chain , " there are no blocks to pop " ) ;
2016-02-15 17:33:21 +00:00
2015-06-08 15:50:35 +00:00
_fork_db . pop_block ( ) ;
2016-02-15 17:33:21 +00:00
_block_id_to_block . remove ( head_id ) ;
pop_undo ( ) ;
2015-09-16 19:50:09 +00:00
_popped_tx . insert ( _popped_tx . begin ( ) , head_block - > transactions . begin ( ) , head_block - > transactions . end ( ) ) ;
2015-06-08 15:50:35 +00:00
} FC_CAPTURE_AND_RETHROW ( ) }
void database : : clear_pending ( )
{ try {
2015-09-16 19:50:09 +00:00
assert ( ( _pending_tx . size ( ) = = 0 ) | | _pending_tx_session . valid ( ) ) ;
_pending_tx . clear ( ) ;
_pending_tx_session . reset ( ) ;
2015-06-08 15:50:35 +00:00
} FC_CAPTURE_AND_RETHROW ( ) }
uint32_t database : : push_applied_operation ( const operation & op )
{
_applied_ops . emplace_back ( op ) ;
2015-12-16 16:23:44 +00:00
operation_history_object & oh = * ( _applied_ops . back ( ) ) ;
2015-06-08 15:50:35 +00:00
oh . block_num = _current_block_num ;
oh . trx_in_block = _current_trx_in_block ;
oh . op_in_trx = _current_op_in_trx ;
oh . virtual_op = _current_virtual_op + + ;
return _applied_ops . size ( ) - 1 ;
}
void database : : set_applied_operation_result ( uint32_t op_id , const operation_result & result )
{
assert ( op_id < _applied_ops . size ( ) ) ;
2015-12-16 22:02:21 +00:00
if ( _applied_ops [ op_id ] )
_applied_ops [ op_id ] - > result = result ;
else
{
elog ( " Could not set operation result (head_block_num=${b}) " , ( " b " , head_block_num ( ) ) ) ;
}
2015-06-08 15:50:35 +00:00
}
2015-12-16 16:23:44 +00:00
const vector < optional < operation_history_object > > & database : : get_applied_operations ( ) const
2015-06-08 15:50:35 +00:00
{
return _applied_ops ;
}
//////////////////// private methods ////////////////////
void database : : apply_block ( const signed_block & next_block , uint32_t skip )
2015-06-18 02:22:54 +00:00
{
2015-07-09 18:09:21 +00:00
auto block_num = next_block . block_num ( ) ;
2015-08-17 17:52:45 +00:00
if ( _checkpoints . size ( ) & & _checkpoints . rbegin ( ) - > second ! = block_id_type ( ) )
2015-07-09 18:09:21 +00:00
{
auto itr = _checkpoints . find ( block_num ) ;
if ( itr ! = _checkpoints . end ( ) )
FC_ASSERT ( next_block . id ( ) = = itr - > second , " Block did not match checkpoint " , ( " checkpoint " , * itr ) ( " block_id " , next_block . id ( ) ) ) ;
2015-08-17 17:52:45 +00:00
if ( _checkpoints . rbegin ( ) - > first > = block_num )
skip = ~ 0 ; // WE CAN SKIP ALMOST EVERYTHING
2015-07-09 18:09:21 +00:00
}
2015-09-09 15:11:58 +00:00
detail : : with_skip_flags ( * this , skip , [ & ] ( )
2015-06-18 02:22:54 +00:00
{
_apply_block ( next_block ) ;
} ) ;
return ;
}
void database : : _apply_block ( const signed_block & next_block )
2015-06-08 15:50:35 +00:00
{ try {
2015-10-09 15:42:56 +00:00
uint32_t next_block_num = next_block . block_num ( ) ;
2015-06-18 02:22:54 +00:00
uint32_t skip = get_node_properties ( ) . skip_flags ;
2015-06-08 15:50:35 +00:00
_applied_ops . clear ( ) ;
2015-06-16 22:45:33 +00:00
FC_ASSERT ( ( skip & skip_merkle_check ) | | next_block . transaction_merkle_root = = next_block . calculate_merkle_root ( ) , " " , ( " next_block.transaction_merkle_root " , next_block . transaction_merkle_root ) ( " calc " , next_block . calculate_merkle_root ( ) ) ( " next_block " , next_block ) ( " id " , next_block . id ( ) ) ) ;
2015-06-08 15:50:35 +00:00
const witness_object & signing_witness = validate_block_header ( skip , next_block ) ;
const auto & global_props = get_global_properties ( ) ;
const auto & dynamic_global_props = get < dynamic_global_property_object > ( dynamic_global_property_id_type ( ) ) ;
2015-07-24 18:03:59 +00:00
bool maint_needed = ( dynamic_global_props . next_maintenance_time < = next_block . timestamp ) ;
2015-06-08 15:50:35 +00:00
2015-10-09 15:42:56 +00:00
_current_block_num = next_block_num ;
2015-06-08 15:50:35 +00:00
_current_trx_in_block = 0 ;
for ( const auto & trx : next_block . transactions )
{
/* We do not need to push the undo state for each transaction
* because they either all apply and are valid or the
* entire block fails to apply . We only need an " undo " state
* for transactions when validating broadcast transactions or
* when building a block .
*/
2017-05-10 18:20:18 +00:00
apply_transaction ( trx , skip ) ;
2015-06-08 15:50:35 +00:00
+ + _current_trx_in_block ;
}
2017-04-21 12:24:40 +00:00
if ( global_props . parameters . witness_schedule_algorithm = = GRAPHENE_WITNESS_SCHEDULED_ALGORITHM )
update_witness_schedule ( next_block ) ;
2015-06-08 15:50:35 +00:00
update_global_dynamic_data ( next_block ) ;
update_signing_witness ( signing_witness , next_block ) ;
2015-10-02 21:19:03 +00:00
update_last_irreversible_block ( ) ;
2015-06-08 15:50:35 +00:00
// Are we at the maintenance interval?
2015-07-24 18:03:59 +00:00
if ( maint_needed )
2015-06-08 15:50:35 +00:00
perform_chain_maintenance ( next_block , global_props ) ;
create_block_summary ( next_block ) ;
2017-08-15 22:44:09 +00:00
place_delayed_bets ( ) ; // must happen after update_global_dynamic_data() updates the time
2015-06-08 15:50:35 +00:00
clear_expired_transactions ( ) ;
clear_expired_proposals ( ) ;
clear_expired_orders ( ) ;
update_expired_feeds ( ) ;
update_withdraw_permissions ( ) ;
2016-09-12 22:28:45 +00:00
update_tournaments ( ) ;
2015-06-08 15:50:35 +00:00
2015-07-24 18:03:59 +00:00
// n.b., update_maintenance_flag() happens this late
// because get_slot_time() / get_slot_at_time() is needed above
// TODO: figure out if we could collapse this function into
// update_global_dynamic_data() as perhaps these methods only need
// to be called for header validation?
update_maintenance_flag ( maint_needed ) ;
2017-04-19 18:31:00 +00:00
if ( global_props . parameters . witness_schedule_algorithm = = GRAPHENE_WITNESS_SHUFFLED_ALGORITHM )
update_witness_schedule ( ) ;
2016-03-03 19:12:27 +00:00
if ( ! _node_property_object . debug_updates . empty ( ) )
apply_debug_updates ( ) ;
2015-07-24 18:03:59 +00:00
2015-06-08 15:50:35 +00:00
// notify observers that the block has been applied
applied_block ( next_block ) ; //emit
_applied_ops . clear ( ) ;
2015-06-22 22:11:54 +00:00
notify_changed_objects ( ) ;
2017-04-21 12:24:40 +00:00
2015-06-22 22:11:54 +00:00
} FC_CAPTURE_AND_RETHROW ( ( next_block . block_num ( ) ) ) }
2017-03-08 01:37:59 +00:00
2015-06-08 15:50:35 +00:00
2015-07-01 17:53:04 +00:00
processed_transaction database : : apply_transaction ( const signed_transaction & trx , uint32_t skip )
2015-06-18 02:22:54 +00:00
{
processed_transaction result ;
2015-09-09 15:11:58 +00:00
detail : : with_skip_flags ( * this , skip , [ & ] ( )
2015-06-18 02:22:54 +00:00
{
2015-07-01 17:53:04 +00:00
result = _apply_transaction ( trx ) ;
} ) ;
2015-06-18 02:22:54 +00:00
return result ;
}
2015-07-01 17:53:04 +00:00
processed_transaction database : : _apply_transaction ( const signed_transaction & trx )
2015-06-08 15:50:35 +00:00
{ try {
2015-06-18 02:22:54 +00:00
uint32_t skip = get_node_properties ( ) . skip_flags ;
2016-01-04 01:40:21 +00:00
2016-01-07 20:42:59 +00:00
if ( true | | ! ( skip & skip_validate ) ) /* issue #505 explains why this skip_flag is disabled */
2016-01-04 01:40:21 +00:00
trx . validate ( ) ;
2015-06-08 15:50:35 +00:00
auto & trx_idx = get_mutable_index_type < transaction_index > ( ) ;
2015-08-06 16:41:45 +00:00
const chain_id_type & chain_id = get_chain_id ( ) ;
2015-06-08 15:50:35 +00:00
auto trx_id = trx . id ( ) ;
FC_ASSERT ( ( skip & skip_transaction_dupe_check ) | |
trx_idx . indices ( ) . get < by_trx_id > ( ) . find ( trx_id ) = = trx_idx . indices ( ) . get < by_trx_id > ( ) . end ( ) ) ;
2015-06-18 02:22:54 +00:00
transaction_evaluation_state eval_state ( this ) ;
2015-06-08 15:50:35 +00:00
const chain_parameters & chain_parameters = get_global_properties ( ) . parameters ;
eval_state . _trx = & trx ;
2015-07-16 22:13:11 +00:00
if ( ! ( skip & ( skip_transaction_signatures | skip_authority_check ) ) )
2015-06-08 15:50:35 +00:00
{
2015-07-16 22:13:11 +00:00
auto get_active = [ & ] ( account_id_type id ) { return & id ( * this ) . active ; } ;
auto get_owner = [ & ] ( account_id_type id ) { return & id ( * this ) . owner ; } ;
2015-08-06 16:41:45 +00:00
trx . verify_authority ( chain_id , get_active , get_owner , get_global_properties ( ) . parameters . max_authority_depth ) ;
2015-06-08 15:50:35 +00:00
}
//Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is
//expired, and TaPoS makes no sense as no blocks exist.
if ( BOOST_LIKELY ( head_block_num ( ) > 0 ) )
{
2015-07-14 22:46:58 +00:00
if ( ! ( skip & skip_tapos_check ) )
2015-06-08 15:50:35 +00:00
{
2015-07-14 21:56:42 +00:00
const auto & tapos_block_summary = block_summary_id_type ( trx . ref_block_num ) ( * this ) ;
2015-06-08 15:50:35 +00:00
//Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration
FC_ASSERT ( trx . ref_block_prefix = = tapos_block_summary . block_id . _hash [ 1 ] ) ;
2015-08-17 15:44:11 +00:00
}
2015-09-16 19:50:09 +00:00
fc : : time_point_sec now = head_block_time ( ) ;
FC_ASSERT ( trx . expiration < = now + chain_parameters . maximum_time_until_expiration , " " ,
( " trx.expiration " , trx . expiration ) ( " now " , now ) ( " max_til_exp " , chain_parameters . maximum_time_until_expiration ) ) ;
FC_ASSERT ( now < = trx . expiration , " " , ( " now " , now ) ( " trx.exp " , trx . expiration ) ) ;
2015-08-17 15:44:11 +00:00
}
2015-06-08 15:50:35 +00:00
//Insert transaction into unique transactions database.
if ( ! ( skip & skip_transaction_dupe_check ) )
{
create < transaction_object > ( [ & ] ( transaction_object & transaction ) {
transaction . trx_id = trx_id ;
transaction . trx = trx ;
} ) ;
}
2015-07-01 17:53:04 +00:00
eval_state . operation_results . reserve ( trx . operations . size ( ) ) ;
2015-06-08 15:50:35 +00:00
2015-06-18 19:51:11 +00:00
//Finally process the operations
2015-06-08 15:50:35 +00:00
processed_transaction ptrx ( trx ) ;
_current_op_in_trx = 0 ;
for ( const auto & op : ptrx . operations )
{
eval_state . operation_results . emplace_back ( apply_operation ( eval_state , op ) ) ;
+ + _current_op_in_trx ;
}
2015-07-01 17:53:04 +00:00
ptrx . operation_results = std : : move ( eval_state . operation_results ) ;
2015-06-08 15:50:35 +00:00
2015-06-18 19:51:11 +00:00
//Make sure the temp account has no non-zero balances
2016-01-21 15:29:54 +00:00
const auto & index = get_index_type < account_balance_index > ( ) . indices ( ) . get < by_account_asset > ( ) ;
auto range = index . equal_range ( boost : : make_tuple ( GRAPHENE_TEMP_ACCOUNT ) ) ;
2015-06-18 19:51:11 +00:00
std : : for_each ( range . first , range . second , [ ] ( const account_balance_object & b ) { FC_ASSERT ( b . balance = = 0 ) ; } ) ;
2015-06-08 15:50:35 +00:00
return ptrx ;
} FC_CAPTURE_AND_RETHROW ( ( trx ) ) }
operation_result database : : apply_operation ( transaction_evaluation_state & eval_state , const operation & op )
2015-06-30 19:11:26 +00:00
{ try {
2015-06-08 15:50:35 +00:00
int i_which = op . which ( ) ;
uint64_t u_which = uint64_t ( i_which ) ;
if ( i_which < 0 )
assert ( " Negative operation tag " & & false ) ;
if ( u_which > = _operation_evaluators . size ( ) )
assert ( " No registered evaluator for this operation " & & false ) ;
unique_ptr < op_evaluator > & eval = _operation_evaluators [ u_which ] ;
if ( ! eval )
assert ( " No registered evaluator for this operation " & & false ) ;
auto op_id = push_applied_operation ( op ) ;
auto result = eval - > evaluate ( eval_state , op , true ) ;
set_applied_operation_result ( op_id , result ) ;
return result ;
2016-01-29 00:46:40 +00:00
} FC_CAPTURE_AND_RETHROW ( ( op ) ) }
2015-06-08 15:50:35 +00:00
const witness_object & database : : validate_block_header ( uint32_t skip , const signed_block & next_block ) const
{
2015-09-16 19:50:09 +00:00
FC_ASSERT ( head_block_id ( ) = = next_block . previous , " " , ( " head_block_id " , head_block_id ( ) ) ( " next.prev " , next_block . previous ) ) ;
FC_ASSERT ( head_block_time ( ) < next_block . timestamp , " " , ( " head_block_time " , head_block_time ( ) ) ( " next " , next_block . timestamp ) ( " blocknum " , next_block . block_num ( ) ) ) ;
2015-06-08 15:50:35 +00:00
const witness_object & witness = next_block . witness ( * this ) ;
2017-05-30 22:49:15 +00:00
//DLN: TODO: Temporarily commented out to test shuffle vs RNG scheduling algorithm for witnesses, this was causing shuffle agorithm to fail during create_witness test. This should be re-enabled for RNG, and maybe for shuffle too, don't really know for sure.
// 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)("null_secret_hash", secret_hash_type::hash( secret_hash_type())));
2015-06-08 15:50:35 +00:00
2015-08-24 22:50:09 +00:00
if ( ! ( skip & skip_witness_signature ) )
FC_ASSERT ( next_block . validate_signee ( witness . signing_key ) ) ;
2015-06-08 15:50:35 +00:00
2015-10-07 13:10:03 +00:00
if ( ! ( skip & skip_witness_schedule_check ) )
2015-08-24 22:50:09 +00:00
{
uint32_t slot_num = get_slot_at_time ( next_block . timestamp ) ;
FC_ASSERT ( slot_num > 0 ) ;
2015-08-25 18:46:56 +00:00
witness_id_type scheduled_witness = get_scheduled_witness ( slot_num ) ;
2015-10-07 13:10:03 +00:00
2015-10-13 13:29:48 +00:00
FC_ASSERT ( next_block . witness = = scheduled_witness , " Witness produced block at wrong time " ,
( " block witness " , next_block . witness ) ( " scheduled " , scheduled_witness ) ( " slot_num " , slot_num ) ) ;
2015-08-24 22:50:09 +00:00
}
2015-06-08 15:50:35 +00:00
return witness ;
}
void database : : create_block_summary ( const signed_block & next_block )
{
2015-07-14 21:56:42 +00:00
block_summary_id_type sid ( next_block . block_num ( ) & 0xffff ) ;
modify ( sid ( * this ) , [ & ] ( block_summary_object & p ) {
2015-06-08 15:50:35 +00:00
p . block_id = next_block . id ( ) ;
} ) ;
}
2015-07-09 18:09:21 +00:00
void database : : add_checkpoints ( const flat_map < uint32_t , block_id_type > & checkpts )
{
for ( const auto & i : checkpts )
_checkpoints [ i . first ] = i . second ;
}
2015-09-24 21:07:01 +00:00
bool database : : before_last_checkpoint ( ) const
{
return ( _checkpoints . size ( ) > 0 ) & & ( _checkpoints . rbegin ( ) - > first > = head_block_num ( ) ) ;
}
2015-06-08 15:50:35 +00:00
} }