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

This commit is contained in:
Srdjan Obucina 2020-01-13 10:02:01 +01:00
commit 0e47eeccf2
14 changed files with 346 additions and 4 deletions

View file

@ -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<account_id_type>& result )

View file

@ -253,6 +253,7 @@ void database::initialize_evaluators()
register_evaluator<add_sidechain_address_evaluator>();
register_evaluator<update_sidechain_address_evaluator>();
register_evaluator<delete_sidechain_address_evaluator>();
register_evaluator<son_report_down_evaluator>();
}
void database::initialize_indexes()

View file

@ -491,6 +491,70 @@ void database::update_active_sons()
});
_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>( [&]( account_object& obj ) {
uint64_t total_votes = 0;
obj.name = "son_btc_account";
obj.statistics = create<account_statistics_object>([&]( 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

View file

@ -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<account_id_type>& result )

View file

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

View file

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

View file

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

View file

@ -40,4 +40,13 @@ public:
object_id_type do_apply(const son_heartbeat_operation& o);
};
class son_report_down_evaluator : public evaluator<son_report_down_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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@
#include <graphene/app/plugin.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/peerplays_sidechain/defs.hpp>

View file

@ -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<peerplays_sidechain::sidechain_net_manager> net_manager;
std::map<chain::public_key_type, fc::ecc::private_key> _private_keys;
std::set<chain::son_id_type> _sons;
fc::future<void> _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<std::string> key_id_to_wif_pair_strings = options["peerplays-private-key"].as<std::vector<std::string>>();
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<std::pair<chain::public_key_type, std::string> >(key_id_to_wif_pair_string, 5);
ilog("Public Key: ${public}", ("public", key_id_to_wif_pair.first));
fc::optional<fc::ecc::private_key> 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<fc::ecc::private_key>(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<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);
chain::signed_transaction trx = d.create_signed_transaction(_private_keys.begin()->second, op);
fc::future<bool> 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() :

View file

@ -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<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() );
BOOST_CHECK( obj->status == son_status::active);
const auto& son_btc_account = db.create<account_object>( [&]( account_object& obj ) {
obj.name = "son_btc_account";
obj.statistics = db.create<account_statistics_object>([&]( 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()