From 01fb1db6a606bedde25bcd0bd702a10e14cdb569 Mon Sep 17 00:00:00 2001 From: satyakoneru Date: Mon, 6 Jan 2020 23:59:35 +1100 Subject: [PATCH] SON194-SON195 - Report SON Down, addition of SON Account for sidechain consensus (#244) * SON194-SON195 - Addition of SON BTC Account and report son down changes * SON194-SON195 - SON BTC Account errors rectification * SON194-SON195 - Adding Tests * User sidechain address mappings (#240) * WIP: Sidechain objects * Revert "WIP: Sidechain objects" This reverts commit 8676940a281604688771e96ceb1e65a35d98e8e5. * WIP: User sidechain address mappings * Fix reflection problem * Reflect missing members of sidechain_address_update_operation * Add sidechain address operation tests * Enable RPC calls * Fix build errors due to merge conflict * Fix RPC, add CLI wallet commands for sidechain addresses * Improved peerplays_sidechain_plugin_impl * Remove short param for son-id * Fix crashing errors on bitcoin event received * Code review changes * SON207 - Introduce scheduling for SONs similar to witnesses (#251) * Extend SON objects to contain sidechain public keys (#254) Co-authored-by: obucinac --- libraries/app/impacted.cpp | 3 + libraries/chain/db_init.cpp | 1 + libraries/chain/db_maint.cpp | 64 ++++++++++ libraries/chain/db_notify.cpp | 3 + .../chain/protocol/chain_parameters.hpp | 5 + .../graphene/chain/protocol/operations.hpp | 3 +- .../include/graphene/chain/protocol/son.hpp | 18 ++- .../include/graphene/chain/son_evaluator.hpp | 9 ++ libraries/chain/proposal_evaluator.cpp | 4 + libraries/chain/son_evaluator.cpp | 32 +++++ tests/tests/son_operations_tests.cpp | 114 ++++++++++++++++++ 11 files changed, 254 insertions(+), 2 deletions(-) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 5b6f5411..c8b1122e 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -319,6 +319,9 @@ struct get_impacted_account_visitor void operator()( const sidechain_address_delete_operation& op ){ _impacted.insert( op.sidechain_address_account ); } + void operator()( const son_report_down_operation& op ){ + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 31c0dcd5..2aab032d 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -253,6 +253,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 8fb72566..267c58f5 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -461,6 +461,70 @@ void database::update_active_sons() flat_set active_sons(gpo.active_sons.begin(), gpo.active_sons.end()); _sso.scheduler.update(active_sons); }); + + if(gpo.active_sons.size() > 0 ) { + if(gpo.parameters.get_son_btc_account_id() == GRAPHENE_NULL_ACCOUNT) { + const auto& son_btc_account = create( [&]( account_object& obj ) { + uint64_t total_votes = 0; + obj.name = "son_btc_account"; + obj.statistics = create([&]( account_statistics_object& acc_stat ){ acc_stat.owner = obj.id; }).id; + obj.membership_expiration_date = time_point_sec::maximum(); + obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + obj.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + + for( const auto& son_id : gpo.active_sons ) + { + const son_object& son = get(son_id); + total_votes += _vote_tally_buffer[son.vote_id]; + } + // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, + // then I want to keep the most significant 16 bits of what's left. + int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + + for( const auto& son_id : gpo.active_sons ) + { + // Ensure that everyone has at least one vote. Zero weights aren't allowed. + const son_object& son = get(son_id); + uint16_t votes = std::max((_vote_tally_buffer[son.vote_id] >> bits_to_drop), uint64_t(1) ); + obj.active.account_auths[son.son_account] += votes; + obj.active.weight_threshold += votes; + } + obj.active.weight_threshold *= 2; + obj.active.weight_threshold /= 3; + obj.active.weight_threshold += 1; + }); + + modify( gpo, [&]( global_property_object& gpo ) { + gpo.parameters.extensions.value.son_btc_account = son_btc_account.get_id(); + if( gpo.pending_parameters ) + gpo.pending_parameters->extensions.value.son_btc_account = son_btc_account.get_id(); + }); + } else { + modify( get(gpo.parameters.get_son_btc_account_id()), [&]( account_object& obj ) + { + uint64_t total_votes = 0; + for( const auto& son_id : gpo.active_sons ) + { + const son_object& son = get(son_id); + total_votes += _vote_tally_buffer[son.vote_id]; + } + // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, + // then I want to keep the most significant 16 bits of what's left. + int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + for( const auto& son_id : gpo.active_sons ) + { + // Ensure that everyone has at least one vote. Zero weights aren't allowed. + const son_object& son = get(son_id); + uint16_t votes = std::max((_vote_tally_buffer[son.vote_id] >> bits_to_drop), uint64_t(1) ); + obj.active.account_auths[son.son_account] += votes; + obj.active.weight_threshold += votes; + } + obj.active.weight_threshold *= 2; + obj.active.weight_threshold /= 3; + obj.active.weight_threshold += 1; + }); + } + } } FC_CAPTURE_AND_RETHROW() } void database::initialize_budget_record( fc::time_point_sec now, budget_record& rec )const diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 83a58c45..d53955a3 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -306,6 +306,9 @@ struct get_impacted_account_visitor void operator()( const sidechain_address_delete_operation& op ) { _impacted.insert( op.sidechain_address_account ); } + void operator()( const son_report_down_operation& op ) { + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index c62274ba..51024e16 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -46,6 +46,7 @@ namespace graphene { namespace chain { optional < uint32_t > son_vesting_amount; optional < uint32_t > son_vesting_period; optional < uint32_t > son_pay_daily_max; + optional < account_id_type > son_btc_account; }; struct chain_parameters @@ -138,6 +139,9 @@ namespace graphene { namespace chain { inline uint16_t son_pay_daily_max()const { return extensions.value.son_pay_daily_max.valid() ? *extensions.value.son_pay_daily_max : MIN_SON_PAY_DAILY_MAX; } + inline account_id_type get_son_btc_account_id() const { + return extensions.value.son_btc_account.valid() ? *extensions.value.son_btc_account : GRAPHENE_NULL_ACCOUNT; + } }; } } // graphene::chain @@ -155,6 +159,7 @@ FC_REFLECT( graphene::chain::parameter_extension, (son_vesting_amount) (son_vesting_period) (son_pay_daily_max) + (son_btc_account) ) FC_REFLECT( graphene::chain::chain_parameters, diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index e1cc5225..646f2e69 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -144,7 +144,8 @@ namespace graphene { namespace chain { son_heartbeat_operation, sidechain_address_add_operation, sidechain_address_update_operation, - sidechain_address_delete_operation + sidechain_address_delete_operation, + son_report_down_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/son.hpp b/libraries/chain/include/graphene/chain/protocol/son.hpp index 914928a6..6f4eaa7e 100644 --- a/libraries/chain/include/graphene/chain/protocol/son.hpp +++ b/libraries/chain/include/graphene/chain/protocol/son.hpp @@ -63,6 +63,19 @@ namespace graphene { namespace chain { share_type calculate_fee(const fee_parameters_type& k)const { return 0; } }; + struct son_report_down_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type payer; + time_point_sec down_ts; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + } } // namespace graphene::chain FC_REFLECT(graphene::chain::son_create_operation::fee_parameters_type, (fee) ) @@ -77,4 +90,7 @@ FC_REFLECT(graphene::chain::son_delete_operation::fee_parameters_type, (fee) ) FC_REFLECT(graphene::chain::son_delete_operation, (fee)(son_id)(payer)(owner_account) ) FC_REFLECT(graphene::chain::son_heartbeat_operation::fee_parameters_type, (fee) ) -FC_REFLECT(graphene::chain::son_heartbeat_operation, (fee)(son_id)(owner_account)(ts) ) \ No newline at end of file +FC_REFLECT(graphene::chain::son_heartbeat_operation, (fee)(son_id)(owner_account)(ts) ) + +FC_REFLECT(graphene::chain::son_report_down_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_report_down_operation, (fee)(son_id)(payer)(down_ts) ) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/son_evaluator.hpp b/libraries/chain/include/graphene/chain/son_evaluator.hpp index 6b82f5e5..4615c95b 100644 --- a/libraries/chain/include/graphene/chain/son_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/son_evaluator.hpp @@ -40,4 +40,13 @@ public: object_id_type do_apply(const son_heartbeat_operation& o); }; +class son_report_down_evaluator : public evaluator +{ +public: + typedef son_report_down_operation operation_type; + + void_result do_evaluate(const son_report_down_operation& o); + object_id_type do_apply(const son_report_down_operation& o); +}; + } } // namespace graphene::chain diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index b50d4b82..dc1aba3e 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -152,6 +152,10 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_heartbeat_operation not allowed yet!" ); } + void operator()(const son_report_down_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_report_down_operation not allowed yet!" ); + } + // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { for (const op_wrapper &op : v.proposed_ops) diff --git a/libraries/chain/son_evaluator.cpp b/libraries/chain/son_evaluator.cpp index cee9740a..619d2e22 100644 --- a/libraries/chain/son_evaluator.cpp +++ b/libraries/chain/son_evaluator.cpp @@ -143,4 +143,36 @@ object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_operation& return op.son_id; } FC_CAPTURE_AND_RETHROW( (op) ) } +void_result son_report_down_evaluator::do_evaluate(const son_report_down_operation& op) +{ try { + FC_ASSERT(op.payer == db().get_global_properties().parameters.get_son_btc_account_id(), "Payer should be the son btc account"); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_id) != idx.end() ); + auto itr = idx.find(op.son_id); + auto stats = itr->statistics( db() ); + FC_ASSERT(itr->status == son_status::active, "Inactive/Deregistered/in_maintenance SONs cannot be reported on as down"); + FC_ASSERT(op.down_ts >= stats.last_active_timestamp, "down_ts should be greater than last_active_timestamp"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_report_down_evaluator::do_apply(const son_report_down_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + if (itr->status == son_status::active) { + db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) + { + sso.last_down_timestamp = op.down_ts; + }); + + db().modify(*itr, [&op](son_object &so) { + so.status = son_status::in_maintenance; + }); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + } } // namespace graphene::chain diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp index 6751ff03..ad8cf0bd 100644 --- a/tests/tests/son_operations_tests.cpp +++ b/tests/tests/son_operations_tests.cpp @@ -746,4 +746,118 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); } } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( son_report_down_test ) { + + try + { + INVOKE(son_heartbeat_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + + const auto& sidx = db.get_index_type().indices().get(); + BOOST_REQUIRE( sidx.size() == 1 ); + auto son_stats_obj = sidx.find( obj->statistics ); + BOOST_REQUIRE( son_stats_obj != sidx.end() ); + + BOOST_CHECK( obj->status == son_status::active); + + const auto& son_btc_account = db.create( [&]( account_object& obj ) { + obj.name = "son_btc_account"; + obj.statistics = db.create([&]( account_statistics_object& acc_stat ){ acc_stat.owner = obj.id; }).id; + obj.membership_expiration_date = time_point_sec::maximum(); + obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + obj.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + + obj.owner.add_authority( bob_id, 1 ); + obj.active.add_authority( bob_id, 1 ); + obj.active.weight_threshold = 1; + obj.owner.weight_threshold = 1; + }); + + db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + { + _gpo.parameters.extensions.value.son_pay_daily_max = 200; + _gpo.parameters.witness_pay_per_block = 0; + + _gpo.parameters.extensions.value.son_btc_account = son_btc_account.get_id(); + if( _gpo.pending_parameters ) + _gpo.pending_parameters->extensions.value.son_btc_account = son_btc_account.get_id(); + }); + + { + // Check that transaction fails if down_ts < last_active_timestamp + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.get_son_btc_account_id(); + op.son_id = son_id_type(0); + op.down_ts = fc::time_point_sec(son_stats_obj->last_active_timestamp - fc::seconds(1)); + + trx.operations.push_back(op); + sign(trx, bob_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + + { + // Check that transaction fails if payer is not son_btc_account. + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = alice_id; + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + + { + // Check that transaction succeeds after getting enough approvals on son_btc_account. + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.get_son_btc_account_id(); + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + + BOOST_CHECK( obj->status == son_status::in_maintenance); + BOOST_CHECK( son_stats_obj->last_down_timestamp == op.down_ts); + } + + { + // Check that transaction fails if report down sent for an in_maintenance SON. + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.get_son_btc_account_id(); + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END()