BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface software
account_history_plugin.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015 Cryptonomex, Inc., and contributors.
3  *
4  * The MIT License
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
26 
28 
36 #include <graphene/chain/hardfork.hpp>
37 
38 #include <fc/thread/thread.hpp>
39 
40 namespace graphene { namespace account_history {
41 
42 namespace detail
43 {
44 
45 
47 {
48  public:
50  : _self( _plugin )
51  { }
53 
54 
58  void update_account_histories( const signed_block& b );
59 
61  {
62  return _self.database();
63  }
64 
66  flat_set<account_id_type> _tracked_accounts;
67  flat_set<account_id_type> _extended_history_accounts;
68  flat_set<account_id_type> _extended_history_registrars;
69  bool _partial_operations = false;
70  primary_index< operation_history_index >* _oho_index;
71  uint64_t _max_ops_per_account = -1;
73  private:
75  void add_account_history( const account_id_type account_id, const operation_history_id_type op_id );
76 
77 };
78 
80 {
81  return;
82 }
83 
85 {
87  const vector<optional< operation_history_object > >& hist = db.get_applied_operations();
88  bool is_first = true;
89  auto skip_oho_id = [&is_first,&db,this]() {
90  if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo
91  {
92  db.remove( db.create<operation_history_object>( []( operation_history_object& obj) {} ) );
93  is_first = false;
94  }
95  else
96  _oho_index->use_next_id();
97  };
98 
99  for( const optional< operation_history_object >& o_op : hist )
100  {
102 
103  auto create_oho = [&]() {
104  is_first = false;
106  {
107  if( o_op.valid() )
108  {
109  h.op = o_op->op;
110  h.result = o_op->result;
111  h.block_num = o_op->block_num;
112  h.trx_in_block = o_op->trx_in_block;
113  h.op_in_trx = o_op->op_in_trx;
114  h.virtual_op = o_op->virtual_op;
115  }
116  } ) );
117  };
118 
119  if( !o_op.valid() || ( _max_ops_per_account == 0 && _partial_operations ) )
120  {
121  // Note: the 2nd and 3rd checks above are for better performance, when the db is not clean,
122  // they will break consistency of account_stats.total_ops and removed_ops and most_recent_op
123  skip_oho_id();
124  continue;
125  }
126  else if( !_partial_operations )
127  // add to the operation history index
128  oho = create_oho();
129 
130  const operation_history_object& op = *o_op;
131 
132  // get the set of accounts this operation applies to
133  flat_set<account_id_type> impacted;
134  vector<authority> other;
135  // fee payer is added here
136  operation_get_required_authorities( op.op, impacted, impacted, other,
137  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
138 
139  if( op.op.is_type< account_create_operation >() )
140  impacted.insert( op.result.get<object_id_type>() );
141  else
142  operation_get_impacted_accounts( op.op, impacted,
143  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
144 
145  for( auto& a : other )
146  for( auto& item : a.account_auths )
147  impacted.insert( item.first );
148 
149  // be here, either _max_ops_per_account > 0, or _partial_operations == false, or both
150  // if _partial_operations == false, oho should have been created above
151  // so the only case should be checked here is:
152  // whether need to create oho if _max_ops_per_account > 0 and _partial_operations == true
153 
154  // for each operation this account applies to that is in the config link it into the history
155  if( _tracked_accounts.size() == 0 ) // tracking all accounts
156  {
157  // if tracking all accounts, when impacted is not empty (although it will always be),
158  // still need to create oho if _max_ops_per_account > 0 and _partial_operations == true
159  // so always need to create oho if not done
160  if (!impacted.empty() && !oho.valid()) { oho = create_oho(); }
161 
162  if( _max_ops_per_account > 0 )
163  {
164  // Note: the check above is for better performance, when the db is not clean,
165  // it breaks consistency of account_stats.total_ops and removed_ops and most_recent_op,
166  // but it ensures it's safe to remove old entries in add_account_history(...)
167  for( auto& account_id : impacted )
168  {
169  // we don't do index_account_keys here anymore, because
170  // that indexing now happens in observers' post_evaluate()
171 
172  // add history
173  add_account_history( account_id, oho->id );
174  }
175  }
176  }
177  else // tracking a subset of accounts
178  {
179  // whether need to create oho if _max_ops_per_account > 0 and _partial_operations == true ?
180  // the answer: only need to create oho if a tracked account is impacted and need to save history
181 
182  if( _max_ops_per_account > 0 )
183  {
184  // Note: the check above is for better performance, when the db is not clean,
185  // it breaks consistency of account_stats.total_ops and removed_ops and most_recent_op,
186  // but it ensures it's safe to remove old entries in add_account_history(...)
187  for( auto account_id : _tracked_accounts )
188  {
189  if( impacted.find( account_id ) != impacted.end() )
190  {
191  if (!oho.valid()) { oho = create_oho(); }
192  // add history
193  add_account_history( account_id, oho->id );
194  }
195  }
196  }
197  }
198  if (_partial_operations && ! oho.valid())
199  skip_oho_id();
200  }
201 }
202 
203 void account_history_plugin_impl::add_account_history( const account_id_type account_id, const operation_history_id_type op_id )
204 {
206  const auto& stats_obj = account_id(db).statistics(db);
207  // add new entry
209  obj.operation_id = op_id;
210  obj.account = account_id;
211  obj.sequence = stats_obj.total_ops + 1;
212  obj.next = stats_obj.most_recent_op;
213  });
214  db.modify( stats_obj, [&]( account_statistics_object& obj ){
215  obj.most_recent_op = ath.id;
216  obj.total_ops = ath.sequence;
217  });
218  // Amount of history to keep depends on if account is in the "extended history" list
219  bool extended_hist = false;
220  for ( auto eh_account_id : _extended_history_accounts ) {
221  extended_hist |= (account_id == eh_account_id);
222  }
223  if ( _extended_history_registrars.size() > 0 ) {
224  const account_id_type registrar_id = account_id(db).registrar;
225  for ( auto eh_registrar_id : _extended_history_registrars ) {
226  extended_hist |= (registrar_id == eh_registrar_id);
227  }
228  }
229  // _max_ops_per_account is guaranteed to be non-zero outside; max_ops_to_keep
230  // will likewise be non-zero, and also non-negative (it is unsigned).
231  auto max_ops_to_keep = _max_ops_per_account;
232  if (extended_hist && _extended_max_ops_per_account > max_ops_to_keep) {
233  max_ops_to_keep = _extended_max_ops_per_account;
234  }
235  // Remove the earliest account history entry if too many.
236  if( stats_obj.total_ops - stats_obj.removed_ops > max_ops_to_keep )
237  {
238  // look for the earliest entry
239  const auto& his_idx = db.get_index_type<account_transaction_history_index>();
240  const auto& by_seq_idx = his_idx.indices().get<by_seq>();
241  auto itr = by_seq_idx.lower_bound( boost::make_tuple( account_id, 0 ) );
242  // make sure don't remove the one just added
243  if( itr != by_seq_idx.end() && itr->account == account_id && itr->id != ath.id )
244  {
245  // if found, remove the entry, and adjust account stats object
246  const auto remove_op_id = itr->operation_id;
247  const auto itr_remove = itr;
248  ++itr;
249  db.remove( *itr_remove );
250  db.modify( stats_obj, [&]( account_statistics_object& obj ){
251  obj.removed_ops = obj.removed_ops + 1;
252  });
253  // modify previous node's next pointer
254  // this should be always true, but just have a check here
255  if( itr != by_seq_idx.end() && itr->account == account_id )
256  {
257  db.modify( *itr, [&]( account_transaction_history_object& obj ){
258  obj.next = account_transaction_history_id_type();
259  });
260  }
261  // else need to modify the head pointer, but it shouldn't be true
262 
263  // remove the operation history entry (1.11.x) if configured and no reference left
264  if( _partial_operations )
265  {
266  // check for references
267  const auto& by_opid_idx = his_idx.indices().get<by_opid>();
268  if( by_opid_idx.find( remove_op_id ) == by_opid_idx.end() )
269  {
270  // if no reference, remove
271  db.remove( remove_op_id(db) );
272  }
273  }
274  }
275  }
276 }
277 
278 } // end namespace detail
279 
280 
281 
282 
283 
284 
286  my( new detail::account_history_plugin_impl(*this) )
287 {
288 }
289 
291 {
292 }
293 
295 {
296  return "account_history";
297 }
298 
300  boost::program_options::options_description& cli,
301  boost::program_options::options_description& cfg
302  )
303 {
304  cli.add_options()
305  ("track-account", boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),
306  "Account ID to track history for (may specify multiple times; if unset will track all accounts)")
307  ("partial-operations", boost::program_options::value<bool>(),
308  "Keep only those operations in memory that are related to account history tracking")
309  ("max-ops-per-account", boost::program_options::value<uint64_t>(),
310  "Maximum number of operations per account that will be kept in memory")
311  ("extended-max-ops-per-account", boost::program_options::value<uint64_t>(),
312  "Maximum number of operations to keep for accounts for which extended history is kept")
313  ("extended-history-by-account",
314  boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),
315  "Track longer history for these accounts (may specify multiple times)")
316  ("extended-history-by-registrar",
317  boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),
318  "Track longer history for accounts with this registrar (may specify multiple times)")
319  ;
320  cfg.add(cli);
321 }
322 
323 void account_history_plugin::plugin_initialize(const boost::program_options::variables_map& options)
324 {
325  database().applied_block.connect( [&]( const signed_block& b){ my->update_account_histories(b); } );
328 
329  LOAD_VALUE_SET(options, "track-account", my->_tracked_accounts, graphene::chain::account_id_type);
330  if (options.count("partial-operations")) {
331  my->_partial_operations = options["partial-operations"].as<bool>();
332  }
333  if (options.count("max-ops-per-account")) {
334  my->_max_ops_per_account = options["max-ops-per-account"].as<uint64_t>();
335  }
336  if (options.count("extended-max-ops-per-account")) {
337  auto emopa = options["extended-max-ops-per-account"].as<uint64_t>();
338  my->_extended_max_ops_per_account = (emopa > my->_max_ops_per_account) ? emopa : my->_max_ops_per_account;
339  }
340  LOAD_VALUE_SET(options, "extended-history-by-account", my->_extended_history_accounts,
341  graphene::chain::account_id_type);
342  LOAD_VALUE_SET(options, "extended-history-by-registrar", my->_extended_history_registrars,
343  graphene::chain::account_id_type);
344 }
345 
347 {
348 }
349 
350 flat_set<account_id_type> account_history_plugin::tracked_accounts() const
351 {
352  return my->_tracked_accounts;
353 }
354 
355 } }
bool is_type() const
virtual void plugin_initialize(const boost::program_options::variables_map &options) override
Perform early startup routines and register plugin indexes, callbacks, etc.
void modify(const T &obj, const Lambda &m)
Wraps a derived index to intercept calls to create, modify, and remove so that callbacks may be fired...
Definition: index.hpp:309
virtual void plugin_set_program_options(boost::program_options::options_description &cli, boost::program_options::options_description &cfg) override
Fill in command line parameters used by the plugin.
fc::signal< void(const signed_block &)> applied_block
Definition: database.hpp:197
tracks the history of all logical operations on blockchain stateAll operations and virtual operations...
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
Definition: api.cpp:56
a node in a linked list of operation_history_objectsAccount history is important for users and wallet...
bool valid() const
Definition: optional.hpp:186
virtual void plugin_startup() override
Begin normal runtime operations.
std::unique_ptr< detail::account_history_plugin_impl > my
provides stack-based nullable value similar to boost::optional
Definition: optional.hpp:20
flat_set< account_id_type > tracked_accounts() const
const vector< optional< operation_history_object > > & get_applied_operations() const
Definition: db_block.cpp:548
chain::database & database()
Definition: plugin.hpp:114
#define LOAD_VALUE_SET(options, name, container, type)
Definition: plugin.hpp:140
account_transaction_history_id_type next
the operation position within the given account
void remove(const object &obj)
void operation_get_required_authorities(const operation &op, flat_set< account_id_type > &active, flat_set< account_id_type > &owner, vector< authority > &other, bool ignore_custom_operation_required_auths)
Definition: operations.cpp:103
const T & create(F &&constructor)
void operation_get_impacted_accounts(const operation &op, flat_set< account_id_type > &result, bool ignore_custom_operation_required_auths)
Definition: db_notify.cpp:342
account_transaction_history_id_type most_recent_op
const IndexType & get_index_type() const
const index_type & indices() const