From 01fb1db6a606bedde25bcd0bd702a10e14cdb569 Mon Sep 17 00:00:00 2001 From: satyakoneru Date: Mon, 6 Jan 2020 23:59:35 +1100 Subject: [PATCH 1/2] 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() From 59a02b1460baa877b18605e1fad86f9d021bc966 Mon Sep 17 00:00:00 2001 From: satyakoneru Date: Tue, 7 Jan 2020 00:06:49 +1100 Subject: [PATCH 2/2] SON206 - Plugin SON Heartbeat changes (#250) * SON206 - Plugin SON Heartbeat changes * SON206 - Plugin SON Heartbeat changes, comment removal * SON206 - Plugin SON Heartbeat changes, stub testing and changes * SON206 - Plugin SON Heartbeat changes, removing debugs prints --- .../include/graphene/chain/son_object.hpp | 4 + .../peerplays_sidechain_plugin.hpp | 1 + .../peerplays_sidechain_plugin.cpp | 89 ++++++++++++++++++- 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/libraries/chain/include/graphene/chain/son_object.hpp b/libraries/chain/include/graphene/chain/son_object.hpp index 11cabc2a..8a876bfd 100644 --- a/libraries/chain/include/graphene/chain/son_object.hpp +++ b/libraries/chain/include/graphene/chain/son_object.hpp @@ -103,4 +103,8 @@ FC_REFLECT_DERIVED( graphene::chain::son_statistics_object, (graphene::db::object), (owner) (txs_signed) + (total_downtime) + (current_interval_downtime) + (last_down_timestamp) + (last_active_timestamp) ) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp index 2f72ae06..e986fe8e 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index a8741cff..0a6b4964 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -23,14 +23,18 @@ class peerplays_sidechain_plugin_impl boost::program_options::options_description& cfg); void plugin_initialize(const boost::program_options::variables_map& options); void plugin_startup(); - -private: + void schedule_heartbeat_loop(); + void heartbeat_loop(); + private: peerplays_sidechain_plugin& plugin; bool config_ready_son; bool config_ready_bitcoin; std::unique_ptr net_manager; + std::map _private_keys; + std::set _sons; + fc::future _heartbeat_task; }; peerplays_sidechain_plugin_impl::peerplays_sidechain_plugin_impl(peerplays_sidechain_plugin& _plugin) : @@ -43,6 +47,14 @@ peerplays_sidechain_plugin_impl::peerplays_sidechain_plugin_impl(peerplays_sidec peerplays_sidechain_plugin_impl::~peerplays_sidechain_plugin_impl() { + try { + if( _heartbeat_task.valid() ) + _heartbeat_task.cancel_and_wait(__FUNCTION__); + } catch(fc::canceled_exception&) { + //Expected exception. Move along. + } catch(fc::exception& e) { + edump((e.to_detail_string())); + } } void peerplays_sidechain_plugin_impl::plugin_set_program_options( @@ -74,6 +86,31 @@ void peerplays_sidechain_plugin_impl::plugin_initialize(const boost::program_opt { config_ready_son = options.count( "son-id" ) && options.count( "peerplays-private-key" ); if (config_ready_son) { + LOAD_VALUE_SET(options, "son-id", _sons, chain::son_id_type) + if( options.count("peerplays-private-key") ) + { + const std::vector key_id_to_wif_pair_strings = options["peerplays-private-key"].as>(); + for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings) + { + auto key_id_to_wif_pair = graphene::app::dejsonify >(key_id_to_wif_pair_string, 5); + ilog("Public Key: ${public}", ("public", key_id_to_wif_pair.first)); + fc::optional private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second); + if (!private_key) + { + // the key isn't in WIF format; see if they are still passing the old native private key format. This is + // just here to ease the transition, can be removed soon + try + { + private_key = fc::variant(key_id_to_wif_pair.second, 2).as(1); + } + catch (const fc::exception&) + { + FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second)); + } + } + _private_keys[key_id_to_wif_pair.first] = *private_key; + } + } } else { wlog("Haven't set up SON parameters"); throw; @@ -117,11 +154,59 @@ void peerplays_sidechain_plugin_impl::plugin_startup() ilog("Bitcoin sidechain handler running"); } + if( !_sons.empty() && !_private_keys.empty() ) + { + ilog("Starting heartbeats for ${n} sons.", ("n", _sons.size())); + schedule_heartbeat_loop(); + } else + elog("No sons configured! Please add SON IDs and private keys to configuration."); + //if (config_ready_ethereum) { // ilog("Ethereum sidechain handler running"); //} } +void peerplays_sidechain_plugin_impl::schedule_heartbeat_loop() +{ + fc::time_point now = fc::time_point::now(); + int64_t time_to_next_heartbeat = 180000000; + + fc::time_point next_wakeup( now + fc::microseconds( time_to_next_heartbeat ) ); + + _heartbeat_task = fc::schedule([this]{heartbeat_loop();}, + next_wakeup, "SON Heartbeat Production"); +} + +void peerplays_sidechain_plugin_impl::heartbeat_loop() +{ + chain::database& d = plugin.database(); + chain::son_id_type son_id = *(_sons.begin()); + const chain::global_property_object& gpo = d.get_global_properties(); + auto it = std::find(gpo.active_sons.begin(), gpo.active_sons.end(), son_id); + if(it != gpo.active_sons.end()) { + 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); + chain::signed_transaction trx = d.create_signed_transaction(_private_keys.begin()->second, op); + fc::future fut = fc::async( [&](){ + try { + d.push_transaction(trx); + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch(fc::exception e){ + ilog("peerplays_sidechain_plugin: sending heartbeat failed with exception ${e}",("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + schedule_heartbeat_loop(); +} + } // end namespace detail peerplays_sidechain_plugin::peerplays_sidechain_plugin() :