diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 7618fda50b..eb2bc9b19d 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -205,7 +205,7 @@ void database::update_active_witnesses() /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) - share_type stake_tally = 0; + share_type stake_tally = 0; size_t witness_count = 0; if( stake_target > 0 ) @@ -959,8 +959,8 @@ void process_hf_1465( database& db ) graphene::chain::share_type max_supply = current_asset.options.max_supply; if (current_supply > max_supply && max_supply != GRAPHENE_MAX_SHARE_SUPPLY) { - wlog( "Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.", - ("asset", current_asset.symbol) + wlog( "Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.", + ("asset", current_asset.symbol) ("current_supply", current_supply.value) ("old", max_supply)); db.modify( current_asset, [current_supply](asset_object& obj) { @@ -1150,6 +1150,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g distribute_fba_balances(*this); create_buyback_orders(*this); + on_maintenance_begin( next_block.block_num() ); + struct vote_tally_helper { database& d; const global_property_object& props; @@ -1179,6 +1181,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g + (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0) + stats.core_in_balance.value; + d.on_voting_stake_calculated( stake_account, opinion_account, voting_stake ); + for( vote_id_type id : opinion_account.options.votes ) { uint32_t offset = id.instance(); @@ -1233,7 +1237,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g update_worker_votes(); const auto& dgpo = get_dynamic_global_properties(); - + modify(gpo, [&dgpo](global_property_object& p) { // Remove scaling of account registration fee p.parameters.get_mutable_fees().get().basic_fee >>= p.parameters.account_fee_scale_bitshifts * @@ -1327,6 +1331,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g // process_budget needs to run at the bottom because // it needs to know the next_maintenance_time process_budget(); + + on_maintenance_end(); } } } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index d17bec6821..ffd61f44d8 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -275,10 +275,10 @@ struct get_impacted_account_visitor } void operator()( const htlc_extend_operation& op ) { - _impacted.insert( op.fee_payer() ); + _impacted.insert( op.fee_payer() ); } - void operator()( const htlc_refund_operation& op ) - { + void operator()( const htlc_refund_operation& op ) + { _impacted.insert( op.fee_payer() ); } }; @@ -455,7 +455,7 @@ void database::notify_on_pending_transaction( const signed_transaction& tx ) void database::notify_changed_objects() { try { - if( _undo_db.enabled() ) + if( _undo_db.enabled() ) { const auto& head_undo = _undo_db.head(); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 395818389b..761eca51d6 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -217,6 +217,21 @@ namespace graphene { namespace chain { */ fc::signal&, const vector&, const flat_set&)> removed_objects; + /** + * Emitted after the calculation of the voting_stake (occurs in the maintenance interval) + */ + fc::signal on_voting_stake_calculated; + + /** + * Emitted after the beginning of the maintenance interval + */ + fc::signal on_maintenance_begin; + + /** + * Emitted after the end of the maintenance interval + */ + fc::signal on_maintenance_end; + //////////////////// db_witness_schedule.cpp //////////////////// /** @@ -333,7 +348,7 @@ namespace graphene { namespace chain { * to newly created VBID and return it. * * Otherwise, credit amount to ovbid. - * + * * @return ID of newly created VBO, but only if VBO was created. */ optional< vesting_balance_id_type > deposit_lazy_vesting( diff --git a/libraries/db/include/graphene/db/object.hpp b/libraries/db/include/graphene/db/object.hpp index b5a6724a2b..4c2d5580fb 100644 --- a/libraries/db/include/graphene/db/object.hpp +++ b/libraries/db/include/graphene/db/object.hpp @@ -101,6 +101,11 @@ namespace graphene { namespace db { } virtual variant to_variant()const { return variant( static_cast(*this), MAX_NESTING ); } virtual vector pack()const { return fc::raw::pack( static_cast(*this) ); } + + static constexpr uint16_t space_type() + { + return (uint16_t)DerivedClass::space_id << 8 | DerivedClass::type_id; + } }; typedef flat_map annotation_map; diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index 412b185e08..29cfe496a1 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory( debug_witness ) add_subdirectory( snapshot ) add_subdirectory( es_objects ) add_subdirectory( api_helper_indexes ) +add_subdirectory( voting_stat ) diff --git a/libraries/plugins/README.md b/libraries/plugins/README.md index 411bab9b13..446c11b0cf 100644 --- a/libraries/plugins/README.md +++ b/libraries/plugins/README.md @@ -8,15 +8,16 @@ Plugins are optional to run by node operator according to their needs. However, # Available Plugins -Folder | Name | Description | Category | Status | SpaceID +Folder | Name | Description | Category | Status | SpaceID -----------------------------------|--------------------------|-----------------------------------------------------------------------------|----------------|---------------|--------------| [account_history](account_history) | Account History | Save account history data | History | Stable | 4 -[api_helper_indexes](api_helper_indexes) | API Helper Indexes | Provides some helper indexes used by various API calls | Database API | Stable | +[api_helper_indexes](api_helper_indexes) | API Helper Indexes | Provides some helper indexes used by various API calls | Database API | Stable | [debug_witness](debug_witness) | Debug Witness | Run "what-if" tests | Debug | Stable | [delayed_node](delayed_node) | Delayed Node | Avoid forks by running a several times confirmed and delayed blockchain | Business | Stable | [elasticsearch](elasticsearch) | ElasticSearch Operations | Save account history data into elasticsearch database | History | Experimental | 6 [es_objects](es_objects) | ElasticSearch Objects | Save selected objects into elasticsearch database | History | Experimental | [grouped_orders](grouped_orders) | Grouped Orders | Expose api to create a grouped order book of bitshares markets | Market data | Experimental | [market_history](market_history) | Market History | Save market history data | Market data | Stable | 5 -[snapshot](snapshot) | Snapshot | Get a json of all objects in blockchain at a specificed time or block | Debug | Stable | -[witness](witness) | Witness | Generate and sign blocks | Block producer | Stable | +[snapshot](snapshot) | Snapshot | Get a json of all objects in blockchain at a specificed time or block | Debug | Stable | +[witness](witness) | Witness | Generate and sign blocks | Block producer | Stable | +[voting_stat](voting_stat) | Voting Statistics | Generate objects from voting related data (works together with es objects) | History | Experimental | 8 \ No newline at end of file diff --git a/libraries/plugins/es_objects/CMakeLists.txt b/libraries/plugins/es_objects/CMakeLists.txt index 2cae2ffde4..ba4e2cd379 100644 --- a/libraries/plugins/es_objects/CMakeLists.txt +++ b/libraries/plugins/es_objects/CMakeLists.txt @@ -15,7 +15,7 @@ if(MSVC) set_source_files_properties(es_objects.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) endif(MSVC) -target_link_libraries( graphene_es_objects graphene_chain graphene_app ${CURL_LIBRARIES} ) +target_link_libraries( graphene_es_objects graphene_chain graphene_app graphene_voting_stat ${CURL_LIBRARIES} ) target_include_directories( graphene_es_objects PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/plugins/es_objects/es_objects.cpp b/libraries/plugins/es_objects/es_objects.cpp index 79b9f1cdba..605c332779 100644 --- a/libraries/plugins/es_objects/es_objects.cpp +++ b/libraries/plugins/es_objects/es_objects.cpp @@ -30,11 +30,16 @@ #include #include #include +#include +#include #include namespace graphene { namespace es_objects { +using voting_stat::voting_statistics_object; +using voting_stat::voteable_statistics_object; + namespace detail { @@ -61,6 +66,10 @@ class es_objects_plugin_impl bool _es_objects_balances = true; bool _es_objects_limit_orders = true; bool _es_objects_asset_bitasset = true; + bool _es_objects_voting_statistics = true; + bool _es_objects_voteable_statistics = true; + bool _es_objects_statistics_delete_allowed = true; + std::string _es_objects_index_prefix = "objects-"; uint32_t _es_objects_start_es_after_block = 0; CURL *curl; // curl handler @@ -141,61 +150,126 @@ bool es_objects_plugin_impl::index_database(const vector& ids, s limit_documents = _es_objects_bulk_replay; - for (auto const &value: ids) { - if (value.is() && _es_objects_proposals) { - auto obj = db.find_object(value); - auto p = static_cast(obj); - if (p != nullptr) { - if (action == "delete") - remove_from_database(p->id, "proposal"); - else - prepareTemplate(*p, "proposal"); + for (auto const &value: ids) + { + switch( value.space_type() ) + { + case( proposal_object::space_type() ): + { + if( _es_objects_proposals ) { + auto obj = db.find_object(value); + auto p = static_cast(obj); + if (p != nullptr) { + if (action == "delete") + remove_from_database(p->id, "proposal"); + else + prepareTemplate(*p, "proposal"); + } + } + break; + } + case( account_object::space_type() ): + { + if( _es_objects_accounts ) { + auto obj = db.find_object(value); + auto a = static_cast(obj); + if (a != nullptr) { + if (action == "delete") + remove_from_database(a->id, "account"); + else + prepareTemplate(*a, "account"); + } + } + break; } - } else if (value.is() && _es_objects_accounts) { - auto obj = db.find_object(value); - auto a = static_cast(obj); - if (a != nullptr) { - if (action == "delete") - remove_from_database(a->id, "account"); - else - prepareTemplate(*a, "account"); + case( asset_object::space_type() ): + { + if( _es_objects_assets ) { + auto obj = db.find_object(value); + auto a = static_cast(obj); + if (a != nullptr) { + if (action == "delete") + remove_from_database(a->id, "asset"); + else + prepareTemplate(*a, "asset"); + } + } + break; } - } else if (value.is() && _es_objects_assets) { - auto obj = db.find_object(value); - auto a = static_cast(obj); - if (a != nullptr) { - if (action == "delete") - remove_from_database(a->id, "asset"); - else - prepareTemplate(*a, "asset"); + case( account_balance_object::space_type() ): + { + if( _es_objects_balances ) { + auto obj = db.find_object(value); + auto b = static_cast(obj); + if (b != nullptr) { + if (action == "delete") + remove_from_database(b->id, "balance"); + else + prepareTemplate(*b, "balance"); + } + } + break; } - } else if (value.is() && _es_objects_balances) { - auto obj = db.find_object(value); - auto b = static_cast(obj); - if (b != nullptr) { - if (action == "delete") - remove_from_database(b->id, "balance"); - else - prepareTemplate(*b, "balance"); + case( limit_order_object::space_type() ): + { + if( _es_objects_limit_orders ) { + auto obj = db.find_object(value); + auto l = static_cast(obj); + if (l != nullptr) { + if (action == "delete") + remove_from_database(l->id, "limitorder"); + else + prepareTemplate(*l, "limitorder"); + } + } + break; } - } else if (value.is() && _es_objects_limit_orders) { - auto obj = db.find_object(value); - auto l = static_cast(obj); - if (l != nullptr) { - if (action == "delete") - remove_from_database(l->id, "limitorder"); - else - prepareTemplate(*l, "limitorder"); + case( asset_bitasset_data_object::space_type() ): + { + if( _es_objects_asset_bitasset ) { + auto obj = db.find_object(value); + auto ba = static_cast(obj); + if (ba != nullptr) { + if (action == "delete") + remove_from_database(ba->id, "bitasset"); + else + prepareTemplate(*ba, "bitasset"); + } + } + break; } - } else if (value.is() && _es_objects_asset_bitasset) { - auto obj = db.find_object(value); - auto ba = static_cast(obj); - if (ba != nullptr) { - if (action == "delete") - remove_from_database(ba->id, "bitasset"); - else - prepareTemplate(*ba, "bitasset"); + case( voting_statistics_object::space_type() ): + { + if( _es_objects_voting_statistics ) { + auto obj = db.find_object(value); + auto vs = static_cast(obj); + + if (vs != nullptr) { + if (action == "delete" && _es_objects_statistics_delete_allowed ) // TODO will lead to error when false + remove_from_database(vs->id, "voting-statistics"); + else + prepareTemplate(*vs, "voting-statistics"); + } + } + break; } + case( voteable_statistics_object::space_type() ): + { + if( _es_objects_voteable_statistics ) { + auto obj = db.find_object(value); + auto vs = static_cast(obj); + + if (vs != nullptr) { + if (action == "delete" && _es_objects_statistics_delete_allowed ) // TODO will lead to error when false + remove_from_database(vs->id, "voteable-statistics"); + else + prepareTemplate(*vs, "voteable-statistics"); + } + } + break; + } + default: + break; } } @@ -308,12 +382,16 @@ void es_objects_plugin::plugin_set_program_options( ("es-objects-balances", boost::program_options::value(), "Store balances objects(true)") ("es-objects-limit-orders", boost::program_options::value(), "Store limit order objects(true)") ("es-objects-asset-bitasset", boost::program_options::value(), "Store feed data(true)") + ("es-objects-voting-statistics", boost::program_options::value(), "Store voting statistcs(true)") + ("es-objects-voteable-statistics", boost::program_options::value(), "Store voteable statistcs(true)") + ("es-objects-statistics-delete-allowed", boost::program_options::value(), + "Allows the deletion of statistics objects from es(true)") ("es-objects-index-prefix", boost::program_options::value(), - "Add a prefix to the index(objects-)") + "Add a prefix to the index(objects-)") ("es-objects-keep-only-current", boost::program_options::value(), - "Keep only current state of the objects(true)") + "Keep only current state of the objects(true)") ("es-objects-start-es-after-block", boost::program_options::value(), - "Start doing ES job after block(0)") + "Start doing ES job after block(0)") ; cfg.add(cli); } @@ -350,6 +428,15 @@ void es_objects_plugin::plugin_initialize(const boost::program_options::variable if (options.count("es-objects-asset-bitasset")) { my->_es_objects_asset_bitasset = options["es-objects-asset-bitasset"].as(); } + if (options.count("es-objects-voting-statistics")) { + my->_es_objects_voting_statistics = options["es-objects-voting-statistics"].as(); + } + if (options.count("es-objects-voteable-statistics")) { + my->_es_objects_voteable_statistics = options["es-objects-voteable-statistics"].as(); + } + if (options.count("es-objects-statistics-delete-allowed")) { + my->_es_objects_statistics_delete_allowed = options["es-objects-statistics-delete-allowed"].as(); + } if (options.count("es-objects-index-prefix")) { my->_es_objects_index_prefix = options["es-objects-index-prefix"].as(); } diff --git a/libraries/plugins/voting_stat/CMakeLists.txt b/libraries/plugins/voting_stat/CMakeLists.txt new file mode 100644 index 0000000000..d59937295f --- /dev/null +++ b/libraries/plugins/voting_stat/CMakeLists.txt @@ -0,0 +1,20 @@ +file( GLOB HEADER "include/graphene/voting_stat/*.hpp" ) + +add_library( graphene_voting_stat voting_stat_plugin.cpp ) + +target_link_libraries( graphene_voting_stat graphene_chain graphene_app fc ) +target_include_directories( graphene_voting_stat + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +if(MSVC) + set_source_files_properties( voting_stat_plugin.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) +endif(MSVC) + +install( TARGETS + graphene_voting_stat + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib ) + +INSTALL( FILES ${HEADERS} DESTINATION "include/graphene/voting_stat" ) diff --git a/libraries/plugins/voting_stat/include/graphene/voting_stat/maintenance_counter_object.hpp b/libraries/plugins/voting_stat/include/graphene/voting_stat/maintenance_counter_object.hpp new file mode 100644 index 0000000000..78047223e5 --- /dev/null +++ b/libraries/plugins/voting_stat/include/graphene/voting_stat/maintenance_counter_object.hpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 Blockchain Projects BV. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include + +using graphene::chain::object_id_type; +using graphene::chain::account_id_type; +using graphene::chain::vote_id_type; + +using graphene::db::object; +using graphene::db::abstract_object; +using graphene::db::generic_index; +using graphene::db::by_id; + +using namespace boost::multi_index; +using boost::container::flat_map; +using boost::container::flat_set; + +namespace graphene { namespace voting_stat { + /** + * @brief tracks the number maintenance interval occurences + * @ingroup object + * @ingroup voting_stat_plugin + * + * The number of maintenance intervals to be tracked is set in this object. Since a fork can occur during a + * maintenance interval, it is not sufficient to track the number of intervals through a plugin internal + * variable. In the case of a fork this object will be reverted together with the internal maintenance counter. + * Through the lifetime of the plugin there will be only one of this objects. + * + * @note By default this object are not tracked, the voting_stat_plugin must be loaded for this object to + * be maintained. + */ + class maintenance_counter_object : public abstract_object + { + public: + static const uint8_t space_id = VOTING_STAT_SPACE_ID; + static const uint8_t type_id = voting_stat_object_type_ids::maintenance_counter_object_type_id; + + maintenance_counter_object(){} + + bool counter_reached( chain::database& db ) const + { + if( counter == max_counter ) + { + db.modify( *this, [](maintenance_counter_object& o) { + o.counter = 0; + }); + return true; + } + db.modify( *this, [](maintenance_counter_object& o) { + o.counter += 1; + }); + return false; + } + + uint16_t max_counter = 12; // every 12th maintenance interval vote*_objects will be created + uint16_t counter = 12; + }; + + typedef multi_index_container< maintenance_counter_object, + indexed_by< + ordered_unique< tag, + member< object, object_id_type, &object::id > + > + > + > maintenance_counter_multi_index_type; + + typedef generic_index< + maintenance_counter_object, maintenance_counter_multi_index_type > maintenance_counter_index; + +}} // graphene::chain + +FC_REFLECT_DERIVED( graphene::voting_stat::maintenance_counter_object, (graphene::chain::object), + (max_counter)(counter) ) diff --git a/libraries/plugins/voting_stat/include/graphene/voting_stat/voteable_statistics_object.hpp b/libraries/plugins/voting_stat/include/graphene/voting_stat/voteable_statistics_object.hpp new file mode 100644 index 0000000000..73a3a715a7 --- /dev/null +++ b/libraries/plugins/voting_stat/include/graphene/voting_stat/voteable_statistics_object.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019 Blockchain Projects BV. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include + +#include + +#include + +#include +#include + +#include + +using graphene::chain::account_id_type; +using graphene::chain::vote_id_type; +using graphene::db::generic_index; + +namespace graphene { namespace voting_stat { + + /** + * @brief tracks the history voting related data per account + * @ingroup object + * @ingroup voting_stat_plugin + * + * The calculation of the voting stake, performed in the maintenance interval, results + * in the creation or, if present, in the update of a voting_statistics_object. + * + * @note By default these objects are not tracked, the voting_stat_plugin must + * be loaded for these objects to be maintained. + */ + class voteable_statistics_object : public db::abstract_object + { + public: + static const uint8_t space_id = VOTING_STAT_SPACE_ID; + static const uint8_t type_id = voting_stat_object_type_ids::voteable_statistics_object_type_id; + + voteable_statistics_object(){} + + /* the block_num where the maintenance interval was performed */ + uint32_t block_number; + /* vote_id of the voteable_object */ + vote_id_type vote_id; + /* the accounts which this voteable is voted by with the number of votes */ + flat_map< account_id_type, uint64_t > voted_by; + + /* returns the total votes of this object */ + uint64_t get_votes() const + { + return boost::accumulate( voted_by | boost::adaptors::map_values, 0 ); + } + }; + + + //struct by_block_number{}; // declared in voting_statistics_object + typedef multi_index_container< voteable_statistics_object, + indexed_by< + ordered_unique< tag, + member< object, object_id_type, &object::id > + >, + ordered_unique< tag, + composite_key< voteable_statistics_object, + member< voteable_statistics_object, uint32_t, &voteable_statistics_object::block_number >, + member< voteable_statistics_object, vote_id_type, &voteable_statistics_object::vote_id > + > + > + > + > voteable_statistics_multi_index_type; + + typedef db::generic_index< voteable_statistics_object, + voteable_statistics_multi_index_type + > voteable_statistics_index; + +}} // graphene::chain + +FC_REFLECT_DERIVED( graphene::voting_stat::voteable_statistics_object, (graphene::chain::object), + (block_number)(vote_id)(voted_by) ) diff --git a/libraries/plugins/voting_stat/include/graphene/voting_stat/voting_stat_plugin.hpp b/libraries/plugins/voting_stat/include/graphene/voting_stat/voting_stat_plugin.hpp new file mode 100644 index 0000000000..fbd6b14771 --- /dev/null +++ b/libraries/plugins/voting_stat/include/graphene/voting_stat/voting_stat_plugin.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Blockchain Projects BV. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +namespace graphene { namespace voting_stat { +// +// Plugins should #define their SPACE_ID's so plugins with +// conflicting SPACE_ID assignments can be compiled into the +// same binary (by simply re-assigning some of the conflicting #defined +// SPACE_ID's in a build script). +// +// Assignment of SPACE_ID's cannot be done at run-time because +// various template automagic depends on them being known at compile +// time. +// +#ifndef VOTING_STAT_SPACE_ID +#define VOTING_STAT_SPACE_ID 8 +#endif + +enum voting_stat_object_type_ids +{ + voting_statistics_object_type_id, + voteable_statistics_object_type_id, + maintenance_counter_object_type_id +}; + +namespace detail +{ + class voting_stat_plugin_impl; +} + +class voting_stat_plugin : public graphene::app::plugin +{ + public: + voting_stat_plugin(); + virtual ~voting_stat_plugin(); + + std::string plugin_name()const override; + virtual void plugin_set_program_options( + boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) override; + virtual void plugin_initialize(const boost::program_options::variables_map& options) override; + virtual void plugin_startup() override; + + friend class detail::voting_stat_plugin_impl; + std::unique_ptr my; +}; + +}} // graphene::voting_stat + + diff --git a/libraries/plugins/voting_stat/include/graphene/voting_stat/voting_statistics_object.hpp b/libraries/plugins/voting_stat/include/graphene/voting_stat/voting_statistics_object.hpp new file mode 100644 index 0000000000..b5e13243fb --- /dev/null +++ b/libraries/plugins/voting_stat/include/graphene/voting_stat/voting_statistics_object.hpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 Blockchain Projects BV. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include + +using graphene::chain::object_id_type; +using graphene::chain::account_id_type; +using graphene::chain::vote_id_type; + +using graphene::db::object; +using graphene::db::abstract_object; +using graphene::db::generic_index; +using graphene::db::by_id; + +using namespace boost::multi_index; +using boost::container::flat_map; +using boost::container::flat_set; + +namespace graphene { namespace voting_stat { + /** + * @brief tracks the history of the voting stake for an account + * @ingroup object + * @ingroup voting_stat_plugin + * + * The calculation of the voting stake, performed in the maintenance interval, results in the creation or, + * if present, in the update of a voting_statistics_object. + * + * @note By default these objects are not tracked, the voting_stat_plugin must be loaded for these objects to + * be maintained. + */ + class voting_statistics_object : public abstract_object + { + public: + static const uint8_t space_id = VOTING_STAT_SPACE_ID; + static const uint8_t type_id = voting_stat_object_type_ids::voting_statistics_object_type_id; + + voting_statistics_object(){} + + /* the block number where the maintenance interval was performed */ + uint32_t block_number; + /* the owner of the stake */ + account_id_type account; + /* the stake which was generated by this account */ + uint64_t stake = 0; + /* the proxy for this account */ + account_id_type proxy = GRAPHENE_PROXY_TO_SELF_ACCOUNT; + /* the accounts which this account was a proxy for with the proxied stakes */ + flat_map< account_id_type, uint64_t > proxy_for; + + + /* the vote_id's this account was voting for */ + flat_set votes; + + /* returns the total voting stake this account can vote with */ + uint64_t get_total_voting_stake() const + { + uint64_t init = has_proxy() ? 0 : stake; + return boost::accumulate( proxy_for | boost::adaptors::map_values, init ); + } + + bool has_proxy() const + { + return GRAPHENE_PROXY_TO_SELF_ACCOUNT != proxy; + } + }; + + + struct by_block_number{}; + typedef multi_index_container< voting_statistics_object, + indexed_by< + ordered_unique< tag, + member< object, object_id_type, &object::id > + >, + ordered_unique< tag, + composite_key< voting_statistics_object, + member< voting_statistics_object, uint32_t, &voting_statistics_object::block_number >, + member< voting_statistics_object, account_id_type, &voting_statistics_object::account > + > + > + > + > voting_statistics_multi_index_type; + + typedef generic_index< voting_statistics_object, voting_statistics_multi_index_type > voting_statistics_index; + +}} // graphene::chain + +FC_REFLECT_DERIVED( graphene::voting_stat::voting_statistics_object, (graphene::chain::object), + (block_number)(account)(stake)(proxy)(proxy_for)(votes) ) diff --git a/libraries/plugins/voting_stat/voting_stat_plugin.cpp b/libraries/plugins/voting_stat/voting_stat_plugin.cpp new file mode 100644 index 0000000000..0c02931866 --- /dev/null +++ b/libraries/plugins/voting_stat/voting_stat_plugin.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2019 Blockchain Projects BV. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +namespace graphene { namespace voting_stat { + +using namespace graphene::chain; +using graphene::db::primary_index; + +namespace detail { + +class voting_stat_plugin_impl +{ +public: + voting_stat_plugin_impl(voting_stat_plugin& _plugin) : _self( _plugin ){} + virtual ~voting_stat_plugin_impl(){} + + boost::signals2::connection _on_voting_stake_calc_conn; + std::unique_ptr _on_voting_stake_calc_block; + + /** + * plugin parameters + */ + bool _keep_objects_in_db = true; + bool _track_worker_votes = true; + bool _track_witness_votes = true; + bool _track_committee_votes = true; + + /** + * @brief callback for database::on_stake_calculated + * + * 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); + + /** + * @brief callback for database::on_maintenance_begin + * + * 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(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(uint32_t block_num) +{ + if( !_keep_objects_in_db ) + delete_all_statistics_objects(); + + auto& db = database(); + const auto& maint_counter_obj = *db.get_index_type().indices().get().begin(); + if( maint_counter_obj.counter_reached( db ) ) + { + _on_voting_stake_calc_block->unblock(); + _maint_block = block_num; + _create_voteable = true; + } +} + +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(); + + 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( + const account_object& stake_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; + 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 ) + { + 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 + { + db.modify( *stake_stat_range.first, + [this, &stake_account, &proxy_id, stake]( voting_statistics_object& o ){ + o.stake = stake; + o.proxy = proxy_id; + o.votes = stake_account.options.votes; + } + ); + } + + 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 ) + { + 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 + { + db.modify( *proxy_stat_range.first, + [&stake_id, stake]( voting_statistics_object& o ){ + o.proxy_for.emplace( stake_id, stake ); + } + ); + } +} + +} // namespace::detail + + + +voting_stat_plugin::voting_stat_plugin() : + my( new detail::voting_stat_plugin_impl(*this) ) +{ +} + +voting_stat_plugin::~voting_stat_plugin() +{ +} + +std::string voting_stat_plugin::plugin_name()const +{ + return "voting_stat"; +} + +void voting_stat_plugin::plugin_set_program_options( + boost::program_options::options_description& cli, + 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 >(); + db.add_index< primary_index >(); + db.add_index< primary_index >(); + + 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(); + } + uint16_t track_every_x_maint = 12; + if( options.count("voting-stat-track-every-x-maint") ) + { + track_every_x_maint = options["voting-stat-track-every-x-maint"].as(); + if( track_every_x_maint == 0 ) + track_every_x_maint = 1; + } + const auto& maint_counter_idx = db.get_index_type().indices().get(); + if( maint_counter_idx.empty() ) + { + db.create( [track_every_x_maint]( maintenance_counter_object& o ) { + o.max_counter = track_every_x_maint; + o.counter = track_every_x_maint; + }); + } + else + { + db.modify( *maint_counter_idx.begin(), + [track_every_x_maint]( maintenance_counter_object& o ) + { + o.max_counter = track_every_x_maint; + o.counter = track_every_x_maint; + } + ); + } + + 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( [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() +{ +} + +} } diff --git a/programs/witness_node/CMakeLists.txt b/programs/witness_node/CMakeLists.txt index d671a93c17..2b2b6bef2e 100644 --- a/programs/witness_node/CMakeLists.txt +++ b/programs/witness_node/CMakeLists.txt @@ -13,7 +13,7 @@ endif() target_link_libraries( witness_node PRIVATE graphene_app graphene_delayed_node graphene_account_history graphene_elasticsearch graphene_market_history graphene_grouped_orders graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full graphene_snapshot graphene_es_objects - graphene_api_helper_indexes + graphene_api_helper_indexes graphene_voting_stat fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) install( TARGETS diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index 400d09f0ff..137913c003 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -50,7 +51,7 @@ #include #ifdef WIN32 -# include +# include #else # include #endif @@ -96,8 +97,10 @@ int main(int argc, char** argv) { auto es_objects_plug = node->register_plugin(); auto grouped_orders_plug = node->register_plugin(); auto api_helper_indexes_plug = node->register_plugin(); + auto voting_stat_plug = node->register_plugin(); // add plugin options to config + try { bpo::options_description cli, cfg; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d05bf13183..b1447ed3e0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ file(GLOB UNIT_TESTS "tests/*.cpp") add_executable( chain_test ${COMMON_SOURCES} ${UNIT_TESTS} ) target_link_libraries( chain_test graphene_chain graphene_app graphene_witness graphene_account_history graphene_elasticsearch - graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes + graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes graphene_voting_stat fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} ) if(MSVC) set_source_files_properties( tests/serialization_tests.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) diff --git a/tests/tests/voting_stat_tests.cpp b/tests/tests/voting_stat_tests.cpp new file mode 100644 index 0000000000..04e9753d0a --- /dev/null +++ b/tests/tests/voting_stat_tests.cpp @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2019 Blockchain Projects BV. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include + +#include + +#include +//#include +//#include + +#include + +#include + +#include +#include + +#include "../common/database_fixture.hpp" + +using namespace fc; +using namespace graphene::app; +using namespace graphene::chain; +using namespace graphene::chain::test; +using namespace graphene::voting_stat; +//using namespace graphene::es_objects; +using namespace graphene::utilities; +namespace bpo = boost::program_options; + +struct voting_stat_fixture : public database_fixture +{ + vote_id_type default_vote_id; + // CURL *_curl; + // ES _es; + + voting_stat_fixture() + { + try + { + // _curl = curl_easy_init(); + // _es.curl = _curl; + // _es.elasticsearch_url = "http://localhost:9200/"; + // _es.index_prefix = "objects-"; + + app.register_plugin( true ); + //app.register_plugin( true ); + } + catch(fc::exception &e) + { + edump((e.to_detail_string() )); + } + + }; + + void make_next_maintenance_interval() + { + generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); + generate_block(); + } + + void set_account_options( account_id_type id, optional proxy = optional() ) + { + witness_id_type wit_id = *db.get_global_properties().active_witnesses.begin(); + default_vote_id = wit_id(db).vote_id; + + account_options opt; + opt.votes = { default_vote_id }; + opt.num_witness = opt.votes.size(); + if( proxy ) + opt.voting_account = *proxy; + + account_update_operation op; + op.account = id; + op.new_options = opt; + + signed_transaction trx; + trx.operations.push_back( op ); + set_expiration( db, trx ); + PUSH_TX( db, trx, ~0 ); + } + + const voting_statistics_object& get_voting_statistics_object( account_id_type id ) + { + const auto& idx = db.get_index_type().indices().get(); + auto last_block = idx.rbegin()->block_number; + auto range = idx.equal_range( boost::make_tuple( last_block, id ) ); + if( range.first == range.second ) + BOOST_FAIL( "object could not be found, which should never happen" ); + return *range.first; + } +}; + +BOOST_FIXTURE_TEST_SUITE( voting_stat_tests, voting_stat_fixture ) + +BOOST_AUTO_TEST_CASE( test_voting_statistics_object_tracking_without_proxy ) +{ try { + + bpo::options_description cli; + bpo::options_description cfg; + + auto voting_stat = app.get_plugin("voting_stat"); + voting_stat->plugin_set_program_options( cli, cfg ); + + const char* const plugin_argv[]{ "voting_stat", + "--voting-stat-track-every-x-maint", "1", + "--voting-stat-keep-objects-in-db", "true" + }; + int plugin_argc = sizeof(plugin_argv)/sizeof(char*); + + bpo::variables_map var_map; + bpo::store( bpo::parse_command_line( plugin_argc, plugin_argv, cfg ), var_map ); + app.initialize_plugins( var_map ); + + + ACTOR( alice ); + set_account_options( alice_id ); + + + const auto& alice_acc = alice_id(db); + BOOST_CHECK( *alice_acc.options.votes.begin() == default_vote_id ); + BOOST_CHECK( alice_acc.options.voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + const auto& alice_stat1 = get_voting_statistics_object( alice_id ); + + BOOST_CHECK( alice_stat1.proxy == GRAPHENE_PROXY_TO_SELF_ACCOUNT ); + BOOST_CHECK( !alice_stat1.has_proxy() ); + BOOST_CHECK( alice_stat1.proxy_for.empty() ); + BOOST_CHECK( alice_stat1.stake == 1 ); + BOOST_CHECK( *alice_stat1.votes.begin() == default_vote_id ); + BOOST_CHECK( alice_stat1.get_total_voting_stake() == 1 ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + const auto& alice_stat2 = get_voting_statistics_object( alice_id ); + + BOOST_CHECK( alice_stat2.proxy == GRAPHENE_PROXY_TO_SELF_ACCOUNT ); + BOOST_CHECK( !alice_stat2.has_proxy() ); + BOOST_CHECK( alice_stat2.proxy_for.empty() ); + BOOST_CHECK( alice_stat2.stake == 2 ); + BOOST_CHECK( *alice_stat2.votes.begin() == default_vote_id ); + BOOST_CHECK( alice_stat2.get_total_voting_stake() == 2 ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( test_voting_statistics_with_proxy_delete_after_interval ) +{ try { + + bpo::options_description cli_vs; + bpo::options_description cli_es; + bpo::options_description cfg; + + auto voting_stat = app.get_plugin("voting_stat"); + voting_stat->plugin_set_program_options( cli_vs, cfg ); + + //auto es_objects = app.get_plugin("es_objects"); + //es_objects->plugin_set_program_options( cli_es, cfg ); + + const char* const plugin_argv[]{ "voting_stat", + "--voting-stat-track-every-x-maint", "1", + "--voting-stat-keep-objects-in-db", "false", + "--voting-stat-track-witness-votes", "false", + "--voting-stat-track-committee-votes", "false", + "--voting-stat-track-worker-votes", "false", + + // "--es-objects-bulk-replay", "1", + // "--es-objects-proposals", "false", + // "--es-objects-accounts", "false", + // "--es-objects-assets", "false", + // "--es-objects-balances", "false", + // "--es-objects-limit-orders", "false", + // "--es-objects-asset-bitasset", "false", + // "--es-objects-keep-only-current", "true", + }; + int plugin_argc = sizeof(plugin_argv)/sizeof(char*); + + bpo::variables_map var_map; + bpo::store( bpo::parse_command_line( plugin_argc, plugin_argv, cfg ), var_map ); + app.initialize_plugins( var_map ); + + // auto objects_deleted = graphene::utilities::deleteAll(_es); + // if( !objects_deleted ) + // BOOST_FAIL( "elastic search DB could not be deleted" ); + + + ACTORS( (alice)(bob)(charlie) ); + transfer( committee_account, alice_id, asset(1) ); + transfer( committee_account, bob_id, asset(2) ); + transfer( committee_account, charlie_id, asset(3) ); + + // proxy chain: alice => bob => charlie + set_account_options( alice_id, bob_id ); + set_account_options( bob_id, charlie_id ); + set_account_options( charlie_id ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( alice_stat.has_proxy() ); + BOOST_CHECK( alice_stat.proxy == bob_id ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == 0 ); + + BOOST_CHECK( bob_stat.has_proxy() ); + BOOST_CHECK( bob_stat.proxy == charlie_id ); + auto alice_proxied = *bob_stat.proxy_for.begin(); + BOOST_CHECK( alice_proxied.first == alice_id && alice_proxied.second == 1 ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 1 ); + + BOOST_CHECK( !charlie_stat.has_proxy() ); + auto bob_proxied = *charlie_stat.proxy_for.begin(); + BOOST_CHECK( bob_proxied.first == bob_id && bob_proxied.second == 2 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == (2 + 3) ); + } + + // proxy: alice => alice; bob => charlie; + set_account_options( alice_id, GRAPHENE_PROXY_TO_SELF_ACCOUNT ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( !alice_stat.has_proxy() ); + BOOST_CHECK( alice_stat.proxy_for.empty() ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == 1 ); + + BOOST_CHECK( bob_stat.has_proxy() ); + BOOST_CHECK( bob_stat.proxy_for.empty() ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 0 ); + + BOOST_CHECK( !charlie_stat.has_proxy() ); + auto bob_proxied = *charlie_stat.proxy_for.begin(); + BOOST_CHECK( bob_proxied.first == bob_id && bob_proxied.second == 2 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == (2 + 3) ); + } + + // proxy: alice => alice; bob => charlie; charlie => alice; stake increase + set_account_options( charlie_id, alice_id ); + transfer( committee_account, alice_id, asset(10) ); + transfer( committee_account, bob_id, asset(20) ); + transfer( committee_account, charlie_id, asset(30) ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( !alice_stat.has_proxy() ); + auto charlie_proxied = *alice_stat.proxy_for.begin(); + BOOST_CHECK( charlie_proxied.first == charlie_id && charlie_proxied.second == 33 ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == (11 + 33) ); + + BOOST_CHECK( bob_stat.has_proxy() ); + BOOST_CHECK( bob_stat.proxy_for.empty() ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 0 ); + BOOST_CHECK( bob_stat.stake == 22 ); + + BOOST_CHECK( charlie_stat.has_proxy() ); + auto bob_proxied = *charlie_stat.proxy_for.begin(); + BOOST_CHECK( bob_proxied.first == bob_id && bob_proxied.second == 22 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == 22 ); + } + + // only stake increase + transfer( committee_account, alice_id, asset(100) ); + transfer( committee_account, bob_id, asset(200) ); + transfer( committee_account, charlie_id, asset(300) ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( alice_stat.stake == 111 ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == (111 + 333) ); + BOOST_CHECK( bob_stat.stake == 222 ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 0 ); + BOOST_CHECK( charlie_stat.stake == 333 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == 222 ); + } + + // fc::usleep(fc::milliseconds(2000)); + // string query = "{ \"query\" : { \"bool\" : { \"must\" : [{\"match_all\": {}}] } } }"; + // _es.endpoint = _es.index_prefix + "*/data/_count"; + // _es.query = query; + // auto res = graphene::utilities::simpleQuery(_es); + // variant j = fc::json::from_string(res); + // BOOST_CHECK( j["count"].as_int64() == 12 ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( test_voting_statistics_with_proxy_keep_after_interval ) +{ try { + + // this test is the same as above just that objects are not deleted with each interval + + bpo::options_description cli_vs; + bpo::options_description cli_es; + bpo::options_description cfg; + + auto voting_stat = app.get_plugin("voting_stat"); + voting_stat->plugin_set_program_options( cli_vs, cfg ); + + //auto es_objects = app.get_plugin("es_objects"); + //es_objects->plugin_set_program_options( cli_es, cfg ); + + const char* const plugin_argv[]{ "voting_stat", + "--voting-stat-track-every-x-maint", "1", + "--voting-stat-keep-objects-in-db", "true", + "--voting-stat-track-witness-votes", "false", + "--voting-stat-track-committee-votes", "false", + "--voting-stat-track-worker-votes", "false", + + // "--es-objects-bulk-replay", "1", + // "--es-objects-proposals", "false", + // "--es-objects-accounts", "false", + // "--es-objects-assets", "false", + // "--es-objects-balances", "false", + // "--es-objects-limit-orders", "false", + // "--es-objects-asset-bitasset", "false", + // "--es-objects-keep-only-current", "true", + }; + int plugin_argc = sizeof(plugin_argv)/sizeof(char*); + + bpo::variables_map var_map; + bpo::store( bpo::parse_command_line( plugin_argc, plugin_argv, cfg ), var_map ); + app.initialize_plugins( var_map ); + + // auto objects_deleted = graphene::utilities::deleteAll(_es); + // if( !objects_deleted ) + // BOOST_FAIL( "elastic search DB could not be deleted" ); + + + ACTORS( (alice)(bob)(charlie) ); + transfer( committee_account, alice_id, asset(1) ); + transfer( committee_account, bob_id, asset(2) ); + transfer( committee_account, charlie_id, asset(3) ); + + // proxy chain: alice => bob => charlie + set_account_options( alice_id, bob_id ); + set_account_options( bob_id, charlie_id ); + set_account_options( charlie_id ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( alice_stat.has_proxy() ); + BOOST_CHECK( alice_stat.proxy == bob_id ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == 0 ); + + BOOST_CHECK( bob_stat.has_proxy() ); + BOOST_CHECK( bob_stat.proxy == charlie_id ); + auto alice_proxied = *bob_stat.proxy_for.begin(); + BOOST_CHECK( alice_proxied.first == alice_id && alice_proxied.second == 1 ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 1 ); + + BOOST_CHECK( !charlie_stat.has_proxy() ); + auto bob_proxied = *charlie_stat.proxy_for.begin(); + BOOST_CHECK( bob_proxied.first == bob_id && bob_proxied.second == 2 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == (2 + 3) ); + } + + // proxy: alice => alice; bob => charlie; + set_account_options( alice_id, GRAPHENE_PROXY_TO_SELF_ACCOUNT ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( !alice_stat.has_proxy() ); + BOOST_CHECK( alice_stat.proxy_for.empty() ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == 1 ); + + BOOST_CHECK( bob_stat.has_proxy() ); + BOOST_CHECK( bob_stat.proxy_for.empty() ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 0 ); + + BOOST_CHECK( !charlie_stat.has_proxy() ); + auto bob_proxied = *charlie_stat.proxy_for.begin(); + BOOST_CHECK( bob_proxied.first == bob_id && bob_proxied.second == 2 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == (2 + 3) ); + } + + // proxy: alice => alice; bob => charlie; charlie => alice; stake increase + set_account_options( charlie_id, alice_id ); + transfer( committee_account, alice_id, asset(10) ); + transfer( committee_account, bob_id, asset(20) ); + transfer( committee_account, charlie_id, asset(30) ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( !alice_stat.has_proxy() ); + auto charlie_proxied = *alice_stat.proxy_for.begin(); + BOOST_CHECK( charlie_proxied.first == charlie_id && charlie_proxied.second == 33 ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == (11 + 33) ); + + BOOST_CHECK( bob_stat.has_proxy() ); + BOOST_CHECK( bob_stat.proxy_for.empty() ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 0 ); + BOOST_CHECK( bob_stat.stake == 22 ); + + BOOST_CHECK( charlie_stat.has_proxy() ); + auto bob_proxied = *charlie_stat.proxy_for.begin(); + BOOST_CHECK( bob_proxied.first == bob_id && bob_proxied.second == 22 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == 22 ); + } + + // only stake increase + transfer( committee_account, alice_id, asset(100) ); + transfer( committee_account, bob_id, asset(200) ); + transfer( committee_account, charlie_id, asset(300) ); + + make_next_maintenance_interval(); + { + const auto& alice_stat = get_voting_statistics_object( alice_id ); + const auto& bob_stat = get_voting_statistics_object( bob_id ); + const auto& charlie_stat = get_voting_statistics_object( charlie_id ); + + BOOST_CHECK( alice_stat.stake == 111 ); + BOOST_CHECK( alice_stat.get_total_voting_stake() == (111 + 333) ); + BOOST_CHECK( bob_stat.stake == 222 ); + BOOST_CHECK( bob_stat.get_total_voting_stake() == 0 ); + BOOST_CHECK( charlie_stat.stake == 333 ); + BOOST_CHECK( charlie_stat.get_total_voting_stake() == 222 ); + } + + // fc::usleep(fc::milliseconds(2000)); + // string query = "{ \"query\" : { \"bool\" : { \"must\" : [{\"match_all\": {}}] } } }"; + // _es.endpoint = _es.index_prefix + "*/data/_count"; + // _es.query = query; + // auto res = graphene::utilities::simpleQuery(_es); + // variant j = fc::json::from_string(res); + // BOOST_CHECK( j["count"].as_int64() == 12 ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( test_voteable_objects_tracking_with_es ) +{ try { + + bpo::options_description cli_vs; + bpo::options_description cli_es; + bpo::options_description cfg; + + auto voting_stat = app.get_plugin("voting_stat"); + voting_stat->plugin_set_program_options( cli_vs, cfg ); + + // auto es_objects = app.get_plugin("es_objects"); + // es_objects->plugin_set_program_options( cli_es, cfg ); + + const char* const plugin_argv[]{ "voting_stat", + "--voting-stat-track-every-x-maint", "1", + "--voting-stat-keep-objects-in-db", "false", + "--voting-stat-track-worker-votes", "true", + "--voting-stat-track-witness-votes", "true", + "--voting-stat-track-committee-votes", "true", + + // "--es-objects-voting-statistics", "true", + // "--es-objects-voteable-statistics", "true", + // "--es-objects-statistics-delete-allowed", "false", + + // "--es-objects-bulk-replay", "1", + // "--es-objects-proposals", "false", + // "--es-objects-accounts", "false", + // "--es-objects-assets", "false", + // "--es-objects-balances", "false", + // "--es-objects-limit-orders", "false", + // "--es-objects-asset-bitasset", "false", + // "--es-objects-keep-only-current", "true", + }; + int plugin_argc = sizeof(plugin_argv)/sizeof(char*); + + bpo::variables_map var_map; + bpo::store( bpo::parse_command_line( plugin_argc, plugin_argv, cfg ), var_map ); + app.initialize_plugins( var_map ); + + // auto objects_deleted = graphene::utilities::deleteAll(_es); + // if( !objects_deleted ) + // BOOST_FAIL( "elastic search DB could not be deleted" ); + + + ACTOR( alice ); + uint64_t alice_stake = 100; + upgrade_to_lifetime_member( alice_id ); + transfer( committee_account, alice_id, asset(alice_stake) ); + set_account_options( alice_id ); + + create_worker( alice_id ); + create_worker( alice_id ); + + + const auto& voteable_idx = db.get_index_type().indices().get(); + const auto& witness_idx = db.get_index_type().indices().get(); + const auto& committee_idx = db.get_index_type().indices().get(); + const auto& worker_idx = db.get_index_type().indices().get(); + + uint64_t num_witnesses = witness_idx.size(); + uint64_t num_committee_members = committee_idx.size(); + uint64_t num_workers = worker_idx.size(); + + uint64_t expected_voteables = num_witnesses + num_committee_members + num_workers; + + make_next_maintenance_interval(); + BOOST_CHECK( voteable_idx.size() == expected_voteables ); + auto last_block = voteable_idx.rbegin()->block_number; + auto default_voteable_votes = voteable_idx.equal_range( boost::make_tuple( last_block, default_vote_id) ) + .first->get_votes(); + BOOST_CHECK( default_voteable_votes == alice_stake ); + + make_next_maintenance_interval(); + BOOST_CHECK( voteable_idx.size() == expected_voteables ); + + + // auto expected_objs_in_es = 2*(expected_voteables + 1); + + // fc::usleep(fc::milliseconds(2000)); + // string query = "{ \"query\" : { \"bool\" : { \"must\" : [{\"match_all\": {}}] } } }"; + // _es.endpoint = _es.index_prefix + "*/data/_count"; + // _es.query = query; + // auto res = graphene::utilities::simpleQuery(_es); + // variant j = fc::json::from_string(res); + // uint64_t obj_count = j["count"].as_uint64(); + // BOOST_CHECK( obj_count == expected_objs_in_es ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( test_voting_stat_plugin_track_every_x_interval ) +{ try { + + bpo::options_description cli; + bpo::options_description cfg; + + auto voting_stat = app.get_plugin("voting_stat"); + voting_stat->plugin_set_program_options( cli, cfg ); + + const char* const plugin_argv[]{ "voting_stat", + "--voting-stat-track-every-x-maint", "2", + "--voting-stat-keep-objects-in-db", "true" + }; + int plugin_argc = sizeof(plugin_argv)/sizeof(char*); + + bpo::variables_map var_map; + bpo::store( bpo::parse_command_line( plugin_argc, plugin_argv, cfg ), var_map ); + app.initialize_plugins( var_map ); + + + ACTOR( alice ); + set_account_options( alice_id ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + const auto& alice_stat1 = get_voting_statistics_object( alice_id ); + BOOST_CHECK( alice_stat1.stake == 1 ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + const auto& alice_stat2 = get_voting_statistics_object( alice_id ); + // since this interval is even it should not be tracked + BOOST_CHECK( alice_stat2.stake == 1 ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + const auto& alice_stat3 = get_voting_statistics_object( alice_id ); + // this should result in the correct object since all odd intervals are tracked + BOOST_CHECK( alice_stat3.stake == 3 ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( test_delete_after_interval_and_pushed_to_es ) +{ try { + + bpo::options_description cli_vs; + bpo::options_description cli_es; + bpo::options_description cfg; + + auto voting_stat = app.get_plugin("voting_stat"); + voting_stat->plugin_set_program_options( cli_vs, cfg ); + + //auto es_objects = app.get_plugin("es_objects"); + //es_objects->plugin_set_program_options( cli_es, cfg ); + + const char* const plugin_argv[]{ "voting_stat", + "--voting-stat-track-every-x-maint", "1", + "--voting-stat-keep-objects-in-db", "false", + + // "--es-objects-voting-statistics", "true", + // "--es-objects-voteable-statistics", "false", + // "--es-objects-statistics-delete-allowed", "false", + + // "--es-objects-bulk-replay", "1", + // "--es-objects-proposals", "false", + // "--es-objects-accounts", "false", + // "--es-objects-assets", "false", + // "--es-objects-balances", "false", + // "--es-objects-limit-orders", "false", + // "--es-objects-asset-bitasset", "false", + // "--es-objects-keep-only-current", "true", + }; + int plugin_argc = sizeof(plugin_argv)/sizeof(char*); + + bpo::variables_map var_map; + bpo::store( bpo::parse_command_line( plugin_argc, plugin_argv, cfg ), var_map ); + app.initialize_plugins( var_map ); + + // auto objects_deleted = graphene::utilities::deleteAll(_es); + // if( !objects_deleted ) + // BOOST_FAIL( "elastic search DB could not be deleted" ); + + + const auto& idx = db.get_index_type().indices().get(); + ACTOR( alice ); + set_account_options( alice_id ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + BOOST_CHECK( idx.size() == 1 ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + BOOST_CHECK( idx.size() == 1 ); + + + transfer( committee_account, alice_id, asset(1) ); + make_next_maintenance_interval(); + BOOST_CHECK( idx.size() == 1 ); + + + // fc::usleep(fc::milliseconds(2000)); + // string query = "{ \"query\" : { \"bool\" : { \"must\" : [{\"match_all\": {}}] } } }"; + // _es.endpoint = _es.index_prefix + "*/data/_count"; + // _es.query = query; + // auto res = graphene::utilities::simpleQuery(_es); + // variant j = fc::json::from_string(res); + // int64_t obj_count = j["count"].as_int64(); + // BOOST_CHECK( obj_count == 3 ); + +} FC_LOG_AND_RETHROW() } + +/* +// TODO REMOVE +BOOST_AUTO_TEST_CASE( test_indices ) +{ try { + + bpo::options_description cli_vs; + bpo::options_description cli_es; + bpo::options_description cfg; + + auto voting_stat = app.get_plugin("voting_stat"); + voting_stat->plugin_set_program_options( cli_vs, cfg ); + + const char* const plugin_argv[]{ "voting_stat", + "--voting-stat-track-every-x-maint", "1", + "--voting-stat-keep-objects-in-db", "true", + }; + int plugin_argc = sizeof(plugin_argv)/sizeof(char*); + + bpo::variables_map var_map; + bpo::store( bpo::parse_command_line( plugin_argc, plugin_argv, cfg ), var_map ); + app.initialize_plugins( var_map ); + + + ACTORS((alice)(bob)); + + transfer( committee_account, alice_id, asset(1) ); + transfer( committee_account, bob_id, asset(1) ); + make_next_maintenance_interval(); + + transfer( committee_account, alice_id, asset(1) ); + transfer( committee_account, bob_id, asset(1) ); + make_next_maintenance_interval(); + + transfer( committee_account, alice_id, asset(1) ); + transfer( committee_account, bob_id, asset(1) ); + make_next_maintenance_interval(); + + const auto& block_idx = db.get_index_type().indices().get(); + edump(("BLOCK")); + for( const auto& o : block_idx ) + edump((o.block_number)(o.account)); + + auto beg = block_idx.lower_bound( block_idx.begin()->block_number ); + auto end = block_idx.upper_bound( block_idx.begin()->block_number ); + + edump(("BLOCK BOUND")); + for(; beg != end; ++beg ) + edump((beg->block_number)(beg->account)); + + + edump(("RANGEPLAY")); + auto last_block = block_idx.begin()->block_number; + auto block_range = block_idx.equal_range( last_block ); + for( auto o = block_range.first; o != block_range.second; ++o ) { + edump((o->block_number)(o->account)); + } + + for( const auto& o : block_idx ) + db.remove(o); + + auto range = block_idx.equal_range( boost::make_tuple( last_block, alice_id ) ); + edump((range.first==range.second)); + + uint64_t delt = std::distance( range.first, range.second ); + edump((delt)); + + //edump((range.first->block_number)(range.first->account)); + +} FC_LOG_AND_RETHROW() } +*/ + +BOOST_AUTO_TEST_SUITE_END()