BitShares-Core  4.0.0
BitShares blockchain implementation and command-line interface software
elasticsearch_plugin.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017 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 
28 #include <graphene/chain/hardfork.hpp>
29 #include <curl/curl.h>
30 
31 namespace graphene { namespace elasticsearch {
32 
33 namespace detail
34 {
35 
37 {
38  public:
40  : _self( _plugin )
41  { curl = curl_easy_init(); }
43 
44  bool update_account_histories( const signed_block& b );
45 
47  {
48  return _self.database();
49  }
50 
52  primary_index< operation_history_index >* _oho_index;
53 
54  std::string _elasticsearch_node_url = "http://localhost:9200/";
55  uint32_t _elasticsearch_bulk_replay = 10000;
56  uint32_t _elasticsearch_bulk_sync = 100;
57  bool _elasticsearch_visitor = false;
58  std::string _elasticsearch_basic_auth = "";
59  std::string _elasticsearch_index_prefix = "bitshares-";
64  CURL *curl; // curl handler
65  vector <string> bulk_lines; // vector of op lines
66  vector<std::string> prepare;
67 
69  uint32_t limit_documents;
70  int16_t op_type;
75  std::string bulk_line;
76  std::string index_name;
77  bool is_sync = false;
78  private:
79  bool add_elasticsearch( const account_id_type account_id, const optional<operation_history_object>& oho, const uint32_t block_number );
80  const account_transaction_history_object& addNewEntry(const account_statistics_object& stats_obj,
81  const account_id_type& account_id,
82  const optional <operation_history_object>& oho);
83  const account_statistics_object& getStatsObject(const account_id_type& account_id);
84  void growStats(const account_statistics_object& stats_obj, const account_transaction_history_object& ath);
85  void getOperationType(const optional <operation_history_object>& oho);
86  void doOperationHistory(const optional <operation_history_object>& oho);
87  void doBlock(uint32_t trx_in_block, const signed_block& b);
88  void doVisitor(const optional <operation_history_object>& oho);
89  void checkState(const fc::time_point_sec& block_time);
90  void cleanObjects(const account_transaction_history_id_type& ath, const account_id_type& account_id);
91  void createBulkLine(const account_transaction_history_object& ath);
92  void prepareBulk(const account_transaction_history_id_type& ath_id);
93  void populateESstruct();
94 };
95 
97 {
98  if (curl) {
99  curl_easy_cleanup(curl);
100  curl = nullptr;
101  }
102  return;
103 }
104 
106 {
107  checkState(b.timestamp);
109 
111  const vector<optional< operation_history_object > >& hist = db.get_applied_operations();
112  bool is_first = true;
113  auto skip_oho_id = [&is_first,&db,this]() {
114  if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo
115  {
116  db.remove( db.create<operation_history_object>( []( operation_history_object& obj) {} ) );
117  is_first = false;
118  }
119  else
120  _oho_index->use_next_id();
121  };
122  for( const optional< operation_history_object >& o_op : hist ) {
123  optional <operation_history_object> oho;
124 
125  auto create_oho = [&]() {
126  is_first = false;
127  return optional<operation_history_object>(
129  if (o_op.valid())
130  {
131  h.op = o_op->op;
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  }
138  }));
139  };
140 
141  if( !o_op.valid() ) {
142  skip_oho_id();
143  continue;
144  }
145  oho = create_oho();
146 
147  // populate what we can before impacted loop
148  getOperationType(oho);
149  doOperationHistory(oho);
150  doBlock(oho->trx_in_block, b);
152  doVisitor(oho);
153 
154  const operation_history_object& op = *o_op;
155 
156  // get the set of accounts this operation applies to
157  flat_set<account_id_type> impacted;
158  vector<authority> other;
159  // fee_payer is added here
160  operation_get_required_authorities( op.op, impacted, impacted, other,
161  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
162 
163  if( op.op.is_type< account_create_operation >() )
164  impacted.insert( op.result.get<object_id_type>() );
165  else
166  operation_get_impacted_accounts( op.op, impacted,
167  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );
168 
169  for( auto& a : other )
170  for( auto& item : a.account_auths )
171  impacted.insert( item.first );
172 
173  for( auto& account_id : impacted )
174  {
175  if(!add_elasticsearch( account_id, oho, b.block_num() ))
176  return false;
177  }
178  }
179  // we send bulk at end of block when we are in sync for better real time client experience
180  if(is_sync)
181  {
182  populateESstruct();
183  if(es.bulk_lines.size() > 0)
184  {
185  prepare.clear();
186  if(!graphene::utilities::SendBulk(std::move(es)))
187  return false;
188  else
189  bulk_lines.clear();
190  }
191  }
192 
193  if(bulk_lines.size() != limit_documents)
194  bulk_lines.reserve(limit_documents);
195 
196  return true;
197 }
198 
199 void elasticsearch_plugin_impl::checkState(const fc::time_point_sec& block_time)
200 {
201  if((fc::time_point::now() - block_time) < fc::seconds(30))
202  {
204  is_sync = true;
205  }
206  else
207  {
209  is_sync = false;
210  }
211 }
212 
213 void elasticsearch_plugin_impl::getOperationType(const optional <operation_history_object>& oho)
214 {
215  if (!oho->id.is_null())
216  op_type = oho->op.which();
217 }
218 
219 void elasticsearch_plugin_impl::doOperationHistory(const optional <operation_history_object>& oho)
220 {
221  os.trx_in_block = oho->trx_in_block;
222  os.op_in_trx = oho->op_in_trx;
223  os.operation_result = fc::json::to_string(oho->result);
224  os.virtual_op = oho->virtual_op;
225 
228  adaptor_struct adaptor;
229  os.op_object = adaptor.adapt(os.op_object.get_object());
230  }
232  os.op = fc::json::to_string(oho->op);
233 }
234 
235 void elasticsearch_plugin_impl::doBlock(uint32_t trx_in_block, const signed_block& b)
236 {
237  std::string trx_id = "";
238  if(trx_in_block < b.transactions.size())
239  trx_id = b.transactions[trx_in_block].id().str();
240  bs.block_num = b.block_num();
241  bs.block_time = b.timestamp;
242  bs.trx_id = trx_id;
243 }
244 
245 void elasticsearch_plugin_impl::doVisitor(const optional <operation_history_object>& oho)
246 {
248 
249  operation_visitor o_v;
250  oho->op.visit(o_v);
251 
252  auto fee_asset = o_v.fee_asset(db);
253  vs.fee_data.asset = o_v.fee_asset;
254  vs.fee_data.asset_name = fee_asset.symbol;
256  vs.fee_data.amount_units = (o_v.fee_amount.value)/(double)asset::scaled_precision(fee_asset.precision).value;
257 
258  auto transfer_asset = o_v.transfer_asset_id(db);
260  vs.transfer_data.asset_name = transfer_asset.symbol;
262  vs.transfer_data.amount_units = (o_v.transfer_amount.value)/(double)asset::scaled_precision(transfer_asset.precision).value;
265 
266  auto fill_pays_asset = o_v.fill_pays_asset_id(db);
267  auto fill_receives_asset = o_v.fill_receives_asset_id(db);
271  vs.fill_data.pays_asset_name = fill_pays_asset.symbol;
273  vs.fill_data.pays_amount_units = (o_v.fill_pays_amount.value)/(double)asset::scaled_precision(fill_pays_asset.precision).value;
275  vs.fill_data.receives_asset_name = fill_receives_asset.symbol;
277  vs.fill_data.receives_amount_units = (o_v.fill_receives_amount.value)/(double)asset::scaled_precision(fill_receives_asset.precision).value;
278 
279  auto fill_price = (o_v.fill_receives_amount.value/(double)asset::scaled_precision(fill_receives_asset.precision).value) /
280  (o_v.fill_pays_amount.value/(double)asset::scaled_precision(fill_pays_asset.precision).value);
281  vs.fill_data.fill_price_units = fill_price;
284 }
285 
286 bool elasticsearch_plugin_impl::add_elasticsearch( const account_id_type account_id,
287  const optional <operation_history_object>& oho,
288  const uint32_t block_number)
289 {
290  const auto &stats_obj = getStatsObject(account_id);
291  const auto &ath = addNewEntry(stats_obj, account_id, oho);
292  growStats(stats_obj, ath);
293  if(block_number > _elasticsearch_start_es_after_block) {
294  createBulkLine(ath);
295  prepareBulk(ath.id);
296  }
297  cleanObjects(ath.id, account_id);
298 
299  if (curl && bulk_lines.size() >= limit_documents) { // we are in bulk time, ready to add data to elasticsearech
300  prepare.clear();
301  populateESstruct();
302  if(!graphene::utilities::SendBulk(std::move(es)))
303  return false;
304  else
305  bulk_lines.clear();
306  }
307 
308  return true;
309 }
310 
311 const account_statistics_object& elasticsearch_plugin_impl::getStatsObject(const account_id_type& account_id)
312 {
314  const auto &stats_obj = db.get_account_stats_by_owner(account_id);
315 
316  return stats_obj;
317 }
318 
319 const account_transaction_history_object& elasticsearch_plugin_impl::addNewEntry(const account_statistics_object& stats_obj,
320  const account_id_type& account_id,
321  const optional <operation_history_object>& oho)
322 {
325  obj.operation_id = oho->id;
326  obj.account = account_id;
327  obj.sequence = stats_obj.total_ops + 1;
328  obj.next = stats_obj.most_recent_op;
329  });
330 
331  return ath;
332 }
333 
334 void elasticsearch_plugin_impl::growStats(const account_statistics_object& stats_obj,
336 {
338  db.modify(stats_obj, [&](account_statistics_object &obj) {
339  obj.most_recent_op = ath.id;
340  obj.total_ops = ath.sequence;
341  });
342 }
343 
344 void elasticsearch_plugin_impl::createBulkLine(const account_transaction_history_object& ath)
345 {
349  bulk_line_struct.operation_id_num = ath.operation_id.instance.value;
354 }
355 
356 void elasticsearch_plugin_impl::prepareBulk(const account_transaction_history_id_type& ath_id)
357 {
358  const std::string _id = fc::json::to_string(ath_id);
359  fc::mutable_variant_object bulk_header;
360  bulk_header["_index"] = index_name;
361  bulk_header["_type"] = "data";
362  bulk_header["_id"] = fc::to_string(ath_id.space_id) + "." + fc::to_string(ath_id.type_id) + "."
363  + fc::to_string(ath_id.instance.value);
364  prepare = graphene::utilities::createBulk(bulk_header, std::move(bulk_line));
365  std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk_lines));
366  prepare.clear();
367 }
368 
369 void elasticsearch_plugin_impl::cleanObjects(const account_transaction_history_id_type& ath_id, const account_id_type& account_id)
370 {
372  // remove everything except current object from ath
373  const auto &his_idx = db.get_index_type<account_transaction_history_index>();
374  const auto &by_seq_idx = his_idx.indices().get<by_seq>();
375  auto itr = by_seq_idx.lower_bound(boost::make_tuple(account_id, 0));
376  if (itr != by_seq_idx.end() && itr->account == account_id && itr->id != ath_id) {
377  // if found, remove the entry
378  const auto remove_op_id = itr->operation_id;
379  const auto itr_remove = itr;
380  ++itr;
381  db.remove( *itr_remove );
382  // modify previous node's next pointer
383  // this should be always true, but just have a check here
384  if( itr != by_seq_idx.end() && itr->account == account_id )
385  {
386  db.modify( *itr, [&]( account_transaction_history_object& obj ){
387  obj.next = account_transaction_history_id_type();
388  });
389  }
390  // do the same on oho
391  const auto &by_opid_idx = his_idx.indices().get<by_opid>();
392  if (by_opid_idx.find(remove_op_id) == by_opid_idx.end()) {
393  db.remove(remove_op_id(db));
394  }
395  }
396 }
397 
398 void elasticsearch_plugin_impl::populateESstruct()
399 {
400  es.curl = curl;
401  es.bulk_lines = std::move(bulk_lines);
405  es.endpoint = "";
406  es.query = "";
407 }
408 
409 } // end namespace detail
410 
412  my( new detail::elasticsearch_plugin_impl(*this) )
413 {
414 }
415 
417 {
418 }
419 
421 {
422  return "elasticsearch";
423 }
425 {
426  return "Stores account history data in elasticsearch database(EXPERIMENTAL).";
427 }
428 
430  boost::program_options::options_description& cli,
431  boost::program_options::options_description& cfg
432  )
433 {
434  cli.add_options()
435  ("elasticsearch-node-url", boost::program_options::value<std::string>(),
436  "Elastic Search database node url(http://localhost:9200/)")
437  ("elasticsearch-bulk-replay", boost::program_options::value<uint32_t>(),
438  "Number of bulk documents to index on replay(10000)")
439  ("elasticsearch-bulk-sync", boost::program_options::value<uint32_t>(),
440  "Number of bulk documents to index on a syncronied chain(100)")
441  ("elasticsearch-visitor", boost::program_options::value<bool>(),
442  "Use visitor to index additional data(slows down the replay(false))")
443  ("elasticsearch-basic-auth", boost::program_options::value<std::string>(),
444  "Pass basic auth to elasticsearch database('')")
445  ("elasticsearch-index-prefix", boost::program_options::value<std::string>(),
446  "Add a prefix to the index(bitshares-)")
447  ("elasticsearch-operation-object", boost::program_options::value<bool>(),
448  "Save operation as object(true)")
449  ("elasticsearch-start-es-after-block", boost::program_options::value<uint32_t>(),
450  "Start doing ES job after block(0)")
451  ("elasticsearch-operation-string", boost::program_options::value<bool>(),
452  "Save operation as string. Needed to serve history api calls(false)")
453  ("elasticsearch-mode", boost::program_options::value<uint16_t>(),
454  "Mode of operation: only_save(0), only_query(1), all(2) - Default: 0")
455  ;
456  cfg.add(cli);
457 }
458 
459 void elasticsearch_plugin::plugin_initialize(const boost::program_options::variables_map& options)
460 {
463 
464  if (options.count("elasticsearch-node-url")) {
465  my->_elasticsearch_node_url = options["elasticsearch-node-url"].as<std::string>();
466  }
467  if (options.count("elasticsearch-bulk-replay")) {
468  my->_elasticsearch_bulk_replay = options["elasticsearch-bulk-replay"].as<uint32_t>();
469  }
470  if (options.count("elasticsearch-bulk-sync")) {
471  my->_elasticsearch_bulk_sync = options["elasticsearch-bulk-sync"].as<uint32_t>();
472  }
473  if (options.count("elasticsearch-visitor")) {
474  my->_elasticsearch_visitor = options["elasticsearch-visitor"].as<bool>();
475  }
476  if (options.count("elasticsearch-basic-auth")) {
477  my->_elasticsearch_basic_auth = options["elasticsearch-basic-auth"].as<std::string>();
478  }
479  if (options.count("elasticsearch-index-prefix")) {
480  my->_elasticsearch_index_prefix = options["elasticsearch-index-prefix"].as<std::string>();
481  }
482  if (options.count("elasticsearch-operation-object")) {
483  my->_elasticsearch_operation_object = options["elasticsearch-operation-object"].as<bool>();
484  }
485  if (options.count("elasticsearch-start-es-after-block")) {
486  my->_elasticsearch_start_es_after_block = options["elasticsearch-start-es-after-block"].as<uint32_t>();
487  }
488  if (options.count("elasticsearch-operation-string")) {
489  my->_elasticsearch_operation_string = options["elasticsearch-operation-string"].as<bool>();
490  }
491  if (options.count("elasticsearch-mode")) {
492  const auto option_number = options["elasticsearch-mode"].as<uint16_t>();
493  if(option_number > mode::all)
494  FC_THROW_EXCEPTION(graphene::chain::plugin_exception, "Elasticsearch mode not valid");
495  my->_elasticsearch_mode = static_cast<mode>(options["elasticsearch-mode"].as<uint16_t>());
496  }
497 
498  if(my->_elasticsearch_mode != mode::only_query) {
499  if (my->_elasticsearch_mode == mode::all && !my->_elasticsearch_operation_string)
500  FC_THROW_EXCEPTION(graphene::chain::plugin_exception,
501  "If elasticsearch-mode is set to all then elasticsearch-operation-string need to be true");
502 
503  database().applied_block.connect([this](const signed_block &b) {
504  if (!my->update_account_histories(b))
505  FC_THROW_EXCEPTION(graphene::chain::plugin_exception,
506  "Error populating ES database, we are going to keep trying.");
507  });
508  }
509 }
510 
512 {
514  es.curl = my->curl;
515  es.elasticsearch_url = my->_elasticsearch_node_url;
516  es.auth = my->_elasticsearch_basic_auth;
517 
519  FC_THROW_EXCEPTION(fc::exception, "ES database is not up in url ${url}", ("url", my->_elasticsearch_node_url));
520  ilog("elasticsearch ACCOUNT HISTORY: plugin_startup() begin");
521 }
522 
524 {
525  const string operation_id_string = std::string(object_id_type(id));
526 
527  const string query = R"(
528  {
529  "query": {
530  "match":
531  {
532  "account_history.operation_id": )" + operation_id_string + R"("
533  }
534  }
535  }
536  )";
537 
538  auto es = prepareHistoryQuery(query);
541  const auto source = variant_response["hits"]["hits"][size_t(0)]["_source"];
542  return fromEStoOperation(source);
543 }
544 
545 vector<operation_history_object> elasticsearch_plugin::get_account_history(
546  const account_id_type account_id,
547  operation_history_id_type stop = operation_history_id_type(),
548  unsigned limit = 100,
549  operation_history_id_type start = operation_history_id_type())
550 {
551  const string account_id_string = std::string(object_id_type(account_id));
552 
553  const auto stop_number = stop.instance.value;
554  const auto start_number = start.instance.value;
555 
556  string range = "";
557  if(stop_number == 0)
558  range = " AND operation_id_num: ["+fc::to_string(stop_number)+" TO "+fc::to_string(start_number)+"]";
559  else if(stop_number > 0)
560  range = " AND operation_id_num: {"+fc::to_string(stop_number)+" TO "+fc::to_string(start_number)+"]";
561 
562  const string query = R"(
563  {
564  "size": )" + fc::to_string(limit) + R"(,
565  "sort" : [{ "operation_id_num" : {"order" : "desc"}}],
566  "query": {
567  "bool": {
568  "must": [
569  {
570  "query_string": {
571  "query": "account_history.account: )" + account_id_string + range + R"("
572  }
573  }
574  ]
575  }
576  }
577  }
578  )";
579 
580  auto es = prepareHistoryQuery(query);
581 
582  vector<operation_history_object> result;
583 
585  return result;
586 
589 
590  const auto hits = variant_response["hits"]["total"];
591  const auto size = std::min(static_cast<uint32_t>(hits.as_uint64()), limit);
592 
593  for(unsigned i=0; i<size; i++)
594  {
595  const auto source = variant_response["hits"]["hits"][size_t(i)]["_source"];
596  result.push_back(fromEStoOperation(source));
597  }
598  return result;
599 }
600 
601 operation_history_object elasticsearch_plugin::fromEStoOperation(variant source)
602 {
604 
605  const auto operation_id = source["account_history"]["operation_id"];
606  fc::from_variant( operation_id, result.id, GRAPHENE_MAX_NESTED_OBJECTS );
607 
608  const auto op = fc::json::from_string(source["operation_history"]["op"].as_string());
610 
611  const auto operation_result = fc::json::from_string(source["operation_history"]["operation_result"].as_string());
613 
614  result.block_num = source["block_data"]["block_num"].as_uint64();
615  result.trx_in_block = source["operation_history"]["trx_in_block"].as_uint64();
616  result.op_in_trx = source["operation_history"]["op_in_trx"].as_uint64();
617  result.trx_in_block = source["operation_history"]["virtual_op"].as_uint64();
618 
619  return result;
620 }
621 
622 graphene::utilities::ES elasticsearch_plugin::prepareHistoryQuery(string query)
623 {
624  CURL *curl;
625  curl = curl_easy_init();
626 
628  es.curl = curl;
629  es.elasticsearch_url = my->_elasticsearch_node_url;
630  es.index_prefix = my->_elasticsearch_index_prefix;
631  es.endpoint = es.index_prefix + "*/data/_search";
632  es.query = query;
633 
634  return es;
635 }
636 
638 {
639  return my->_elasticsearch_mode;
640 }
641 
642 
643 } }
bool is_type() const
static string to_string(const variant &v, output_formatting format=stringify_large_ints_and_doubles, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:638
vector< operation_history_object > get_account_history(const account_id_type account_id, operation_history_id_type stop, unsigned limit, operation_history_id_type start)
void modify(const T &obj, const Lambda &m)
const auto response
Wraps a derived index to intercept calls to create, modify, and remove so that callbacks may be fired...
Definition: index.hpp:309
std::unique_ptr< detail::elasticsearch_plugin_impl > my
virtual void plugin_initialize(const boost::program_options::variables_map &options) override
Perform early startup routines and register plugin indexes, callbacks, etc.
fc::signal< void(const signed_block &)> applied_block
Definition: database.hpp:197
#define GRAPHENE_MAX_NESTED_OBJECTS
Definition: config.hpp:31
tracks the history of all logical operations on blockchain stateAll operations and virtual operations...
operation_history_object get_operation_by_id(operation_history_id_type id)
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
Definition: api.cpp:56
optional< visitor_struct > additional_data
a node in a linked list of operation_history_objectsAccount history is important for users and wallet...
Used to generate a useful error report when an exception is thrown.At each level in the stack where t...
Definition: exception.hpp:56
primary_index< operation_history_index > * _oho_index
const auto source
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.
bool SendBulk(ES &&es)
variant_object & get_object()
Definition: variant.cpp:554
const vector< optional< operation_history_object > > & get_applied_operations() const
Definition: db_block.cpp:548
object_id_type id
Definition: object.hpp:73
chain::database & database()
Definition: plugin.hpp:114
const std::vector< std::string > createBulk(const fc::mutable_variant_object &bulk_header, std::string &&data)
account_transaction_history_object account_history
microseconds seconds(int64_t s)
Definition: time.hpp:34
const std::string simpleQuery(ES &es)
#define ilog(FORMAT,...)
Definition: logger.hpp:117
#define FC_THROW_EXCEPTION(EXCEPTION, FORMAT,...)
Definition: exception.hpp:378
bool checkES(ES &es)
virtual void plugin_startup() override
Begin normal runtime operations.
const account_statistics_object & get_account_stats_by_owner(account_id_type owner) const
Definition: db_getter.cpp:139
void from_variant(const variant &var, flat_set< T, A... > &vo, uint32_t _max_depth)
Definition: flat.hpp:116
std::string to_string(double)
Definition: string.cpp:73
variant variant_response
account_transaction_history_id_type next
the operation position within the given account
static variant from_string(const string &utf8_str, parse_type ptype=legacy_parser, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:458
void remove(const object &obj)
const std::string generateIndexName(const fc::time_point_sec &block_date, const std::string &_elasticsearch_index_prefix)
#define FC_PACK_MAX_DEPTH
Definition: config.hpp:3
static time_point now()
Definition: time.cpp:13
static share_type scaled_precision(uint8_t precision)
Definition: asset.hpp:93
std::vector< std::string > bulk_lines
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)
An order-perserving dictionary of variant&#39;s.
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
operation_history_id_type operation_id
the account this operation applies to
T value
Definition: safe.hpp:22
const index_type & indices() const
variant adapt(const variant_object &op)