Scorum
witness_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  */
26 
34 
35 #include <fc/time.hpp>
36 
37 #include <graphene/utilities/key_conversion.hpp>
38 
39 #include <fc/smart_ref_impl.hpp>
40 #include <fc/thread/thread.hpp>
41 
42 #include <iostream>
43 #include <memory>
44 
45 #define DISTANCE_CALC_PRECISION (10000)
46 
47 namespace scorum {
48 namespace witness {
49 
50 namespace bpo = boost::program_options;
51 
52 using protocol::signed_transaction;
53 using chain::account_object;
54 
55 namespace detail {
56 using namespace scorum::chain;
57 
59 {
60 public:
62  : _self(plugin)
63  {
64  }
65 
66  void plugin_initialize();
67 
68  void pre_transaction(const signed_transaction& trx);
69  void pre_operation(const operation_notification& note);
70  void on_block(const signed_block& b);
71 
72  void update_account_bandwidth(const account_object& a, uint32_t trx_size, const bandwidth_type type);
73 
75 };
76 
78 {
79 }
80 
81 void check_memo(const std::string& memo, const account_object& account, const account_authority_object& auth)
82 {
83  std::vector<public_key_type> keys;
84 
85  try
86  {
87  // Check if memo is a private key
88  keys.push_back(fc::ecc::extended_private_key::from_base58(memo).get_public_key());
89  }
90  catch (fc::parse_error_exception&)
91  {
92  }
93  catch (fc::assert_exception&)
94  {
95  }
96 
97  // Get possible keys if memo was an account password
98  std::string owner_seed = account.name + "owner" + memo;
99  auto owner_secret = fc::sha256::hash(owner_seed.c_str(), owner_seed.size());
100  keys.push_back(fc::ecc::private_key::regenerate(owner_secret).get_public_key());
101 
102  std::string active_seed = account.name + "active" + memo;
103  auto active_secret = fc::sha256::hash(active_seed.c_str(), active_seed.size());
104  keys.push_back(fc::ecc::private_key::regenerate(active_secret).get_public_key());
105 
106  std::string posting_seed = account.name + "posting" + memo;
107  auto posting_secret = fc::sha256::hash(posting_seed.c_str(), posting_seed.size());
108  keys.push_back(fc::ecc::private_key::regenerate(posting_secret).get_public_key());
109 
110  // Check keys against public keys in authorites
111  for (auto& key_weight_pair : auth.owner.key_auths)
112  {
113  for (auto& key : keys)
114  SCORUM_ASSERT(key_weight_pair.first != key, chain::plugin_exception,
115  "Detected private owner key in memo field. You should change your owner keys.");
116  }
117 
118  for (auto& key_weight_pair : auth.active.key_auths)
119  {
120  for (auto& key : keys)
121  SCORUM_ASSERT(key_weight_pair.first != key, chain::plugin_exception,
122  "Detected private active key in memo field. You should change your active keys.");
123  }
124 
125  for (auto& key_weight_pair : auth.posting.key_auths)
126  {
127  for (auto& key : keys)
128  SCORUM_ASSERT(key_weight_pair.first != key, chain::plugin_exception,
129  "Detected private posting key in memo field. You should change your posting keys.");
130  }
131 
132  const auto& memo_key = account.memo_key;
133  for (auto& key : keys)
134  SCORUM_ASSERT(memo_key != key, chain::plugin_exception,
135  "Detected private memo key in memo field. You should change your memo key.");
136 }
137 
139 {
141  : _db(db)
142  {
143  }
144 
146 
147  template <typename T> void operator()(const T&) const
148  {
149  }
150 
152  {
153  for (auto& e : o.extensions)
154  {
156  SCORUM_ASSERT(cpb.beneficiaries.size() <= 8, chain::plugin_exception,
157  "Cannot specify more than 8 beneficiaries.");
158  }
159  }
160 
161  void operator()(const comment_operation& o) const
162  {
163  auto& comment_service = _db.obtain_service<dbs_comment>();
164 
165  if (o.parent_author != SCORUM_ROOT_POST_PARENT_ACCOUNT)
166  {
167  if (comment_service.is_exists(o.parent_author, o.parent_permlink))
168  {
169  const auto& parent = comment_service.get(o.parent_author, o.parent_permlink);
170 
171  SCORUM_ASSERT(parent.depth < SCORUM_SOFT_MAX_COMMENT_DEPTH, chain::plugin_exception,
172  "Comment is nested ${x} posts deep, maximum depth is ${y}.",
173  ("x", parent.depth)("y", SCORUM_SOFT_MAX_COMMENT_DEPTH));
174  }
175  }
176 
177  if (comment_service.is_exists(o.author, o.permlink))
178  {
179  const auto& comment = comment_service.get(o.author, o.permlink);
180 
181  if (comment.cashout_time == fc::time_point_sec::maximum())
182  {
183  FC_THROW_EXCEPTION(chain::plugin_exception, "The comment is archived");
184  }
185  }
186  }
187 
188  void operator()(const transfer_operation& o) const
189  {
190  if (o.memo.length() > 0)
191  check_memo(o.memo, _db.get<account_object, chain::by_name>(o.from),
192  _db.get<account_authority_object, chain::by_account>(o.from));
193  }
194 };
195 
197 {
198  const auto& _db = _self.database();
199  flat_set<account_name_type> required;
200  std::vector<authority> other;
201  trx.get_required_authorities(required, required, required, other);
202 
203  auto trx_size = fc::raw::pack_size(trx);
204 
205  auto& account_svc = _db.account_service();
206 
207  for (const auto& auth : required)
208  {
209  const auto& acnt = account_svc.get_account(auth);
210 
211  update_account_bandwidth(acnt, trx_size, bandwidth_type::forum);
212 
213  for (const auto& op : trx.operations)
214  {
215  if (is_market_operation(op))
216  {
217  update_account_bandwidth(acnt, trx_size * 10, bandwidth_type::market);
218  break;
219  }
220  }
221  }
222 }
223 
225 {
226  const auto& _db = _self.database();
227  if (_db.is_producing())
228  {
229  note.op.visit(operation_visitor(_db));
230  }
231 }
232 
234 {
235  auto& db = _self.database();
236  int64_t max_block_size
237  = db.obtain_service<dbs_dynamic_global_property>().get().median_chain_props.maximum_block_size;
238 
239  auto reserve_ratio_ptr = db.find(reserve_ratio_id_type());
240 
241  if (BOOST_UNLIKELY(reserve_ratio_ptr == nullptr))
242  {
244  r.average_block_size = 0;
245  r.current_reserve_ratio = SCORUM_MAX_RESERVE_RATIO * RESERVE_RATIO_PRECISION;
246  r.max_virtual_bandwidth = (uint128_t(SCORUM_MAX_BLOCK_SIZE) * SCORUM_MAX_RESERVE_RATIO
249  });
250  }
251  else
252  {
253  db.modify(*reserve_ratio_ptr, [&](reserve_ratio_object& r) {
254  r.average_block_size = (99 * r.average_block_size + fc::raw::pack_size(b)) / 100;
255 
269  if (db.head_block_num() % 20 == 0)
270  {
271  int64_t distance
272  = ((r.average_block_size - (max_block_size / 4)) * DISTANCE_CALC_PRECISION) / (max_block_size / 4);
273  auto old_reserve_ratio = r.current_reserve_ratio;
274 
275  if (distance > 0)
276  {
278  -= (r.current_reserve_ratio * distance) / (distance + DISTANCE_CALC_PRECISION);
279 
280  // We do not want the reserve ratio to drop below 1
282  {
284  }
285  }
286  else
287  {
288  // By default, we should always slowly increase the reserve ratio.
290  += std::max(RESERVE_RATIO_MIN_INCREMENT,
291  (r.current_reserve_ratio * distance) / (distance - DISTANCE_CALC_PRECISION));
292 
294  {
296  }
297  }
298 
299  if (old_reserve_ratio != r.current_reserve_ratio)
300  {
301  ilog("Reserve ratio updated from ${old} to ${new}. Block: ${blocknum}",
302  ("old", old_reserve_ratio)("new", r.current_reserve_ratio)("blocknum", db.head_block_num()));
303  }
304 
306  = (uint128_t(max_block_size) * uint128_t(r.current_reserve_ratio)
309  }
310  });
311  }
312 }
313 
315  uint32_t trx_size,
316  const bandwidth_type type)
317 {
318  database& _db = _self.database();
319  const auto& props = _db.obtain_service<dbs_dynamic_global_property>().get();
320  bool has_bandwidth = true;
321 
322  if (props.total_scorumpower.amount > 0)
323  {
324  auto band = _db.find<account_bandwidth_object, by_account_bandwidth_type>(boost::make_tuple(a.name, type));
325 
326  if (band == nullptr)
327  {
328  band = &_db.create<account_bandwidth_object>([&](account_bandwidth_object& b) {
329  b.account = a.name;
330  b.type = type;
331  });
332  }
333 
334  share_type new_bandwidth;
335  share_type trx_bandwidth = trx_size * SCORUM_BANDWIDTH_PRECISION;
336  auto delta_time = (_db.head_block_time() - band->last_bandwidth_update).to_seconds();
337 
339  {
340  new_bandwidth = 0;
341  }
342  else
343  new_bandwidth
344  = (((SCORUM_BANDWIDTH_AVERAGE_WINDOW_SECONDS - delta_time) * fc::uint128(band->average_bandwidth.value))
346  .to_uint64();
347 
348  new_bandwidth += trx_bandwidth;
349 
350  _db.modify(*band, [&](account_bandwidth_object& b) {
351  b.average_bandwidth = new_bandwidth;
352  b.lifetime_bandwidth += trx_bandwidth;
354  });
355 
356  fc::uint128 account_vshares(a.effective_scorumpower().amount.value);
357  fc::uint128 total_vshares(props.total_scorumpower.amount.value);
358  fc::uint128 account_average_bandwidth(band->average_bandwidth.value);
359  fc::uint128 max_virtual_bandwidth(_db.get(reserve_ratio_id_type()).max_virtual_bandwidth);
360 
361  has_bandwidth = (account_vshares * max_virtual_bandwidth) > (account_average_bandwidth * total_vshares);
362 
363  if (_db.is_producing())
364  SCORUM_ASSERT(has_bandwidth, chain::plugin_exception,
365  "Account: ${account} bandwidth limit exceeded. Please wait to transact or power up SCR.",
366  ("account", a.name)("account_vshares", account_vshares)("account_average_bandwidth",
367  account_average_bandwidth)(
368  "max_virtual_bandwidth", max_virtual_bandwidth)("total_scorumpower", total_vshares));
369  }
370 }
371 }
372 
374  : plugin(app)
375  , _my(new detail::witness_plugin_impl(*this))
376 {
377 }
378 
380 {
381  try
382  {
383  if (_block_production_task.valid())
384  {
385  _block_production_task.cancel_and_wait(__FUNCTION__);
386  }
387  }
388  catch (fc::canceled_exception&)
389  {
390  // Expected exception. Move along.
391  }
392  catch (fc::exception& e)
393  {
394  edump((e.to_detail_string()));
395  }
396 }
397 
398 void witness_plugin::plugin_set_program_options(boost::program_options::options_description& command_line_options,
399  boost::program_options::options_description& config_file_options)
400 {
401  std::string witness_id_example = "initwitness";
402  // clang-format off
403  command_line_options.add_options()
404  ("enable-stale-production",
405  bpo::bool_switch()->notifier([this](bool e) { _production_enabled = e; }),
406  "Enable block production, even if the chain is stale.")
407  ("required-participation",
408  bpo::bool_switch()->notifier([this](int e) { _required_witness_participation = uint32_t(e * SCORUM_1_PERCENT); }),
409  "Percent of witnesses (0-99) that must be participating in order to produce blocks")
410  ("witness,w",
411  bpo::value<std::vector<std::string>>()->composing()->multitoken(),
412  ("name of witness controlled by this node (e.g. " + witness_id_example + " )").c_str())
413  ("private-key",
414  bpo::value<std::vector<std::string>>()->composing()->multitoken(),
415  "WIF PRIVATE KEY to be used by one or more witnesses or miners");
416 
417  // clang-format on
418  config_file_options.add(command_line_options);
419 }
420 
421 std::string witness_plugin::plugin_name() const
422 {
423  return "witness";
424 }
425 
426 void witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)
427 {
428  try
429  {
430  _options = &options;
431  LOAD_VALUE_SET(options, "witness", _witnesses, std::string)
432 
433  if (options.count("private-key"))
434  {
435  const std::vector<std::string> keys = options["private-key"].as<std::vector<std::string>>();
436  for (const std::string& wif_key : keys)
437  {
438  fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(wif_key);
439  FC_ASSERT(private_key.valid(), "unable to parse private key");
440  _private_keys[private_key->get_public_key()] = *private_key;
441  }
442  }
443 
444  chain::database& db = database();
445 
446  db.on_pre_apply_transaction.connect([&](const signed_transaction& tx) { _my->pre_transaction(tx); });
447  db.pre_apply_operation.connect([&](const operation_notification& note) { _my->pre_operation(note); });
448  db.applied_block.connect([&](const signed_block& b) { _my->on_block(b); });
449 
452  }
453  FC_LOG_AND_RETHROW()
454 
455  print_greeting();
456 }
457 
459 {
460  try
461  {
462  ilog("witness plugin: plugin_startup() begin");
463 
464  if (!_witnesses.empty())
465  {
466  ilog("Launching block production for ${n} witnesses.", ("n", _witnesses.size()));
467  idump((_witnesses));
468  app().set_block_production(true);
469  if (_production_enabled)
470  {
471  _production_skip_flags |= scorum::chain::database::skip_undo_history_check;
472  }
473  schedule_production_loop();
474  }
475  else
476  {
477  elog("No witnesses configured! Please add witness names and private keys to configuration.");
478  }
479  ilog("witness plugin: plugin_startup() end");
480  }
481  FC_CAPTURE_AND_RETHROW()
482 }
483 
485 {
486  return;
487 }
488 
489 void witness_plugin::schedule_production_loop()
490 {
491  static const int64_t ONE_SECOND_MS = 1000000;
492  // Schedule for the next second's tick regardless of chain state
493  // If we would wait less than 50ms, wait for the whole second.
494  fc::time_point fc_now = fc::time_point::now();
495  int64_t time_to_next_second = ONE_SECOND_MS - (fc_now.time_since_epoch().count() % ONE_SECOND_MS);
496  if (time_to_next_second < 50000) // we must sleep for at least 50ms
497  {
498  time_to_next_second += ONE_SECOND_MS;
499  }
500 
501  fc::time_point next_wakeup(fc_now + fc::microseconds(time_to_next_second));
502 
503  // wdump( (now.time_since_epoch().count())(next_wakeup.time_since_epoch().count()) );
504  _block_production_task = fc::schedule([this] { block_production_loop(); }, next_wakeup, "Witness Block Production");
505 }
506 
507 void witness_plugin::block_production_loop()
508 {
509  const fc::time_point genesis_time = database().get_genesis_time();
510 
511  if (fc::time_point::now() < genesis_time)
512  {
513  wlog("waiting until genesis time to produce block: ${t}", ("t", genesis_time));
514  schedule_production_loop();
515  return;
516  }
517 
519  fc::mutable_variant_object capture;
520  try
521  {
522  result = maybe_produce_block(capture);
523  }
524  catch (const fc::canceled_exception&)
525  {
526  // We're trying to exit. Go ahead and let this one out.
527  throw;
528  }
529  catch (const scorum::chain::unknown_hardfork_exception& e)
530  {
531  // Hit a hardfork that the current node know nothing about, stop production and inform user
532  elog("${e}\nNode may be out of date...", ("e", e.to_detail_string()));
533  throw;
534  }
535  catch (const fc::exception& e)
536  {
537  elog("Got exception while generating block:\n${e}", ("e", e.to_detail_string()));
539  }
540 
541  switch (result)
542  {
544  ilog("Generated block #${n} with timestamp ${t} at time ${c} by ${w}", (capture));
545  break;
547  // ilog("Not producing block because production is disabled until we receive a recent block (see:
548  // --enable-stale-production)");
549  break;
551  // ilog("Not producing block because it isn't my turn");
552  break;
554  // ilog("Not producing block because slot has not yet arrived");
555  break;
557  ilog("Not producing block for ${scheduled_witness} because I don't have the private key for ${scheduled_key}",
558  (capture));
559  break;
561  elog(
562  "Not producing block because node appears to be on a minority fork with only ${pct}% witness participation",
563  (capture));
564  break;
566  elog("Not producing block because node didn't wake up within 500ms of the slot time.");
567  break;
569  elog("Not producing block because the last block was generated by the same witness.\nThis node is probably "
570  "disconnected from the network so block production has been disabled.\nDisable this check with "
571  "--allow-consecutive option.");
572  break;
574  elog("Failure when producing block with no transactions");
575  break;
577  break;
578  }
579 
580  dlog("result = ${r}", ("r", result));
581  schedule_production_loop();
582 }
583 
585 witness_plugin::maybe_produce_block(fc::mutable_variant_object& capture)
586 {
587  chain::database& db = database();
588  fc::time_point now_fine = fc::time_point::now();
589  fc::time_point_sec now = now_fine + fc::microseconds(500000);
590 
591  // If the next block production opportunity is in the present or future, we're synced.
592  if (!_production_enabled)
593  {
594  if (db.get_slot_time(1) >= now)
595  {
596  _production_enabled = true;
597  }
598  else
599  {
601  }
602  }
603 
604  // is anyone scheduled to produce now or one second in the future?
605  uint32_t slot = db.get_slot_at_time(now);
606  if (slot == 0)
607  {
608  capture("next_time", db.get_slot_time(1));
610  }
611 
612  //
613  // this assert should not fail, because now <= db.head_block_time()
614  // should have resulted in slot == 0.
615  //
616  // if this assert triggers, there is a serious bug in get_slot_at_time()
617  // which would result in allowing a later block to have a timestamp
618  // less than or equal to the previous block
619  //
620  assert(now > db.head_block_time());
621 
622  std::string scheduled_witness = db.get_scheduled_witness(slot);
623  // we must control the witness scheduled to produce the next block.
624  if (_witnesses.find(scheduled_witness) == _witnesses.end())
625  {
626  capture("scheduled_witness", scheduled_witness);
628  }
629 
630  const auto& witness_by_name = db.get_index<chain::witness_index>().indices().get<chain::by_name>();
631  auto itr = witness_by_name.find(scheduled_witness);
632 
633  fc::time_point_sec scheduled_time = db.get_slot_time(slot);
634  scorum::protocol::public_key_type scheduled_key = itr->signing_key;
635  auto private_key_itr = _private_keys.find(scheduled_key);
636 
637  if (private_key_itr == _private_keys.end())
638  {
639  capture("scheduled_witness", scheduled_witness);
640  capture("scheduled_key", scheduled_key);
642  }
643 
644  uint32_t prate = db.witness_participation_rate();
645  if (prate < _required_witness_participation)
646  {
647  capture("pct", uint32_t(100 * uint64_t(prate) / SCORUM_1_PERCENT));
649  }
650 
651  fc::microseconds dlt = scheduled_time - now;
652  dlog("scheduled_time (${s} s) - now (${n} s) = ${d} ms",
653  ("s", scheduled_time.sec_since_epoch())("n", now.sec_since_epoch())("d", dlt.count()));
654 
655  if (llabs(dlt.count()) > fc::milliseconds(500).count())
656  {
657  capture("scheduled_time", scheduled_time)("now", now);
659  }
660 
661  int retry = 0;
662  do
663  {
664  try
665  {
666  auto block
667  = db.generate_block(scheduled_time, scheduled_witness, private_key_itr->second, _production_skip_flags);
668  capture("n", block.block_num())("t", block.timestamp)("c", now)("w", scheduled_witness);
669  fc::async([this, block]() { p2p_node().broadcast(graphene::net::block_message(block)); });
670 
672  }
673  catch (fc::exception& e)
674  {
675  elog("${e}", ("e", e.to_detail_string()));
676  elog("Clearing pending transactions and attempting again");
677  db.clear_pending();
678  retry++;
679  }
680  } while (retry < 2);
681 
683 }
684 }
685 } // scorum::witness
686 
void set_block_production(bool producing_blocks)
application & app() const
Definition: plugin.hpp:119
void print_greeting()
Definition: plugin.cpp:69
chain::database & database()
Definition: plugin.hpp:115
graphene::net::node & p2p_node()
Definition: plugin.hpp:128
tracks the blockchain state in an extensible manner
Definition: database.hpp:52
database(uint32_t opt)
Definition: database.cpp:168
time_point_sec head_block_time() const
Definition: database.cpp:1222
time_point_sec get_genesis_time() const
Definition: genesis.cpp:30
fc::signal< void(const signed_transaction &)> on_pre_apply_transaction
Definition: database.hpp:233
fc::signal< void(const signed_block &)> applied_block
Definition: database.hpp:221
@ skip_undo_history_check
used while reindexing
Definition: database.hpp:87
bool is_producing() const
Definition: database.hpp:71
fc::signal< void(const operation_notification &)> pre_apply_operation
Definition: database.hpp:209
const comment_object & get(const comment_id_type &comment_id) const override
Definition: comment.cpp:19
virtual const object_type & create(const modifier_type &modifier) override
void pre_operation(const operation_notification &note)
void update_account_bandwidth(const account_object &a, uint32_t trx_size, const bandwidth_type type)
void pre_transaction(const signed_transaction &trx)
virtual void plugin_initialize(const boost::program_options::variables_map &options) override
Perform early startup routines and register plugin indexes, callbacks, etc.
virtual void plugin_startup() override
Begin normal runtime operations.
virtual void plugin_set_program_options(boost::program_options::options_description &command_line_options, boost::program_options::options_description &config_file_options) override
Fill in command line parameters used by the plugin.
virtual void plugin_shutdown() override
Cleanly shut down the plugin.
std::string plugin_name() const override
#define SCORUM_MAX_RESERVE_RATIO
Definition: config.hpp:218
#define SCORUM_BANDWIDTH_AVERAGE_WINDOW_SECONDS
Definition: config.hpp:213
#define SCORUM_SOFT_MAX_COMMENT_DEPTH
Definition: config.hpp:216
#define SCORUM_1_PERCENT
Definition: config.hpp:201
#define SCORUM_BANDWIDTH_PRECISION
1 million
Definition: config.hpp:214
#define SCORUM_BLOCK_INTERVAL
Definition: config.hpp:171
#define SCORUM_ASSERT(expr, exc_type, FORMAT,...)
Definition: exceptions.hpp:6
shared_multi_index_container< witness_object, indexed_by< ordered_unique< tag< by_id >, member< witness_object, witness_id_type, &witness_object::id > >, ordered_unique< tag< by_name >, member< witness_object, account_name_type, &witness_object::owner > >, ordered_unique< tag< by_vote_name >, composite_key< witness_object, member< witness_object, share_type, &witness_object::votes >, member< witness_object, account_name_type, &witness_object::owner > >, composite_key_compare< std::greater< share_type >, std::less< account_name_type > > >, ordered_unique< tag< by_schedule_time >, composite_key< witness_object, member< witness_object, fc::uint128, &witness_object::virtual_scheduled_time >, member< witness_object, witness_id_type, &witness_object::id > > > > > witness_index
@ market
Rate limiting for all other actions.
@ forum
Rate limiting for all forum related actions.
fc::safe< share_value_type > share_type
Definition: types.hpp:73
bool is_market_operation(const operation &op)
Definition: operations.cpp:26
void check_memo(const std::string &memo, const account_object &account, const account_authority_object &auth)
shared_multi_index_container< account_bandwidth_object, indexed_by< ordered_unique< tag< by_id >, member< account_bandwidth_object, account_bandwidth_id_type, &account_bandwidth_object::id > >, ordered_unique< tag< by_account_bandwidth_type >, composite_key< account_bandwidth_object, member< account_bandwidth_object, account_name_type, &account_bandwidth_object::account >, member< account_bandwidth_object, bandwidth_type, &account_bandwidth_object::type > > > > > account_bandwidth_index
oid< reserve_ratio_object > reserve_ratio_id_type
shared_multi_index_container< reserve_ratio_object, indexed_by< ordered_unique< tag< by_id >, member< reserve_ratio_object, reserve_ratio_id_type, &reserve_ratio_object::id > > > > reserve_ratio_index
Definition: asset.cpp:15
#define LOAD_VALUE_SET(options, name, container, type)
Definition: plugin.hpp:145
#define SCORUM_DEFINE_PLUGIN(plugin_name, plugin_class)
Definition: plugin.hpp:156
comment_options_extensions_type extensions
allows voters to receive curation rewards. Rewards return to reward fund.
std::vector< beneficiary_route_type > beneficiaries
Definition: comment.hpp:31
std::vector< operation > operations
Definition: transaction.hpp:18
void get_required_authorities(flat_set< account_name_type > &active, flat_set< account_name_type > &owner, flat_set< account_name_type > &posting, std::vector< authority > &other) const
Definition: transaction.cpp:79
Transfers SCR from one account to another.
void operator()(const comment_options_operation &o) const
operation_visitor(const chain::database &db)
void operator()(const transfer_operation &o) const
void operator()(const comment_operation &o) const
#define DISTANCE_CALC_PRECISION
#define RESERVE_RATIO_PRECISION
#define RESERVE_RATIO_MIN_INCREMENT