diff --git a/libraries/plugins/voting_stat/voting_stat_plugin.cpp b/libraries/plugins/voting_stat/voting_stat_plugin.cpp index 36787a8feb..64295398b3 100644 --- a/libraries/plugins/voting_stat/voting_stat_plugin.cpp +++ b/libraries/plugins/voting_stat/voting_stat_plugin.cpp @@ -24,9 +24,18 @@ #include #include +#include +#include +#include +#include #include +#include +#include + +#include + namespace graphene { namespace voting_stat { namespace detail { @@ -34,133 +43,230 @@ namespace detail { class voting_stat_plugin_impl { public: - voting_stat_plugin_impl(voting_stat_plugin& _plugin) - : _self( _plugin ){} - + voting_stat_plugin_impl(voting_stat_plugin& _plugin) : _self( _plugin ){} virtual ~voting_stat_plugin_impl(){} - graphene::chain::database& database() - { - return _self.database(); - } + boost::signals2::connection _on_voting_stake_calc_conn; + std::unique_ptr _on_voting_stake_calc_block; + + uint16_t _maint_counter = 0; + + /** + * plugin parameters + */ + bool _keep_objects_in_db = true; + uint16_t _track_every_x_maint = 12; + bool _track_worker_votes = true; + bool _track_witness_votes = true; + bool _track_committee_votes = true; /** * @brief callback for database::on_stake_calculated - * updates the given stake anc proxy account + * + * This function is triggered when the calculation of a stake for a given account is done inside the maintenance + * interval. It creates/updates the voting_statistics_object for a stake account. Optionally if a proxy is set + * in the stake account, also the voting_statistics_object for the proxy account is created/updated. */ - void on_stake_calculated( - const account_object& stake_account, - const account_object& proxy_account, - uint64_t stake ); + void on_stake_calculated(const account_object& stake_account, const account_object& proxy_account, uint64_t stake); /** * @brief callback for database::on_maintenance_begin - * updates the block_id to the one where the maintenance interval occurs + * + * Updates the block number to the one where the maintenance interval occurs and unblocks the + * database::on_stake_calculated signal, so that statistics objects can be created. */ - void on_maintenance_begin( const block_id_type& block_id ); + void on_maintenance_begin(uint32_t block_num); + + /** + * @brief callback for database::on_maintenance_end + * + * Disconnects the on_stake_calculated callback and deletes all statistics objects if + * _voting_stat_delete_objects_after_interval=true + */ + void on_maintenance_end(); + + void create_voteable_statistics_objects(); + void delete_all_statistics_objects(); + + graphene::chain::database& database() + { + return _self.database(); + } private: voting_stat_plugin& _self; - + + uint32_t _maint_block; + bool _create_voteable = false; }; -void voting_stat_plugin_impl::on_maintenance_begin( const block_id_type& block_id ) +void voting_stat_plugin_impl::on_maintenance_begin(uint32_t block_num) { - graphene::chain::voting_statistics_object::block_id = block_id; + if( !_keep_objects_in_db ) + delete_all_statistics_objects(); + + if( _maint_counter == _track_every_x_maint ) + { + _on_voting_stake_calc_block->unblock(); + _maint_counter = 0; + _maint_block = block_num; + _create_voteable = true; + } + ++_maint_counter; +} + +void voting_stat_plugin_impl::on_maintenance_end() +{ + _on_voting_stake_calc_block->block(); + if( _create_voteable ) { + _create_voteable = false; + create_voteable_statistics_objects(); + } +} + +void voting_stat_plugin_impl::delete_all_statistics_objects() +{ + auto& db = database(); + + // TODO find better way to delete + const auto& voting_idx = db.get_index_type().indices().get(); + for( const auto& voting_obj : voting_idx ) { + db.remove( voting_obj ); + } + + const auto& voteable_idx = db.get_index_type().indices().get(); + for( const auto& voteable_obj : voteable_idx ) { + db.remove( voteable_obj ); + } +} + +void voting_stat_plugin_impl::create_voteable_statistics_objects() +{ + auto& db = database(); + + // TODO secondary index for workers where current_time < worker_end_time + // will reduce the iteration time + if( _track_worker_votes ) + { + const auto& worker_idx = db.get_index_type().indices().get(); + + auto now = db.head_block_time(); + for( const auto& worker : worker_idx ) + { + if( now > worker.work_end_date ) + continue; + + db.create( [this, &worker]( voteable_statistics_object& o ){ + o.block_number = _maint_block; + o.vote_id = worker.vote_for; + }); + } + } + + if( _track_witness_votes ) + { + const auto& witness_idx = db.get_index_type().indices().get(); + for( const auto& witness : witness_idx ) + { + db.create( [this, &witness]( voteable_statistics_object& o ){ + o.block_number = _maint_block; + o.vote_id = witness.vote_id; + }); + } + } + + if( _track_committee_votes ) + { + const auto& committee_idx = db.get_index_type().indices().get(); + for( const auto& committee_member : committee_idx ) + { + db.create( [this, &committee_member](voteable_statistics_object& o){ + o.block_number = _maint_block; + o.vote_id = committee_member.vote_id; + }); + } + } + + + const auto& voting_stat_idx = db.get_index_type().indices().get(); + auto voting_stat_range = voting_stat_idx.equal_range( _maint_block ); + + const auto& voteable_idx = db.get_index_type().indices().get(); + + for( auto voting_stat_it = voting_stat_range.first; voting_stat_it != voting_stat_range.second; ++voting_stat_it ) + { + uint64_t total_stake = voting_stat_it->get_total_voting_stake(); + if( !total_stake ) + continue; // don't bother inserting a 0 stake + + const flat_set& votes = voting_stat_it->votes; + for( const auto& vote_id : votes ) + { + auto voteable_obj_range = voteable_idx.equal_range( boost::make_tuple( _maint_block, vote_id ) ); + if( voteable_obj_range.first == voteable_obj_range.second ) + continue; // when the obj isn't found it isn't needed hence skip it + + db.modify( *voteable_obj_range.first, + [voting_stat_it, total_stake]( voteable_statistics_object& o ){ + o.voted_by.emplace( voting_stat_it->account, total_stake ); + } + ); + } + } } -void voting_stat_plugin_impl::on_stake_calculated( +void voting_stat_plugin_impl::on_stake_calculated( const account_object& stake_account, - const account_object& proxy_account, + const account_object& proxy_account, uint64_t stake ) { auto& db = database(); account_id_type stake_id = stake_account.id; account_id_type proxy_id = proxy_account.id; - account_id_type new_proxy_id = stake_id == proxy_id ? - GRAPHENE_PROXY_TO_SELF_ACCOUNT : proxy_id; - account_id_type old_proxy_id; - bool has_stake_changed; - - const auto& idx = db.get_index_type().indices().get(); - auto stake_stat_it = idx.find( stake_id ); - if( stake_stat_it == idx.end() ) + proxy_id = ( stake_id == proxy_id ? GRAPHENE_PROXY_TO_SELF_ACCOUNT : proxy_id ); + + const auto& voting_stat_idx = db.get_index_type().indices().get(); + + auto stake_stat_range = voting_stat_idx.equal_range( boost::make_tuple( _maint_block, stake_id ) ); + if( stake_stat_range.first == stake_stat_range.second ) { - has_stake_changed = true; - old_proxy_id = GRAPHENE_PROXY_TO_SELF_ACCOUNT; - db.create( - [&stake_account, new_proxy_id, stake] (voting_statistics_object& o){ - o.account = stake_account.id; - o.stake = stake; - o.proxy = new_proxy_id; - o.votes = stake_account.options.votes; - } - ); - stake_stat_it = idx.find( stake_id ); + db.create( [this, &stake_account, &proxy_id, stake]( voting_statistics_object& o ){ + o.block_number = _maint_block; + o.account = stake_account.id; + o.stake = stake; + o.proxy = proxy_id; + o.votes = stake_account.options.votes; + }); } else { - has_stake_changed = stake_stat_it->stake != stake; - old_proxy_id = stake_stat_it->proxy; - db.modify( *stake_stat_it, - [&stake_account, new_proxy_id, stake] (voting_statistics_object& o) { + db.modify( *stake_stat_range.first, + [this, &stake_account, &proxy_id, stake]( voting_statistics_object& o ){ o.stake = stake; - o.proxy = new_proxy_id; + o.proxy = proxy_id; o.votes = stake_account.options.votes; } - ); + ); } - - if( old_proxy_id != new_proxy_id ) + + if( proxy_id == GRAPHENE_PROXY_TO_SELF_ACCOUNT ) + return; + + auto proxy_stat_range = voting_stat_idx.equal_range( boost::make_tuple( _maint_block, proxy_id ) ); + if( proxy_stat_range.first == proxy_stat_range.second ) { - if( old_proxy_id != GRAPHENE_PROXY_TO_SELF_ACCOUNT ) - { - /* if the proxy account has changed delete the proxied stake from the old proxy account */ - auto old_proxy_stat_it = idx.find( old_proxy_id ); - db.modify( *old_proxy_stat_it, - [stake_id] (voting_statistics_object& o) { - o.proxy_for.erase( stake_id ); - } - ); - } - - if( new_proxy_id != GRAPHENE_PROXY_TO_SELF_ACCOUNT ) - { - auto proxy_stat_it = idx.find( new_proxy_id ); - if( proxy_stat_it == idx.end() ) - { - /* if the new proxy account doesn't exist, create and add the proxied stake */ - db.create( - [stake_id, new_proxy_id, stake] (voting_statistics_object& o) { - o.account = new_proxy_id; - o.proxy_for.emplace( stake_id, stake ); - } - ); - } - else - { - /* insert the stake into the new proxy account */ - auto proxy_stat_it = idx.find( proxy_account.id ); - db.modify( *proxy_stat_it, - [stake_id, stake] (voting_statistics_object& o) { - auto insertion_return = o.proxy_for.emplace( stake_id, stake ); - if( !insertion_return.second ) - insertion_return.first->second = stake; - } - ); - } - } + db.create( [this, &stake_id, &proxy_id, stake]( voting_statistics_object& o ){ + o.block_number = _maint_block; + o.account = proxy_id; + o.proxy_for.emplace( stake_id, stake ); + }); } - else if( stake_stat_it->has_proxy() && has_stake_changed ) + else { - /* when the proxied stake has changed update the proxy account with the new stake */ - auto proxy_stat_it = idx.find( proxy_account.id ); - db.modify( *proxy_stat_it, - [stake_id, stake] (voting_statistics_object& o) { - auto insertion_return = o.proxy_for.emplace( stake_id, stake ); - if( !insertion_return.second ) - insertion_return.first->second = stake; + db.modify( *proxy_stat_range.first, + [&stake_id, stake]( voting_statistics_object& o ){ + o.proxy_for.emplace( stake_id, stake ); } ); } @@ -186,27 +292,74 @@ std::string voting_stat_plugin::plugin_name()const void voting_stat_plugin::plugin_set_program_options( boost::program_options::options_description& cli, - boost::program_options::options_description& cfg - ) + boost::program_options::options_description& cfg ) { + cli.add_options() + ( + "voting-stat-track-every-x-maint", boost::program_options::value(), + "Every x maintenance interval statistic objects will be created (12=2per day)" + ) + ( + "voting-stat-keep-objects-in-db", boost::program_options::value(), + "Every created object will be deleted after the maintenance interval (true)" + ) + ( + "voting-stat-track-worker-votes", boost::program_options::value(), + "Worker votes will be tracked (true)" + ) + ( + "voting-stat-track-witness-votes", boost::program_options::value(), + "Witness votes will be tracked (true)" + ) + ( + "voting-stat-track-committee-votes", boost::program_options::value(), + "Committee votes will be tracked (true)" + ); + cfg.add(cli); } void voting_stat_plugin::plugin_initialize(const boost::program_options::variables_map& options) { auto& db = database(); - db.add_index< primary_index< voting_statistics_index > >(); + db.add_index< primary_index >(); + db.add_index< primary_index >(); - db.on_voting_stake_calculated.connect( - [&](const account_object& stake_account, const account_object& proxy_account, - const uint64_t stake) { - my->on_stake_calculated( stake_account, proxy_account, stake ); - } + if( options.count("voting-stat-track-every-x-maint") ){ + my->_track_every_x_maint = options["voting-stat-track-every-x-maint"].as(); + if( my->_track_every_x_maint == 0 ) + my->_track_every_x_maint = 1; + my->_maint_counter = my->_track_every_x_maint; + } + if( options.count("voting-stat-keep-objects-in-db") ){ + my->_keep_objects_in_db = options["voting-stat-keep-objects-in-db"].as(); + } + if( options.count("voting-stat-track-worker-votes") ){ + my->_track_worker_votes = options["voting-stat-track-worker-votes"].as(); + } + if( options.count("voting-stat-track-witness-votes") ){ + my->_track_witness_votes = options["voting-stat-track-witness-votes"].as(); + } + if( options.count("voting-stat-track-committee-votes") ){ + my->_track_committee_votes = options["voting-stat-track-committee-votes"].as(); + } + + my->_on_voting_stake_calc_conn = db.on_voting_stake_calculated.connect( + [&]( const account_object& stake_account, const account_object& proxy_account, const uint64_t stake ){ + my->on_stake_calculated( stake_account, proxy_account, stake ); + } + ); + + my->_on_voting_stake_calc_block = std::unique_ptr( + new boost::signals2::shared_connection_block(my->_on_voting_stake_calc_conn) ); - - db.on_maintenance_begin.connect( - [&](const block_id_type& block_id){ my->on_maintenance_begin( block_id ); + + db.on_maintenance_begin.connect( [this](uint32_t block_num){ + my->on_maintenance_begin( block_num ); }); + db.on_maintenance_end.connect( [this](){ + my->on_maintenance_end(); + }); } void voting_stat_plugin::plugin_startup()