diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 29cefcfc..c652a798 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -375,6 +375,11 @@ namespace detail { } _chain_db->add_checkpoints( loaded_checkpoints ); + if( _options->count("enable-standby-votes-tracking") ) + { + _chain_db->enable_standby_votes_tracking( _options->at("enable-standby-votes-tracking").as() ); + } + bool replay = false; std::string replay_reason = "reason not provided"; @@ -925,6 +930,9 @@ void application::set_program_options(boost::program_options::options_descriptio ("genesis-json", bpo::value(), "File to read Genesis State from") ("dbg-init-key", bpo::value(), "Block signing key to use for init witnesses, overrides genesis file") ("api-access", bpo::value(), "JSON file specifying API permissions") + ("enable-standby-votes-tracking", bpo::value()->implicit_value(true), + "Whether to enable tracking of votes of standby witnesses and committee members. " + "Set it to true to provide accurate data to API clients, set to false for slightly better performance.") ; command_line_options.add(configuration_file_options); command_line_options.add_options() diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 3ec84d14..58288945 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -185,13 +185,27 @@ void database::update_active_witnesses() const global_property_object& gpo = get_global_properties(); - const auto& all_witnesses = get_index_type().indices(); + auto update_witness_total_votes = [this]( const witness_object& wit ) { + modify( wit, [this]( witness_object& obj ) + { + obj.total_votes = _vote_tally_buffer[obj.vote_id]; + }); + }; - for( const witness_object& wit : all_witnesses ) + if( _track_standby_votes ) { - modify( wit, [&]( witness_object& obj ){ - obj.total_votes = _vote_tally_buffer[wit.vote_id]; - }); + const auto& all_witnesses = get_index_type().indices(); + for( const witness_object& wit : all_witnesses ) + { + update_witness_total_votes( wit ); + } + } + else + { + for( const witness_object& wit : wits ) + { + update_witness_total_votes( wit ); + } } // Update witness authority @@ -267,13 +281,29 @@ void database::update_active_committee_members() const chain_property_object& cpo = get_chain_properties(); auto committee_members = sort_votable_objects(std::max(committee_member_count*2+1, (size_t)cpo.immutable_parameters.min_committee_member_count)); - for( const committee_member_object& del : committee_members ) - { - modify( del, [&]( committee_member_object& obj ){ - obj.total_votes = _vote_tally_buffer[del.vote_id]; - }); - } + auto update_committee_member_total_votes = [this]( const committee_member_object& cm ) { + modify( cm, [this]( committee_member_object& obj ) + { + obj.total_votes = _vote_tally_buffer[obj.vote_id]; + }); + }; + if( _track_standby_votes ) + { + const auto& all_committee_members = get_index_type().indices(); + for( const committee_member_object& cm : all_committee_members ) + { + update_committee_member_total_votes( cm ); + } + } + else + { + for( const committee_member_object& cm : committee_members ) + { + update_committee_member_total_votes( cm ); + } + } + // Update committee authorities if( !committee_members.empty() ) { diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 483bfc8a..cd0a9766 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -460,6 +460,8 @@ namespace graphene { namespace chain { /** * @} */ + /// Enable or disable tracking of votes of standby witnesses and committee members + inline void enable_standby_votes_tracking(bool enable) { _track_standby_votes = enable; } protected: //Mark pop_undo() as protected -- we do not want outside calling pop_undo(); it should call pop_block() instead void pop_undo() { object_database::pop_undo(); } @@ -561,6 +563,11 @@ namespace graphene { namespace chain { flat_map _checkpoints; node_property_object _node_property_object; + + /// Whether to update votes of standby witnesses and committee members when performing chain maintenance. + /// Set it to true to provide accurate data to API clients, set to false to have better performance. + bool _track_standby_votes = true; + fc::hash_ctr_rng _random_number_generator; bool _slow_replays = false; diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index f31ec00a..d613bfba 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -127,6 +127,12 @@ database_fixture::database_fixture() options.insert(std::make_pair("track-account", boost::program_options::variable_value(track_account, false))); } + // standby votes tracking + if( boost::unit_test::framework::current_test_case().p_name.value == "track_votes_witnesses_disabled" || + boost::unit_test::framework::current_test_case().p_name.value == "track_votes_committee_disabled") { + app.chain_database()->enable_standby_votes_tracking( false ); + } + // app.initialize(); ahplugin->plugin_set_app(&app); ahplugin->plugin_initialize(options); diff --git a/tests/tests/voting_tests.cpp b/tests/tests/voting_tests.cpp index b88f485a..870fd359 100644 --- a/tests/tests/voting_tests.cpp +++ b/tests/tests/voting_tests.cpp @@ -163,4 +163,360 @@ BOOST_AUTO_TEST_CASE(last_voting_date_proxy) } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_CASE(put_my_witnesses) +{ + try + { + graphene::app::database_api db_api1(db); + + ACTORS( (witness0) + (witness1) + (witness2) + (witness3) + (witness4) + (witness5) + (witness6) + (witness7) + (witness8) + (witness9) + (witness10) + (witness11) + (witness12) + (witness13) ); + + // Upgrade all accounts to LTM + upgrade_to_lifetime_member(witness0_id); + upgrade_to_lifetime_member(witness1_id); + upgrade_to_lifetime_member(witness2_id); + upgrade_to_lifetime_member(witness3_id); + upgrade_to_lifetime_member(witness4_id); + upgrade_to_lifetime_member(witness5_id); + upgrade_to_lifetime_member(witness6_id); + upgrade_to_lifetime_member(witness7_id); + upgrade_to_lifetime_member(witness8_id); + upgrade_to_lifetime_member(witness9_id); + upgrade_to_lifetime_member(witness10_id); + upgrade_to_lifetime_member(witness11_id); + upgrade_to_lifetime_member(witness12_id); + upgrade_to_lifetime_member(witness13_id); + + // Create all the witnesses + const witness_id_type witness0_witness_id = create_witness(witness0_id, witness0_private_key).id; + const witness_id_type witness1_witness_id = create_witness(witness1_id, witness1_private_key).id; + const witness_id_type witness2_witness_id = create_witness(witness2_id, witness2_private_key).id; + const witness_id_type witness3_witness_id = create_witness(witness3_id, witness3_private_key).id; + const witness_id_type witness4_witness_id = create_witness(witness4_id, witness4_private_key).id; + const witness_id_type witness5_witness_id = create_witness(witness5_id, witness5_private_key).id; + const witness_id_type witness6_witness_id = create_witness(witness6_id, witness6_private_key).id; + const witness_id_type witness7_witness_id = create_witness(witness7_id, witness7_private_key).id; + const witness_id_type witness8_witness_id = create_witness(witness8_id, witness8_private_key).id; + const witness_id_type witness9_witness_id = create_witness(witness9_id, witness9_private_key).id; + const witness_id_type witness10_witness_id = create_witness(witness10_id, witness10_private_key).id; + const witness_id_type witness11_witness_id = create_witness(witness11_id, witness11_private_key).id; + const witness_id_type witness12_witness_id = create_witness(witness12_id, witness12_private_key).id; + const witness_id_type witness13_witness_id = create_witness(witness13_id, witness13_private_key).id; + + // Create a vector with private key of all witnesses, will be used to activate 11 witnesses at a time + const vector private_keys = { + witness0_private_key, + witness1_private_key, + witness2_private_key, + witness3_private_key, + witness4_private_key, + witness5_private_key, + witness6_private_key, + witness7_private_key, + witness8_private_key, + witness9_private_key, + witness10_private_key, + witness11_private_key, + witness12_private_key, + witness13_private_key + + }; + + // create a map with account id and witness id of the first 11 witnesses + const flat_map witness_map = { + {witness0_id, witness0_witness_id}, + {witness1_id, witness1_witness_id}, + {witness2_id, witness2_witness_id}, + {witness3_id, witness3_witness_id}, + {witness4_id, witness4_witness_id}, + {witness5_id, witness5_witness_id}, + {witness6_id, witness6_witness_id}, + {witness7_id, witness7_witness_id}, + {witness8_id, witness8_witness_id}, + {witness9_id, witness9_witness_id}, + {witness10_id, witness10_witness_id}, + {witness11_id, witness11_witness_id}, + {witness12_id, witness12_witness_id}, + {witness13_id, witness13_witness_id} + }; + + // Check current default witnesses, default chain is configured with 10 witnesses + auto witnesses = db.get_global_properties().active_witnesses; + BOOST_CHECK_EQUAL(witnesses.size(), 10); + BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 1); + BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 2); + BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 3); + BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 4); + BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 5); + BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 6); + BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 7); + BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 8); + BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 9); + BOOST_CHECK_EQUAL(witnesses.begin()[9].instance.value, 10); + + // Activate all witnesses + // Each witness is voted with incremental stake so last witness created will be the ones with more votes + int c = 0; + for (auto l : witness_map) { + int stake = 100 + c + 10; + transfer(committee_account, l.first, asset(stake)); + { + set_expiration(db, trx); + account_update_operation op; + op.account = l.first; + op.new_options = l.first(db).options; + op.new_options->votes.insert(l.second(db).vote_id); + + trx.operations.push_back(op); + sign(trx, private_keys.at(c)); + PUSH_TX(db, trx); + trx.clear(); + } + ++c; + } + + // Trigger the new witnesses + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + // Check my witnesses are now in control of the system + witnesses = db.get_global_properties().active_witnesses; + BOOST_CHECK_EQUAL(witnesses.size(), 11); + BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 14); + BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 15); + BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 16); + BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 17); + BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 18); + BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 19); + BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 20); + BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 21); + BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 22); + BOOST_CHECK_EQUAL(witnesses.begin()[9].instance.value, 23); + BOOST_CHECK_EQUAL(witnesses.begin()[10].instance.value, 24); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(track_votes_witnesses_enabled) +{ + try + { + graphene::app::database_api db_api1(db); + + INVOKE(put_my_witnesses); + + const account_id_type witness1_id= get_account("witness1").id; + auto witness1_object = db_api1.get_witness_by_account(witness1_id); + BOOST_CHECK_EQUAL(witness1_object->total_votes, 111); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(track_votes_witnesses_disabled) +{ + try + { + graphene::app::database_api db_api1(db); + + INVOKE(put_my_witnesses); + + const account_id_type witness1_id= get_account("witness1").id; + auto witness1_object = db_api1.get_witness_by_account(witness1_id); + BOOST_CHECK_EQUAL(witness1_object->total_votes, 0); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(put_my_committee_members) +{ + try + { + graphene::app::database_api db_api1(db); + + ACTORS( (committee0) + (committee1) + (committee2) + (committee3) + (committee4) + (committee5) + (committee6) + (committee7) + (committee8) + (committee9) + (committee10) + (committee11) + (committee12) + (committee13) ); + + // Upgrade all accounts to LTM + upgrade_to_lifetime_member(committee0_id); + upgrade_to_lifetime_member(committee1_id); + upgrade_to_lifetime_member(committee2_id); + upgrade_to_lifetime_member(committee3_id); + upgrade_to_lifetime_member(committee4_id); + upgrade_to_lifetime_member(committee5_id); + upgrade_to_lifetime_member(committee6_id); + upgrade_to_lifetime_member(committee7_id); + upgrade_to_lifetime_member(committee8_id); + upgrade_to_lifetime_member(committee9_id); + upgrade_to_lifetime_member(committee10_id); + upgrade_to_lifetime_member(committee11_id); + upgrade_to_lifetime_member(committee12_id); + upgrade_to_lifetime_member(committee13_id); + + // Create all the committee + const committee_member_id_type committee0_committee_id = create_committee_member(committee0_id(db)).id; + const committee_member_id_type committee1_committee_id = create_committee_member(committee1_id(db)).id; + const committee_member_id_type committee2_committee_id = create_committee_member(committee2_id(db)).id; + const committee_member_id_type committee3_committee_id = create_committee_member(committee3_id(db)).id; + const committee_member_id_type committee4_committee_id = create_committee_member(committee4_id(db)).id; + const committee_member_id_type committee5_committee_id = create_committee_member(committee5_id(db)).id; + const committee_member_id_type committee6_committee_id = create_committee_member(committee6_id(db)).id; + const committee_member_id_type committee7_committee_id = create_committee_member(committee7_id(db)).id; + const committee_member_id_type committee8_committee_id = create_committee_member(committee8_id(db)).id; + const committee_member_id_type committee9_committee_id = create_committee_member(committee9_id(db)).id; + const committee_member_id_type committee10_committee_id = create_committee_member(committee10_id(db)).id; + const committee_member_id_type committee11_committee_id = create_committee_member(committee11_id(db)).id; + const committee_member_id_type committee12_committee_id = create_committee_member(committee12_id(db)).id; + const committee_member_id_type committee13_committee_id = create_committee_member(committee13_id(db)).id; + + // Create a vector with private key of all witnesses, will be used to activate 11 witnesses at a time + const vector private_keys = { + committee0_private_key, + committee1_private_key, + committee2_private_key, + committee3_private_key, + committee4_private_key, + committee5_private_key, + committee6_private_key, + committee7_private_key, + committee8_private_key, + committee9_private_key, + committee10_private_key, + committee11_private_key, + committee12_private_key, + committee13_private_key + }; + + // create a map with account id and committee id of the first 11 witnesses + const flat_map committee_map = { + {committee0_id, committee0_committee_id}, + {committee1_id, committee1_committee_id}, + {committee2_id, committee2_committee_id}, + {committee3_id, committee3_committee_id}, + {committee4_id, committee4_committee_id}, + {committee5_id, committee5_committee_id}, + {committee6_id, committee6_committee_id}, + {committee7_id, committee7_committee_id}, + {committee8_id, committee8_committee_id}, + {committee9_id, committee9_committee_id}, + {committee10_id, committee10_committee_id}, + {committee11_id, committee11_committee_id}, + {committee12_id, committee12_committee_id}, + {committee13_id, committee13_committee_id} + }; + + // Check current default witnesses, default chain is configured with 10 witnesses + auto committee_members = db.get_global_properties().active_committee_members; + + BOOST_CHECK_EQUAL(committee_members.size(), 10); + BOOST_CHECK_EQUAL(committee_members.begin()[0].instance.value, 0); + BOOST_CHECK_EQUAL(committee_members.begin()[1].instance.value, 1); + BOOST_CHECK_EQUAL(committee_members.begin()[2].instance.value, 2); + BOOST_CHECK_EQUAL(committee_members.begin()[3].instance.value, 3); + BOOST_CHECK_EQUAL(committee_members.begin()[4].instance.value, 4); + BOOST_CHECK_EQUAL(committee_members.begin()[5].instance.value, 5); + BOOST_CHECK_EQUAL(committee_members.begin()[6].instance.value, 6); + BOOST_CHECK_EQUAL(committee_members.begin()[7].instance.value, 7); + BOOST_CHECK_EQUAL(committee_members.begin()[8].instance.value, 8); + BOOST_CHECK_EQUAL(committee_members.begin()[9].instance.value, 9); + + // Activate all committee + // Each witness is voted with incremental stake so last witness created will be the ones with more votes + int c = 0; + for (auto committee : committee_map) { + int stake = 100 + c + 10; + transfer(committee_account, committee.first, asset(stake)); + { + set_expiration(db, trx); + account_update_operation op; + op.account = committee.first; + op.new_options = committee.first(db).options; + op.new_options->votes.insert(committee.second(db).vote_id); + + trx.operations.push_back(op); + sign(trx, private_keys.at(c)); + PUSH_TX(db, trx); + trx.clear(); + } + ++c; + } + + // Trigger the new committee + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + // Check my witnesses are now in control of the system + committee_members = db.get_global_properties().active_committee_members; + BOOST_CHECK_EQUAL(committee_members.size(), 11); + + /* TODO we are not in full control, seems to committee members have votes by default + BOOST_CHECK_EQUAL(committee_members.begin()[0].instance.value, 14); + BOOST_CHECK_EQUAL(committee_members.begin()[1].instance.value, 15); + BOOST_CHECK_EQUAL(committee_members.begin()[2].instance.value, 16); + BOOST_CHECK_EQUAL(committee_members.begin()[3].instance.value, 17); + BOOST_CHECK_EQUAL(committee_members.begin()[4].instance.value, 18); + BOOST_CHECK_EQUAL(committee_members.begin()[5].instance.value, 19); + BOOST_CHECK_EQUAL(committee_members.begin()[6].instance.value, 20); + BOOST_CHECK_EQUAL(committee_members.begin()[7].instance.value, 21); + BOOST_CHECK_EQUAL(committee_members.begin()[8].instance.value, 22); + BOOST_CHECK_EQUAL(committee_members.begin()[9].instance.value, 23); + BOOST_CHECK_EQUAL(committee_members.begin()[10].instance.value, 24); + */ + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(track_votes_committee_enabled) +{ + try + { + graphene::app::database_api db_api1(db); + + INVOKE(put_my_committee_members); + + const account_id_type committee1_id= get_account("committee1").id; + auto committee1_object = db_api1.get_committee_member_by_account(committee1_id); + BOOST_CHECK_EQUAL(committee1_object->total_votes, 111); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(track_votes_committee_disabled) +{ + try + { + graphene::app::database_api db_api1(db); + + INVOKE(put_my_committee_members); + + const account_id_type committee1_id= get_account("committee1").id; + auto committee1_object = db_api1.get_committee_member_by_account(committee1_id); + BOOST_CHECK_EQUAL(committee1_object->total_votes, 0); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file