SON118-Add Budget for SON (#165)

* SON118-Add Budget for SON

* SON118 - Compilation errors fix

* SON118 - Proper commenting around pay_sons function

* SON118 - Comment correction, SON statistics object implementation type correction

* SON118 - Add missing index init and reflect enums

* SON118 - Correcting the indentation

* SON118 SON144 - Add unit test, code fixes and resolve failures for existing tests

* SON118 SON144 - Removing extra spaces added
This commit is contained in:
satyakoneru 2019-10-18 01:46:48 +11:00 committed by Bobinson K B
parent b3b994c6ea
commit ee7aae56da
14 changed files with 343 additions and 3 deletions

View file

@ -114,6 +114,7 @@ add_library( graphene_chain
affiliate_payout.cpp
son_evaluator.cpp
son_object.cpp
${HEADERS}
${PROTOCOL_HEADERS}

View file

@ -317,6 +317,7 @@ void database::initialize_indexes()
add_index< primary_index<lottery_balance_index > >();
add_index< primary_index<sweeps_vesting_balance_index > >();
add_index< primary_index<son_stats_index > >();
}

View file

@ -117,6 +117,53 @@ void database::update_worker_votes()
}
}
void database::pay_sons()
{
time_point_sec now = head_block_time();
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
// Current requirement is that we have to pay every 24 hours, so the following check
if( dpo.son_budget.value > 0 && now - dpo.last_son_payout_time >= fc::days(1)) {
uint64_t total_txs_signed = 0;
share_type son_budget = dpo.son_budget;
get_index_type<son_stats_index>().inspect_all_objects([this, &total_txs_signed](const object& o) {
const son_statistics_object& s = static_cast<const son_statistics_object&>(o);
total_txs_signed += s.txs_signed;
});
// Now pay off each SON proportional to the number of transactions signed.
get_index_type<son_stats_index>().inspect_all_objects([this, &total_txs_signed, &dpo, &son_budget](const object& o) {
const son_statistics_object& s = static_cast<const son_statistics_object&>(o);
if(s.txs_signed > 0){
auto son_params = get_global_properties().parameters;
share_type pay = (s.txs_signed * son_budget.value)/total_txs_signed;
const auto& idx = get_index_type<son_index>().indices().get<by_id>();
auto son_obj = idx.find( s.owner );
modify( *son_obj, [&]( son_object& _son_obj)
{
_son_obj.pay_son_fee(pay, *this);
});
//Remove the amount paid out to SON from global SON Budget
modify( dpo, [&]( dynamic_global_property_object& _dpo )
{
_dpo.son_budget -= pay;
} );
//Reset the tx counter in each son statistics object
modify( s, [&]( son_statistics_object& _s)
{
_s.txs_signed = 0;
});
}
});
//Note the last son pay out time
modify( dpo, [&]( dynamic_global_property_object& _dpo )
{
_dpo.last_son_payout_time = now;
});
}
}
void database::pay_workers( share_type& budget )
{
// ilog("Processing payroll! Available budget is ${b}", ("b", budget));
@ -505,6 +552,21 @@ void database::process_budget()
rec.witness_budget = witness_budget;
available_funds -= witness_budget;
// We should not factor-in the son budget before SON HARDFORK
share_type son_budget = 0;
if(now >= HARDFORK_SON_TIME){
// Before making a budget we should pay out SONs for the last day
// This function should check if its time to pay sons
// and modify the global son funds accordingly, whatever is left is passed on to next budget
pay_sons();
rec.leftover_son_funds = dpo.son_budget;
available_funds += rec.leftover_son_funds;
son_budget = gpo.parameters.son_pay_daily_max();
son_budget = std::min(son_budget, available_funds);
rec.son_budget = son_budget;
available_funds -= son_budget;
}
fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value;
worker_budget_u128 *= uint64_t(time_to_maint);
worker_budget_u128 /= 60*60*24;
@ -524,9 +586,11 @@ void database::process_budget()
rec.supply_delta = rec.witness_budget
+ rec.worker_budget
+ rec.son_budget
- rec.leftover_worker_funds
- rec.from_accumulated_fees
- rec.from_unused_witness_budget;
- rec.from_unused_witness_budget
- rec.leftover_son_funds;
modify(core, [&]( asset_dynamic_data_object& _core )
{
@ -535,9 +599,11 @@ void database::process_budget()
assert( rec.supply_delta ==
witness_budget
+ worker_budget
+ son_budget
- leftover_worker_funds
- _core.accumulated_fees
- dpo.witness_budget
- dpo.son_budget
);
_core.accumulated_fees = 0;
});
@ -548,6 +614,7 @@ void database::process_budget()
// available_funds, we replace it with witness_budget
// instead of adding it.
_dpo.witness_budget = witness_budget;
_dpo.son_budget = son_budget;
_dpo.last_budget_time = now;
});

View file

@ -46,9 +46,11 @@ struct budget_record
// sinks of budget, should sum up to total_budget
share_type witness_budget = 0;
share_type worker_budget = 0;
share_type son_budget = 0;
// unused budget
share_type leftover_worker_funds = 0;
share_type leftover_son_funds = 0;
// change in supply due to budget operations
share_type supply_delta = 0;

View file

@ -232,8 +232,8 @@
#define TOURNAMENT_MAX_START_TIME_IN_FUTURE (60*60*24*7*4) // 1 month
#define TOURNAMENT_MAX_START_DELAY (60*60*24*7) // 1 week
#define MIN_SON_MEMBER_COUNT 15
#define SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE (2*GRAPHENE_1_PERCENT)
#define SWEEPS_DEFAULT_DISTRIBUTION_ASSET (graphene::chain::asset_id_type(0))
#define SWEEPS_VESTING_BALANCE_MULTIPLIER 100000000
#define SWEEPS_ACCUMULATOR_ACCOUNT (graphene::chain::account_id_type(0))
#define MIN_SON_PAY_DAILY_MAX (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(200))

View file

@ -516,6 +516,7 @@ namespace graphene { namespace chain {
void initialize_budget_record( fc::time_point_sec now, budget_record& rec )const;
void process_budget();
void pay_workers( share_type& budget );
void pay_sons();
void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props);
void update_active_witnesses();
void update_active_committee_members();

View file

@ -79,6 +79,9 @@ namespace graphene { namespace chain {
time_point_sec next_maintenance_time;
time_point_sec last_budget_time;
share_type witness_budget;
//Last SON Payout time, it can be different to the maintenance interval time
time_point_sec last_son_payout_time;
share_type son_budget = 0;
uint32_t accounts_registered_this_interval = 0;
/**
* Every time a block is missed this increases by

View file

@ -41,6 +41,7 @@ namespace graphene { namespace chain {
optional< uint16_t > sweeps_distribution_percentage;
optional< asset_id_type > sweeps_distribution_asset;
optional< account_id_type > sweeps_vesting_accumulator_account;
optional < uint32_t > son_pay_daily_max;
};
struct chain_parameters
@ -124,6 +125,9 @@ namespace graphene { namespace chain {
inline uint16_t son_count()const {
return extensions.value.son_count.valid() ? *extensions.value.son_count : MIN_SON_MEMBER_COUNT;
}
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;
}
};
} } // graphene::chain
@ -138,6 +142,7 @@ FC_REFLECT( graphene::chain::parameter_extension,
(sweeps_distribution_percentage)
(sweeps_distribution_asset)
(sweeps_vesting_accumulator_account)
(son_pay_daily_max)
)
FC_REFLECT( graphene::chain::chain_parameters,

View file

@ -174,7 +174,8 @@ namespace graphene { namespace chain {
impl_betting_market_position_object_type,
impl_global_betting_statistics_object_type,
impl_lottery_balance_object_type,
impl_sweeps_vesting_balance_object_type
impl_sweeps_vesting_balance_object_type,
impl_son_statistics_object_type
};
//typedef fc::unsigned_int object_id_type;
@ -256,6 +257,7 @@ namespace graphene { namespace chain {
class global_betting_statistics_object;
class lottery_balance_object;
class sweeps_vesting_balance_object;
class son_statistics_object;
typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type;
typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type;
@ -284,6 +286,7 @@ namespace graphene { namespace chain {
typedef object_id< implementation_ids, impl_global_betting_statistics_object_type, global_betting_statistics_object > global_betting_statistics_id_type;
typedef object_id< implementation_ids, impl_lottery_balance_object_type, lottery_balance_object > lottery_balance_id_type;
typedef object_id< implementation_ids, impl_sweeps_vesting_balance_object_type, sweeps_vesting_balance_object> sweeps_vesting_balance_id_type;
typedef object_id< implementation_ids, impl_son_statistics_object_type, son_statistics_object > son_statistics_id_type;
typedef fc::array<char, GRAPHENE_MAX_ASSET_SYMBOL_LENGTH> symbol_type;
typedef fc::ripemd160 block_id_type;
@ -441,6 +444,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
(impl_global_betting_statistics_object_type)
(impl_lottery_balance_object_type)
(impl_sweeps_vesting_balance_object_type)
(impl_son_statistics_object_type)
)
FC_REFLECT_TYPENAME( graphene::chain::share_type )

View file

@ -6,6 +6,25 @@
namespace graphene { namespace chain {
using namespace graphene::db;
/**
* @class son_statistics_object
* @ingroup object
* @ingroup implementation
*
* This object contains regularly updated statistical data about an SON. It is provided for the purpose of
* separating the SON transaction data that changes frequently from the SON object data that is mostly static.
*/
class son_statistics_object : public graphene::db::abstract_object<son_statistics_object>
{
public:
static const uint8_t space_id = implementation_ids;
static const uint8_t type_id = impl_son_statistics_object_type;
son_id_type owner;
// Transactions signed since the last son payouts
uint64_t txs_signed = 0;
};
/**
* @class son_object
* @brief tracks information about a SON account.
@ -24,6 +43,9 @@ namespace graphene { namespace chain {
vesting_balance_id_type deposit;
public_key_type signing_key;
vesting_balance_id_type pay_vb;
son_statistics_id_type statistics;
void pay_son_fee(share_type pay, database& db);
};
struct by_account;
@ -43,7 +65,22 @@ namespace graphene { namespace chain {
>
>;
using son_index = generic_index<son_object, son_multi_index_type>;
using son_stats_multi_index_type = multi_index_container<
son_statistics_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >
>
>;
using son_stats_index = generic_index<son_statistics_object, son_stats_multi_index_type>;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object),
(son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb) )
FC_REFLECT_DERIVED( graphene::chain::son_statistics_object,
(graphene::db::object),
(owner)
(txs_signed)
)

View file

@ -27,6 +27,7 @@ object_id_type create_son_evaluator::do_apply(const son_create_operation& op)
obj.deposit = op.deposit;
obj.signing_key = op.signing_key;
obj.pay_vb = op.pay_vb;
obj.statistics = db().create<son_statistics_object>([&](son_statistics_object& s){s.owner = obj.id;}).id;
});
return new_son_object.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -0,0 +1,8 @@
#include <graphene/chain/database.hpp>
#include <graphene/chain/son_object.hpp>
namespace graphene { namespace chain {
void son_object::pay_son_fee(share_type pay, database& db) {
db.adjust_balance(son_account, pay);
}
}}

View file

@ -272,6 +272,7 @@ void database_fixture::verify_asset_supplies( const database& db )
total_balances[db.get_global_properties().parameters.sweeps_distribution_asset()] += sweeps_vestings / SWEEPS_VESTING_BALANCE_MULTIPLIER;
total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget;
total_balances[asset_id_type()] += db.get_dynamic_global_properties().son_budget;
for( const auto& item : total_debts )
{

View file

@ -182,5 +182,214 @@ catch (fc::exception &e) {
edump((e.to_detail_string()));
throw;
}
}
BOOST_AUTO_TEST_CASE( son_pay_test )
{
try
{
const dynamic_global_property_object& dpo = db.get_dynamic_global_properties();
const auto block_interval = db.get_global_properties().parameters.block_interval;
BOOST_CHECK( dpo.son_budget.value == 0);
generate_blocks(HARDFORK_SON_TIME);
while (db.head_block_time() <= HARDFORK_SON_TIME) {
generate_block();
}
generate_block();
set_expiration(db, trx);
ACTORS((alice)(bob));
// Send some core to the actors
transfer( committee_account, alice_id, asset( 20000 * 100000) );
transfer( committee_account, bob_id, asset( 20000 * 100000) );
generate_block();
// Enable default fee schedule to collect fees
enable_fees();
// Make SON Budget small for testing purposes
// Make witness budget zero so that amount can be allocated to SON
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;
} );
// Upgrades pay fee and this goes to reserve
upgrade_to_lifetime_member(alice);
upgrade_to_lifetime_member(bob);
// Note payment time just to generate enough blocks to make budget
auto pay_fee_time = db.head_block_time().sec_since_epoch();
generate_block();
// Do maintenance from the upcoming block
auto schedule_maint = [&]()
{
db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo )
{
_dpo.next_maintenance_time = db.head_block_time() + 1;
} );
};
// Generate enough blocks to make budget
while( db.head_block_time().sec_since_epoch() - pay_fee_time < 100 * block_interval )
{
generate_block();
}
// Enough blocks generated schedule maintenance now
schedule_maint();
// This block triggers maintenance
generate_block();
// Check that the SON Budget is allocated and Witness budget is zero
BOOST_CHECK( dpo.son_budget.value == 200);
BOOST_CHECK( dpo.witness_budget.value == 0);
// 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(10);
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<object_id_type>();
}
// create payment vesting
vesting_balance_id_type payment1;
{
vesting_balance_create_operation op;
op.creator = alice_id;
op.owner = alice_id;
op.amount = asset(10);
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<object_id_type>();
}
// create deposit vesting
vesting_balance_id_type deposit2;
{
vesting_balance_create_operation op;
op.creator = bob_id;
op.owner = bob_id;
op.amount = asset(10);
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<object_id_type>();
}
// create payment vesting
vesting_balance_id_type payment2;
{
vesting_balance_create_operation op;
op.creator = bob_id;
op.owner = bob_id;
op.amount = asset(10);
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<object_id_type>();
}
// 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<son_index>().indices().get<by_account>();
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<son_stats_index>().indices().get<by_id>();
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 the transaction signed statistics of Alice's SON
db.modify( *son_stats_obj1, [&]( son_statistics_object& _s)
{
_s.txs_signed = 2;
});
// Modify the transaction signed statistics of Bob's SON
db.modify( *son_stats_obj2, [&]( son_statistics_object& _s)
{
_s.txs_signed = 3;
});
// Note the balances before the maintenance
int64_t obj1_balance = db.get_balance(obj1->son_account, asset_id_type()).amount.value;
int64_t obj2_balance = db.get_balance(obj2->son_account, asset_id_type()).amount.value;
// Next maintenance triggerred
generate_blocks(dpo.next_maintenance_time);
generate_block();
// Check if the signed transaction statistics are reset for both SONs
BOOST_REQUIRE_EQUAL(son_stats_obj1->txs_signed, 0);
BOOST_REQUIRE_EQUAL(son_stats_obj2->txs_signed, 0);
// Check that Alice and Bob are paid for signing the transactions in the previous day/cycle
BOOST_REQUIRE_EQUAL(db.get_balance(obj1->son_account, asset_id_type()).amount.value, 80+obj1_balance);
BOOST_REQUIRE_EQUAL(db.get_balance(obj2->son_account, asset_id_type()).amount.value, 120+obj2_balance);
// Check the SON Budget is again allocated after maintenance
BOOST_CHECK( dpo.son_budget.value == 200);
BOOST_CHECK( dpo.witness_budget.value == 0);
}FC_LOG_AND_RETHROW()
} BOOST_AUTO_TEST_SUITE_END()