Merge branch 'feature/SONs-base' into feature/SON-98

This commit is contained in:
Srdjan Obucina 2020-01-20 13:11:23 +01:00
commit ebb6662210
16 changed files with 337 additions and 14 deletions

View file

@ -8,6 +8,7 @@ stages:
build:
stage: build
script:
- git submodule sync
- git submodule update --init --recursive
- cmake .
- make -j$(nproc)

View file

@ -313,6 +313,9 @@ struct get_impacted_account_visitor
void operator()( const son_report_down_operation& op ){
_impacted.insert( op.payer );
}
void operator()( const son_maintenance_operation& op ){
_impacted.insert( op.owner_account );
}
void operator()( const son_wallet_create_operation& op ){
_impacted.insert( op.payer );
}

View file

@ -253,6 +253,7 @@ void database::initialize_evaluators()
register_evaluator<delete_son_evaluator>();
register_evaluator<son_heartbeat_evaluator>();
register_evaluator<son_report_down_evaluator>();
register_evaluator<son_maintenance_evaluator>();
register_evaluator<create_son_wallet_evaluator>();
register_evaluator<update_son_wallet_evaluator>();
register_evaluator<close_son_wallet_evaluator>();

View file

@ -472,9 +472,9 @@ void database::update_active_sons()
ilog( "Active SONs set CHANGED" );
// Expire for current son_wallet_object wallet, if exists
const auto& idx = get_index_type<son_wallet_index>().indices().get<by_id>();
auto obj = idx.rbegin();
if (obj != idx.rend()) {
const auto& idx_swi = get_index_type<son_wallet_index>().indices().get<by_id>();
auto obj = idx_swi.rbegin();
if (obj != idx_swi.rend()) {
modify(*obj, [&, &obj](son_wallet_object &swo) {
swo.expires = head_block_time();
});
@ -486,6 +486,57 @@ void database::update_active_sons()
obj.expires = time_point_sec::maximum();
obj.sons.insert(obj.sons.end(), new_active_sons.begin(), new_active_sons.end());
});
vector<son_info> 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<son_index>().indices().get<by_id>();
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<son_info> 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 ){

View file

@ -300,6 +300,9 @@ struct get_impacted_account_visitor
void operator()( const son_report_down_operation& op ) {
_impacted.insert( op.payer );
}
void operator()( const son_maintenance_operation& op ) {
_impacted.insert( op.owner_account );
}
void operator()( const son_wallet_create_operation& op ) {
_impacted.insert( op.payer );
}

View file

@ -144,6 +144,7 @@ namespace graphene { namespace chain {
son_delete_operation,
son_heartbeat_operation,
son_report_down_operation,
son_maintenance_operation,
son_wallet_create_operation,
son_wallet_update_operation,
son_wallet_close_operation,

View file

@ -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) )
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) )

View file

@ -49,4 +49,13 @@ public:
object_id_type do_apply(const son_report_down_operation& o);
};
class son_maintenance_evaluator : public evaluator<son_maintenance_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

View file

@ -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),

View file

@ -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)

View file

@ -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<son_id_type> 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 )
@ -175,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<son_index>().indices().get<by_id>();
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<son_index>().indices().get<by_id>();
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

View file

@ -190,6 +190,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<chain::son_index>().indices().get<by_id>();
auto son_obj = idx.find( son_id );
if(son_obj == idx.end())
return;
const chain::global_property_object& gpo = d.get_global_properties();
vector<son_id_type> active_son_ids;
active_son_ids.reserve(gpo.active_sons.size());
@ -200,11 +204,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<chain::son_index>().indices().get<by_id>();
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);

View file

@ -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.

View file

@ -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<string, son_id_type> list_active_sons()
{ try {
global_property_object gpo = get_global_properties();
@ -4418,6 +4453,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<string, son_id_type> wallet_api::list_sons(const string& lowerbound, uint32_t limit)
{
return my->_remote_db->lookup_son_accounts(lowerbound, limit);

View file

@ -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<graphene::peerplays_sidechain::sidechain_type, string> 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()

View file

@ -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<son_index>().indices().get<by_account>();
@ -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;
{
@ -723,7 +751,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);
}