Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac46c2343a | ||
|
|
20e598276d |
10 changed files with 176 additions and 3 deletions
|
|
@ -307,6 +307,9 @@ struct get_impacted_account_visitor
|
||||||
void operator()( const son_delete_operation& op ){
|
void operator()( const son_delete_operation& op ){
|
||||||
_impacted.insert( op.owner_account );
|
_impacted.insert( op.owner_account );
|
||||||
}
|
}
|
||||||
|
void operator()( const son_heartbeat_operation& op ){
|
||||||
|
_impacted.insert( op.owner_account );
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
|
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ void database::initialize_evaluators()
|
||||||
register_evaluator<create_son_evaluator>();
|
register_evaluator<create_son_evaluator>();
|
||||||
register_evaluator<update_son_evaluator>();
|
register_evaluator<update_son_evaluator>();
|
||||||
register_evaluator<delete_son_evaluator>();
|
register_evaluator<delete_son_evaluator>();
|
||||||
|
register_evaluator<son_heartbeat_evaluator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void database::initialize_indexes()
|
void database::initialize_indexes()
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,9 @@ struct get_impacted_account_visitor
|
||||||
void operator()( const son_delete_operation& op ) {
|
void operator()( const son_delete_operation& op ) {
|
||||||
_impacted.insert( op.owner_account );
|
_impacted.insert( op.owner_account );
|
||||||
}
|
}
|
||||||
|
void operator()( const son_heartbeat_operation& op ) {
|
||||||
|
_impacted.insert( op.owner_account );
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
|
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,8 @@ namespace graphene { namespace chain {
|
||||||
sweeps_vesting_claim_operation,
|
sweeps_vesting_claim_operation,
|
||||||
son_create_operation,
|
son_create_operation,
|
||||||
son_update_operation,
|
son_update_operation,
|
||||||
son_delete_operation
|
son_delete_operation,
|
||||||
|
son_heartbeat_operation
|
||||||
> operation;
|
> operation;
|
||||||
|
|
||||||
/// @} // operations group
|
/// @} // operations group
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,19 @@ namespace graphene { namespace chain {
|
||||||
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
|
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct son_heartbeat_operation : public base_operation
|
||||||
|
{
|
||||||
|
struct fee_parameters_type { uint64_t fee = 0; };
|
||||||
|
|
||||||
|
asset fee;
|
||||||
|
son_id_type son_id;
|
||||||
|
account_id_type owner_account;
|
||||||
|
time_point_sec ts;
|
||||||
|
|
||||||
|
account_id_type fee_payer()const { return owner_account; }
|
||||||
|
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
} } // namespace graphene::chain
|
} } // namespace graphene::chain
|
||||||
|
|
||||||
FC_REFLECT(graphene::chain::son_create_operation::fee_parameters_type, (fee) )
|
FC_REFLECT(graphene::chain::son_create_operation::fee_parameters_type, (fee) )
|
||||||
|
|
@ -59,3 +72,6 @@ FC_REFLECT(graphene::chain::son_update_operation, (fee)(son_id)(owner_account)(n
|
||||||
|
|
||||||
FC_REFLECT(graphene::chain::son_delete_operation::fee_parameters_type, (fee) )
|
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_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) )
|
||||||
|
|
@ -31,4 +31,13 @@ public:
|
||||||
void_result do_apply(const son_delete_operation& o);
|
void_result do_apply(const son_delete_operation& o);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class son_heartbeat_evaluator : public evaluator<son_heartbeat_evaluator>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef son_heartbeat_operation operation_type;
|
||||||
|
|
||||||
|
void_result do_evaluate(const son_heartbeat_operation& o);
|
||||||
|
object_id_type do_apply(const son_heartbeat_operation& o);
|
||||||
|
};
|
||||||
|
|
||||||
} } // namespace graphene::chain
|
} } // namespace graphene::chain
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ namespace graphene { namespace chain {
|
||||||
{
|
{
|
||||||
inactive,
|
inactive,
|
||||||
active,
|
active,
|
||||||
in_maintenance
|
in_maintenance,
|
||||||
|
deregistered
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* @class son_statistics_object
|
* @class son_statistics_object
|
||||||
|
|
@ -31,8 +32,12 @@ namespace graphene { namespace chain {
|
||||||
uint64_t txs_signed = 0;
|
uint64_t txs_signed = 0;
|
||||||
// Total Downtime barring the current down time in seconds, used for stats to present to user
|
// Total Downtime barring the current down time in seconds, used for stats to present to user
|
||||||
uint64_t total_downtime = 0;
|
uint64_t total_downtime = 0;
|
||||||
|
// Current Interval Downtime since last maintenance
|
||||||
|
uint64_t current_interval_downtime = 0;
|
||||||
// Down timestamp, if son status is in_maintenance use this
|
// Down timestamp, if son status is in_maintenance use this
|
||||||
fc::time_point_sec last_down_timestamp;
|
fc::time_point_sec last_down_timestamp;
|
||||||
|
// Last Active heartbeat timestamp
|
||||||
|
fc::time_point_sec last_active_timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,7 +92,7 @@ namespace graphene { namespace chain {
|
||||||
using son_stats_index = generic_index<son_statistics_object, son_stats_multi_index_type>;
|
using son_stats_index = generic_index<son_statistics_object, son_stats_multi_index_type>;
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
||||||
FC_REFLECT_ENUM(graphene::chain::son_status, (inactive)(active)(in_maintenance) )
|
FC_REFLECT_ENUM(graphene::chain::son_status, (inactive)(active)(in_maintenance)(deregistered) )
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object),
|
FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object),
|
||||||
(son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb) )
|
(son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb) )
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,10 @@ struct proposal_operation_hardfork_visitor
|
||||||
FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_delete_operation not allowed yet!" );
|
FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_delete_operation not allowed yet!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(const son_heartbeat_operation &v) const {
|
||||||
|
FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_heartbeat_operation not allowed yet!" );
|
||||||
|
}
|
||||||
|
|
||||||
// loop and self visit in proposals
|
// loop and self visit in proposals
|
||||||
void operator()(const proposal_create_operation &v) const {
|
void operator()(const proposal_create_operation &v) const {
|
||||||
for (const op_wrapper &op : v.proposed_ops)
|
for (const op_wrapper &op : v.proposed_ops)
|
||||||
|
|
|
||||||
|
|
@ -94,4 +94,51 @@ void_result delete_son_evaluator::do_apply(const son_delete_operation& op)
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
|
void_result son_heartbeat_evaluator::do_evaluate(const son_heartbeat_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>();
|
||||||
|
FC_ASSERT( idx.find(op.son_id) != idx.end() );
|
||||||
|
auto itr = idx.find(op.son_id);
|
||||||
|
auto stats = itr->statistics( db() );
|
||||||
|
// Inactive SONs need not send heartbeats
|
||||||
|
FC_ASSERT(itr->status == son_status::active || itr->status == son_status::in_maintenance, "Inactive SONs need not send heartbeats");
|
||||||
|
// Account for network delays
|
||||||
|
fc::time_point_sec min_ts = db().head_block_time() - fc::seconds(5 * db().block_interval());
|
||||||
|
// Account for server ntp sync difference
|
||||||
|
fc::time_point_sec max_ts = db().head_block_time() + fc::seconds(2 * db().block_interval());
|
||||||
|
FC_ASSERT(op.ts > stats.last_active_timestamp, "Heartbeat sent without waiting minimum time");
|
||||||
|
FC_ASSERT(op.ts > stats.last_down_timestamp, "Heartbeat sent is invalid can't be <= last down timestamp");
|
||||||
|
FC_ASSERT(op.ts >= min_ts, "Heartbeat ts is behind the min threshold");
|
||||||
|
FC_ASSERT(op.ts <= max_ts, "Heartbeat ts is above the max threshold");
|
||||||
|
return void_result();
|
||||||
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
|
object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_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::in_maintenance) {
|
||||||
|
db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso )
|
||||||
|
{
|
||||||
|
sso.current_interval_downtime += op.ts.sec_since_epoch() - sso.last_down_timestamp.sec_since_epoch();
|
||||||
|
sso.last_active_timestamp = op.ts;
|
||||||
|
} );
|
||||||
|
|
||||||
|
db().modify(*itr, [&op](son_object &so) {
|
||||||
|
so.status = son_status::active;
|
||||||
|
});
|
||||||
|
} else if (itr->status == son_status::active) {
|
||||||
|
db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso )
|
||||||
|
{
|
||||||
|
sso.last_active_timestamp = op.ts;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return op.son_id;
|
||||||
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
} } // namespace graphene::chain
|
} } // namespace graphene::chain
|
||||||
|
|
|
||||||
|
|
@ -652,4 +652,88 @@ BOOST_AUTO_TEST_CASE( son_witness_proposal_test )
|
||||||
generate_block();
|
generate_block();
|
||||||
} FC_LOG_AND_RETHROW()
|
} FC_LOG_AND_RETHROW()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE( son_heartbeat_test ) {
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
INVOKE(create_son_test);
|
||||||
|
GET_ACTOR(alice);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Send Heartbeat for an inactive SON
|
||||||
|
son_heartbeat_operation op;
|
||||||
|
op.owner_account = alice_id;
|
||||||
|
op.son_id = son_id_type(0);
|
||||||
|
op.ts = fc::time_point::now();
|
||||||
|
|
||||||
|
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>();
|
||||||
|
BOOST_REQUIRE( idx.size() == 1 );
|
||||||
|
auto obj = idx.find( alice_id );
|
||||||
|
BOOST_REQUIRE( obj != idx.end() );
|
||||||
|
|
||||||
|
const auto& sidx = db.get_index_type<son_stats_index>().indices().get<by_id>();
|
||||||
|
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 in_maintenance
|
||||||
|
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() - fc::hours(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
uint64_t downtime = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
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, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
generate_block();
|
||||||
|
// Send Heartbeat for an active 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);
|
||||||
|
BOOST_CHECK( obj->status == son_status::active);
|
||||||
|
BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts);
|
||||||
|
}
|
||||||
|
} FC_LOG_AND_RETHROW()
|
||||||
} BOOST_AUTO_TEST_SUITE_END()
|
} BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue