BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface software
proposal_evaluator.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-2018 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  */
27 #include <graphene/chain/hardfork.hpp>
28 
29 namespace graphene { namespace chain {
30 
31 namespace detail {
32  void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options);
33 
34  void check_asset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const asset_options& options);
35  void check_bitasset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const bitasset_options& options);
37  const asset_update_operation::ext& extensions );
38 
40  const asset_publish_feed_operation::ext& extensions );
41  void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options);
42 
44  const bitasset_options& options); // HF_REMOVABLE
45 
46  void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options);
47 
49  const bitasset_options& options); // HF_REMOVABLE
50 
52  const asset_claim_fees_operation& op); // HF_REMOVABLE
53 }
54 
56 {
57  typedef void result_type;
58  const database& db;
61 
63  : db( _db ), block_time(bt), next_maintenance_time( db.get_dynamic_global_properties().next_maintenance_time ) {}
64 
65  template<typename T>
66  void operator()(const T &v) const {}
67 
72  if( v.bitasset_opts.valid() ) {
74  detail::check_bitasset_options_hf_bsip74( block_time, *v.bitasset_opts ); // HF_REMOVABLE
75  detail::check_bitasset_options_hf_bsip77( block_time, *v.bitasset_opts ); // HF_REMOVABLE
76  detail::check_bitasset_options_hf_bsip87( block_time, *v.bitasset_opts ); // HF_REMOVABLE
77  }
78 
79  // TODO move as many validations as possible to validate() if not triggered before hardfork
80  if( HARDFORK_BSIP_48_75_PASSED( block_time ) )
81  {
83  }
84  }
85 
90 
92 
93  // TODO move as many validations as possible to validate() if not triggered before hardfork
94  if( HARDFORK_BSIP_48_75_PASSED( block_time ) )
95  {
96  v.new_options.validate_flags( true );
97  }
98 
99  }
100 
103  detail::check_bitasset_options_hf_bsip74( block_time, v.new_options ); // HF_REMOVABLE
104  detail::check_bitasset_options_hf_bsip77( block_time, v.new_options ); // HF_REMOVABLE
105  detail::check_bitasset_options_hf_bsip87( block_time, v.new_options ); // HF_REMOVABLE
106  }
107 
109  detail::check_asset_claim_fees_hardfork_87_74_collatfee(block_time, v); // HF_REMOVABLE
110  }
111 
113 
115 
116  }
117 
119  if (block_time < HARDFORK_CORE_1468_TIME) {
120  FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(),
121  "Unable to set HTLC options before hardfork 1468");
125  }
126  if (!HARDFORK_BSIP_40_PASSED(block_time)) {
127  FC_ASSERT(!op.new_parameters.extensions.value.custom_authority_options.valid(),
128  "Unable to set Custom Authority Options before hardfork BSIP 40");
130  "Unable to define fees for custom authority operations prior to hardfork BSIP 40");
132  "Unable to define fees for custom authority operations prior to hardfork BSIP 40");
134  "Unable to define fees for custom authority operations prior to hardfork BSIP 40");
135  }
136  if (!HARDFORK_BSIP_85_PASSED(block_time)) {
137  FC_ASSERT(!op.new_parameters.extensions.value.maker_fee_discount_percent.valid(),
138  "Unable to set maker_fee_discount_percent before hardfork BSIP 85");
139  }
140  if (!HARDFORK_BSIP_86_PASSED(block_time)) {
141  FC_ASSERT(!op.new_parameters.extensions.value.market_fee_network_percent.valid(),
142  "Unable to set market_fee_network_percent before hardfork BSIP 86");
143  }
144  if (!HARDFORK_CORE_2103_PASSED(block_time)) {
146  "Unable to define fees for ticket operations prior to hardfork 2103");
148  "Unable to define fees for ticket operations prior to hardfork 2103");
149  }
150  if (!HARDFORK_LIQUIDITY_POOL_PASSED(block_time)) {
152  "Unable to define fees for liquidity pool operations prior to the LP hardfork");
154  "Unable to define fees for liquidity pool operations prior to the LP hardfork");
156  "Unable to define fees for liquidity pool operations prior to the LP hardfork");
158  "Unable to define fees for liquidity pool operations prior to the LP hardfork");
160  "Unable to define fees for liquidity pool operations prior to the LP hardfork");
161  }
162  }
164  FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" );
165  if (block_time < HARDFORK_CORE_BSIP64_TIME)
166  {
167  // memo field added at harfork BSIP64
168  // NOTE: both of these checks can be removed after hardfork time
169  FC_ASSERT( !op.extensions.value.memo.valid(),
170  "Memo unavailable until after HARDFORK BSIP64");
171  // HASH160 added at hardfork BSIP64
173  "HASH160 unavailable until after HARDFORK BSIP64" );
174  }
175  }
177  FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" );
178  }
180  FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" );
181  }
183  FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" );
184  }
186  FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" );
187  }
189  FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" );
190  }
192  FC_ASSERT( HARDFORK_CORE_2103_PASSED(block_time), "Not allowed until hardfork 2103" );
193  }
195  FC_ASSERT( HARDFORK_CORE_2103_PASSED(block_time), "Not allowed until hardfork 2103" );
196  }
198  FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" );
199  }
201  FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" );
202  }
204  FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" );
205  }
207  FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" );
208  }
210  FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" );
211  }
212 
213  // loop and self visit in proposals
215  bool already_contains_proposal_update = false;
216 
217  for (const op_wrapper &op : v.proposed_ops)
218  {
219  op.op.visit(*this);
220  // Do not allow more than 1 proposal_update in a proposal
222  {
223  FC_ASSERT( !already_contains_proposal_update,
224  "At most one proposal update can be nested in a proposal!" );
225  already_contains_proposal_update = true;
226  }
227  }
228  }
229 };
230 
231 struct hardfork_visitor_214 // non-recursive proposal visitor
232 {
233  typedef void result_type;
234 
235  template<typename T>
236  void operator()(const T &v) const {}
237 
238  void operator()(const proposal_update_operation &v) const {
239  FC_ASSERT(false, "Not allowed until hardfork 214");
240  }
241 };
242 
244 {
245  if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance )
246  max_update_instance = v.proposal.instance.value;
247  nested_update_count++;
248 }
249 
251 {
252  if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance )
253  max_update_instance = v.proposal.instance.value;
254  nested_update_count++;
255 }
256 
257 // loop and self visit in proposals
259 {
260  for (const op_wrapper &op : v.proposed_ops)
261  op.op.visit(*this);
262 }
263 
265 { try {
266  const database& d = db();
267 
268  // Calling the proposal hardfork visitor
269  const fc::time_point_sec block_time = d.head_block_time();
270  proposal_operation_hardfork_visitor vtor( d, block_time );
271  vtor( o );
272  if( block_time < HARDFORK_CORE_214_TIME )
273  {
274  // cannot be removed after hf, unfortunately
275  hardfork_visitor_214 hf214;
276  for( const op_wrapper &op : o.proposed_ops )
277  op.op.visit( hf214 );
278  }
279  vtor_1479( o );
280 
281  const auto& global_parameters = d.get_global_properties().parameters;
282 
283  FC_ASSERT( o.expiration_time > block_time, "Proposal has already expired on creation." );
284  FC_ASSERT( o.expiration_time <= block_time + global_parameters.maximum_proposal_lifetime,
285  "Proposal expiration time is too far in the future." );
287  fc::seconds( *o.review_period_seconds ) < ( o.expiration_time - block_time ),
288  "Proposal review period must be less than its overall lifetime." );
289 
290  // Find all authorities required by the proposed operations
291  flat_set<account_id_type> tmp_required_active_auths;
292  vector<authority> other;
293  for( auto& op : o.proposed_ops )
294  {
295  operation_get_required_authorities( op.op, tmp_required_active_auths, _required_owner_auths, other,
296  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( block_time ) );
297  }
298  // All accounts which must provide both owner and active authority should be omitted from the
299  // active authority set; owner authority approval implies active authority approval.
300  std::set_difference( tmp_required_active_auths.begin(), tmp_required_active_auths.end(),
301  _required_owner_auths.begin(), _required_owner_auths.end(),
302  std::inserter( _required_active_auths, _required_active_auths.begin() ) );
303 
304  // TODO: what about other???
305  FC_ASSERT ( other.empty(),
306  "Proposals containing operations requiring non-account authorities are not yet implemented." );
307 
308  // If we're dealing with the committee authority, make sure this transaction has a sufficient review period.
309  if( _required_active_auths.count( GRAPHENE_COMMITTEE_ACCOUNT ) ||
310  _required_owner_auths.count( GRAPHENE_COMMITTEE_ACCOUNT ) )
311  {
313  proposal_create_review_period_required,
314  "Review period not given, but at least ${min} required",
315  ("min", global_parameters.committee_proposal_review_period) );
316  GRAPHENE_ASSERT( *o.review_period_seconds >= global_parameters.committee_proposal_review_period,
317  proposal_create_review_period_insufficient,
318  "Review period of ${t} specified, but at least ${min} required",
319  ("t", *o.review_period_seconds)
320  ("min", global_parameters.committee_proposal_review_period) );
321  }
322 
323  for( const op_wrapper& op : o.proposed_ops )
324  _proposed_trx.operations.push_back( op.op );
325 
326  _proposed_trx.validate();
327 
328  return void_result();
329 } FC_CAPTURE_AND_RETHROW( (o) ) }
330 
332 { try {
333  database& d = db();
334  auto chain_time = d.head_block_time();
335 
336  const proposal_object& proposal = d.create<proposal_object>( [&o, this, chain_time](proposal_object& proposal) {
337  _proposed_trx.expiration = o.expiration_time;
338  proposal.proposed_transaction = _proposed_trx;
339  proposal.expiration_time = o.expiration_time;
340  proposal.proposer = o.fee_paying_account;
341  if( o.review_period_seconds )
342  proposal.review_period_time = o.expiration_time - *o.review_period_seconds;
343 
344  //Populate the required approval sets
345  proposal.required_owner_approvals.insert( _required_owner_auths.begin(), _required_owner_auths.end() );
346  proposal.required_active_approvals.insert( _required_active_auths.begin(), _required_active_auths.end() );
347 
348  if( chain_time > HARDFORK_CORE_1479_TIME )
349  FC_ASSERT( vtor_1479.nested_update_count == 0 || proposal.id.instance() > vtor_1479.max_update_instance,
350  "Cannot update/delete a proposal with a future id!" );
351  else if( vtor_1479.nested_update_count > 0 && proposal.id.instance() <= vtor_1479.max_update_instance )
352  {
353  // Note: This happened on mainnet, proposal 1.10.17503
354  // prevent approval
355  transfer_operation top;
359  proposal.proposed_transaction.operations.emplace_back( top );
360  }
361  });
362 
363  return proposal.id;
364 } FC_CAPTURE_AND_RETHROW( (o) ) }
365 
367 { try {
368  database& d = db();
369 
370  _proposal = &o.proposal(d);
371 
372  if( _proposal->review_period_time && d.head_block_time() >= *_proposal->review_period_time )
374  "This proposal is in its review period. No new approvals may be added." );
375 
376  for( account_id_type id : o.active_approvals_to_remove )
377  {
378  FC_ASSERT( _proposal->available_active_approvals.find(id) != _proposal->available_active_approvals.end(),
379  "", ("id", id)("available", _proposal->available_active_approvals) );
380  }
381  for( account_id_type id : o.owner_approvals_to_remove )
382  {
383  FC_ASSERT( _proposal->available_owner_approvals.find(id) != _proposal->available_owner_approvals.end(),
384  "", ("id", id)("available", _proposal->available_owner_approvals) );
385  }
386 
387  return void_result();
388 } FC_CAPTURE_AND_RETHROW( (o) ) }
389 
391 { try {
392  database& d = db();
393 
394  // Potential optimization: if _executed_proposal is true, we can skip the modify step and make push_proposal
395  // skip signature checks. This isn't done now because I just wrote all the proposals code, and I'm not yet
396  // 100% sure the required approvals are sufficient to authorize the transaction.
397  d.modify(*_proposal, [&o](proposal_object& p) {
400  for( account_id_type id : o.active_approvals_to_remove )
401  p.available_active_approvals.erase(id);
402  for( account_id_type id : o.owner_approvals_to_remove )
403  p.available_owner_approvals.erase(id);
404  for( const auto& id : o.key_approvals_to_add )
405  p.available_key_approvals.insert(id);
406  for( const auto& id : o.key_approvals_to_remove )
407  p.available_key_approvals.erase(id);
408  });
409 
410  // If the proposal has a review period, don't bother attempting to authorize/execute it.
411  // Proposals with a review period may never be executed except at their expiration.
412  if( _proposal->review_period_time )
413  return void_result();
414 
415  if( _proposal->is_authorized_to_execute(d) )
416  {
417  // All required approvals are satisfied. Execute!
418  _executed_proposal = true;
419  try {
420  _processed_transaction = d.push_proposal(*_proposal);
421  } catch(fc::exception& e) {
422  d.modify(*_proposal, [&e](proposal_object& p) {
424  });
425  wlog("Proposed transaction ${id} failed to apply once approved with exception:\n----\n${reason}\n----\nWill try again when it expires.",
426  ("id", o.proposal)("reason", e.to_detail_string()));
427  _proposal_failed = true;
428  }
429  }
430 
431  return void_result();
432 } FC_CAPTURE_AND_RETHROW( (o) ) }
433 
435 { try {
436  database& d = db();
437 
438  _proposal = &o.proposal(d);
439 
440  auto required_approvals = o.using_owner_authority? &_proposal->required_owner_approvals
441  : &_proposal->required_active_approvals;
442  FC_ASSERT( required_approvals->find(o.fee_paying_account) != required_approvals->end(),
443  "Provided authority is not authoritative for this proposal.",
444  ("provided", o.fee_paying_account)("required", *required_approvals));
445 
446  return void_result();
447 } FC_CAPTURE_AND_RETHROW( (o) ) }
448 
450 { try {
451  db().remove(*_proposal);
452 
453  return void_result();
454 } FC_CAPTURE_AND_RETHROW( (o) ) }
455 
456 
457 } } // graphene::chain
bool is_type() const
void operator()(const graphene::chain::liquidity_pool_exchange_operation &op) const
void modify(const T &obj, const Lambda &m)
proposal_operation_hardfork_visitor(const database &_db, const fc::time_point_sec bt)
void check_asset_options_hf_bsip81(const fc::time_point_sec &block_time, const asset_options &options)
void operator()(const graphene::chain::asset_update_bitasset_operation &v) const
void check_bitasset_options_hf_bsip74(const fc::time_point_sec &block_time, const bitasset_options &options)
necessary to support nested operations inside the proposal_create_operation
Definition: operations.hpp:139
void check_bitasset_options_hf_bsip87(const fc::time_point_sec &block_time, const bitasset_options &options)
vector< operation > operations
Definition: transaction.hpp:89
void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const
void operator()(const graphene::chain::asset_create_operation &v) const
flat_set< account_id_type > available_active_approvals
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
Definition: api.cpp:56
used to transfer accumulated fees back to the issuer&#39;s balance.
Definition: asset_ops.hpp:482
The proposal_delete_operation deletes an existing transaction proposalThis operation allows the early...
Definition: proposal.hpp:156
void_result do_evaluate(const proposal_update_operation &o)
void check_bitasset_options_hf_bsip_48_75(const fc::time_point_sec &block_time, const bitasset_options &options)
processed_transaction push_proposal(const proposal_object &proposal)
Definition: db_block.cpp:328
Used to generate a useful error report when an exception is thrown.At each level in the stack where t...
Definition: exception.hpp:56
std::string to_detail_string(log_level ll=log_level::all) const
Definition: exception.cpp:183
bool valid() const
Definition: optional.hpp:186
void operator()(const graphene::chain::htlc_redeem_operation &op) const
void operator()(const graphene::chain::liquidity_pool_deposit_operation &op) const
flat_set< account_id_type > required_owner_approvals
void check_asset_publish_feed_extensions_hf_bsip77(const fc::time_point_sec &block_time, const asset_publish_feed_operation::ext &extensions)
void operator()(const graphene::chain::custom_authority_update_operation &) const
visitor::result_type visit(visitor &v)
Updates an existing ticket.
Definition: ticket.hpp:66
#define wlog(FORMAT,...)
Definition: logger.hpp:123
void_result do_apply(const proposal_update_operation &o)
void operator()(const graphene::chain::proposal_create_operation &v) const
void operator()(const graphene::chain::liquidity_pool_delete_operation &op) const
Publish price feeds for market-issued assetsPrice feed providers use this operation to publish their ...
Definition: asset_ops.hpp:415
void operator()(const graphene::chain::htlc_extend_operation &op) const
flat_set< account_id_type > available_owner_approvals
const global_property_object & get_global_properties() const
Definition: db_getter.cpp:44
#define GRAPHENE_MAX_SHARE_SUPPLY
Definition: config.hpp:38
The proposal_create_operation creates a transaction proposal, for use in multi-sig scenariosCreates a...
Definition: proposal.hpp:70
extension< additional_options_type > extensions
Definition: htlc.hpp:72
uint64_t instance() const
Definition: object_id.hpp:50
void operator()(const graphene::chain::custom_authority_create_operation &) const
flat_set< public_key_type > key_approvals_to_remove
Definition: proposal.hpp:134
void operator()(const graphene::chain::asset_update_operation &v) const
void operator()(const graphene::chain::htlc_create_operation &op) const
std::string to_string(log_level ll=log_level::info) const
Definition: exception.cpp:227
Transfers an amount of one asset from one account to another.
Definition: transfer.hpp:45
object_id_type id
Definition: object.hpp:69
flat_set< account_id_type > active_approvals_to_remove
Definition: proposal.hpp:130
microseconds seconds(int64_t s)
Definition: time.hpp:34
void operator()(const graphene::chain::asset_publish_feed_operation &v) const
flat_set< account_id_type > owner_approvals_to_remove
Definition: proposal.hpp:132
time_point_sec head_block_time() const
Definition: db_getter.cpp:64
void operator()(const graphene::chain::liquidity_pool_withdraw_operation &op) const
#define GRAPHENE_COMMITTEE_ACCOUNT
Definition: config.hpp:125
std::shared_ptr< const fee_schedule > current_fees
current schedule of fees
#define FC_CAPTURE_AND_RETHROW(...)
Definition: exception.hpp:478
Update options specific to BitAssetsBitAssets have some options which are not relevant to other asset...
Definition: asset_ops.hpp:353
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
void_result do_evaluate(const proposal_create_operation &o)
object_id_type do_apply(const proposal_create_operation &o)
void operator()(const proposal_update_operation &v) const
void operator()(const graphene::chain::ticket_update_operation &op) const
#define GRAPHENE_NULL_ACCOUNT
Represents the canonical account with NO authority (nobody can access funds in null account) ...
Definition: config.hpp:131
Update options common to all assetsThere are a number of options which all assets in the network use...
Definition: asset_ops.hpp:307
void operator()(const graphene::chain::asset_claim_fees_operation &v) const
optional< time_point_sec > review_period_time
void operator()(const graphene::chain::ticket_create_operation &op) const
void_result do_apply(const proposal_delete_operation &)
flat_set< account_id_type > required_active_approvals
#define GRAPHENE_RELAXED_COMMITTEE_ACCOUNT
Represents the current committee members.
Definition: config.hpp:129
flat_set< public_key_type > key_approvals_to_add
Definition: proposal.hpp:133
void check_asset_update_extensions_hf_bsip_48_75(const fc::time_point_sec &block_time, const asset_update_operation::ext &extensions)
void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec &block_time, const asset_claim_fees_operation &op)
void check_bitasset_options_hf_bsip77(const fc::time_point_sec &block_time, const bitasset_options &options)
asset amount
The amount of asset to transfer from from to to.
Definition: transfer.hpp:58
void check_asset_options_hf_bsip_48_75(const fc::time_point_sec &block_time, const asset_options &options)
The proposal_update_operation updates an existing transaction proposalThis operation allows accounts ...
Definition: proposal.hpp:119
optional< bitasset_options > bitasset_opts
Definition: asset_ops.hpp:179
account_id_type from
Account to transfer asset from.
Definition: transfer.hpp:54
void validate_flags(bool is_market_issued) const
Definition: asset_ops.cpp:301
#define GRAPHENE_ASSERT(expr, exc_type, FORMAT,...)
Definition: exceptions.hpp:28
Used by committee_members to update the global parameters of the blockchain.This operation allows the...
flat_set< account_id_type > owner_approvals_to_add
Definition: proposal.hpp:131
flat_set< public_key_type > available_key_approvals
void check_asset_options_hf_1774(const fc::time_point_sec &block_time, const asset_options &options)
void operator()(const graphene::chain::liquidity_pool_create_operation &op) const
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
flat_set< account_id_type > active_approvals_to_add
Definition: proposal.hpp:129
account_id_type to
Account to transfer asset to.
Definition: transfer.hpp:56
void_result do_evaluate(const proposal_delete_operation &o)
const T & create(F &&constructor)
void operator()(const graphene::chain::custom_authority_delete_operation &) const
tracks the approval of a partially approved transaction