diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index c6b4564c..7625178a 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -427,34 +427,6 @@ signed_block database::_generate_block( _pending_tx_session.reset(); _pending_tx_session = _undo_db.start_undo_session(); - if( head_block_time() > HARDFORK_SON_TIME ) - { - // Approve proposals raised by me in previous schedule or before - process_son_proposals( witness_obj, block_signing_private_key ); - // Check for new SON Deregistration Proposals to be raised - std::set sons_to_be_dereg = get_sons_to_be_deregistered(); - if(sons_to_be_dereg.size() > 0) - { - // We shouldn't raise proposals for the SONs for which a de-reg - // proposal is already raised. - std::set sons_being_dereg = get_sons_being_deregistered(); - for( auto& son : sons_to_be_dereg) - { - // New SON to be deregistered - if(sons_being_dereg.find(son) == sons_being_dereg.end()) - { - // Creating the de-reg proposal - auto op = create_son_deregister_proposal(son, witness_obj); - if(op.valid()) - { - // Signing and pushing into the txs to be included in the block - _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( block_signing_private_key, *op ) ); - } - } - } - } - } - uint64_t postponed_tx_count = 0; // pop pending state (reset to head block state) for( const processed_transaction& tx : _pending_tx ) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 9749d642..72e0327f 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -170,7 +170,7 @@ std::set database::get_sons_to_be_deregistered() // TODO : We need to add a function that returns if we can deregister SON // i.e. with introduction of PW code, we have to make a decision if the SON // is needed for release of funds from the PW - if(head_block_time() - stats.last_down_timestamp >= fc::hours(SON_DEREGISTER_TIME)) + if(head_block_time() - stats.last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time())) { ret.insert(son.id); } @@ -194,14 +194,14 @@ std::set database::get_sons_being_reported_down() return ret; } -fc::optional database::create_son_deregister_proposal(const son_id_type& son_id, const witness_object& current_witness ) +fc::optional database::create_son_deregister_proposal( son_id_type son_id, account_id_type paying_son ) { son_delete_operation son_dereg_op; - son_dereg_op.payer = current_witness.witness_account; + son_dereg_op.payer = GRAPHENE_SON_ACCOUNT; son_dereg_op.son_id = son_id; proposal_create_operation proposal_op; - proposal_op.fee_paying_account = current_witness.witness_account; + proposal_op.fee_paying_account = paying_son; proposal_op.proposed_ops.push_back( op_wrapper( son_dereg_op ) ); uint32_t lifetime = ( get_global_properties().parameters.block_interval * get_global_properties().active_witnesses.size() ) * 3; proposal_op.expiration_time = time_point_sec( head_block_time().sec_since_epoch() + lifetime ); @@ -222,31 +222,6 @@ signed_transaction database::create_signed_transaction( const fc::ecc::private_k return processed_trx; } -void database::process_son_proposals( const witness_object& current_witness, const fc::ecc::private_key& private_key ) -{ - const auto& son_proposal_idx = get_index_type().indices().get< by_id >(); - const auto& proposal_idx = get_index_type().indices().get< by_id >(); - - auto approve_proposal = [ & ]( const proposal_id_type& id ) - { - proposal_update_operation puo; - puo.fee_paying_account = current_witness.witness_account; - puo.proposal = id; - puo.active_approvals_to_add = { current_witness.witness_account }; - _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( private_key, puo ) ); - }; - - for( auto& son_proposal : son_proposal_idx ) - { - const auto& proposal = proposal_idx.find( son_proposal.proposal_id ); - FC_ASSERT( proposal != proposal_idx.end() ); - if( proposal->proposer == current_witness.witness_account) - { - approve_proposal( proposal->id ); - } - } -} - void database::remove_son_proposal( const proposal_object& proposal ) { try { if( proposal.proposed_transaction.operations.size() == 1 && @@ -262,13 +237,13 @@ void database::remove_son_proposal( const proposal_object& proposal ) } } FC_CAPTURE_AND_RETHROW( (proposal) ) } -bool database::is_son_dereg_valid( const son_id_type& son_id ) +bool database::is_son_dereg_valid( son_id_type son_id ) { const auto& son_idx = get_index_type().indices().get< by_id >(); auto son = son_idx.find( son_id ); FC_ASSERT( son != son_idx.end() ); bool ret = ( son->status == son_status::in_maintenance && - (head_block_time() - son->statistics(*this).last_down_timestamp >= fc::hours(SON_DEREGISTER_TIME))); + (head_block_time() - son->statistics(*this).last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time()))); return ret; } diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index e44d2fcf..65f4ab47 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -234,7 +234,9 @@ #define MIN_SON_MEMBER_COUNT 15 #define SON_VESTING_AMOUNT (50*GRAPHENE_BLOCKCHAIN_PRECISION) // 50 PPY #define SON_VESTING_PERIOD (60*60*24*2) // 2 days -#define SON_DEREGISTER_TIME (12) // 12 Hours +#define SON_DEREGISTER_TIME (60*60*12) // 12 Hours in seconds +#define SON_HEARTBEAT_FREQUENCY (60*3) // 3 minutes in seconds +#define SON_DOWN_TIME (60*3*2) // 2 Heartbeats in seconds #define MIN_SON_PAY_DAILY_MAX (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(200)) #define SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE (2*GRAPHENE_1_PERCENT) #define SWEEPS_DEFAULT_DISTRIBUTION_ASSET (graphene::chain::asset_id_type(0)) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index ea2ccffd..5b8952df 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -302,11 +302,10 @@ namespace graphene { namespace chain { std::set get_sons_being_deregistered(); std::set get_sons_being_reported_down(); std::set get_sons_to_be_deregistered(); - fc::optional create_son_deregister_proposal(const son_id_type& son_id, const witness_object& current_witness ); + fc::optional create_son_deregister_proposal( son_id_type son_id, account_id_type paying_son ); signed_transaction create_signed_transaction( const fc::ecc::private_key& signing_private_key, const operation& op ); - void process_son_proposals( const witness_object& current_witness, const fc::ecc::private_key& private_key ); void remove_son_proposal( const proposal_object& proposal ); - bool is_son_dereg_valid( const son_id_type& son_id ); + bool is_son_dereg_valid( son_id_type son_id ); time_point_sec head_block_time()const; uint32_t head_block_num()const; diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index c62274ba..cd870a2e 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -46,6 +46,9 @@ namespace graphene { namespace chain { optional < uint32_t > son_vesting_amount; optional < uint32_t > son_vesting_period; optional < uint32_t > son_pay_daily_max; + optional < uint32_t > son_deregister_time; + optional < uint32_t > son_heartbeat_frequency; + optional < uint32_t > son_down_time; }; struct chain_parameters @@ -138,6 +141,15 @@ 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 uint16_t son_deregister_time()const { + return extensions.value.son_deregister_time.valid() ? *extensions.value.son_deregister_time : SON_DEREGISTER_TIME; + } + inline uint16_t son_heartbeat_frequency()const { + return extensions.value.son_heartbeat_frequency.valid() ? *extensions.value.son_heartbeat_frequency : SON_HEARTBEAT_FREQUENCY; + } + inline uint16_t son_down_time()const { + return extensions.value.son_down_time.valid() ? *extensions.value.son_down_time : SON_DOWN_TIME; + } }; } } // graphene::chain @@ -155,6 +167,9 @@ FC_REFLECT( graphene::chain::parameter_extension, (son_vesting_amount) (son_vesting_period) (son_pay_daily_max) + (son_deregister_time) + (son_heartbeat_frequency) + (son_down_time) ) FC_REFLECT( graphene::chain::chain_parameters, diff --git a/libraries/chain/son_evaluator.cpp b/libraries/chain/son_evaluator.cpp index 0adfa778..f4a7548a 100644 --- a/libraries/chain/son_evaluator.cpp +++ b/libraries/chain/son_evaluator.cpp @@ -69,8 +69,8 @@ void_result delete_son_evaluator::do_evaluate(const son_delete_operation& op) // Get the current block witness signatory witness_id_type wit_id = db().get_scheduled_witness(1); const witness_object& current_witness = wit_id(db()); - // Either owner can remove or witness - FC_ASSERT(db().get(op.son_id).son_account == op.owner_account || (db().is_son_dereg_valid(op.son_id) && op.payer == current_witness.witness_account)); + // Either owner can remove or consensus son account + FC_ASSERT(op.payer == db().get(op.son_id).son_account || (db().is_son_dereg_valid(op.son_id) && op.payer == GRAPHENE_SON_ACCOUNT)); const auto& idx = db().get_index_type().indices().get(); FC_ASSERT( idx.find(op.son_id) != idx.end() ); return void_result(); diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index ef092994..517e3c7c 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -45,6 +45,7 @@ class peerplays_sidechain_plugin_impl void schedule_heartbeat_loop(); void heartbeat_loop(); void create_son_down_proposals(); + void create_son_deregister_proposals(); void recreate_primary_wallet(); void process_deposits(); void process_withdrawals(); @@ -285,9 +286,9 @@ fc::ecc::private_key peerplays_sidechain_plugin_impl::get_private_key(chain::pub void peerplays_sidechain_plugin_impl::schedule_heartbeat_loop() { fc::time_point now = fc::time_point::now(); - int64_t time_to_next_heartbeat = 180000000; + int64_t time_to_next_heartbeat = plugin.database().get_global_properties().parameters.son_heartbeat_frequency(); - fc::time_point next_wakeup( now + fc::microseconds( time_to_next_heartbeat ) ); + fc::time_point next_wakeup( now + fc::seconds( time_to_next_heartbeat ) ); _heartbeat_task = fc::schedule([this]{heartbeat_loop();}, next_wakeup, "SON Heartbeat Production"); @@ -323,6 +324,47 @@ void peerplays_sidechain_plugin_impl::heartbeat_loop() } } +void peerplays_sidechain_plugin_impl::create_son_deregister_proposals() +{ + chain::database& d = plugin.database(); + std::set sons_to_be_dereg = d.get_sons_to_be_deregistered(); + chain::son_id_type my_son_id = get_current_son_id(); + + if(sons_to_be_dereg.size() > 0) + { + // We shouldn't raise proposals for the SONs for which a de-reg + // proposal is already raised. + std::set sons_being_dereg = d.get_sons_being_deregistered(); + for( auto& son : sons_to_be_dereg) + { + // New SON to be deregistered + if(sons_being_dereg.find(son) == sons_being_dereg.end() && my_son_id != son) + { + // Creating the de-reg proposal + auto op = d.create_son_deregister_proposal(son, get_son_object(my_son_id).son_account); + if(op.valid()) + { + // Signing and pushing into the txs to be included in the block + ilog("peerplays_sidechain_plugin: sending son deregister proposal for ${p} from ${s}", ("p", son) ("s", my_son_id)); + chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(get_son_object(my_son_id).signing_key), *op); + fc::future fut = fc::async( [&](){ + try { + d.push_transaction(trx, database::validation_steps::skip_block_size_check); + if(plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch(fc::exception e){ + ilog("peerplays_sidechain_plugin_impl: sending son dereg proposal failed with exception ${e}",("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + } + } + } +} + void peerplays_sidechain_plugin_impl::create_son_down_proposals() { auto create_son_down_proposal = [&](chain::son_id_type son_id, fc::time_point_sec last_active_ts) { @@ -356,9 +398,9 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals() auto stats = son_obj->statistics(d); fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; fc::time_point_sec last_active_ts = ((stats.last_active_timestamp > last_maintenance_time) ? stats.last_active_timestamp : last_maintenance_time); - int64_t down_threshold = 2*180000000; + int64_t down_threshold = gpo.parameters.son_down_time(); if(((son_obj->status == chain::son_status::active) || (son_obj->status == chain::son_status::request_maintenance)) && - ((fc::time_point::now() - last_active_ts) > fc::microseconds(down_threshold))) { + ((fc::time_point::now() - last_active_ts) > fc::seconds(down_threshold))) { ilog("peerplays_sidechain_plugin: sending son down proposal for ${t} from ${s}",("t",std::string(object_id_type(son_obj->id)))("s",std::string(object_id_type(my_son_id)))); chain::proposal_create_operation op = create_son_down_proposal(son_inf.son_id, last_active_ts); chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(get_son_object(my_son_id).signing_key), op); @@ -412,6 +454,8 @@ void peerplays_sidechain_plugin_impl::on_applied_block( const signed_block& b ) create_son_down_proposals(); + create_son_deregister_proposals(); + recreate_primary_wallet(); process_deposits(); @@ -467,6 +511,12 @@ void peerplays_sidechain_plugin_impl::on_new_objects(const vectorproposed_transaction.operations.size() == 1 + && proposal->proposed_transaction.operations[0].which() == chain::operation::tag::value) { + approve_proposal( son_id, proposal->id ); + continue; + } + if(proposal->proposed_transaction.operations.size() == 1 && proposal->proposed_transaction.operations[0].which() == chain::operation::tag::value) { approve_proposal( son_id, proposal->id ); diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp index 3925f02b..13e3cf1f 100644 --- a/tests/tests/son_operations_tests.cpp +++ b/tests/tests/son_operations_tests.cpp @@ -184,6 +184,71 @@ catch (fc::exception &e) { throw; } } +BOOST_AUTO_TEST_CASE( delete_son_test_with_consensus_account ) { +try { + INVOKE(create_son_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + 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() ); + + // Modify SON's status to active + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + db.modify( *son_stats_obj, [&]( son_statistics_object& _s) + { + _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time()); + }); + + auto deposit_vesting = db.get(vesting_balance_id_type(0)); + auto now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // cant withdraw + + { + trx.clear(); + son_delete_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.payer = GRAPHENE_SON_ACCOUNT; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_REQUIRE( idx.size() == 0 ); + + deposit_vesting = db.get(vesting_balance_id_type(0)); + BOOST_CHECK_EQUAL(deposit_vesting.policy.get().vesting_cliff_seconds, + db.get_global_properties().parameters.son_vesting_period()); // in linear policy + + now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // but still cant withdraw + + generate_blocks(now + fc::seconds(db.get_global_properties().parameters.son_vesting_period())); + generate_block(); + + deposit_vesting = db.get(vesting_balance_id_type(0)); + now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), true); // after 2 days withdraw is allowed +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} } + BOOST_AUTO_TEST_CASE( update_delete_not_own ) { // fee payer needs to be the son object owner try { @@ -458,212 +523,6 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) } -BOOST_AUTO_TEST_CASE( son_witness_proposal_test ) -{ - try - { - const dynamic_global_property_object& dpo = db.get_dynamic_global_properties(); - generate_blocks(HARDFORK_SON_TIME); - generate_block(); - generate_block(); - set_expiration(db, trx); - - ACTORS((alice)(bob)); - - upgrade_to_lifetime_member(alice); - upgrade_to_lifetime_member(bob); - - transfer( committee_account, alice_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); - transfer( committee_account, bob_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); - - set_expiration(db, trx); - generate_block(); - // Now create SONs - std::string test_url1 = "https://create_son_test1"; - std::string test_url2 = "https://create_son_test2"; - - // create deposit vesting - vesting_balance_id_type deposit1; - { - vesting_balance_create_operation op; - op.creator = alice_id; - op.owner = alice_id; - op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::son; - op.policy = dormant_vesting_policy_initializer {}; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - deposit1 = ptx.operation_results[0].get(); - } - - // create payment vesting - vesting_balance_id_type payment1; - { - vesting_balance_create_operation op; - op.creator = alice_id; - op.owner = alice_id; - op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::normal; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - payment1 = ptx.operation_results[0].get(); - } - - // create deposit vesting - vesting_balance_id_type deposit2; - { - vesting_balance_create_operation op; - op.creator = bob_id; - op.owner = bob_id; - op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::son; - op.policy = dormant_vesting_policy_initializer {}; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - deposit2 = ptx.operation_results[0].get(); - } - - // create payment vesting - vesting_balance_id_type payment2; - { - vesting_balance_create_operation op; - op.creator = bob_id; - op.owner = bob_id; - op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::normal; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - payment2 = ptx.operation_results[0].get(); - } - - // alice becomes son - { - son_create_operation op; - op.owner_account = alice_id; - op.url = test_url1; - op.deposit = deposit1; - op.pay_vb = payment1; - op.fee = asset(0); - op.signing_key = alice_public_key; - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - // bob becomes son - { - son_create_operation op; - op.owner_account = bob_id; - op.url = test_url2; - op.deposit = deposit2; - op.pay_vb = payment2; - op.fee = asset(0); - op.signing_key = bob_public_key; - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - sign(trx, bob_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - generate_block(); - // Check if SONs are created properly - const auto& idx = db.get_index_type().indices().get(); - BOOST_REQUIRE( idx.size() == 2 ); - // Alice's SON - auto obj1 = idx.find( alice_id ); - BOOST_REQUIRE( obj1 != idx.end() ); - BOOST_CHECK( obj1->url == test_url1 ); - BOOST_CHECK( obj1->signing_key == alice_public_key ); - BOOST_CHECK( obj1->deposit.instance == deposit1.instance.value ); - BOOST_CHECK( obj1->pay_vb.instance == payment1.instance.value ); - // Bob's SON - auto obj2 = idx.find( bob_id ); - BOOST_REQUIRE( obj2 != idx.end() ); - BOOST_CHECK( obj2->url == test_url2 ); - BOOST_CHECK( obj2->signing_key == bob_public_key ); - BOOST_CHECK( obj2->deposit.instance == deposit2.instance.value ); - BOOST_CHECK( obj2->pay_vb.instance == payment2.instance.value ); - // Get the statistics object for the SONs - const auto& sidx = db.get_index_type().indices().get(); - BOOST_REQUIRE( sidx.size() == 2 ); - auto son_stats_obj1 = sidx.find( obj1->statistics ); - auto son_stats_obj2 = sidx.find( obj2->statistics ); - BOOST_REQUIRE( son_stats_obj1 != sidx.end() ); - BOOST_REQUIRE( son_stats_obj2 != sidx.end() ); - - - // Modify SON's status to in_maintenance - db.modify( *obj1, [&]( son_object& _s) - { - _s.status = son_status::in_maintenance; - }); - - // Modify the Alice's SON down timestamp to now-12 hours - db.modify( *son_stats_obj1, [&]( son_statistics_object& _s) - { - _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - fc::hours(12)); - }); - - // Modify SON's status to in_maintenance - db.modify( *obj2, [&]( son_object& _s) - { - _s.status = son_status::in_maintenance; - }); - - // Modify the Bob's SON down timestamp to now-12 hours - db.modify( *son_stats_obj2, [&]( son_statistics_object& _s) - { - _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - fc::hours(12)); - }); - - const auto& son_proposal_idx = db.get_index_type().indices().get(); - const auto& proposal_idx = db.get_index_type().indices().get(); - - BOOST_CHECK( son_proposal_idx.size() == 0 && proposal_idx.size() == 0 ); - - generate_block(); - witness_id_type proposal_initiator = dpo.current_witness; - - BOOST_CHECK( son_proposal_idx.size() == 2 && proposal_idx.size() == 2 ); - - for(size_t i = 0 ; i < 3 * db.get_global_properties().active_witnesses.size() ; i++ ) - { - generate_block(); - if( dpo.current_witness != proposal_initiator) - { - BOOST_CHECK( son_proposal_idx.size() == 2 && proposal_idx.size() == 2 ); - } - else - { - break; - } - } - BOOST_CHECK( son_proposal_idx.size() == 0 && proposal_idx.size() == 0 ); - BOOST_REQUIRE( idx.size() == 0 ); - generate_block(); - } FC_LOG_AND_RETHROW() - -} - BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { try