diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 997a3a38..66dc2939 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -307,6 +307,9 @@ struct get_impacted_account_visitor void operator()( const son_delete_operation& op ){ _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& result ) diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 63b0fce8..c71b2930 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -294,6 +294,9 @@ struct get_impacted_account_visitor void operator()( const son_delete_operation& op ) { _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& result ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index cbad1e05..a0e72094 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -139,7 +139,8 @@ namespace graphene { namespace chain { sweeps_vesting_claim_operation, son_create_operation, son_update_operation, - son_delete_operation + son_delete_operation, + son_heartbeat_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 93b8a0a4..08e74a2d 100644 --- a/libraries/chain/include/graphene/chain/protocol/son.hpp +++ b/libraries/chain/include/graphene/chain/protocol/son.hpp @@ -47,6 +47,19 @@ namespace graphene { namespace chain { 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 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)(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 diff --git a/libraries/chain/include/graphene/chain/son_evaluator.hpp b/libraries/chain/include/graphene/chain/son_evaluator.hpp index bb6a1820..6b82f5e5 100644 --- a/libraries/chain/include/graphene/chain/son_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/son_evaluator.hpp @@ -31,4 +31,13 @@ public: void_result do_apply(const son_delete_operation& o); }; +class son_heartbeat_evaluator : public 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 diff --git a/libraries/chain/include/graphene/chain/son_object.hpp b/libraries/chain/include/graphene/chain/son_object.hpp index 77316a4d..5db51ac0 100644 --- a/libraries/chain/include/graphene/chain/son_object.hpp +++ b/libraries/chain/include/graphene/chain/son_object.hpp @@ -10,7 +10,8 @@ namespace graphene { namespace chain { { inactive, active, - in_maintenance + in_maintenance, + deregistered }; /** * @class son_statistics_object @@ -31,8 +32,12 @@ namespace graphene { namespace chain { uint64_t txs_signed = 0; // Total Downtime barring the current down time in seconds, used for stats to present to user 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 fc::time_point_sec last_down_timestamp; + // Last Active heartbeat timestamp + fc::time_point_sec last_active_timestamp; }; /** diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index d377e0d8..b50d4b82 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -148,6 +148,10 @@ struct proposal_operation_hardfork_visitor 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 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 ad581348..4300bdbb 100644 --- a/libraries/chain/son_evaluator.cpp +++ b/libraries/chain/son_evaluator.cpp @@ -94,4 +94,51 @@ void_result delete_son_evaluator::do_apply(const son_delete_operation& op) return void_result(); } 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().indices().get(); + 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().indices().get(); + 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