From 691468dff0d98a76066557a51a7eefb2a22f9ada Mon Sep 17 00:00:00 2001 From: Roshan Syed Date: Fri, 17 Jan 2020 08:22:26 -0400 Subject: [PATCH 1/3] Updated gitlab CI to sync submodules (#265) --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 188f4a45..530caf2f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,7 @@ stages: build: stage: build script: + - git submodule sync - git submodule update --init --recursive - cmake . - make -j$(nproc) From 5af31dd90db61eb5b617c17138f73f2471e2736f Mon Sep 17 00:00:00 2001 From: satyakoneru Date: Sat, 18 Jan 2020 07:28:13 +1100 Subject: [PATCH 2/3] SON217 - SON Maintenance,Heartbeat state transition changes (#264) * SON217 - SON Maintenance,Heartbeat state transition changes * SON217 - SON Maintenance,Heartbeat state transition changes --- libraries/chain/son_evaluator.cpp | 24 +++++++++++-- .../peerplays_sidechain_plugin.cpp | 8 +++-- tests/tests/son_operations_tests.cpp | 35 ++++++++++++++++++- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/libraries/chain/son_evaluator.cpp b/libraries/chain/son_evaluator.cpp index 619d2e22..fc4802d2 100644 --- a/libraries/chain/son_evaluator.cpp +++ b/libraries/chain/son_evaluator.cpp @@ -123,6 +123,22 @@ object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_operation& auto itr = idx.find(op.son_id); if(itr != idx.end()) { + const global_property_object& gpo = db().get_global_properties(); + vector active_son_ids; + active_son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_son_ids, active_son_ids.end()), + [](const son_info& swi) { + return swi.son_id; + }); + + auto it_son = std::find(active_son_ids.begin(), active_son_ids.end(), op.son_id); + bool is_son_active = true; + + if(it_son == active_son_ids.end()) { + is_son_active = false; + } + if(itr->status == son_status::in_maintenance) { db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) { @@ -130,8 +146,12 @@ object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_operation& sso.last_active_timestamp = op.ts; } ); - db().modify(*itr, [&op](son_object &so) { - so.status = son_status::active; + db().modify(*itr, [&is_son_active](son_object &so) { + if(is_son_active) { + so.status = son_status::active; + } else { + so.status = son_status::inactive; + } }); } else if (itr->status == son_status::active) { db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index e78c78c5..2874605b 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -186,6 +186,10 @@ void peerplays_sidechain_plugin_impl::heartbeat_loop() { chain::database& d = plugin.database(); chain::son_id_type son_id = *(_sons.begin()); + const auto& idx = d.get_index_type().indices().get(); + auto son_obj = idx.find( son_id ); + if(son_obj == idx.end()) + return; const chain::global_property_object& gpo = d.get_global_properties(); vector active_son_ids; active_son_ids.reserve(gpo.active_sons.size()); @@ -196,11 +200,9 @@ void peerplays_sidechain_plugin_impl::heartbeat_loop() }); auto it = std::find(active_son_ids.begin(), active_son_ids.end(), son_id); - if(it != active_son_ids.end()) { + if(it != active_son_ids.end() || son_obj->status == chain::son_status::in_maintenance) { ilog("peerplays_sidechain_plugin: sending heartbeat"); chain::son_heartbeat_operation op; - const auto& idx = d.get_index_type().indices().get(); - auto son_obj = idx.find( son_id ); op.owner_account = son_obj->son_account; op.son_id = son_id; op.ts = fc::time_point::now() + fc::seconds(0); diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp index ad8cf0bd..f6037084 100644 --- a/tests/tests/son_operations_tests.cpp +++ b/tests/tests/son_operations_tests.cpp @@ -723,7 +723,40 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { generate_block(); trx.clear(); BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); - downtime = op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); + downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); + BOOST_CHECK( obj->status == son_status::inactive); + BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + } + + // Modify SON's status to in_maintenance + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + // SON is selected as one of the active SONs + db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + { + son_info son_inf; + son_inf.son_id = son_id_type(0); + _gpo.active_sons.push_back(son_inf); + }); + + { + generate_block(); + // Send Heartbeat for an in_maintenance SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = (db.head_block_time()+fc::seconds(2*db.block_interval())); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, downtime + op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); + downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); BOOST_CHECK( obj->status == son_status::active); BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); } From 11339c17342b51c7a5857e7359ecac5889c485cd Mon Sep 17 00:00:00 2001 From: gladcow Date: Fri, 17 Jan 2020 23:30:45 +0300 Subject: [PATCH 3/3] [SON-202] Implement cli_wallet commands for maintenance mode (#261) * Add stop_son_maintenance CLI call * fix bug with SON activation * son_maintenance_operation * son_maintenance_operation tests * cli test for son maintenance state * start_son_maintenance CLI call * keep maintenance state during active SON set changes --- libraries/app/impacted.cpp | 3 + libraries/chain/db_init.cpp | 1 + libraries/chain/db_maint.cpp | 50 +++++++++++++++ libraries/chain/db_notify.cpp | 3 + .../graphene/chain/protocol/operations.hpp | 3 +- .../include/graphene/chain/protocol/son.hpp | 18 +++++- .../include/graphene/chain/son_evaluator.hpp | 9 +++ .../include/graphene/chain/son_object.hpp | 2 +- libraries/chain/proposal_evaluator.cpp | 4 ++ libraries/chain/son_evaluator.cpp | 27 ++++++++ .../wallet/include/graphene/wallet/wallet.hpp | 18 ++++++ libraries/wallet/wallet.cpp | 45 ++++++++++++++ tests/cli/son.cpp | 61 +++++++++++++++++++ tests/tests/son_operations_tests.cpp | 34 ++++++++++- 14 files changed, 272 insertions(+), 6 deletions(-) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index c8b1122e..0aefe922 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -310,6 +310,9 @@ struct get_impacted_account_visitor void operator()( const son_heartbeat_operation& op ){ _impacted.insert( op.owner_account ); } + void operator()( const son_maintenance_operation& op ){ + _impacted.insert( op.owner_account ); + } void operator()( const sidechain_address_add_operation& op ){ _impacted.insert( op.sidechain_address_account ); } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 2aab032d..5b5f8029 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -250,6 +250,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 7b111fd4..31813724 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -471,6 +471,56 @@ void database::update_active_sons() } else { ilog( "Active SONs set CHANGED" ); // Store new SON info, initiate wallet recreation and transfer of funds + vector sons_to_remove; + // find all cur_active_sons members that is not in new_active_sons + for_each(cur_active_sons.begin(), cur_active_sons.end(), + [&sons_to_remove, &new_active_sons](const son_info& si) + { + if(std::find(new_active_sons.begin(), new_active_sons.end(), si) == + new_active_sons.end()) + { + sons_to_remove.push_back(si); + } + } + ); + const auto& idx = get_index_type().indices().get(); + for( const son_info& si : sons_to_remove ) + { + auto son = idx.find( si.son_id ); + if(son == idx.end()) // SON is deleted already + continue; + // keep maintenance status for nodes becoming inactive + if(son->status == son_status::active) + { + modify( *son, [&]( son_object& obj ){ + obj.status = son_status::inactive; + }); + } + } + vector sons_to_add; + // find all new_active_sons members that is not in cur_active_sons + for_each(new_active_sons.begin(), new_active_sons.end(), + [&sons_to_add, &cur_active_sons](const son_info& si) + { + if(std::find(cur_active_sons.begin(), cur_active_sons.end(), si) == + cur_active_sons.end()) + { + sons_to_add.push_back(si); + } + } + ); + for( const son_info& si : sons_to_add ) + { + auto son = idx.find( si.son_id ); + FC_ASSERT(son != idx.end(), "Invalid SON in active list, id={sonid}.", ("sonid", si.son_id)); + // keep maintenance status for new nodes + if(son->status == son_status::inactive) + { + modify( *son, [&]( son_object& obj ){ + obj.status = son_status::active; + }); + } + } } modify(gpo, [&]( global_property_object& gp ){ diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index d53955a3..c7946906 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -297,6 +297,9 @@ struct get_impacted_account_visitor void operator()( const son_heartbeat_operation& op ) { _impacted.insert( op.owner_account ); } + void operator()( const son_maintenance_operation& op ) { + _impacted.insert( op.owner_account ); + } void operator()( const sidechain_address_add_operation& op ) { _impacted.insert( op.sidechain_address_account ); } diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 646f2e69..07695705 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -145,7 +145,8 @@ namespace graphene { namespace chain { sidechain_address_add_operation, sidechain_address_update_operation, sidechain_address_delete_operation, - son_report_down_operation + son_report_down_operation, + son_maintenance_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 6f4eaa7e..dc11d5be 100644 --- a/libraries/chain/include/graphene/chain/protocol/son.hpp +++ b/libraries/chain/include/graphene/chain/protocol/son.hpp @@ -63,6 +63,7 @@ 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; }; @@ -76,6 +77,18 @@ namespace graphene { namespace chain { share_type calculate_fee(const fee_parameters_type& k)const { return 0; } }; + struct son_maintenance_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + + account_id_type fee_payer()const { return owner_account; } + 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) ) @@ -93,4 +106,7 @@ FC_REFLECT(graphene::chain::son_heartbeat_operation::fee_parameters_type, (fee) 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 +FC_REFLECT(graphene::chain::son_report_down_operation, (fee)(son_id)(payer)(down_ts) ) + +FC_REFLECT(graphene::chain::son_maintenance_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_maintenance_operation, (fee)(son_id)(owner_account) ) diff --git a/libraries/chain/include/graphene/chain/son_evaluator.hpp b/libraries/chain/include/graphene/chain/son_evaluator.hpp index 4615c95b..87554fbc 100644 --- a/libraries/chain/include/graphene/chain/son_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/son_evaluator.hpp @@ -49,4 +49,13 @@ public: object_id_type do_apply(const son_report_down_operation& o); }; +class son_maintenance_evaluator : public evaluator +{ +public: + typedef son_maintenance_operation operation_type; + + void_result do_evaluate(const son_maintenance_operation& o); + object_id_type do_apply(const son_maintenance_operation& o); +}; + } } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_object.hpp b/libraries/chain/include/graphene/chain/son_object.hpp index 8a876bfd..50f7385a 100644 --- a/libraries/chain/include/graphene/chain/son_object.hpp +++ b/libraries/chain/include/graphene/chain/son_object.hpp @@ -97,7 +97,7 @@ namespace graphene { namespace chain { FC_REFLECT_ENUM(graphene::chain::son_status, (inactive)(active)(in_maintenance)(deregistered) ) FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object), - (son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb)(sidechain_public_keys) ) + (son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb)(status)(sidechain_public_keys) ) FC_REFLECT_DERIVED( graphene::chain::son_statistics_object, (graphene::db::object), diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index dc1aba3e..767ca415 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -156,6 +156,10 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_report_down_operation not allowed yet!" ); } + void operator()(const son_maintenance_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_maintenance_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 fc4802d2..34f4daa3 100644 --- a/libraries/chain/son_evaluator.cpp +++ b/libraries/chain/son_evaluator.cpp @@ -195,4 +195,31 @@ object_id_type son_report_down_evaluator::do_apply(const son_report_down_operati return op.son_id; } FC_CAPTURE_AND_RETHROW( (op) ) } +void_result son_maintenance_evaluator::do_evaluate(const son_maintenance_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT(db().get(op.son_id).son_account == op.owner_account); + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + FC_ASSERT( itr != idx.end() ); + // Inactive SONs can't go to maintenance + FC_ASSERT(itr->status == son_status::active || itr->status == son_status::in_maintenance, "Inactive SONs can't go to maintenance"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_maintenance_evaluator::do_apply(const son_maintenance_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, [](son_object &so) { + so.status = son_status::in_maintenance; + }); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + } } // namespace graphene::chain diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index a158587a..aeb9107d 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1350,6 +1350,24 @@ class wallet_api signed_transaction delete_son(string owner_account, bool broadcast = false); + /** Modify status of the SON owned by the given account to maintenance. + * + * @param owner_account the name or id of the account which is owning the SON + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction + */ + signed_transaction start_son_maintenance(string owner_account, + bool broadcast = false); + + /** Modify status of the SON owned by the given account back to active. + * + * @param owner_account the name or id of the account which is owning the SON + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction + */ + signed_transaction stop_son_maintenance(string owner_account, + bool broadcast = false); + /** Lists all SONs in the blockchain. * This returns a list of all account names that own SON, and the associated SON id, * sorted by name. This lists SONs whether they are currently voted in or not. diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index be837940..658587f4 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1939,6 +1939,41 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) } + signed_transaction start_son_maintenance(string owner_account, + bool broadcast) + { try { + son_object son = get_son(owner_account); + + son_maintenance_operation op; + op.owner_account = son.son_account; + op.son_id = son.id; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account) ) } + + signed_transaction stop_son_maintenance(string owner_account, + bool broadcast) + { try { + son_object son = get_son(owner_account); + + son_heartbeat_operation op; + op.owner_account = son.son_account; + op.son_id = son.id; + op.ts = _remote_db->get_dynamic_global_properties().time; // or fc::time_point_sec(fc::time_point::now()) ??? + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account) ) } + map list_active_sons() { try { global_property_object gpo = get_global_properties(); @@ -4403,6 +4438,16 @@ signed_transaction wallet_api::delete_son(string owner_account, return my->delete_son(owner_account, broadcast); } +signed_transaction wallet_api::start_son_maintenance(string owner_account, bool broadcast) +{ + return my->start_son_maintenance(owner_account, broadcast); +} + +signed_transaction wallet_api::stop_son_maintenance(string owner_account, bool broadcast) +{ + return my->stop_son_maintenance(owner_account, broadcast); +} + map wallet_api::list_sons(const string& lowerbound, uint32_t limit) { return my->_remote_db->lookup_son_accounts(lowerbound, limit); diff --git a/tests/cli/son.cpp b/tests/cli/son.cpp index b3b596c7..5f8ad78a 100644 --- a/tests/cli/son.cpp +++ b/tests/cli/son.cpp @@ -658,6 +658,67 @@ BOOST_FIXTURE_TEST_CASE( cli_list_active_sons, cli_fixture ) BOOST_TEST_MESSAGE("SON cli wallet tests for list_active_sons end"); } +BOOST_AUTO_TEST_CASE( maintenance_test ) +{ + BOOST_TEST_MESSAGE("SON maintenance cli wallet tests begin"); + try + { + son_test_helper sth(*this); + + std::string name("sonaccount1"); + + global_property_object gpo; + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count; + + flat_map sidechain_public_keys; + + // create son accounts + for(unsigned int i = 0; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + } + BOOST_CHECK(generate_maintenance_block()); + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 1; i < son_number + 1; i++) + { + con.wallet_api_ptr->vote_for_son("sonaccount" + fc::to_pretty_string(i), name, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + + son_object son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::active); + + // put SON in maintenance mode + con.wallet_api_ptr->start_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is in maintenance + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::in_maintenance); + + // restore SON activity + con.wallet_api_ptr->stop_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is active + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::active); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON maintenance cli wallet tests end"); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp index f6037084..e0e56e19 100644 --- a/tests/tests/son_operations_tests.cpp +++ b/tests/tests/son_operations_tests.cpp @@ -684,6 +684,19 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); trx.clear(); } + + { + // Try to go in maintenance for an inactive SON + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + + 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(); + } generate_block(); const auto& idx = db.get_index_type().indices().get(); @@ -696,17 +709,32 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { auto son_stats_obj = sidx.find( obj->statistics ); BOOST_REQUIRE( son_stats_obj != sidx.end() ); - // Modify SON's status to in_maintenance + // Modify SON's status to active db.modify( *obj, [&]( son_object& _s) { - _s.status = son_status::in_maintenance; + _s.status = son_status::active; }); db.modify( *son_stats_obj, [&]( son_statistics_object& _s) { - _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - fc::hours(1)); + _s.last_down_timestamp = fc::time_point_sec(db.head_block_time()); }); + { + generate_block(); + // Put SON in maintenance + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_CHECK( obj->status == son_status::in_maintenance); + } + uint64_t downtime = 0; {