36 #include <graphene/chain/hardfork.hpp>
42 namespace graphene {
namespace account_history {
69 flat_set<account_id_type> _tracked_accounts;
70 flat_set<account_id_type> _extended_history_accounts;
71 flat_set<account_id_type> _extended_history_registrars;
72 bool _partial_operations =
false;
74 uint64_t _max_ops_per_account = -1;
75 uint64_t _extended_max_ops_per_account = -1;
76 uint32_t _min_blocks_to_keep = 30000;
77 uint64_t _max_ops_per_acc_by_min_blocks = 1000;
79 uint32_t _latest_block_number_to_remove = 0;
81 uint64_t get_max_ops_to_keep(
const account_id_type& account_id );
89 void remove_old_histories();
95 void init_program_options(
const boost::program_options::variables_map& options);
98 template<
typename T >
99 static T get_biggest_number_to_remove( T biggest_number, T amount_to_keep )
101 return ( biggest_number > amount_to_keep ) ? ( biggest_number - amount_to_keep ) : 0;
104 void account_history_plugin_impl::update_account_histories(
const signed_block& b )
106 _latest_block_number_to_remove = get_biggest_number_to_remove( b.
block_num(), _min_blocks_to_keep );
110 bool is_first =
true;
111 auto skip_oho_id = [&is_first,&db,
this]() {
112 if( is_first && db._undo_db.enabled() )
118 _oho_index->use_next_id();
121 for(
const optional< operation_history_object >& o_op : hist )
123 optional<operation_history_object> oho;
125 auto create_oho = [&]() {
127 return optional<operation_history_object>( db.create<operation_history_object>( [&]( operation_history_object& h )
132 h.result = o_op->result;
133 h.block_num = o_op->block_num;
134 h.trx_in_block = o_op->trx_in_block;
135 h.op_in_trx = o_op->op_in_trx;
136 h.virtual_op = o_op->virtual_op;
137 h.is_virtual = o_op->is_virtual;
138 h.block_time = o_op->block_time;
143 if( !o_op.valid() || ( _max_ops_per_account == 0 && _partial_operations ) )
150 else if( !_partial_operations )
154 const operation_history_object& op = *o_op;
157 flat_set<account_id_type> impacted;
158 vector<authority> other;
161 MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
163 if( op.op.is_type< account_create_operation >() )
164 impacted.insert( account_id_type( op.result.get<object_id_type>() ) );
167 if( HARDFORK_CORE_265_PASSED(b.
timestamp) || !op.op.is_type< account_create_operation >() )
170 MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
176 if( op_result.value.impacted_accounts.valid() )
178 for(
const auto& a : *op_result.value.impacted_accounts )
179 impacted.insert( a );
183 for(
auto& a : other )
184 for(
auto& item : a.account_auths )
185 impacted.insert( item.first );
193 if( _tracked_accounts.size() == 0 )
198 if (!impacted.empty() && !oho.valid()) { oho = create_oho(); }
200 if( _max_ops_per_account > 0 )
205 for(
auto& account_id : impacted )
211 add_account_history( account_id, *oho );
220 if( _max_ops_per_account > 0 )
225 for(
auto account_id : _tracked_accounts )
227 if( impacted.find( account_id ) != impacted.end() )
229 if (!oho.valid()) { oho = create_oho(); }
231 add_account_history( account_id, *oho );
236 if (_partial_operations && ! oho.valid())
240 remove_old_histories();
243 void account_history_plugin_impl::add_account_history(
const account_id_type& account_id,
244 const operation_history_object& op )
249 const auto& aho = db.
create<account_history_object>( [&account_id,&op,&stats_obj](account_history_object& obj){
250 obj.operation_id = op.id;
251 obj.account = account_id;
252 obj.sequence = stats_obj.total_ops + 1;
253 obj.next = stats_obj.most_recent_op;
255 db.
modify( stats_obj, [&aho]( account_statistics_object& obj ){
256 obj.most_recent_op = aho.id;
257 obj.total_ops = aho.sequence;
260 remove_old_histories_by_account( stats_obj );
263 uint64_t account_history_plugin_impl::get_max_ops_to_keep(
const account_id_type& account_id )
267 bool extended_hist = ( _extended_history_accounts.find( account_id ) != _extended_history_accounts.end() );
268 if( !extended_hist && !_extended_history_registrars.empty() )
270 const account_id_type& registrar_id = account_id(db).registrar;
271 extended_hist = ( _extended_history_registrars.find( registrar_id ) != _extended_history_registrars.end() );
275 auto max_ops_to_keep = _max_ops_per_account;
276 if( extended_hist && _extended_max_ops_per_account > max_ops_to_keep )
278 max_ops_to_keep = _extended_max_ops_per_account;
280 if( 0 == max_ops_to_keep )
282 return max_ops_to_keep;
285 void account_history_plugin_impl::remove_old_histories()
287 if( 0 == _latest_block_number_to_remove )
292 auto itr = exa_idx.begin();
293 while( itr != exa_idx.end() && itr->block_num <= _latest_block_number_to_remove )
296 remove_old_histories_by_account( stats_obj, &(*itr) );
297 itr = exa_idx.begin();
301 void account_history_plugin_impl::check_and_remove_op_history_obj(
const operation_history_object& op )
303 if( _partial_operations )
308 const auto& by_opid_idx = his_idx.indices().get<by_opid>();
309 if( by_opid_idx.find( op.get_id() ) == by_opid_idx.end() )
318 void account_history_plugin_impl::remove_old_histories_by_account(
const account_statistics_object& stats_obj,
319 const exceeded_account_object* p_exa_obj )
322 const account_id_type& account_id = stats_obj.owner;
323 auto max_ops_to_keep = get_max_ops_to_keep( account_id );
324 auto number_of_ops_to_remove = get_biggest_number_to_remove( stats_obj.total_ops, max_ops_to_keep );
325 auto number_of_ops_to_remove_by_blks = get_biggest_number_to_remove( stats_obj.total_ops,
326 _max_ops_per_acc_by_min_blocks );
329 const auto& by_seq_idx = his_idx.indices().get<by_seq>();
331 auto removed_ops = stats_obj.removed_ops;
333 auto aho_itr = ( removed_ops < number_of_ops_to_remove ) ? by_seq_idx.lower_bound( account_id )
334 : by_seq_idx.begin();
336 uint32_t oldest_block_num = _latest_block_number_to_remove;
337 while( removed_ops < number_of_ops_to_remove )
341 if( aho_itr == by_seq_idx.end() || aho_itr->account != account_id || aho_itr->id == stats_obj.most_recent_op )
345 const auto& aho_to_remove = *aho_itr;
346 const auto& remove_op = aho_to_remove.operation_id(db);
347 oldest_block_num = remove_op.block_num;
348 if( remove_op.block_num > _latest_block_number_to_remove && removed_ops >= number_of_ops_to_remove_by_blks )
353 db.
remove( aho_to_remove );
357 check_and_remove_op_history_obj( remove_op );
360 if( removed_ops != stats_obj.removed_ops )
362 db.
modify( stats_obj, [removed_ops]( account_statistics_object& obj ){
363 obj.removed_ops = removed_ops;
367 if( aho_itr != by_seq_idx.end() && aho_itr->account == account_id )
369 db.
modify( *aho_itr, []( account_history_object& obj ){
370 obj.next = account_history_id_type();
379 auto exa_itr = exa_idx.find( account_id );
380 if( exa_itr != exa_idx.end() )
381 p_exa_obj = &(*exa_itr);
383 if( stats_obj.removed_ops < number_of_ops_to_remove )
387 db.
modify( *p_exa_obj, [oldest_block_num]( exceeded_account_object& obj ){
388 obj.block_num = oldest_block_num;
391 db.
create<exceeded_account_object>(
392 [&account_id, oldest_block_num]( exceeded_account_object& obj ){
393 obj.account_id = account_id;
394 obj.block_num = oldest_block_num;
407 my(
std::make_unique<detail::account_history_plugin_impl>(*this) )
416 return "account_history";
420 boost::program_options::options_description& cli,
421 boost::program_options::options_description& cfg
425 (
"track-account", boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),
426 "Account ID to track history for (may specify multiple times; if unset will track all accounts)")
427 (
"partial-operations", boost::program_options::value<bool>(),
428 "Keep only those operations in memory that are related to account history tracking")
429 (
"max-ops-per-account", boost::program_options::value<uint64_t>(),
430 "Maximum number of operations per account that will be kept in memory. "
431 "Note that the actual number may be higher due to the min-blocks-to-keep option.")
432 (
"extended-max-ops-per-account", boost::program_options::value<uint64_t>(),
433 "Maximum number of operations to keep for accounts for which extended history is kept. "
434 "This option only takes effect when track-account is not used and max-ops-per-account is not zero.")
435 (
"extended-history-by-account",
436 boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),
437 "Track longer history for these accounts (may specify multiple times)")
438 (
"extended-history-by-registrar",
439 boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),
440 "Track longer history for accounts with this registrar (may specify multiple times)")
441 (
"min-blocks-to-keep", boost::program_options::value<uint32_t>(),
442 "Operations which are in the latest X blocks will be kept in memory. "
443 "This option only takes effect when track-account is not used and max-ops-per-account is not zero. "
444 "Note that this option may cause more history records to be kept in memory than the limit defined by the "
445 "max-ops-per-account option, but the amount will be limited by the max-ops-per-acc-by-min-blocks option. "
447 (
"max-ops-per-acc-by-min-blocks", boost::program_options::value<uint64_t>(),
448 "A potential higher limit on the maximum number of operations per account to be kept in memory "
449 "when the min-blocks-to-keep option causes the amount to exceed the limit defined by the "
450 "max-ops-per-account option. If this is less than max-ops-per-account, max-ops-per-account will be used. "
458 my->init_program_options( options );
468 void detail::account_history_plugin_impl::init_program_options(
const boost::program_options::variables_map& options)
470 LOAD_VALUE_SET(options,
"track-account", _tracked_accounts, graphene::chain::account_id_type);
475 if( _extended_max_ops_per_account < _max_ops_per_account )
476 _extended_max_ops_per_account = _max_ops_per_account;
478 LOAD_VALUE_SET(options,
"extended-history-by-account", _extended_history_accounts,
479 graphene::chain::account_id_type);
480 LOAD_VALUE_SET(options,
"extended-history-by-registrar", _extended_history_registrars,
481 graphene::chain::account_id_type);
485 if( _max_ops_per_acc_by_min_blocks < _max_ops_per_account )
486 _max_ops_per_acc_by_min_blocks = _max_ops_per_account;
495 return my->_tracked_accounts;