BitShares-Core  4.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  bool _partial_operations = false;
68  primary_index< operation_history_index >* _oho_index;
69  uint64_t _max_ops_per_account = -1;
70  private:
72  void add_account_history( const account_id_type account_id, const operation_history_id_type op_id );
73 
74 };
75 
77 {
78  return;
79 }
80 
82 {
84  const vector<optional< operation_history_object > >& hist = db.get_applied_operations();
85  bool is_first = true;
86  auto skip_oho_id = [&is_first,&db,this]() {
87  if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo
88  {
89  db.remove( db.create<operation_history_object>( []( operation_history_object& obj) {} ) );
90  is_first = false;
91  }
92  else
93  _oho_index->use_next_id();
94  };
95 
96  for( const optional< operation_history_object >& o_op : hist )
97  {
99 
100  auto create_oho = [&]() {
101  is_first = false;
103  {
104  if( o_op.valid() )
105  {
106  h.op = o_op->op;
107  h.result = o_op->result;
108  h.block_num = o_op->block_num;
109  h.trx_in_block = o_op->trx_in_block;
110  h.op_in_trx = o_op->op_in_trx;
111  h.virtual_op = o_op->virtual_op;
112  }
113  } ) );
114  };
115 
116  if( !o_op.valid() || ( _max_ops_per_account == 0 && _partial_operations ) )
117  {
118  // Note: the 2nd and 3rd checks above are for better performance, when the db is not clean,
119  // they will break consistency of account_stats.total_ops and removed_ops and most_recent_op
120  skip_oho_id();
121  continue;
122  }
123  else if( !_partial_operations )
124  // add to the operation history index
125  oho = create_oho();
126 
127  const operation_history_object& op = *o_op;
128 
129  // get the set of accounts this operation applies to
130  flat_set<account_id_type> impacted;
131  vector<authority> other;
132  // fee payer is added here
133  operation_get_required_authorities( op.op, impacted, impacted, other,
134  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
135 
136  if( op.op.is_type< account_create_operation >() )
137  impacted.insert( op.result.get<object_id_type>() );
138  else
139  operation_get_impacted_accounts( op.op, impacted,
140  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
141 
142  for( auto& a : other )
143  for( auto& item : a.account_auths )
144  impacted.insert( item.first );
145 
146  // be here, either _max_ops_per_account > 0, or _partial_operations == false, or both
147  // if _partial_operations == false, oho should have been created above
148  // so the only case should be checked here is:
149  // whether need to create oho if _max_ops_per_account > 0 and _partial_operations == true
150 
151  // for each operation this account applies to that is in the config link it into the history
152  if( _tracked_accounts.size() == 0 ) // tracking all accounts
153  {
154  // if tracking all accounts, when impacted is not empty (although it will always be),
155  // still need to create oho if _max_ops_per_account > 0 and _partial_operations == true
156  // so always need to create oho if not done
157  if (!impacted.empty() && !oho.valid()) { oho = create_oho(); }
158 
159  if( _max_ops_per_account > 0 )
160  {
161  // Note: the check above is for better performance, when the db is not clean,
162  // it breaks consistency of account_stats.total_ops and removed_ops and most_recent_op,
163  // but it ensures it's safe to remove old entries in add_account_history(...)
164  for( auto& account_id : impacted )
165  {
166  // we don't do index_account_keys here anymore, because
167  // that indexing now happens in observers' post_evaluate()
168 
169  // add history
170  add_account_history( account_id, oho->id );
171  }
172  }
173  }
174  else // tracking a subset of accounts
175  {
176  // whether need to create oho if _max_ops_per_account > 0 and _partial_operations == true ?
177  // the answer: only need to create oho if a tracked account is impacted and need to save history
178 
179  if( _max_ops_per_account > 0 )
180  {
181  // Note: the check above is for better performance, when the db is not clean,
182  // it breaks consistency of account_stats.total_ops and removed_ops and most_recent_op,
183  // but it ensures it's safe to remove old entries in add_account_history(...)
184  for( auto account_id : _tracked_accounts )
185  {
186  if( impacted.find( account_id ) != impacted.end() )
187  {
188  if (!oho.valid()) { oho = create_oho(); }
189  // add history
190  add_account_history( account_id, oho->id );
191  }
192  }
193  }
194  }
195  if (_partial_operations && ! oho.valid())
196  skip_oho_id();
197  }
198 }
199 
200 void account_history_plugin_impl::add_account_history( const account_id_type account_id, const operation_history_id_type op_id )
201 {
203  const auto& stats_obj = account_id(db).statistics(db);
204  // add new entry
206  obj.operation_id = op_id;
207  obj.account = account_id;
208  obj.sequence = stats_obj.total_ops + 1;
209  obj.next = stats_obj.most_recent_op;
210  });
211  db.modify( stats_obj, [&]( account_statistics_object& obj ){
212  obj.most_recent_op = ath.id;
213  obj.total_ops = ath.sequence;
214  });
215  // remove the earliest account history entry if too many
216  // _max_ops_per_account is guaranteed to be non-zero outside
217  if( stats_obj.total_ops - stats_obj.removed_ops > _max_ops_per_account )
218  {
219  // look for the earliest entry
220  const auto& his_idx = db.get_index_type<account_transaction_history_index>();
221  const auto& by_seq_idx = his_idx.indices().get<by_seq>();
222  auto itr = by_seq_idx.lower_bound( boost::make_tuple( account_id, 0 ) );
223  // make sure don't remove the one just added
224  if( itr != by_seq_idx.end() && itr->account == account_id && itr->id != ath.id )
225  {
226  // if found, remove the entry, and adjust account stats object
227  const auto remove_op_id = itr->operation_id;
228  const auto itr_remove = itr;
229  ++itr;
230  db.remove( *itr_remove );
231  db.modify( stats_obj, [&]( account_statistics_object& obj ){
232  obj.removed_ops = obj.removed_ops + 1;
233  });
234  // modify previous node's next pointer
235  // this should be always true, but just have a check here
236  if( itr != by_seq_idx.end() && itr->account == account_id )
237  {
238  db.modify( *itr, [&]( account_transaction_history_object& obj ){
239  obj.next = account_transaction_history_id_type();
240  });
241  }
242  // else need to modify the head pointer, but it shouldn't be true
243 
244  // remove the operation history entry (1.11.x) if configured and no reference left
245  if( _partial_operations )
246  {
247  // check for references
248  const auto& by_opid_idx = his_idx.indices().get<by_opid>();
249  if( by_opid_idx.find( remove_op_id ) == by_opid_idx.end() )
250  {
251  // if no reference, remove
252  db.remove( remove_op_id(db) );
253  }
254  }
255  }
256  }
257 }
258 
259 } // end namespace detail
260 
261 
262 
263 
264 
265 
267  my( new detail::account_history_plugin_impl(*this) )
268 {
269 }
270 
272 {
273 }
274 
276 {
277  return "account_history";
278 }
279 
281  boost::program_options::options_description& cli,
282  boost::program_options::options_description& cfg
283  )
284 {
285  cli.add_options()
286  ("track-account", boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(), "Account ID to track history for (may specify multiple times)")
287  ("partial-operations", boost::program_options::value<bool>(), "Keep only those operations in memory that are related to account history tracking")
288  ("max-ops-per-account", boost::program_options::value<uint64_t>(), "Maximum number of operations per account will be kept in memory")
289  ;
290  cfg.add(cli);
291 }
292 
293 void account_history_plugin::plugin_initialize(const boost::program_options::variables_map& options)
294 {
295  database().applied_block.connect( [&]( const signed_block& b){ my->update_account_histories(b); } );
298 
299  LOAD_VALUE_SET(options, "track-account", my->_tracked_accounts, graphene::chain::account_id_type);
300  if (options.count("partial-operations")) {
301  my->_partial_operations = options["partial-operations"].as<bool>();
302  }
303  if (options.count("max-ops-per-account")) {
304  my->_max_ops_per_account = options["max-ops-per-account"].as<uint64_t>();
305  }
306 }
307 
309 {
310 }
311 
312 flat_set<account_id_type> account_history_plugin::tracked_accounts() const
313 {
314  return my->_tracked_accounts;
315 }
316 
317 } }
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:321
account_transaction_history_id_type most_recent_op
const IndexType & get_index_type() const
const index_type & indices() const