From d180c54af36a24ef0b51906c816ad30684eefada Mon Sep 17 00:00:00 2001 From: Troglodactyl Date: Tue, 9 Jun 2015 22:30:10 -0500 Subject: [PATCH 01/20] Update for move to public Github --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 570e7871..f4e8fbd3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is a quick introduction to get new developers up to speed on Graphene. Starting Graphene ----------------- - git clone https://gitlab.bitshares.org/dlarimer/graphene + git clone https://github.com/cryptonomex/graphene.git cd graphene git submodule update --init --recursive cmake -DCMAKE_BUILD_TYPE=Debug . From fa26e39e10c500bca9fa212d05b67ea1970211d0 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 9 Jun 2015 16:32:58 -0400 Subject: [PATCH 02/20] Ignore dirty submodules --- .gitmodules | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitmodules b/.gitmodules index 1cad1009..cd4ee35e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,8 @@ [submodule "libraries/fc"] path = libraries/fc url = https://github.com/cryptonomex/fc + ignore = dirty [submodule "libraries/leveldb"] path = libraries/leveldb url = https://github.com/bitcoin/leveldb.git + ignore = dirty From ed7d485df33b871aa2bdc61b2cbdbcee81274f6e Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 9 Jun 2015 16:46:00 -0400 Subject: [PATCH 03/20] Progress #31: Initial work on new cashback system Lots still to do. There's no longer a way to upgrade an account, genesis can't evaluate, and who knows how many tests are failing... But it builds! --- libraries/chain/account_evaluator.cpp | 62 ++--- libraries/chain/db_maint.cpp | 217 ++++++++++++------ libraries/chain/delegate_evaluator.cpp | 2 +- libraries/chain/evaluator.cpp | 47 +--- .../include/graphene/chain/account_object.hpp | 124 +++++----- .../chain/include/graphene/chain/config.hpp | 9 +- .../chain/include/graphene/chain/database.hpp | 30 ++- .../include/graphene/chain/operations.hpp | 27 +-- .../chain/include/graphene/chain/types.hpp | 23 +- .../chain/withdraw_permission_object.hpp | 10 +- libraries/chain/operations.cpp | 3 +- libraries/chain/witness_evaluator.cpp | 2 +- libraries/chain/worker_evaluator.cpp | 2 +- libraries/wallet/wallet.cpp | 14 +- tests/common/database_fixture.cpp | 13 +- tests/common/database_fixture.hpp | 4 +- tests/tests/authority_tests.cpp | 6 +- tests/tests/block_tests.cpp | 2 +- tests/tests/operation_tests.cpp | 8 +- tests/tests/operation_tests2.cpp | 6 +- tests/tests/uia_tests.cpp | 4 +- 21 files changed, 337 insertions(+), 278 deletions(-) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 3fd2b0ec..5883459c 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -24,22 +24,13 @@ namespace graphene { namespace chain { void_result account_create_evaluator::do_evaluate( const account_create_operation& op ) { try { - FC_ASSERT( db().find_object(op.voting_account) ); - FC_ASSERT( is_relative(op.memo_key) || db().find_object(op.memo_key) ); + database& d = db(); + FC_ASSERT( d.find_object(op.voting_account) ); + FC_ASSERT( is_relative(op.memo_key) || d.find_object(op.memo_key) ); + FC_ASSERT( fee_paying_account->is_lifetime_member() ); + FC_ASSERT( op.referrer(d).is_member(d.head_block_time()) ); - if( fee_paying_account->is_prime() ) - { - FC_ASSERT( op.referrer(db()).is_prime() ); - } - else - { - FC_ASSERT( op.referrer == fee_paying_account->referrer ); - FC_ASSERT( op.referrer_percent == fee_paying_account->referrer_percent, "", - ("op",op) - ("fee_paying_account->referral_percent",fee_paying_account->referrer_percent) ); - } - - const auto& global_props = db().get_global_properties(); + const auto& global_props = d.get_global_properties(); uint32_t max_vote_id = global_props.next_available_vote_id; const auto& chain_params = global_props.parameters; FC_ASSERT( op.num_witness <= chain_params.maximum_witness_count ); @@ -48,11 +39,11 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio FC_ASSERT( op.active.auths.size() <= chain_params.maximum_authority_membership ); for( auto id : op.owner.auths ) { - FC_ASSERT( is_relative(id.first) || db().find(id.first) ); + FC_ASSERT( is_relative(id.first) || d.find_object(id.first) ); } for( auto id : op.active.auths ) { - FC_ASSERT( is_relative(id.first) || db().find(id.first) ); + FC_ASSERT( is_relative(id.first) || d.find_object(id.first) ); } safe counts[vote_id_type::VOTE_TYPE_COUNT]; for( auto id : op.vote ) @@ -67,7 +58,7 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio "", ("count", counts[vote_id_type::committee])("num", op.num_committee)); - auto& acnt_indx = db().get_index_type(); + auto& acnt_indx = d.get_index_type(); if( op.name.size() ) { auto current_account_itr = acnt_indx.indices().get().find( op.name ); @@ -76,7 +67,7 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio // TODO: this check can be removed after GRAPHENE_LEGACY_NAME_IMPORT_PERIOD // legacy account check - if( db().get_dynamic_global_properties().head_block_number < GRAPHENE_LEGACY_NAME_IMPORT_PERIOD ) + if( d.get_dynamic_global_properties().head_block_number < GRAPHENE_LEGACY_NAME_IMPORT_PERIOD ) { auto legacy_account_itr = acnt_indx.indices().get().find( "bts-"+op.name ); if( legacy_account_itr != acnt_indx.indices().get().end() ) @@ -109,18 +100,15 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio }); const auto& new_acnt_object = db().create( [&]( account_object& obj ){ - if( fee_paying_account->is_prime() ) - { - obj.registrar = o.registrar; - obj.referrer = o.referrer; - obj.referrer_percent = o.referrer_percent; - } - else - { - obj.registrar = fee_paying_account->registrar; - obj.referrer = fee_paying_account->referrer; - obj.referrer_percent = fee_paying_account->referrer_percent; - } + obj.registrar = o.registrar; + obj.referrer = o.referrer; + obj.lifetime_referrer = o.referrer(db()).lifetime_referrer; + + auto& params = db().get_global_properties().parameters; + obj.network_fee_percentage = params.network_percent_of_fee; + obj.lifetime_referrer_fee_percentage = params.lifetime_referrer_percent_of_fee; + obj.referrer_rewards_percentage = o.referrer_percent; + obj.name = o.name; obj.owner = owner; obj.active = active; @@ -138,7 +126,7 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio void_result account_update_evaluator::do_evaluate( const account_update_operation& o ) { - database& d = db(); + database& d = db(); FC_ASSERT( !o.memo_key || is_relative(*o.memo_key) || db().find_object(*o.memo_key) ); @@ -163,7 +151,6 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio } acnt = &o.account(d); - if( o.upgrade_to_prime ) FC_ASSERT( !acnt->is_prime() ); if( o.vote ) { @@ -184,11 +171,6 @@ void_result account_update_evaluator::do_apply( const account_update_operation& if( o.voting_account ) a.voting_account = *o.voting_account; if( o.memo_key ) a.memo_key = *o.memo_key; if( o.vote ) a.votes = *o.vote; - if( o.upgrade_to_prime ) - { - a.referrer_percent = 100; - a.referrer = a.id; - } a.num_witness = o.num_witness; a.num_committee = o.num_committee; }); @@ -200,8 +182,8 @@ void_result account_whitelist_evaluator::do_evaluate(const account_whitelist_ope database& d = db(); listed_account = &o.account_to_list(d); - if( !d.get_global_properties().parameters.allow_non_prime_whitelists ) - FC_ASSERT(listed_account->is_prime()); + if( !d.get_global_properties().parameters.allow_non_member_whitelists ) + FC_ASSERT(o.authorizing_account(d).is_lifetime_member()); return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index df397870..b0adbd1c 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -174,74 +174,6 @@ void database::update_active_delegates() }); } FC_CAPTURE_AND_RETHROW() } -void database::update_vote_totals(const global_property_object& props) -{ try { - _vote_tally_buffer.resize(props.next_available_vote_id); - _witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1); - _committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1); - - const account_index& account_idx = get_index_type(); - _total_voting_stake = 0; - - bool count_non_prime_votes = props.parameters.count_non_prime_votes; - auto timestamp = fc::time_point::now(); - for( const account_object& stake_account : account_idx.indices() ) - { - if( count_non_prime_votes || stake_account.is_prime() ) - { - // There may be a difference between the account whose stake is voting and the one specifying opinions. - // Usually they're the same, but if the stake account has specified a voting_account, that account is the one - // specifying the opinions. - const account_object& opinion_account = - (stake_account.voting_account == account_id_type())? stake_account - : get(stake_account.voting_account); - - const auto& stats = stake_account.statistics(*this); - uint64_t voting_stake = stats.total_core_in_orders.value - + (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(*this).balance.amount.value: 0) - + get_balance(stake_account.get_id(), asset_id_type()).amount.value; - - for( vote_id_type id : opinion_account.votes ) - { - uint32_t offset = id.instance(); - // if they somehow managed to specify an illegal offset, ignore it. - if( offset < _vote_tally_buffer.size() ) - _vote_tally_buffer[ offset ] += voting_stake; - } - - if( opinion_account.num_witness <= props.parameters.maximum_witness_count ) - { - uint16_t offset = std::min(size_t(opinion_account.num_witness/2), - _witness_count_histogram_buffer.size() - 1); - // - // votes for a number greater than maximum_witness_count - // are turned into votes for maximum_witness_count. - // - // in particular, this takes care of the case where a - // member was voting for a high number, then the - // parameter was lowered. - // - _witness_count_histogram_buffer[ offset ] += voting_stake; - } - if( opinion_account.num_committee <= props.parameters.maximum_committee_count ) - { - uint16_t offset = std::min(size_t(opinion_account.num_committee/2), - _committee_count_histogram_buffer.size() - 1); - // - // votes for a number greater than maximum_committee_count - // are turned into votes for maximum_committee_count. - // - // same rationale as for witnesses - // - _committee_count_histogram_buffer[ offset ] += voting_stake; - } - - _total_voting_stake += voting_stake; - } - } - ilog("Tallied votes in ${time} milliseconds.", ("time", (fc::time_point::now() - timestamp).count() / 1000.0)); -} FC_CAPTURE_AND_RETHROW() } - share_type database::get_max_budget( fc::time_point_sec now )const { const dynamic_global_property_object& dpo = get_dynamic_global_properties(); @@ -352,7 +284,154 @@ void database::process_budget() void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { - update_vote_totals(global_props); + const auto& gpo = get_global_properties(); + + struct vote_tally_helper { + database& d; + const global_property_object& props; + + vote_tally_helper(database& d, const global_property_object& gpo) + : d(d), props(gpo) + { + d._vote_tally_buffer.resize(props.next_available_vote_id); + d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1); + d._committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1); + d._total_voting_stake = 0; + } + + void operator()(const account_object& stake_account) { + if( props.parameters.count_non_member_votes || stake_account.is_member(d.head_block_time()) ) + { + // There may be a difference between the account whose stake is voting and the one specifying opinions. + // Usually they're the same, but if the stake account has specified a voting_account, that account is the one + // specifying the opinions. + const account_object& opinion_account = + (stake_account.voting_account == account_id_type())? stake_account + : d.get(stake_account.voting_account); + + const auto& stats = stake_account.statistics(d); + uint64_t voting_stake = stats.total_core_in_orders.value + + (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0) + + d.get_balance(stake_account.get_id(), asset_id_type()).amount.value; + + for( vote_id_type id : opinion_account.votes ) + { + uint32_t offset = id.instance(); + // if they somehow managed to specify an illegal offset, ignore it. + if( offset < d._vote_tally_buffer.size() ) + d._vote_tally_buffer[ offset ] += voting_stake; + } + + if( opinion_account.num_witness <= props.parameters.maximum_witness_count ) + { + uint16_t offset = std::min(size_t(opinion_account.num_witness/2), + d._witness_count_histogram_buffer.size() - 1); + // votes for a number greater than maximum_witness_count + // are turned into votes for maximum_witness_count. + // + // in particular, this takes care of the case where a + // member was voting for a high number, then the + // parameter was lowered. + d._witness_count_histogram_buffer[ offset ] += voting_stake; + } + if( opinion_account.num_committee <= props.parameters.maximum_committee_count ) + { + uint16_t offset = std::min(size_t(opinion_account.num_committee/2), + d._committee_count_histogram_buffer.size() - 1); + // votes for a number greater than maximum_committee_count + // are turned into votes for maximum_committee_count. + // + // same rationale as for witnesses + d._committee_count_histogram_buffer[ offset ] += voting_stake; + } + + d._total_voting_stake += voting_stake; + } + } + } tally_helper(*this, gpo); + struct process_fees_helper { + database& d; + const global_property_object& props; + + process_fees_helper(database& d, const global_property_object& gpo) + : d(d), props(gpo) {} + + void operator()(const account_object& a) { + const account_statistics_object& stats = a.statistics(d); + + auto cut_fee = [](share_type a, uint16_t p) -> share_type + { + fc::uint128 r(a.value); + r *= p; + r /= GRAPHENE_100_PERCENT; + return r.to_uint64(); + }; + + if( stats.pending_fees > 0 ) + { + share_type core_fee_subtotal(stats.pending_fees); + share_type bulk_cashback = share_type(0); + if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min && + a.is_member(d.head_block_time()) ) + { + uint64_t bulk_discount_percent = 0; + if( stats.lifetime_fees_paid >= props.parameters.bulk_discount_threshold_max ) + bulk_discount_percent = props.parameters.max_bulk_discount_percent_of_fee; + else if(props.parameters.bulk_discount_threshold_max.value != + props.parameters.bulk_discount_threshold_min.value) + { + bulk_discount_percent = + (props.parameters.max_bulk_discount_percent_of_fee * + (stats.lifetime_fees_paid.value - + props.parameters.bulk_discount_threshold_min.value)) / + (props.parameters.bulk_discount_threshold_max.value - + props.parameters.bulk_discount_threshold_min.value); + } + assert( bulk_discount_percent <= GRAPHENE_100_PERCENT ); + assert( bulk_discount_percent >= 0 ); + + bulk_cashback = cut_fee(core_fee_subtotal, bulk_discount_percent); + assert( bulk_cashback <= core_fee_subtotal ); + } + + share_type core_fee_total = core_fee_subtotal - bulk_cashback; + share_type network_cut = cut_fee(core_fee_total, a.network_fee_percentage); + assert( network_cut <= core_fee_total ); + share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); + share_type accumulated = network_cut - burned; + assert( accumulated + burned == network_cut ); + share_type referral = core_fee_total - network_cut; + + d.modify(dynamic_asset_data_id_type()(d), [burned,accumulated](asset_dynamic_data_object& d) { + d.accumulated_fees += accumulated + burned; + }); + + d.modify(a.statistics(d), [core_fee_total](account_statistics_object& s) { + s.lifetime_fees_paid += core_fee_total; + s.pending_fees = 0; + }); + + d.deposit_cashback( a, bulk_cashback ); + + assert( referral + bulk_cashback + accumulated + burned == core_fee_subtotal ); + + // Potential optimization: Skip some of this math and object lookups by special casing on the account type. + // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to + // it directly. + share_type lifetime_cut = cut_fee(referral, a.lifetime_referrer_fee_percentage); + share_type referrer_cut = cut_fee(referral, a.referrer_rewards_percentage); + share_type registrar_cut = referral - lifetime_cut = referrer_cut; + + d.deposit_cashback(d.get(a.lifetime_referrer), lifetime_cut); + d.deposit_cashback(d.get(a.referrer), referrer_cut); + d.deposit_cashback(d.get(a.registrar), registrar_cut); + + assert( lifetime_cut + referrer_cut + registrar_cut == referral ); + } + } + } fees_helper(*this, gpo); + + perform_account_maintenance(std::tie(tally_helper, fees_helper)); struct clear_canary { clear_canary(vector& target): target(target){} diff --git a/libraries/chain/delegate_evaluator.cpp b/libraries/chain/delegate_evaluator.cpp index 06fca086..b86409f8 100644 --- a/libraries/chain/delegate_evaluator.cpp +++ b/libraries/chain/delegate_evaluator.cpp @@ -24,7 +24,7 @@ namespace graphene { namespace chain { object_id_type delegate_create_evaluator::do_evaluate( const delegate_create_operation& op ) { - FC_ASSERT(db().get(op.delegate_account).is_prime()); + FC_ASSERT(db().get(op.delegate_account).is_lifetime_member()); return object_id_type(); } diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 8614d0fb..74cf5c14 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -61,55 +61,14 @@ namespace graphene { namespace chain { void generic_evaluator::pay_fee() { try { - asset core_fee_subtotal(core_fee_paid); - const auto& gp = db().get_global_properties(); - share_type bulk_cashback = share_type(0); - if( fee_paying_account_statistics->lifetime_fees_paid > gp.parameters.bulk_discount_threshold_min && - fee_paying_account->is_prime() ) - { - uint64_t bulk_discount_percent = 0; - if( fee_paying_account_statistics->lifetime_fees_paid > gp.parameters.bulk_discount_threshold_max ) - bulk_discount_percent = gp.parameters.max_bulk_discount_percent_of_fee; - else if(gp.parameters.bulk_discount_threshold_max.value - gp.parameters.bulk_discount_threshold_min.value != 0) - { - bulk_discount_percent = - (gp.parameters.max_bulk_discount_percent_of_fee * - (fee_paying_account_statistics->lifetime_fees_paid.value - - gp.parameters.bulk_discount_threshold_min.value)) / - (gp.parameters.bulk_discount_threshold_max.value - gp.parameters.bulk_discount_threshold_min.value); - } - assert( bulk_discount_percent <= GRAPHENE_100_PERCENT ); - assert( bulk_discount_percent >= 0 ); - - bulk_cashback = (core_fee_subtotal.amount.value * bulk_discount_percent) / GRAPHENE_100_PERCENT; - assert( bulk_cashback <= core_fee_subtotal.amount ); - } - - share_type core_fee_total = core_fee_subtotal.amount - bulk_cashback; - share_type accumulated = (core_fee_total.value * gp.parameters.witness_percent_of_fee)/GRAPHENE_100_PERCENT; - share_type burned = (core_fee_total.value * gp.parameters.burn_percent_of_fee)/GRAPHENE_100_PERCENT; - share_type referral = core_fee_total.value - accumulated - burned; - auto& d = db(); - - assert( accumulated + burned <= core_fee_total ); - if( fee_asset->get_id() != asset_id_type() ) - d.modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { + db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { d.accumulated_fees += fee_from_account.amount; d.fee_pool -= core_fee_paid; }); - d.modify(dynamic_asset_data_id_type()(d), [burned,accumulated](asset_dynamic_data_object& d) { - d.accumulated_fees += accumulated + burned; + db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) { + s.pending_fees += core_fee_paid; }); - - d.modify(fee_paying_account->statistics(d), [core_fee_total](account_statistics_object& s) { - s.lifetime_fees_paid += core_fee_total; - }); - - d.deposit_cashback( fee_paying_account->referrer(d), referral ); - d.deposit_cashback( *fee_paying_account, bulk_cashback ); - - assert( referral + bulk_cashback + accumulated + burned == core_fee_subtotal.amount ); } FC_CAPTURE_AND_RETHROW() } bool generic_evaluator::verify_authority( const account_object& a, authority::classification c ) diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index fa63da5f..9306a486 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -39,32 +39,34 @@ namespace graphene { namespace chain { static const uint8_t type_id = impl_account_statistics_object_type; /** - * Keep the most recent operation as a root pointer to - * a linked list of the transaction history. This field is - * not required by core validation and could in theory be - * made an annotation on the account object, but because - * transaction history is so common and this object is already - * cached in the undo buffer (because it likely affected the - * balances of this account) it is convienent to simply - * track this data here. Account balance objects don't currenty - * inherit from annotated object. + * Keep the most recent operation as a root pointer to a linked list of the transaction history. This field is + * not required by core validation and could in theory be made an annotation on the account object, but + * because transaction history is so common and this object is already cached in the undo buffer (because it + * likely affected the balances of this account) it is convienent to simply track this data here. Account + * balance objects don't currenty inherit from annotated object. */ account_transaction_history_id_type most_recent_op; /** - * When calculating votes it is necessary to know how much is - * stored in orders (and thus unavailable for transfers). Rather - * than maintaining an index of [asset,owner,order_id] we will - * simply maintain the running total here and update it every - * time an order is created or modified. + * When calculating votes it is necessary to know how much is stored in orders (and thus unavailable for + * transfers). Rather than maintaining an index of [asset,owner,order_id] we will simply maintain the running + * total here and update it every time an order is created or modified. */ - share_type total_core_in_orders; + share_type total_core_in_orders; /** - * Tracks the total fees paid by this account for the purpose - * of calculating bulk discounts. + * Tracks the total fees paid by this account for the purpose of calculating bulk discounts. */ - share_type lifetime_fees_paid; + share_type lifetime_fees_paid; + + /** + * Tracks the fees paid by this account which have not been disseminated to the various parties that receive + * them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid + * doing massive amounts of uint128 math on each and every operation. + * + * These fees will be paid out and this counter will reset during the maintenance interval. + */ + share_type pending_fees; }; /** @@ -102,53 +104,54 @@ namespace graphene { namespace chain { public: static const uint8_t space_id = protocol_ids; static const uint8_t type_id = account_object_type; - /** - * The account that paid the fee to register this account, this account is - * known as the primary referrer and is entitled to a percent of transaction - * fees. - */ - account_id_type registrar; /** - * The registrar may be a faucet with its own revenue sharing model that allows - * users to refer each other. + * The time at which this account's membership expires. + * If set to any time in the past, the account is a basic account. + * If set to time_point_sec::maximum(), the account is a lifetime member. + * If set to any time not in the past less than time_point_sec::maximum(), the account is an annual member. + * + * See @ref is_lifetime_member, @ref is_basic_account, @ref is_annual_member, and @ref is_member */ - account_id_type referrer; + time_point_sec membership_expiration_date; - /** - * Any referral fees not paid to referrer are paid to registrar - */ - uint8_t referrer_percent = 0; + ///The account that paid the fee to register this account. Receives a percentage of referral rewards. + account_id_type registrar; + /// The account credited as referring this account. Receives a percentage of referral rewards. + account_id_type referrer; + /// The lifetime member at the top of the referral tree. Receives a percentage of referral rewards. + account_id_type lifetime_referrer; + /// Percentage of fee which should go to network. + uint16_t network_fee_percentage; + /// Percentage of fee which should go to lifetime referrer. + uint16_t lifetime_referrer_fee_percentage = 0; + /// Percentage of referral rewards (leftover fee after paying network and lifetime referrer) which should go + /// to referrer. The remainder of referral rewards goes to the registrar. + uint16_t referrer_rewards_percentage = 0; - /// The account's name. This name must be unique among all account names on the graph. The name may be empty. - string name; - + /// The account's name. This name must be unique among all account names on the graph. May not be empty. + string name; /** * The owner authority represents absolute control over the account. Usually the keys in this authority will * be kept in cold storage, as they should not be needed very often and compromise of these keys constitutes * complete and irrevocable loss of the account. Generally the only time the owner authority is required is to * update the active authority. */ - authority owner; - + authority owner; /// The owner authority contains the hot keys of the account. This authority has control over nearly all /// operations the account may perform. - authority active; - + authority active; /// The memo key is the key this account will typically use to encrypt/sign transaction memos and other non- /// validated account activities. This field is here to prevent confusion if the active authority has zero or /// multiple keys in it. - key_id_type memo_key; - + key_id_type memo_key; /// If this field is set to an account ID other than 0, this account's votes will be ignored and its stake /// will be counted as voting for the referenced account's selected votes instead. - account_id_type voting_account; - - - uint16_t num_witness = 0; - uint16_t num_committee = 0; + account_id_type voting_account; + uint16_t num_witness = 0; + uint16_t num_committee = 0; /// This is the list of vote IDs this account votes for. The weight of these votes is determined by this /// account's balance of core asset. flat_set votes; @@ -163,7 +166,7 @@ namespace graphene { namespace chain { * account cannot update this set, except by transferring ownership of the account, which will clear it. Other * accounts may add or remove their IDs from this set. */ - flat_set whitelisting_accounts; + flat_set whitelisting_accounts; /** * This is a set of all accounts which have 'blacklisted' this account. Blacklisting is only used in core @@ -171,20 +174,34 @@ namespace graphene { namespace chain { * account cannot update this set, and it will be preserved even if the account is transferred. Other accounts * may add or remove their IDs from this set. */ - flat_set blacklisting_accounts; + flat_set blacklisting_accounts; /** * Vesting balance which receives cashback_reward deposits. */ optional cashback_vb; - /** - * @return true if this is a prime account, false otherwise. - */ - bool is_prime()const + /// @return true if this is a lifetime member account; false otherwise. + bool is_lifetime_member()const { return get_id() == referrer; } + /// @return true if this is a basic account; false otherwise. + bool is_basic_account(time_point_sec now)const + { + return now > membership_expiration_date; + } + /// @return true if the account is an unexpired annual member; false otherwise. + /// @note This method will return false for lifetime members. + bool is_annual_member(time_point_sec now)const + { + return !is_lifetime_member() && !is_basic_account(now); + } + /// @return true if the account is an annual or lifetime member; false otherwise. + bool is_member(time_point_sec now)const + { + return !is_basic_account(now); + } /** * @return true if this account is whitelisted and not blacklisted to transact in the provided asset; false @@ -253,9 +270,12 @@ namespace graphene { namespace chain { typedef generic_index account_index; }} + FC_REFLECT_DERIVED( graphene::chain::account_object, (graphene::db::annotated_object), - (registrar)(referrer)(referrer_percent)(name)(owner)(active)(memo_key)(voting_account)(num_witness)(num_committee)(votes) + (membership_expiration_date)(registrar)(referrer)(lifetime_referrer) + (network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage) + (name)(owner)(active)(memo_key)(voting_account)(num_witness)(num_committee)(votes) (statistics)(whitelisting_accounts)(blacklisting_accounts)(cashback_vb) ) FC_REFLECT_DERIVED( graphene::chain::account_balance_object, diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 74ae5163..75ca540c 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -49,6 +49,7 @@ #define GRAPHENE_DEFAULT_TRANSFER_FEE (1*GRAPHENE_BLOCKCHAIN_PRECISION) #define GRAPHENE_MAX_INSTANCE_ID (uint64_t(-1)>>16) #define GRAPHENE_100_PERCENT 10000 +#define GRAPHENE_1_PERCENT (GRAPHENE_100_PERCENT/100) /** NOTE: making this a power of 2 (say 2^15) would greatly accelerate fee calcs */ #define GRAPHENE_MAX_MARKET_FEE_PERCENT GRAPHENE_100_PERCENT #define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY (60*60*24) ///< 1 day @@ -72,12 +73,14 @@ #define GRAPHENE_DEFAULT_MAX_COMMITTEE (1001) // SHOULD BE ODD #define GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC (60*60*24*7*4) // Four weeks #define GRAPHENE_DEFAULT_GENESIS_PROPOSAL_REVIEW_PERIOD_SEC (60*60*24*7*2) // Two weeks -#define GRAPHENE_DEFAULT_WITNESS_PERCENT (10000/100) // 1% -#define GRAPHENE_DEFAULT_MAX_BULK_DISCOUNT_PERCENT (10000/2) // 50% +#define GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) +#define GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE (30*GRAPHENE_1_PERCENT) +#define GRAPHENE_DEFAULT_MAX_BULK_DISCOUNT_PERCENT (50*GRAPHENE_1_PERCENT) #define GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MIN ( GRAPHENE_BLOCKCHAIN_PRECISION*int64_t(1000) ) #define GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MAX ( GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MIN*int64_t(100) ) #define GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC (60*60*24*365) ///< 1 year -#define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE (10000/5) // 20% +#define GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD (GRAPHENE_BLOCKCHAIN_PRECISION*int64_t(100)) +#define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) #define GRAPHENE_DEFAULT_WITNESS_PAY_PERCENT_OF_ACCUMULATED ( 1728000) /// gives a half life of 1 year assuming 1 second blocks #define GRAPHENE_WITNESS_PAY_PERCENT_PRECISION (1000000000) #define GRAPHENE_GENESIS_TIMESTAMP (1431700000) /// Should be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 2580d63a..7a3f320d 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -357,7 +358,9 @@ namespace graphene { namespace chain { void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props); void update_active_witnesses(); void update_active_delegates(); - void update_vote_totals(const global_property_object& props); + + template + void perform_account_maintenance(std::tuple helpers); ///@} ///@} @@ -394,4 +397,29 @@ namespace graphene { namespace chain { uint64_t _total_voting_stake; }; + namespace detail + { + template + struct seq { }; + + template + struct gen_seq : gen_seq { }; + + template + struct gen_seq<0, Is...> : seq { }; + + template + void for_each(T&& t, const account_object& a, seq) + { + auto l = { (std::get(t)(a), 0)... }; + } + } + template + void database::perform_account_maintenance(std::tuple helpers) + { + const auto& idx = get_index_type().indices(); + for( const account_object& a : idx ) + detail::for_each(helpers, a, detail::gen_seq()); + } + } } diff --git a/libraries/chain/include/graphene/chain/operations.hpp b/libraries/chain/include/graphene/chain/operations.hpp index 9353568b..4f86f7e0 100644 --- a/libraries/chain/include/graphene/chain/operations.hpp +++ b/libraries/chain/include/graphene/chain/operations.hpp @@ -15,7 +15,7 @@ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -/* Copyright (C) Cryptonomex, Inc - All Rights Reserved +/* Copyright (C) Cryptonomex, Inc - All Rights Reserved * * All modifications become property of Cryptonomex, Inc. * @@ -125,14 +125,13 @@ namespace graphene { namespace chain { struct account_create_operation { asset fee; + /// This account pays the fee. Must be a lifetime member. account_id_type registrar; - /** - * If fee_paying_account->is_prime then referrer can be - * any other account that is also prime. Otherwise referrer must - * equal fee_paying_account->referrer. - */ + /// This account receives a portion of the fee split between registrar and referrer. Must be a member. account_id_type referrer; + /// Of the fee split between registrar and referrer, this percentage goes to the referrer. The rest goes to the + /// registrar. uint8_t referrer_percent = 0; string name; @@ -214,12 +213,6 @@ namespace graphene { namespace chain { uint16_t num_witness = 0; uint16_t num_committee = 0; - /** - * If set to true, upgrades the account to a prime account by setting the account's referrer to itself. This may - * only be set to true when the account being modified is not already a prime account. - */ - bool upgrade_to_prime = false; - account_id_type fee_payer()const { return account; } void get_required_auth(flat_set& active_auth_set , flat_set& owner_auth_set)const; void validate()const; @@ -1089,13 +1082,13 @@ namespace graphene { namespace chain { * authorizing account. This operation is primarily useful for scheduling recurring payments. * * Withdrawal permissions define withdrawal periods, which is a span of time during which the authorized account may - * make a withdrawal. Any number of withdraws may be made so long as the total amount withdrawn is less than the limit - * for any given period. + * make a withdrawal. Any number of withdrawals may be made so long as the total amount withdrawn per period does + * not exceed the limit for any given period. * * Withdrawal permissions authorize only a specific pairing, i.e. a permission only authorizes one specified * authorized account to withdraw from one specified authorizing account. Withdrawals are limited and may not exceet - * the withdrawal limit. The withdrawal must be made in the same asset as the limit; attempts with withdraw any other - * asset type will be rejected. + * the withdrawal limit. The withdrawal must be made in the same asset as the limit; attempts with withdraw any + * other asset type will be rejected. * * The fee for this operation is paid by withdraw_from_account, and this account is required to authorize this * operation. @@ -1748,7 +1741,7 @@ FC_REFLECT( graphene::chain::account_create_operation, FC_REFLECT_TYPENAME( fc::flat_set ) FC_REFLECT( graphene::chain::account_update_operation, - (fee)(account)(owner)(active)(voting_account)(memo_key)(num_witness)(num_committee)(vote)(upgrade_to_prime) + (fee)(account)(owner)(active)(voting_account)(memo_key)(num_witness)(num_committee)(vote) ) FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing) diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 2fe3e855..70b3ecec 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -460,23 +460,26 @@ namespace graphene { namespace chain { uint16_t maximum_witness_count = GRAPHENE_DEFAULT_MAX_WITNESSES; ///< maximum number of active witnesses uint16_t maximum_committee_count = GRAPHENE_DEFAULT_MAX_COMMITTEE; ///< maximum number of active delegates uint16_t maximum_authority_membership = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP; ///< largest number of keys/accounts an authority can have - uint16_t burn_percent_of_fee = GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE; ///< the percentage of every fee that is taken out of circulation - uint16_t witness_percent_of_fee = GRAPHENE_DEFAULT_WITNESS_PERCENT; ///< percent of revenue paid to witnesses + uint16_t burn_percent_of_fee = GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE; ///< the percentage of the network's allocation of a fee that is taken out of circulation + uint16_t network_percent_of_fee = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; ///< percent of transaction fees paid to network + uint16_t lifetime_referrer_percent_of_fee = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE; ///< percent of transaction fees paid to network uint32_t cashback_vesting_period_seconds = GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC; ///< time after cashback rewards are accrued before they become liquid + share_type cashback_vesting_threshold = GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD; ///< the maximum cashback that can be received without vesting uint16_t max_bulk_discount_percent_of_fee = GRAPHENE_DEFAULT_MAX_BULK_DISCOUNT_PERCENT; ///< the maximum percentage discount for bulk discounts share_type bulk_discount_threshold_min = GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MIN; ///< the minimum amount of fees paid to qualify for bulk discounts share_type bulk_discount_threshold_max = GRAPHENE_DEFAULT_BULK_DISCOUNT_THRESHOLD_MAX; ///< the amount of fees paid to qualify for the max bulk discount percent - bool count_non_prime_votes = true; ///< set to false to restrict voting privlegages to prime accounts - bool allow_non_prime_whitelists = false; ///< true if non-prime accounts may set whitelists and blacklists; false otherwise + bool count_non_member_votes = true; ///< set to false to restrict voting privlegages to member accounts + bool allow_non_member_whitelists = false; ///< true if non-member accounts may set whitelists and blacklists; false otherwise share_type witness_pay_per_block = GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK; ///< CORE to be allocated to witnesses (per block) share_type worker_budget_per_day = GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY; ///< CORE to be allocated to workers (per day) void validate()const { - FC_ASSERT( witness_percent_of_fee <= GRAPHENE_100_PERCENT ); FC_ASSERT( burn_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( network_percent_of_fee <= GRAPHENE_100_PERCENT ); FC_ASSERT( max_bulk_discount_percent_of_fee <= GRAPHENE_100_PERCENT ); - FC_ASSERT( burn_percent_of_fee + witness_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT ); + FC_ASSERT( network_percent_of_fee + lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT ); FC_ASSERT( bulk_discount_threshold_min <= bulk_discount_threshold_max ); FC_ASSERT( bulk_discount_threshold_min > 0 ); @@ -656,13 +659,15 @@ FC_REFLECT( graphene::chain::chain_parameters, (maximum_asset_feed_publishers) (maximum_authority_membership) (burn_percent_of_fee) - (witness_percent_of_fee) + (network_percent_of_fee) + (lifetime_referrer_percent_of_fee) (max_bulk_discount_percent_of_fee) (cashback_vesting_period_seconds) + (cashback_vesting_threshold) (bulk_discount_threshold_min) (bulk_discount_threshold_max) - (count_non_prime_votes) - (allow_non_prime_whitelists) + (count_non_member_votes) + (allow_non_member_whitelists) (witness_pay_per_block) (worker_budget_per_day) ) diff --git a/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp b/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp index 8cb9bd59..a89d1cc1 100644 --- a/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp +++ b/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp @@ -28,9 +28,9 @@ namespace graphene { namespace chain { * * The primary purpose of this object is to enable recurring payments on the blockchain. An account which wishes to * process a recurring payment may use a @ref withdraw_permission_claim_operation to reference an object of this type - * and withdraw up to @ref withdrawal_limit from @ref withdraw_from_account. Only @ref authorized_account may do this. - * Any number of withdraws may be made so long as the total amount withdrawn is less than the limit for any given - * period. + * and withdraw up to @ref withdrawal_limit from @ref withdraw_from_account. Only @ref authorized_account may do + * this. Any number of withdrawals may be made so long as the total amount withdrawn per period does not exceed the + * limit for any given period. */ class withdraw_permission_object : public graphene::db::abstract_object { @@ -54,14 +54,14 @@ namespace graphene { namespace chain { /// tracks the total amount share_type claimed_this_period; /// True if the permission may still be claimed for this period; false if it has already been used - asset available_this_period( fc::time_point_sec current_time )const + asset available_this_period( fc::time_point_sec current_time )const { if( current_time >= period_start_time + withdrawal_period_sec ) return withdrawal_limit; return asset( ( withdrawal_limit.amount > claimed_this_period ) ? withdrawal_limit.amount - claimed_this_period - : 0, withdrawal_limit.asset_id ); + : 0, withdrawal_limit.asset_id ); } }; diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp index ddd63008..70c7d75f 100644 --- a/libraries/chain/operations.cpp +++ b/libraries/chain/operations.cpp @@ -122,7 +122,6 @@ share_type account_create_operation::calculate_fee( const fee_schedule_type& sch } share_type account_update_operation::calculate_fee( const fee_schedule_type& schedule )const { - if( upgrade_to_prime ) return schedule.at(prime_upgrade_fee_type); return schedule.at(account_create_fee_type); } void account_update_operation::get_required_auth(flat_set& active_auth_set, @@ -138,7 +137,7 @@ void account_update_operation::validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( account != account_id_type() ); - FC_ASSERT( owner || active || voting_account || memo_key || vote || upgrade_to_prime ); + FC_ASSERT( owner || active || voting_account || memo_key || vote ); } diff --git a/libraries/chain/witness_evaluator.cpp b/libraries/chain/witness_evaluator.cpp index ee007122..b8f9a19f 100644 --- a/libraries/chain/witness_evaluator.cpp +++ b/libraries/chain/witness_evaluator.cpp @@ -25,7 +25,7 @@ namespace graphene { namespace chain { object_id_type witness_create_evaluator::do_evaluate( const witness_create_operation& op ) { - FC_ASSERT(db().get(op.witness_account).is_prime()); + FC_ASSERT(db().get(op.witness_account).is_lifetime_member()); return object_id_type(); } diff --git a/libraries/chain/worker_evaluator.cpp b/libraries/chain/worker_evaluator.cpp index a20e68ab..9faa8f29 100644 --- a/libraries/chain/worker_evaluator.cpp +++ b/libraries/chain/worker_evaluator.cpp @@ -26,7 +26,7 @@ object_id_type worker_create_evaluator::do_evaluate(const worker_create_evaluato { try { database& d = db(); - FC_ASSERT(d.get(o.owner).is_prime()); + FC_ASSERT(d.get(o.owner).is_lifetime_member()); FC_ASSERT(o.work_begin_date >= d.head_block_time()); return object_id_type(); diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index edc9b761..3af8a3be 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -695,7 +695,7 @@ public: account_object registrar_account_object = this->get_account( registrar_account ); - FC_ASSERT( registrar_account_object.is_prime() ); + FC_ASSERT( registrar_account_object.is_lifetime_member() ); account_id_type registrar_account_id = registrar_account_object.id; @@ -761,17 +761,11 @@ public: { try { FC_ASSERT( !self.is_locked() ); account_object account_obj = get_account(name); - FC_ASSERT( !account_obj.is_prime() ); - - account_update_operation update_op; - update_op.account = account_obj.id; - update_op.num_witness = account_obj.num_witness; - update_op.num_committee = account_obj.num_committee; - update_op.upgrade_to_prime = true; + FC_ASSERT( !account_obj.is_lifetime_member() ); + // TODO signed_transaction tx; - tx.operations.push_back( update_op ); tx.visit( operation_set_fee( _remote_db->get_global_properties().parameters.current_fees ) ); tx.validate(); @@ -802,7 +796,7 @@ public: account_object referrer_account_object = this->get_account( referrer_account ); account_create_op.referrer = referrer_account_object.id; - account_create_op.referrer_percent = referrer_account_object.referrer_percent; + account_create_op.referrer_percent = referrer_account_object.referrer_rewards_percentage; // get pay_from_account_id key_create_operation owner_key_create_op; diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 713ad37d..7be7e4ed 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -676,21 +676,18 @@ void database_fixture::enable_fees( } ); } -void database_fixture::upgrade_to_prime(account_id_type account) +void database_fixture::upgrade_to_lifetime_member(account_id_type account) { - upgrade_to_prime(account(db)); + upgrade_to_lifetime_member(account(db)); } -void database_fixture::upgrade_to_prime( const account_object& account ) +void database_fixture::upgrade_to_lifetime_member( const account_object& account ) { try { - account_update_operation op; - op.account = account.id; - op.upgrade_to_prime = true; - trx.operations.emplace_back(operation(op)); + // TODO db.push_transaction( trx, ~0 ); - FC_ASSERT( account.is_prime() ); + FC_ASSERT( account.is_lifetime_member() ); trx.clear(); } FC_CAPTURE_AND_RETHROW((account)) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 2a80d9cc..66b2ceeb 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -202,8 +202,8 @@ struct database_fixture { void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() ); void fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount ); void enable_fees( share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION ); - void upgrade_to_prime( account_id_type account ); - void upgrade_to_prime( const account_object& account ); + void upgrade_to_lifetime_member( account_id_type account ); + void upgrade_to_lifetime_member( const account_object& account ); void print_market( const string& syma, const string& symb )const; string pretty( const asset& a )const; void print_short_order( const short_order_object& cur )const; diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index 05bb91dc..8aac3ef0 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -434,7 +434,7 @@ BOOST_FIXTURE_TEST_CASE( fired_delegates, database_fixture ) for( int i = 0; i < 15; ++i ) { const auto& account = create_account("delegate" + fc::to_string(i+1), delegate_key_object.id); - upgrade_to_prime(account); + upgrade_to_lifetime_member(account); delegates.insert(create_delegate(account).vote_id); } @@ -980,8 +980,8 @@ BOOST_FIXTURE_TEST_CASE( bogus_signature, database_fixture ) BOOST_FIXTURE_TEST_CASE( voting_account, database_fixture ) { try { ACTORS((nathan)(vikram)); - upgrade_to_prime(nathan_id); - upgrade_to_prime(vikram_id); + upgrade_to_lifetime_member(nathan_id); + upgrade_to_lifetime_member(vikram_id); delegate_id_type nathan_delegate = create_delegate(nathan_id(db)).id; delegate_id_type vikram_delegate = create_delegate(vikram_id(db)).id; diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 25a0eded..c74e182d 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -424,7 +424,7 @@ BOOST_FIXTURE_TEST_CASE( maintenance_interval, database_fixture ) BOOST_CHECK_GT(maintenence_time.sec_since_epoch(), db.head_block_time().sec_since_epoch()); auto initial_properties = db.get_global_properties(); const account_object& nathan = create_account("nathan"); - upgrade_to_prime(nathan); + upgrade_to_lifetime_member(nathan); const delegate_object nathans_delegate = create_delegate(nathan); { account_update_operation op; diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 29c8ec57..79ce95a2 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -186,13 +186,13 @@ BOOST_AUTO_TEST_CASE( update_account ) transfer(account_id_type()(db), nathan, asset(3000000)); enable_fees(); - op.upgrade_to_prime = true; + // TODO: op.upgrade_to_prime = true; op.fee = op.calculate_fee( db.get_global_properties().parameters.current_fees ); trx.operations.push_back(op); db.push_transaction(trx, ~0); BOOST_CHECK( nathan.referrer == nathan.id ); - BOOST_CHECK( nathan.referrer_percent == 100 ); + BOOST_CHECK( nathan.referrer_rewards_percentage == 100 ); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; @@ -1872,7 +1872,7 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test ) BOOST_CHECK_EQUAL(core->dynamic_asset_data_id(db).accumulated_fees.value, 0); account_update_operation uop; uop.account = nathan->get_id(); - uop.upgrade_to_prime = true; + // TODO: uop.upgrade_to_prime = true; trx.set_expiration(db.head_block_id()); trx.operations.push_back(uop); trx.visit(operation_set_fee(db.current_fee_schedule())); @@ -2124,7 +2124,7 @@ BOOST_AUTO_TEST_CASE( unimp_transfer_cashback_test ) const account_object& sam = create_account( "sam" ); transfer(account_id_type()(db), sam, asset(30000)); - upgrade_to_prime(sam); + upgrade_to_lifetime_member(sam); ilog( "Creating alice" ); const account_object& alice = create_account( "alice", sam, sam, 0 ); diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index cb7c11d9..67f11d19 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -388,7 +388,7 @@ BOOST_AUTO_TEST_CASE( mia_feeds ) BOOST_AUTO_TEST_CASE( witness_create ) { try { ACTOR(nathan); - upgrade_to_prime(nathan_id); + upgrade_to_lifetime_member(nathan_id); trx.clear(); witness_id_type nathan_witness_id = create_witness(nathan_id, nathan_key_id, nathan_private_key).id; // Give nathan some voting stake @@ -494,7 +494,7 @@ BOOST_AUTO_TEST_CASE( global_settle_test ) BOOST_AUTO_TEST_CASE( worker_create_test ) { try { ACTOR(nathan); - upgrade_to_prime(nathan_id); + upgrade_to_lifetime_member(nathan_id); generate_block(); { @@ -615,7 +615,7 @@ BOOST_AUTO_TEST_CASE( worker_pay_test ) BOOST_AUTO_TEST_CASE( refund_worker_test ) {try{ ACTOR(nathan); - upgrade_to_prime(nathan_id); + upgrade_to_lifetime_member(nathan_id); generate_block(); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); trx.set_expiration(db.head_block_id()); diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index 8eb3e68e..c896d7fb 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE( issue_whitelist_uia ) INVOKE(create_advanced_uia); const asset_object& advanced = get_asset("ADVANCED"); const account_object& nathan = create_account("nathan"); - upgrade_to_prime(nathan); + upgrade_to_lifetime_member(nathan); trx.clear(); asset_issue_operation op({asset(), advanced.issuer, advanced.amount(1000), nathan.id}); @@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE( transfer_whitelist_uia ) const asset_object& advanced = get_asset("ADVANCED"); const account_object& nathan = get_account("nathan"); const account_object& dan = create_account("dan"); - upgrade_to_prime(dan); + upgrade_to_lifetime_member(dan); trx.clear(); transfer_operation op({advanced.amount(0), nathan.id, dan.id, advanced.amount(100)}); From 18b163b99ee323a69105df6492ca8739c4d6d3e5 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 9 Jun 2015 17:05:30 -0400 Subject: [PATCH 04/20] Progress #31: Fix genesis initialization --- libraries/chain/db_init.cpp | 5 +++++ libraries/chain/include/graphene/chain/account_object.hpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 811f99ce..b2f3dacd 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -153,6 +153,7 @@ void database::init_genesis(const genesis_allocation& initial_allocation) }); const account_object& genesis_account = create( [&](account_object& n) { + n.membership_expiration_date = time_point_sec::maximum(); n.name = "genesis"; n.owner.add_authority(genesis_key.get_id(), 1); n.owner.weight_threshold = 1; @@ -174,6 +175,10 @@ void database::init_genesis(const genesis_allocation& initial_allocation) const account_object& delegate_account = create( [&](account_object& a) { a.active = a.owner = genesis_account.owner; + a.referrer = account_id_type(i); + a.registrar = account_id_type(i); + a.lifetime_referrer = account_id_type(i); + a.membership_expiration_date = fc::time_point_sec::maximum(); a.name = string("init") + fc::to_string(i); a.statistics = stats_obj.id; }); diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 9306a486..be74e87d 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -184,7 +184,7 @@ namespace graphene { namespace chain { /// @return true if this is a lifetime member account; false otherwise. bool is_lifetime_member()const { - return get_id() == referrer; + return membership_expiration_date == time_point_sec::maximum(); } /// @return true if this is a basic account; false otherwise. bool is_basic_account(time_point_sec now)const From 7d96e0c1248a1160840821b6a5612942f625c932 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 9 Jun 2015 17:07:23 -0400 Subject: [PATCH 05/20] Remove annoying log line --- libraries/plugins/witness/witness.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/plugins/witness/witness.cpp b/libraries/plugins/witness/witness.cpp index 8a881f64..71fcafa5 100644 --- a/libraries/plugins/witness/witness.cpp +++ b/libraries/plugins/witness/witness.cpp @@ -140,7 +140,6 @@ void witness_plugin::block_production_loop() // we must control the witness scheduled to produce the next block. if( _witnesses.find( scheduled_witness ) == _witnesses.end() ) { - elog("Not producing block because it's not my turn."); return false; } From 7f0d1ebbed9b895e6310cedc8138af50b0bf621c Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 9 Jun 2015 18:42:18 -0400 Subject: [PATCH 06/20] Progress #31, resolve #21 Declare, but not yet implement, account_upgrade_operation which can upgrade accounts to members. Resolve #21 by removing all the sketchy pointer arithmetic stuff --- .../chain/include/graphene/chain/database.hpp | 1 + .../include/graphene/chain/operations.hpp | 94 +++++--- .../chain/include/graphene/chain/types.hpp | 202 ++++++------------ libraries/chain/operations.cpp | 100 +++++---- .../account_history_plugin.cpp | 7 +- tests/common/database_fixture.cpp | 6 +- tests/tests/operation_tests.cpp | 2 +- 7 files changed, 197 insertions(+), 215 deletions(-) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 7a3f320d..020ae45d 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -412,6 +412,7 @@ namespace graphene { namespace chain { void for_each(T&& t, const account_object& a, seq) { auto l = { (std::get(t)(a), 0)... }; + (void)l; } } template diff --git a/libraries/chain/include/graphene/chain/operations.hpp b/libraries/chain/include/graphene/chain/operations.hpp index 4f86f7e0..d53b649a 100644 --- a/libraries/chain/include/graphene/chain/operations.hpp +++ b/libraries/chain/include/graphene/chain/operations.hpp @@ -113,7 +113,7 @@ namespace graphene { namespace chain { account_id_type fee_payer()const { return fee_paying_account; } void get_required_auth(flat_set& active_auth_set , flat_set&)const; - share_type calculate_fee( const fee_schedule_type& k )const{ return k.at( key_create_fee_type ); } + share_type calculate_fee( const fee_schedule_type& k )const{ return k.key_create_fee; } void validate()const; void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } @@ -193,7 +193,7 @@ namespace graphene { namespace chain { account_id_type fee_payer()const { return authorizing_account; } void get_required_auth(flat_set& active_auth_set, flat_set&)const; void validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT(new_listing < 0x4); } - share_type calculate_fee(const fee_schedule_type& k)const { return k.at(account_whitelist_fee_type); } + share_type calculate_fee(const fee_schedule_type& k)const { return k.account_whitelist_fee; } void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } }; @@ -220,6 +220,62 @@ namespace graphene { namespace chain { void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } }; + /** + * @brief Manage an account's membership status + * @ingroup operations + * + * This operation is used to upgrade an account to a member, or renew its subscription. If an account which is an + * unexpired annual subscription member publishes this operation with @ref upgrade_to_lifetime_member set to false, + * the account's membership expiration date will be pushed backward one year. If a basic account publishes it with + * @ref upgrade_to_lifetime_member set to false, the account will be upgraded to a subscription member with an + * expiration date one year after the processing time of this operation. + * + * Any account may use this operation to become a lifetime member by setting @ref upgrade_to_lifetime_member to + * true. Once an account has become a lifetime member, it may not use this operation anymore. + */ + struct account_upgrade_operation + { + asset fee; + /// The account to upgrade; must not already be a lifetime member + account_id_type account_to_upgrade; + /// If true, the account will be upgraded to a lifetime member; otherwise, it will add a year to the subscription + bool upgrade_to_lifetime_member = false; + + account_id_type fee_payer()const { return account_to_upgrade; } + void get_required_auth(flat_set& active_auth_set , flat_set&)const + { active_auth_set.insert(account_to_upgrade); } + void validate()const; + share_type calculate_fee( const fee_schedule_type& k )const; + void get_balance_delta( balance_accumulator& acc, const operation_result& = asset())const { acc.adjust( fee_payer(), -fee ); } + }; + + /** + * @brief transfers the account to another account while clearing the white list + * @ingroup operations + * + * In theory an account can be transferred by simply updating the authorities, but that kind + * of transfer lacks semantic meaning and is more often done to rotate keys without transferring + * ownership. This operation is used to indicate the legal transfer of title to this account and + * a break in the operation history. + * + * The account_id's owner/active/voting/memo authority should be set to new_owner + * + * This operation will clear the account's whitelist statuses, but not the blacklist statuses. + */ + struct account_transfer_operation + { + asset fee; + account_id_type account_id; + account_id_type new_owner; + + account_id_type fee_payer()const { return account_id; } + void get_required_auth(flat_set& active_auth_set, flat_set&)const; + void validate()const; + share_type calculate_fee( const fee_schedule_type& k )const; + + void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } + }; + /** * @brief Create a delegate object, as a bid to hold a delegate seat on the network. * @ingroup operations @@ -240,34 +296,6 @@ namespace graphene { namespace chain { void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } }; - - /** - * @brief transfers the account to another account while clearing the white list - * @ingroup operations - * - * In theory an account can be transferred by simply updating the authorities, but that kind - * of transfer lacks semantic meaning and is more often done to rotate keys without transferring - * ownership. This operation is used to indicate the legal transfer of title to this account and - * a break in the operation history. - * - * The account_id's owner/active/voting/memo authority should be set to new_owner - * - * This operation will clear the account's whitelist statuses, but not the blacklist statuses. - */ - struct account_transfer_operation - { - asset fee; - account_id_type account_id; - account_id_type new_owner; - - account_id_type fee_payer()const { return account_id; } - void get_required_auth(flat_set& active_auth_set, flat_set&)const; - void validate()const; - share_type calculate_fee( const fee_schedule_type& k )const; - - void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } - }; - /** * @brief Create a witness object, as a bid to hold a witness position on the network. * @ingroup operations @@ -640,7 +668,7 @@ namespace graphene { namespace chain { { active_auth_set.insert(fee_payer()); } void validate()const; share_type calculate_fee( const fee_schedule_type& k )const - { return k.at(asset_update_fee_type); } + { return k.asset_update_fee; } void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); } }; @@ -1565,6 +1593,7 @@ namespace graphene { namespace chain { account_create_operation, account_update_operation, account_whitelist_operation, + account_upgrade_operation, account_transfer_operation, asset_create_operation, asset_update_operation, @@ -1739,10 +1768,10 @@ FC_REFLECT( graphene::chain::account_create_operation, (num_witness)(num_committee)(vote) ) -FC_REFLECT_TYPENAME( fc::flat_set ) FC_REFLECT( graphene::chain::account_update_operation, (fee)(account)(owner)(active)(voting_account)(memo_key)(num_witness)(num_committee)(vote) ) +FC_REFLECT( graphene::chain::account_upgrade_operation, (fee)(account_to_upgrade)(upgrade_to_lifetime_member) ) FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing) FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing, @@ -1840,3 +1869,4 @@ FC_REFLECT( graphene::chain::custom_operation, (fee)(payer)(required_auths)(id)( FC_REFLECT( graphene::chain::void_result, ) FC_REFLECT_TYPENAME( graphene::chain::operation ) +FC_REFLECT_TYPENAME( fc::flat_set ) diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 70b3ecec..75075e00 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -86,53 +86,6 @@ namespace graphene { namespace chain { }; inline bool is_relative( object_id_type o ){ return o.space() == 0; } - /** - * There are many types of fees charged by the network - * for different operations. These fees are published by - * the delegates and can change over time. - */ - enum fee_type - { - key_create_fee_type, ///< the cost to register a public key with the blockchain - account_create_fee_type, ///< the cost to register the cheapest non-free account - account_len8_fee_type, - account_len7_fee_type, - account_len6_fee_type, - account_len5_fee_type, - account_len4_fee_type, - account_len3_fee_type, - account_premium_fee_type, ///< accounts on the reserved list of top 100K domains - account_whitelist_fee_type, ///< the fee to whitelist an account - delegate_create_fee_type, ///< fixed fee for registering as a delegate, used to discourage frivioulous delegates - witness_withdraw_pay_fee_type, ///< fee for withdrawing witness pay - transfer_fee_type, ///< fee for transferring some asset - limit_order_fee_type, ///< fee for placing a limit order in the markets - short_order_fee_type, ///< fee for placing a short order in the markets - publish_feed_fee_type, ///< fee for publishing a price feed - asset_create_fee_type, ///< the cost to register the cheapest asset - asset_update_fee_type, ///< the cost to modify a registered asset - asset_issue_fee_type, ///< the cost to modify a registered asset - asset_fund_fee_pool_fee_type, ///< the cost to add funds to an asset's fee pool - asset_settle_fee_type, ///< the cost to trigger a forced settlement of a market-issued asset - market_fee_type, ///< a percentage charged on market orders - transaction_fee_type, ///< a base price for every transaction - data_fee_type, ///< a price per 1024 bytes of user data - signature_fee_type, ///< a surcharge on transactions with more than 2 signatures. - global_parameters_update_fee_type, ///< the cost to update the global parameters - prime_upgrade_fee_type, ///< the cost to upgrade an account to prime - withdraw_permission_update_fee_type, ///< the cost to create/update a withdraw permission - create_bond_offer_fee_type, - cancel_bond_offer_fee_type, - accept_bond_offer_fee_type, - claim_bond_collateral_fee_type, - file_storage_fee_per_day_type, ///< the cost of leasing a file with 2^16 bytes for 1 day - vesting_balance_create_fee_type, - vesting_balance_withdraw_fee_type, - global_settle_fee_type, - worker_create_fee_type, ///< the cost to create a new worker - worker_delete_fee_type, ///< the cost to delete a worker - FEE_TYPE_COUNT ///< Sentry value which contains the number of different fee types - }; /** * List all object types from all namespaces here so they can @@ -370,53 +323,71 @@ namespace graphene { namespace chain { struct fee_schedule_type { - fee_schedule_type() - { - memset( (char*)this, 0, sizeof(*this) ); - } - void set( uint32_t f, share_type v ){ FC_ASSERT( f < FEE_TYPE_COUNT && v.value <= uint32_t(-1) ); *(&key_create_fee + f) = v.value; } - const share_type at( uint32_t f )const { FC_ASSERT( f < FEE_TYPE_COUNT ); return *(&key_create_fee + f); } - size_t size()const{ return FEE_TYPE_COUNT; } + /** + * @brief The fee_set_visitor struct sets all fees to a particular value in one fell swoop + * + * Example: + * @code + * fee_schedule_type sch; + * // Set all fees to 50 + * fc::reflector::visit(fee_schedule_type::fee_set_visitor{sch, 50}); + * @endcode + */ + struct fee_set_visitor { + fee_schedule_type& f; + uint32_t fee; + template + void operator()(const char*)const + { + f.*member = fee; + } + }; - uint32_t key_create_fee; ///< the cost to register a public key with the blockchain - uint32_t account_create_fee; ///< the cost to register the cheapest non-free account - uint32_t account_len8_fee; - uint32_t account_len7_fee; - uint32_t account_len6_fee; - uint32_t account_len5_fee; - uint32_t account_len4_fee; - uint32_t account_len3_fee; - uint32_t account_premium_fee; ///< accounts on the reserved list of top 100K domains - uint32_t account_whitelist_fee; ///< the fee to whitelist an account - uint32_t delegate_create_fee; ///< fixed fee for registering as a delegate; used to discourage frivioulous delegates - uint32_t witness_withdraw_pay_fee; ///< fee for withdrawing witness pay - uint32_t transfer_fee; ///< fee for transferring some asset - uint32_t limit_order_fee; ///< fee for placing a limit order in the markets - uint32_t short_order_fee; ///< fee for placing a short order in the markets - uint32_t publish_feed_fee; ///< fee for publishing a price feed - uint32_t asset_create_fee; ///< the cost to register the cheapest asset - uint32_t asset_update_fee; ///< the cost to modify a registered asset - uint32_t asset_issue_fee; ///< the cost to modify a registered asset - uint32_t asset_fund_fee_pool_fee; ///< the cost to add funds to an asset's fee pool - uint32_t asset_settle_fee; ///< the cost to trigger a forced settlement of a market-issued asset - uint32_t market_fee; ///< a percentage charged on market orders - uint32_t transaction_fee; ///< a base price for every transaction - uint32_t data_fee; ///< a price per 1024 bytes of user data - uint32_t signature_fee; ///< a surcharge on transactions with more than 2 signatures. - uint32_t global_parameters_update_fee; ///< the cost to update the global parameters - uint32_t prime_upgrade_fee; ///< the cost to upgrade an account to prime - uint32_t withdraw_permission_update_fee; ///< the cost to create/update a withdraw permission - uint32_t create_bond_offer_fee; - uint32_t cancel_bond_offer_fee; - uint32_t accept_bond_offer_fee; - uint32_t claim_bond_collateral_fee; - uint32_t file_storage_fee_per_day; ///< the cost of leasing a file with 2^16 bytes for 1 day - uint32_t vesting_balance_create_fee; - uint32_t vesting_balance_withdraw_fee; - uint32_t global_settle_fee; - uint32_t worker_create_fee; ///< the cost to create a new worker - uint32_t worker_delete_fee; ///< the cost to delete a worker + fee_schedule_type() + { + memset( (char*)this, 0, sizeof(*this) ); + } + + uint32_t key_create_fee; ///< the cost to register a public key with the blockchain + uint32_t account_create_fee; ///< the cost to register the cheapest non-free account + uint32_t account_len8_fee; + uint32_t account_len7_fee; + uint32_t account_len6_fee; + uint32_t account_len5_fee; + uint32_t account_len4_fee; + uint32_t account_len3_fee; + uint32_t account_premium_fee; ///< accounts with premium names; i.e. @ref is_cheap_name returns false + uint32_t account_whitelist_fee; ///< the fee to whitelist an account + uint32_t delegate_create_fee; ///< fixed fee for registering as a delegate; used to discourage frivioulous delegates + uint32_t witness_withdraw_pay_fee; ///< fee for withdrawing witness pay + uint32_t transfer_fee; ///< fee for transferring some asset + uint32_t limit_order_fee; ///< fee for placing a limit order in the markets + uint32_t short_order_fee; ///< fee for placing a short order in the markets + uint32_t publish_feed_fee; ///< fee for publishing a price feed + uint32_t asset_create_fee; ///< the cost to register the cheapest asset + uint32_t asset_update_fee; ///< the cost to modify a registered asset + uint32_t asset_issue_fee; ///< the cost to modify a registered asset + uint32_t asset_fund_fee_pool_fee; ///< the cost to add funds to an asset's fee pool + uint32_t asset_settle_fee; ///< the cost to trigger a forced settlement of a market-issued asset + uint32_t market_fee; ///< a percentage charged on market orders + uint32_t transaction_fee; ///< a base price for every transaction + uint32_t data_fee; ///< a price per 1024 bytes of user data + uint32_t signature_fee; ///< a surcharge on transactions with more than 2 signatures. + uint32_t global_parameters_update_fee; ///< the cost to update the global parameters + uint32_t membership_annual_fee; ///< the annual cost of a membership subscription + uint32_t membership_lifetime_fee; ///< the cost to upgrade to a lifetime member + uint32_t withdraw_permission_update_fee; ///< the cost to create/update a withdraw permission + uint32_t create_bond_offer_fee; + uint32_t cancel_bond_offer_fee; + uint32_t accept_bond_offer_fee; + uint32_t claim_bond_collateral_fee; + uint32_t file_storage_fee_per_day; ///< the cost of leasing a file with 2^16 bytes for 1 day + uint32_t vesting_balance_create_fee; + uint32_t vesting_balance_withdraw_fee; + uint32_t global_settle_fee; + uint32_t worker_create_fee; ///< the cost to create a new worker + uint32_t worker_delete_fee; ///< the cost to delete a worker }; @@ -498,7 +469,6 @@ namespace graphene { namespace chain { "Maximum transaction expiration time must be greater than a block interval" ); FC_ASSERT( maximum_proposal_lifetime - genesis_proposal_review_period > block_interval, "Genesis proposal review period must be less than the maximum proposal lifetime" ); - for( uint32_t i = 0; i < FEE_TYPE_COUNT; ++i ) { FC_ASSERT( current_fees.at(i) >= 0 ); } } }; @@ -588,7 +558,8 @@ FC_REFLECT( graphene::chain::fee_schedule_type, (data_fee) (signature_fee) (global_parameters_update_fee) - (prime_upgrade_fee) + (membership_annual_fee) + (membership_lifetime_fee) (withdraw_permission_update_fee) (create_bond_offer_fee) (cancel_bond_offer_fee) @@ -602,49 +573,6 @@ FC_REFLECT( graphene::chain::fee_schedule_type, (worker_delete_fee) ) - -FC_REFLECT_ENUM( graphene::chain::fee_type, - (key_create_fee_type) - (account_create_fee_type) - (account_len8_fee_type) - (account_len7_fee_type) - (account_len6_fee_type) - (account_len5_fee_type) - (account_len4_fee_type) - (account_len3_fee_type) - (account_premium_fee_type) - (account_whitelist_fee_type) - (delegate_create_fee_type) - (witness_withdraw_pay_fee_type) - (transfer_fee_type) - (limit_order_fee_type) - (short_order_fee_type) - (publish_feed_fee_type) - (asset_create_fee_type) - (asset_update_fee_type) - (asset_issue_fee_type) - (asset_fund_fee_pool_fee_type) - (asset_settle_fee_type) - (market_fee_type) - (transaction_fee_type) - (data_fee_type) - (signature_fee_type) - (global_parameters_update_fee_type) - (prime_upgrade_fee_type) - (withdraw_permission_update_fee_type) - (create_bond_offer_fee_type) - (cancel_bond_offer_fee_type) - (accept_bond_offer_fee_type) - (claim_bond_collateral_fee_type) - (file_storage_fee_per_day_type) - (vesting_balance_create_fee_type) - (vesting_balance_withdraw_fee_type) - (global_settle_fee_type) - (worker_create_fee_type) - (worker_delete_fee_type) - (FEE_TYPE_COUNT) - ) - FC_REFLECT( graphene::chain::chain_parameters, (current_fees) (witness_pay_percent_of_accumulated) diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp index 70c7d75f..3c0f8f0d 100644 --- a/libraries/chain/operations.cpp +++ b/libraries/chain/operations.cpp @@ -108,21 +108,31 @@ bool is_cheap_name( const string& n ) share_type account_create_operation::calculate_fee( const fee_schedule_type& schedule )const { - auto core_fee_required = schedule.at(account_create_fee_type); + auto core_fee_required = schedule.account_create_fee; uint32_t s = name.size(); if( is_cheap_name( name ) ) s = 63; FC_ASSERT( s >= 2 ); - if( s <= 8 ) - core_fee_required = schedule.at(account_create_fee_type+9-s); + if( s == 8 ) + core_fee_required = schedule.account_len8_fee; + else if( s == 7 ) + core_fee_required = schedule.account_len7_fee; + else if( s == 6 ) + core_fee_required = schedule.account_len6_fee; + else if( s == 5 ) + core_fee_required = schedule.account_len5_fee; + else if( s == 4 ) + core_fee_required = schedule.account_len4_fee; + else if( s == 3 ) + core_fee_required = schedule.account_len3_fee; return core_fee_required; } share_type account_update_operation::calculate_fee( const fee_schedule_type& schedule )const { - return schedule.at(account_create_fee_type); + return schedule.account_create_fee; } void account_update_operation::get_required_auth(flat_set& active_auth_set, flat_set& owner_auth_set) const @@ -143,7 +153,7 @@ void account_update_operation::validate()const share_type asset_create_operation::calculate_fee( const fee_schedule_type& schedule )const { - auto core_fee_required = schedule.at(asset_create_fee_type); + auto core_fee_required = schedule.asset_create_fee; uint32_t s = symbol.size(); while( s <= 6 ) { core_fee_required *= 30; ++s; } @@ -153,10 +163,10 @@ share_type asset_create_operation::calculate_fee( const fee_schedule_type& sched share_type transfer_operation::calculate_fee( const fee_schedule_type& schedule )const { - auto core_fee_required = schedule.at( transfer_fee_type ); + share_type core_fee_required = schedule.transfer_fee; if( memo ) { - core_fee_required += share_type((memo->message.size() * schedule.at( data_fee_type ).value)/1024); + core_fee_required += share_type((memo->message.size() * schedule.data_fee)/1024); } return core_fee_required; } @@ -207,7 +217,7 @@ void account_create_operation::validate()const share_type asset_publish_feed_operation::calculate_fee( const fee_schedule_type& schedule )const { - return schedule.at( publish_feed_fee_type ); + return schedule.publish_feed_fee; } void asset_publish_feed_operation::validate()const @@ -278,7 +288,7 @@ void asset_update_operation::validate()const share_type asset_update_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at( asset_update_fee_type ); + return k.asset_update_fee; } void asset_burn_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -295,7 +305,7 @@ void asset_burn_operation::validate()const share_type asset_burn_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at( asset_issue_fee_type ); + return k.asset_issue_fee; } void asset_issue_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -313,12 +323,12 @@ void asset_issue_operation::validate()const share_type asset_issue_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at( asset_issue_fee_type ); + return k.asset_issue_fee; } share_type delegate_create_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at( delegate_create_fee_type ) ; + return k.delegate_create_fee ; } void delegate_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -345,7 +355,7 @@ void asset_fund_fee_pool_operation::validate() const share_type asset_fund_fee_pool_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( asset_fund_fee_pool_fee_type ); + return k.asset_fund_fee_pool_fee; } void limit_order_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -363,7 +373,7 @@ void limit_order_create_operation::validate()const share_type limit_order_create_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( limit_order_fee_type ); + return k.limit_order_fee; } void limit_order_cancel_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -378,7 +388,7 @@ void limit_order_cancel_operation::validate()const share_type limit_order_cancel_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( limit_order_fee_type ); + return k.limit_order_fee; } void short_order_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -396,7 +406,7 @@ void short_order_create_operation::validate()const share_type short_order_create_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( short_order_fee_type ); + return k.short_order_fee; } void short_order_cancel_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const { @@ -410,7 +420,7 @@ void short_order_cancel_operation::validate()const share_type short_order_cancel_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( short_order_fee_type ); + return k.short_order_fee; } void call_order_update_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -432,7 +442,7 @@ void call_order_update_operation::validate()const share_type call_order_update_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( short_order_fee_type ); + return k.short_order_fee; } proposal_create_operation proposal_create_operation::genesis_proposal(const database& db) @@ -514,7 +524,7 @@ void account_transfer_operation::get_required_auth(flat_set& ac share_type account_transfer_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at(transfer_fee_type); + return k.transfer_fee; } @@ -536,7 +546,7 @@ void witness_withdraw_pay_operation::validate() const share_type witness_withdraw_pay_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at(witness_withdraw_pay_fee_type); + return k.witness_withdraw_pay_fee; } void account_whitelist_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -552,7 +562,7 @@ void global_parameters_update_operation::validate() const share_type global_parameters_update_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at(global_parameters_update_fee_type); + return k.global_parameters_update_fee; } void witness_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -567,7 +577,7 @@ void witness_create_operation::validate() const share_type witness_create_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at(delegate_create_fee_type); + return k.delegate_create_fee; } void withdraw_permission_update_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const @@ -586,7 +596,7 @@ void withdraw_permission_update_operation::validate()const share_type withdraw_permission_update_operation::calculate_fee( const fee_schedule_type& schedule )const { - return schedule.at( withdraw_permission_update_fee_type ); + return schedule.withdraw_permission_update_fee; } void withdraw_permission_claim_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const @@ -603,9 +613,9 @@ void withdraw_permission_claim_operation::validate()const share_type withdraw_permission_claim_operation::calculate_fee( const fee_schedule_type& schedule )const { - auto core_fee_required = schedule.at( transfer_fee_type ); + share_type core_fee_required = schedule.transfer_fee; if( memo ) - core_fee_required += share_type((memo->message.size() * schedule.at( data_fee_type ).value)/1024); + core_fee_required += share_type((memo->message.size() * schedule.data_fee)/1024); return core_fee_required; } @@ -622,7 +632,7 @@ void withdraw_permission_delete_operation::validate() const share_type withdraw_permission_delete_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at(withdraw_permission_update_fee_type); + return k.withdraw_permission_update_fee; } void withdraw_permission_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -642,7 +652,7 @@ void withdraw_permission_create_operation::validate() const share_type withdraw_permission_create_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at(withdraw_permission_update_fee_type); + return k.withdraw_permission_update_fee; } @@ -660,7 +670,7 @@ void asset_global_settle_operation::validate()const share_type asset_global_settle_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at(global_settle_fee_type); + return k.global_settle_fee; } void asset_settle_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -676,7 +686,7 @@ void asset_settle_operation::validate() const share_type asset_settle_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at(asset_settle_fee_type); + return k.asset_settle_fee; } @@ -698,7 +708,7 @@ void asset_update_bitasset_operation::validate() const share_type asset_update_bitasset_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( asset_update_fee_type ); + return k.asset_update_fee; } void asset_update_feed_producers_operation::validate() const @@ -716,7 +726,7 @@ void file_write_operation::validate()const share_type file_write_operation::calculate_fee( const fee_schedule_type& k )const { - return ((((k.at( file_storage_fee_per_day_type ).value * lease_seconds)/(60*60*24))*file_size)/0xff) + ((data.size() * k.at( data_fee_type ).value)/1024); + return ((((k.file_storage_fee_per_day * lease_seconds)/(60*60*24))*file_size)/0xff) + ((data.size() * k.data_fee)/1024); } @@ -728,7 +738,7 @@ void vesting_balance_create_operation::get_required_auth(flat_set& active_auth_set, flat_set& )const @@ -824,7 +834,7 @@ void bond_create_offer_operation::validate()const share_type bond_create_offer_operation::calculate_fee( const fee_schedule_type& schedule )const { - return schedule.at( create_bond_offer_fee_type ); + return schedule.create_bond_offer_fee; } @@ -839,7 +849,7 @@ void bond_cancel_offer_operation::validate()const } share_type bond_cancel_offer_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at( cancel_bond_offer_fee_type ); + return k.cancel_bond_offer_fee; } void bond_accept_offer_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const @@ -857,7 +867,7 @@ void bond_accept_offer_operation::validate()const share_type bond_accept_offer_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at( accept_bond_offer_fee_type ); + return k.accept_bond_offer_fee; } void bond_claim_collateral_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const { @@ -874,7 +884,7 @@ void bond_claim_collateral_operation::validate()const share_type bond_claim_collateral_operation::calculate_fee( const fee_schedule_type& k )const { - return k.at( claim_bond_collateral_fee_type ); + return k.claim_bond_collateral_fee; } void worker_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&) const @@ -892,7 +902,7 @@ void worker_create_operation::validate() const share_type worker_create_operation::calculate_fee(const fee_schedule_type& k) const { - return k.at( worker_create_fee_type ); + return k.worker_create_fee; } string memo_message::serialize() const @@ -911,4 +921,16 @@ memo_message memo_message::deserialize(const string& serial) return result; } +void account_upgrade_operation::validate() const +{ + FC_ASSERT( fee.amount >= 0 ); +} + +share_type account_upgrade_operation::calculate_fee(const fee_schedule_type& k) const +{ + if( upgrade_to_lifetime_member ) + return k.membership_lifetime_fee; + return k.membership_annual_fee; +} + } } // namespace graphene::chain diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 54ac8dc2..6ecc2c4f 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -162,6 +162,7 @@ struct operation_get_impacted_accounts add_authority( *o.active ); } } + void operator()( const account_upgrade_operation& )const {} void operator()( const account_transfer_operation& o )const { _impacted.insert( o.new_owner ); @@ -236,12 +237,12 @@ struct operation_get_impacted_accounts void operator()( const bond_create_offer_operation& o )const { } void operator()( const bond_cancel_offer_operation& o )const { } - void operator()( const bond_accept_offer_operation& o )const { + void operator()( const bond_accept_offer_operation& o )const { _impacted.insert( o.borrower ); _impacted.insert( o.lender ); } - void operator()( const bond_claim_collateral_operation& o )const - { + void operator()( const bond_claim_collateral_operation& o )const + { _impacted.insert( o.lender ); _impacted.insert( o.claimer ); } diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 7be7e4ed..c602200f 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -670,9 +670,9 @@ void database_fixture::enable_fees( { db.modify(global_property_id_type()(db), [fee](global_property_object& gpo) { - for( int i=0; i < FEE_TYPE_COUNT; ++i) - gpo.parameters.current_fees.set(i, fee); - gpo.parameters.current_fees.set( prime_upgrade_fee_type, 10*fee.value ); + fc::reflector::visit(fee_schedule_type::fee_set_visitor{gpo.parameters.current_fees, + uint32_t(fee.value)}); + gpo.parameters.current_fees.membership_annual_fee = 10*fee.value; } ); } diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 79ce95a2..17b76f06 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1867,7 +1867,7 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test ) const asset_object* core = &asset_id_type()(db); const account_object* nathan = &get_account("nathan"); enable_fees(100000000); - BOOST_CHECK_GT(db.current_fee_schedule().at(prime_upgrade_fee_type).value, 0); + BOOST_CHECK_GT(db.current_fee_schedule().membership_lifetime_fee, 0); BOOST_CHECK_EQUAL(core->dynamic_asset_data_id(db).accumulated_fees.value, 0); account_update_operation uop; From 4df4e8014a73fbeb8be92a7135206bac93e9bd01 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 10 Jun 2015 10:36:01 -0400 Subject: [PATCH 07/20] Issue #31: Add account_upgrade_evaluator --- .gitmodules | 2 +- libraries/chain/account_evaluator.cpp | 35 ++++++++++++ libraries/chain/db_init.cpp | 1 + .../graphene/chain/account_evaluator.hpp | 57 +++++++++++-------- libraries/fc | 2 +- 5 files changed, 72 insertions(+), 25 deletions(-) diff --git a/.gitmodules b/.gitmodules index cd4ee35e..3e39da4a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "libraries/fc"] path = libraries/fc - url = https://github.com/cryptonomex/fc + url = git@github.com:cryptonomex/fc ignore = dirty [submodule "libraries/leveldb"] path = libraries/leveldb diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 5883459c..e50d4cab 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -206,4 +206,39 @@ void_result account_whitelist_evaluator::do_apply(const account_whitelist_operat return void_result(); } +void_result account_upgrade_evaluator::do_evaluate(const account_upgrade_evaluator::operation_type& o) +{ + database& d = db(); + + account = &d.get(o.account_to_upgrade); + FC_ASSERT(!account->is_lifetime_member()); + + return {}; +} + +void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator::operation_type& o) +{ + database& d = db(); + + d.modify(*account, [&](account_object& a) { + if( o.upgrade_to_lifetime_member ) + { + // Upgrade to lifetime member. I don't care what the account was before. + a.membership_expiration_date = time_point_sec::maximum(); + a.referrer = a.registrar = a.lifetime_referrer = a.get_id(); + } else if( a.is_annual_member(d.head_block_time()) ) { + // Renew an annual subscription that's still in effect. + FC_ASSERT(a.membership_expiration_date - d.head_block_time() < fc::days(3650), + "May not extend annual membership more than a decade into the future."); + a.membership_expiration_date += fc::days(365); + } else { + // Upgrade from basic account. + assert(a.is_basic_account(d.head_block_time())); + a.membership_expiration_date = d.head_block_time() + fc::days(365); + } + }); + + return {}; +} + } } // graphene::chain diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index b2f3dacd..ffc04909 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -64,6 +64,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); diff --git a/libraries/chain/include/graphene/chain/account_evaluator.hpp b/libraries/chain/include/graphene/chain/account_evaluator.hpp index 8520769c..f867c2e4 100644 --- a/libraries/chain/include/graphene/chain/account_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/account_evaluator.hpp @@ -21,35 +21,46 @@ namespace graphene { namespace chain { - class account_create_evaluator : public evaluator - { - public: - typedef account_create_operation operation_type; +class account_create_evaluator : public evaluator +{ +public: + typedef account_create_operation operation_type; - void_result do_evaluate( const account_create_operation& o ); - object_id_type do_apply( const account_create_operation& o ) ; - }; + void_result do_evaluate( const account_create_operation& o ); + object_id_type do_apply( const account_create_operation& o ) ; +}; - class account_update_evaluator : public evaluator - { - public: - typedef account_update_operation operation_type; +class account_update_evaluator : public evaluator +{ +public: + typedef account_update_operation operation_type; - void_result do_evaluate( const account_update_operation& o ); - void_result do_apply( const account_update_operation& o ); + void_result do_evaluate( const account_update_operation& o ); + void_result do_apply( const account_update_operation& o ); - const account_object* acnt; - }; + const account_object* acnt; +}; - class account_whitelist_evaluator : public evaluator - { - public: - typedef account_whitelist_operation operation_type; +class account_upgrade_evaluator : public evaluator +{ +public: + typedef account_upgrade_operation operation_type; - void_result do_evaluate( const account_whitelist_operation& o); - void_result do_apply( const account_whitelist_operation& o); + void_result do_evaluate(const operation_type& o); + void_result do_apply(const operation_type& o); - const account_object* listed_account; - }; + const account_object* account; +}; + +class account_whitelist_evaluator : public evaluator +{ +public: + typedef account_whitelist_operation operation_type; + + void_result do_evaluate( const account_whitelist_operation& o); + void_result do_apply( const account_whitelist_operation& o); + + const account_object* listed_account; +}; } } // graphene::chain diff --git a/libraries/fc b/libraries/fc index 1bbb748c..80366e43 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 1bbb748c4ebbaaf42440cec220562c8c2027cb80 +Subproject commit 80366e4346e85b550b58ce84d5112a1917f17e07 From 876e9a125dce2796e18b107dbd752b040eac578b Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 10 Jun 2015 14:17:13 -0400 Subject: [PATCH 08/20] Resolve #31: Wrap it up and fix the failing tests --- libraries/chain/account_evaluator.cpp | 1 + libraries/chain/db_init.cpp | 4 +++ libraries/chain/db_maint.cpp | 35 ++++++++++-------- .../chain/include/graphene/chain/config.hpp | 1 - .../include/graphene/chain/transaction.hpp | 2 ++ .../chain/include/graphene/chain/types.hpp | 5 +-- libraries/wallet/wallet.cpp | 6 ++-- tests/common/database_fixture.cpp | 11 ++++-- tests/tests/authority_tests.cpp | 1 + tests/tests/operation_tests.cpp | 36 ++++++++++--------- 10 files changed, 60 insertions(+), 42 deletions(-) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index e50d4cab..0e9aed33 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -226,6 +226,7 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator: // Upgrade to lifetime member. I don't care what the account was before. a.membership_expiration_date = time_point_sec::maximum(); a.referrer = a.registrar = a.lifetime_referrer = a.get_id(); + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage; } else if( a.is_annual_member(d.head_block_time()) ) { // Renew an annual subscription that's still in effect. FC_ASSERT(a.membership_expiration_date - d.head_block_time() < fc::days(3650), diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index ffc04909..1b2e343f 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -155,6 +155,8 @@ void database::init_genesis(const genesis_allocation& initial_allocation) const account_object& genesis_account = create( [&](account_object& n) { n.membership_expiration_date = time_point_sec::maximum(); + n.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + n.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; n.name = "genesis"; n.owner.add_authority(genesis_key.get_id(), 1); n.owner.weight_threshold = 1; @@ -179,6 +181,8 @@ void database::init_genesis(const genesis_allocation& initial_allocation) a.referrer = account_id_type(i); a.registrar = account_id_type(i); a.lifetime_referrer = account_id_type(i); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.membership_expiration_date = fc::time_point_sec::maximum(); a.name = string("init") + fc::to_string(i); a.statistics = stats_obj.id; diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index b0adbd1c..84a3597d 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -242,10 +242,10 @@ void database::process_budget() // blocks_to_maint > 0 because time_to_maint > 0, // which means numerator is at least equal to block_interval - share_type available_funds = get_max_budget( now ); + share_type available_funds = get_max_budget(now); share_type witness_budget = gpo.parameters.witness_pay_per_block.value * blocks_to_maint; - witness_budget = std::min( witness_budget, available_funds ); + witness_budget = std::min(witness_budget, available_funds); available_funds -= witness_budget; fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value; @@ -260,21 +260,22 @@ void database::process_budget() available_funds -= worker_budget; share_type leftover_worker_funds = worker_budget; - pay_workers( leftover_worker_funds ); + pay_workers(leftover_worker_funds); available_funds += leftover_worker_funds; - modify( core, [&]( asset_dynamic_data_object& _core ) + modify(core, [&]( asset_dynamic_data_object& _core ) { _core.current_supply = (_core.current_supply + witness_budget + worker_budget - leftover_worker_funds - _core.accumulated_fees); _core.accumulated_fees = 0; - } ); - modify( dpo, [&]( dynamic_global_property_object& _dpo ) + }); + modify(dpo, [&]( dynamic_global_property_object& _dpo ) { + // Should this be +=? _dpo.witness_budget = witness_budget; _dpo.last_budget_time = now; - } ); + }); // available_funds is money we could spend, but don't want to. // we simply let it evaporate back into the reserve. @@ -361,6 +362,11 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g auto cut_fee = [](share_type a, uint16_t p) -> share_type { + if( a == 0 || p == 0 ) + return 0; + if( p == GRAPHENE_100_PERCENT ) + return a; + fc::uint128 r(a.value); r *= p; r /= GRAPHENE_100_PERCENT; @@ -400,10 +406,11 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); share_type accumulated = network_cut - burned; assert( accumulated + burned == network_cut ); - share_type referral = core_fee_total - network_cut; + share_type lifetime_cut = cut_fee(core_fee_total, a.lifetime_referrer_fee_percentage); + share_type referral = core_fee_total - network_cut - lifetime_cut; - d.modify(dynamic_asset_data_id_type()(d), [burned,accumulated](asset_dynamic_data_object& d) { - d.accumulated_fees += accumulated + burned; + d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { + d.accumulated_fees += network_cut; }); d.modify(a.statistics(d), [core_fee_total](account_statistics_object& s) { @@ -413,20 +420,18 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g d.deposit_cashback( a, bulk_cashback ); - assert( referral + bulk_cashback + accumulated + burned == core_fee_subtotal ); - // Potential optimization: Skip some of this math and object lookups by special casing on the account type. // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to // it directly. - share_type lifetime_cut = cut_fee(referral, a.lifetime_referrer_fee_percentage); share_type referrer_cut = cut_fee(referral, a.referrer_rewards_percentage); - share_type registrar_cut = referral - lifetime_cut = referrer_cut; + share_type registrar_cut = referral - referrer_cut; d.deposit_cashback(d.get(a.lifetime_referrer), lifetime_cut); d.deposit_cashback(d.get(a.referrer), referrer_cut); d.deposit_cashback(d.get(a.registrar), registrar_cut); - assert( lifetime_cut + referrer_cut + registrar_cut == referral ); + idump((referrer_cut)(registrar_cut)(bulk_cashback)(accumulated)(burned)(lifetime_cut)(core_fee_subtotal)); + assert( referrer_cut + registrar_cut + bulk_cashback + accumulated + burned + lifetime_cut == core_fee_subtotal ); } } } fees_helper(*this, gpo); diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 75ca540c..1efea9bc 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -81,7 +81,6 @@ #define GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC (60*60*24*365) ///< 1 year #define GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD (GRAPHENE_BLOCKCHAIN_PRECISION*int64_t(100)) #define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) -#define GRAPHENE_DEFAULT_WITNESS_PAY_PERCENT_OF_ACCUMULATED ( 1728000) /// gives a half life of 1 year assuming 1 second blocks #define GRAPHENE_WITNESS_PAY_PERCENT_PRECISION (1000000000) #define GRAPHENE_GENESIS_TIMESTAMP (1431700000) /// Should be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL diff --git a/libraries/chain/include/graphene/chain/transaction.hpp b/libraries/chain/include/graphene/chain/transaction.hpp index 7f947b70..a048fbeb 100644 --- a/libraries/chain/include/graphene/chain/transaction.hpp +++ b/libraries/chain/include/graphene/chain/transaction.hpp @@ -19,6 +19,8 @@ #include #include +#include + // this is for htonl() and ntohl() functions // TODO: write and use FC wrappers for these functions #ifndef WIN32 diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 75075e00..504a059c 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -416,8 +416,7 @@ namespace graphene { namespace chain { struct chain_parameters { - fee_schedule_type current_fees; ///< current schedule of fees, indexed by @ref fee_type - uint32_t witness_pay_percent_of_accumulated = GRAPHENE_DEFAULT_WITNESS_PAY_PERCENT_OF_ACCUMULATED; ///< percentage of accumulated fees in core asset to pay to witnesses for block production + fee_schedule_type current_fees; ///< current schedule of fees uint8_t block_interval = GRAPHENE_DEFAULT_BLOCK_INTERVAL; ///< interval in seconds between blocks uint32_t maintenance_interval = GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL; ///< interval in sections between blockchain maintenance events uint32_t maximum_transaction_size = GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE; ///< maximum allowable size in bytes for a transaction @@ -454,7 +453,6 @@ namespace graphene { namespace chain { FC_ASSERT( bulk_discount_threshold_min <= bulk_discount_threshold_max ); FC_ASSERT( bulk_discount_threshold_min > 0 ); - FC_ASSERT( witness_pay_percent_of_accumulated < GRAPHENE_WITNESS_PAY_PERCENT_PRECISION ); FC_ASSERT( block_interval <= GRAPHENE_MAX_BLOCK_INTERVAL ); FC_ASSERT( block_interval > 0 ); FC_ASSERT( maintenance_interval > block_interval, @@ -575,7 +573,6 @@ FC_REFLECT( graphene::chain::fee_schedule_type, FC_REFLECT( graphene::chain::chain_parameters, (current_fees) - (witness_pay_percent_of_accumulated) (block_interval) (maintenance_interval) (maximum_transaction_size) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 3af8a3be..2d0d6dec 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -763,9 +763,11 @@ public: account_object account_obj = get_account(name); FC_ASSERT( !account_obj.is_lifetime_member() ); - // TODO - signed_transaction tx; + account_upgrade_operation op; + op.account_to_upgrade = account_obj.get_id(); + op.upgrade_to_lifetime_member = true; + tx.operations = {op}; tx.visit( operation_set_fee( _remote_db->get_global_properties().parameters.current_fees ) ); tx.validate(); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index c602200f..6f74f777 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -119,6 +119,7 @@ void database_fixture::verify_asset_supplies( )const for( const account_statistics_object& a : statistics_index ) { reported_core_in_orders += a.total_core_in_orders; + total_balances[asset_id_type()] += a.pending_fees; } for( const limit_order_object& o : db.get_index_type().indices() ) { @@ -672,7 +673,8 @@ void database_fixture::enable_fees( { fc::reflector::visit(fee_schedule_type::fee_set_visitor{gpo.parameters.current_fees, uint32_t(fee.value)}); - gpo.parameters.current_fees.membership_annual_fee = 10*fee.value; + gpo.parameters.current_fees.membership_annual_fee = 3*fee.value; + gpo.parameters.current_fees.membership_lifetime_fee = 10*fee.value; } ); } @@ -685,9 +687,12 @@ void database_fixture::upgrade_to_lifetime_member( const account_object& account { try { - // TODO + account_upgrade_operation op; + op.account_to_upgrade = account.get_id(); + op.upgrade_to_lifetime_member = true; + trx.operations = {op}; db.push_transaction( trx, ~0 ); - FC_ASSERT( account.is_lifetime_member() ); + FC_ASSERT( op.account_to_upgrade(db).is_lifetime_member() ); trx.clear(); } FC_CAPTURE_AND_RETHROW((account)) diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index 8aac3ef0..03585f4b 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -816,6 +816,7 @@ BOOST_FIXTURE_TEST_CASE( max_authority_membership, database_fixture ) private_key_type sam_key = generate_private_key("sam"); account_object sam_account_object = create_account( "sam", sam_key ); + upgrade_to_lifetime_member(sam_account_object); account_object genesis_account_object = genesis_account(db); const asset_object& core = asset_id_type()(db); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 17b76f06..dd669d51 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -107,6 +107,7 @@ BOOST_AUTO_TEST_CASE( child_account ) const auto& nathan_key = register_key(nathan_private_key.get_public_key()); const account_object& nathan = get_account("nathan"); const account_object& root = create_account("root"); + upgrade_to_lifetime_member(root); skip_key_index_test = true; db.modify(nathan, [nathan_key](account_object& a) { @@ -128,7 +129,7 @@ BOOST_AUTO_TEST_CASE( child_account ) BOOST_REQUIRE_THROW(db.push_transaction(trx), fc::exception); trx.signatures.clear(); op.owner = authority(1, account_id_type(nathan.id), 1); - trx.operations.back() = op; + trx.operations = {op}; sign(trx, key_id_type(), fc::ecc::private_key::regenerate(fc::sha256::hash(string("genesis")))); sign(trx, nathan_key.id, nathan_private_key); db.push_transaction(trx); @@ -186,13 +187,16 @@ BOOST_AUTO_TEST_CASE( update_account ) transfer(account_id_type()(db), nathan, asset(3000000)); enable_fees(); - // TODO: op.upgrade_to_prime = true; - op.fee = op.calculate_fee( db.get_global_properties().parameters.current_fees ); - trx.operations.push_back(op); - db.push_transaction(trx, ~0); + { + account_upgrade_operation op; + op.account_to_upgrade = nathan.id; + op.upgrade_to_lifetime_member = true; + op.fee = op.calculate_fee(db.get_global_properties().parameters.current_fees); + trx.operations = {op}; + db.push_transaction(trx, ~0); + } - BOOST_CHECK( nathan.referrer == nathan.id ); - BOOST_CHECK( nathan.referrer_rewards_percentage == 100 ); + BOOST_CHECK( nathan.is_lifetime_member() ); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; @@ -1866,13 +1870,13 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test ) const asset_object* core = &asset_id_type()(db); const account_object* nathan = &get_account("nathan"); - enable_fees(100000000); + enable_fees(105000000); BOOST_CHECK_GT(db.current_fee_schedule().membership_lifetime_fee, 0); BOOST_CHECK_EQUAL(core->dynamic_asset_data_id(db).accumulated_fees.value, 0); - account_update_operation uop; - uop.account = nathan->get_id(); - // TODO: uop.upgrade_to_prime = true; + account_upgrade_operation uop; + uop.account_to_upgrade = nathan->get_id(); + uop.upgrade_to_lifetime_member = true; trx.set_expiration(db.head_block_id()); trx.operations.push_back(uop); trx.visit(operation_set_fee(db.current_fee_schedule())); @@ -1880,17 +1884,13 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test ) trx.sign(key_id_type(),generate_private_key("genesis")); db.push_transaction(trx); trx.clear(); - BOOST_CHECK_EQUAL(get_balance(*nathan, *core), 9000000000); - BOOST_CHECK_EQUAL(core->dynamic_asset_data_id(db).accumulated_fees.value, 210000000); - // TODO: Replace this with another check - //BOOST_CHECK_EQUAL(account_id_type()(db).statistics(db).cashback_rewards.value, 1000000000-210000000); + BOOST_CHECK_EQUAL(get_balance(*nathan, *core), 8950000000); generate_block(); nathan = &get_account("nathan"); core = &asset_id_type()(db); const witness_object* witness = &db.fetch_block_by_number(db.head_block_num())->witness(db); - BOOST_CHECK_GT(core->dynamic_asset_data_id(db).accumulated_fees.value, 0); BOOST_CHECK_EQUAL(witness->accumulated_income.value, 0); auto schedule_maint = [&]() @@ -1900,7 +1900,7 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test ) { _dpo.next_maintenance_time = db.head_block_time() + 1; } ); - } ; + }; // generate some blocks while( db.head_block_num() < 30 ) @@ -1913,6 +1913,8 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test ) // maintenance will be in block 31. time of block 31 - time of block 1 = 30 * 5 seconds. schedule_maint(); + // TODO: Replace this with another check + //BOOST_CHECK_EQUAL(account_id_type()(db).statistics(db).cashback_rewards.value, 1000000000-200000000); // first witness paid from old budget (so no pay) BOOST_CHECK_EQUAL( core->burned(db).value, 0 ); generate_block(); From 4080a2e7ee983f2dbccc585f37a81573c80e9afc Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 10 Jun 2015 15:21:54 -0400 Subject: [PATCH 09/20] Add missing fee for 2-character account names --- libraries/chain/include/graphene/chain/types.hpp | 2 ++ libraries/chain/operations.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 504a059c..91ae6dde 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -357,6 +357,7 @@ namespace graphene { namespace chain { uint32_t account_len5_fee; uint32_t account_len4_fee; uint32_t account_len3_fee; + uint32_t account_len2_fee; uint32_t account_premium_fee; ///< accounts with premium names; i.e. @ref is_cheap_name returns false uint32_t account_whitelist_fee; ///< the fee to whitelist an account uint32_t delegate_create_fee; ///< fixed fee for registering as a delegate; used to discourage frivioulous delegates @@ -538,6 +539,7 @@ FC_REFLECT( graphene::chain::fee_schedule_type, (account_len5_fee) (account_len4_fee) (account_len3_fee) + (account_len2_fee) (account_premium_fee) (account_whitelist_fee) (delegate_create_fee) diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp index 3c0f8f0d..8805895f 100644 --- a/libraries/chain/operations.cpp +++ b/libraries/chain/operations.cpp @@ -127,6 +127,8 @@ share_type account_create_operation::calculate_fee( const fee_schedule_type& sch core_fee_required = schedule.account_len4_fee; else if( s == 3 ) core_fee_required = schedule.account_len3_fee; + else if( s == 2 ) + core_fee_required = schedule.account_len2_fee; return core_fee_required; } From b7436cffce0236539ed1c58876501afd86bc52c7 Mon Sep 17 00:00:00 2001 From: Vikram Rajkumar Date: Wed, 10 Jun 2015 16:31:07 -0400 Subject: [PATCH 10/20] Move docs to submodule for wiki https://github.com/cryptonomex/graphene/wiki --- .gitmodules | 15 ++-- DEV_README.md | 29 -------- build-ubuntu.md | 21 ------ doc/draft-license/HEADER.draft | 14 ---- doc/draft-license/LICENSE.draft | 19 ----- doc/witness-rng.md | 37 --------- doc/witness-scheduler.md | 128 -------------------------------- docs | 1 + 8 files changed, 10 insertions(+), 254 deletions(-) delete mode 100644 DEV_README.md delete mode 100644 build-ubuntu.md delete mode 100644 doc/draft-license/HEADER.draft delete mode 100644 doc/draft-license/LICENSE.draft delete mode 100644 doc/witness-rng.md delete mode 100644 doc/witness-scheduler.md create mode 160000 docs diff --git a/.gitmodules b/.gitmodules index 3e39da4a..c78c21f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,11 @@ +[submodule "docs"] + path = docs + url = git@github.com:cryptonomex/graphene.wiki.git [submodule "libraries/fc"] - path = libraries/fc - url = git@github.com:cryptonomex/fc - ignore = dirty + path = libraries/fc + url = git@github.com:cryptonomex/fc.git + ignore = dirty [submodule "libraries/leveldb"] - path = libraries/leveldb - url = https://github.com/bitcoin/leveldb.git - ignore = dirty + path = libraries/leveldb + url = git@github.com:bitcoin/leveldb.git + ignore = dirty diff --git a/DEV_README.md b/DEV_README.md deleted file mode 100644 index b8133fea..00000000 --- a/DEV_README.md +++ /dev/null @@ -1,29 +0,0 @@ - -How to use fc async to do recurring tasks ------------------------------------------ - - _my_task = fc::async( callable, "My Task" ); - _my_task = fc::schedule( callable, "My Task 2", exec_time ); - -Stuff to know about the code ----------------------------- - -`static_variant` is a *union type* which says "this variable may be either t1 or t2." It is serializable if t1 and t2 are both serializable. - -The file `operations.hpp` documents the available operations, and `database_fixture.hpp` is a good reference for building and submitting transactions for processing. - -Tests also show the way to do many things, but are often cluttered with code that generates corner cases to try to break things in every possible way. - -Visitors are at the end of `operations.hpp` after the large typedef for `operation` as a `static_variant`. TODO: They should be refactored into a separate header. - -Downcasting stuff ------------------ - -- You have an `object_id_type` and want to downcast it to a `key_id_type` : `key_id_type( object_id )` -- You have an `operation_result` and want to downcast it to an `object_id_type` : `op_result.get()` -- Since `operation_result` is a `static_variant`, the above is also how you downcast `static_variant` - -Debugging FC exceptions with GDB --------------------------------- - -- `catch throw` diff --git a/build-ubuntu.md b/build-ubuntu.md deleted file mode 100644 index 5f02ff82..00000000 --- a/build-ubuntu.md +++ /dev/null @@ -1,21 +0,0 @@ - -The Boost which ships with Ubuntu 14.04 LTS is too old. You need to download the Boost tarball for Boost 1.57.0 -(Note, 1.58.0 requires C++14 and will not build on Ubuntu LTS; this requirement was an accident, see ). Build Boost as follows: - - # tarball available at http://sourceforge.net/projects/boost/files/boost/1.57.0/boost_1_57_0.tar.bz2/download - # sha256sum is 910c8c022a33ccec7f088bd65d4f14b466588dda94ba2124e78b8c57db264967 - - BOOST_ROOT=$(echo ~/opt/boost_1_57_0) - - # build Boost from source - cd ~/src/boost_1_57_0 - ./bootstrap.sh --prefix=$BOOST_ROOT - ./b2 link=static variant=debug threading=multi stage - ./b2 link=static variant=debug threading=multi install - -Then we need to tell `cmake` to use the Boost we just built, instead of using the system-wide Boost: - - cd ~/src/graphene - [ -e ./doc/build-ubuntu.md ] && sh -c 'cmake -DBOOST_ROOT="$BOOST_ROOT" -DCMAKE_BUILD_TYPE=Debug .' - -If all goes well, you should see the correct Boost version in the output messages to the above command. diff --git a/doc/draft-license/HEADER.draft b/doc/draft-license/HEADER.draft deleted file mode 100644 index b8e211bd..00000000 --- a/doc/draft-license/HEADER.draft +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This is draft license text currently under development. This license does not apply to anything right now. - * - * Copyright (c) 2015, Cryptonomex, Inc - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. No distribution of binaries compiled from modified versions of the code - * - * 2. Any modifications become property of Cryptonomex, Inc and must be licensed under these same terms - * - * 3. Any hosting provider is authorized to delete any version of this code at the request Cryptonomex, Inc. - */ diff --git a/doc/draft-license/LICENSE.draft b/doc/draft-license/LICENSE.draft deleted file mode 100644 index 90c85fd5..00000000 --- a/doc/draft-license/LICENSE.draft +++ /dev/null @@ -1,19 +0,0 @@ -This is draft license text currently under development. This license does not apply to anything right now. - -Copyright (c) 2015, Cryptonomex, Inc -All rights reserved. - -The graphene source code is proprietary and may not be used for any purpose without written permission from Cryptonomex, Inc. The source code is -provided publicly for the sole purpose of allowing users to audit the code and compile their own executables. Blockchain technology depends upon -a strong consensus on the rules of an open protocol; this source code is provided to completely document the protocol rules (bugs and all). - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. No distribution of binaries compiled from modified versions of the code - -2. Any modifications made to the code become property of Cryptonomex, Inc and must be licensed under these same terms - -3. The software may be forked/cloned on github for the purpose of submitting bug fixes and pull requests. All forked repositories are property of Cryptonomex and by -forking you authorize Github.com to delete your fork at the request Cryptonomex, Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/witness-rng.md b/doc/witness-rng.md deleted file mode 100644 index 672adad2..00000000 --- a/doc/witness-rng.md +++ /dev/null @@ -1,37 +0,0 @@ - -Witness scheduler RNG ---------------------- - -The witness scheduler RNG is a random number generator which uses the -blockchain random number generator state as its seed. - -The witness scheduler RNG creates an infinite stream of random bytes -by computing `sha256( sha256( seed ) + little_endian_64bit(i) )`, increasing -`i` from 0 to 1 to 2, etc. The RNG only runs during a scheduling block, -and `i` starts from `0` in each scheduling block (relying on different -seeds to produce different results). - -This infinite stream of random bytes is equivalent to an infinite -stream of random bits in little bit-endian order. Given a bound `B`, -the bitstream can be used to produce a random number uniformly -distributed in the range `[0, B)` using a sample-and-reject algorithm: - -- Let `n` be the smallest integer such that `2^n >= B`. -- Let `x` be the next `n` bits from the bitstream, interpreted as an integer in little bit-endian order. -- If `x <= B`, return `x`. Otherwise, throw `x` away and repeat. - -The worst-case running time is unbounded, but each iteration has a -termination probability greater than one half. Thus the average-case -running time is `2` iterations, and a running time of more than `N` -iterations will occur (on average) at most once every `2^N` -RNG queries (assuming a worst-case choice of e.g. `B = 2^63+1` for all -queries). Since each RNG query schedules a witness, the query rate -is (over the long term) equal to the block production rate (although -in practice many queries are all performed at once in scheduling -blocks). So while it is, in theory, possible for the algorithm to -require more than 1000 iterations, in practice this will occur on average -only once every `2^1000` blocks (again assuming all queries have -worst-case `B`). - -The sample-and-reject algorithm is totally unbiased; every `x` value -has equal probability. diff --git a/doc/witness-scheduler.md b/doc/witness-scheduler.md deleted file mode 100644 index 3308a393..00000000 --- a/doc/witness-scheduler.md +++ /dev/null @@ -1,128 +0,0 @@ - -Turn/Token witness scheduling algorithm ---------------------------------------- - -The algorithm which determines the order of witnesses is referred -to as the *witness scheduling algorithm*. - -This was designed by a community bounty in thread -https://bitsharestalk.org/index.php/topic,15547.0 -however, Graphene has an additional requirement which -is not taken into account by the solutions in the thread: - -The membership and length of the list of witnesses may change over -time. - -So in this article I'll describe my solution. - -Turns and tokens ----------------- - -The solution is based on terms of *turns* and *tokens*. - -- Newly inserted witnesses start out with a turn and a token. -- In order for a witness to be scheduled, it must have a turn and a token. -- The scheduler maintains a FIFO of witnesses without tokens. -- If no witness has a turn, then the scheduler gives a turn to all witnesses. This is called "emitting a turn." -- While less than half of the witnesses have tokens, give a token to the first witness in the FIFO and remove it from the FIFO. -- Schedule a witness by picking randomly from all witnesses with both a turn and token. -- When a witness is scheduled, it loses its turn and token. - -The generic scheduler ---------------------- - -The generic scheduler implements turns and tokens. It only depends -on the C++11 stdlib and boost (not even using fc). Types provided -by Graphene are template parameters. - -The generic far future scheduler --------------------------------- - -The far future scheduler is implemented with the following rules: - -- Run until you emit a turn. -- Record all witnesses produced. -- Run until you emit a second turn. -- The witnesses produced between the emission of the first turn (exclusive) -and emission of the second turn (inclusive) are called the *far future schedule*. - -Then the schedule for the rest of time is determined by repeating -the future schedule indefinitely. The far future scheduler is required -to give the scheduling algorithm bounded runtime and memory usage even -in chains involving very long gaps. - -Slots ------ - -Due to dynamic block interval, we must carefully keep in mind -the difference between schedule slots and timestamps. A -*schedule slot number* is a positive integer. A slot number of `n` -represents the `n`th next block-interval-aligned timestamp after -the head block. - -Note that the mapping between slot numbers and timestamps will change -if the block interval changes. - -Scheduling blocks ------------------ - -When each block is produced, the blockchain must determine whether -the scheduler needs to be run. If fewer than `num_witnesses` are -scheduled, the scheduler will run until `2*num_witnesses` are scheduled. -A block in which the scheduler runs is called a *scheduling block*. - -Changes in the set of active witnesses do not modify the existing -schedule. Rather, they will be incorporated into new schedule entries -when the scheduler runs in the next scheduling block. Thus, a witness -that has lost an election may still produce 1-2 blocks. Such a witness -is called a *lame duck*. - -Near vs. far schedule ---------------------- - -From a particular chain state, it must be possible to specify a -mapping from slots to witnesses, called the *total witness schedule*. -The total witness schedule is partitioned into a prefix, called the -*near schedule*; the remainder is the *far schedule*. - -When a block occurs, `n` entries are *drained* (removed) from the head -of the total schedule, where `n` is the slot number of the new block -according to its parent block. - -If the block is a scheduling block, the total schedule is further -transformed. The new near schedule contains `2*num_witnesses` entries, -with the previous near schedule as a prefix. The rest of the near -schedule is determined by the current blockchain RNG. - -The new far schedule is determined by running the far future scheduler, -as described above. The far future scheduler also obtains entropy -from the current blockchain RNG. - -As an optimization, the implementation does not run the far future -scheduler until a far-future slot is actually queried. With this -optimization, the only circumstance under which validating nodes must -run the far future scheduler is when a block gap longer than `num_witnesses` -occurs (an extremely rare condition). - -Minimizing impact of selective dropout --------------------------------------- - -The ability of any single malicious witness to affect the results of the -shuffle algorithm is limited because the RNG is based on bit commitment -of the witnesses. However, a malicious witness *is* able to -refuse to produce a block. A run of `m` consecutively scheduled -malicious witnesses can independently make `m` independent choices -of whether to refuse to produce a block. Basically they are able to -control `m` bits of entropy in the shuffle algorithm's output. - -It is difficult-to-impossible to entirely eliminate "the last person -being evil" problem in trustless distributed RNG's. But we can at least -mitigate this vector by rate-limiting changes to the total witness -schedule to a very slow rate. - -If every block schedules a witness, our adversary with `m` malicious -witnesses gets `m` chances per round to selectively drop out in order -to manipulate the shuffle order, allowing `m` attacks per round. -If witnesses are only scheduled once per round, -a selective dropout requires the malicious witness to produce the -scheduling block, limiting the probability to `m/n` attacks per round. diff --git a/docs b/docs new file mode 160000 index 00000000..1b53a8ec --- /dev/null +++ b/docs @@ -0,0 +1 @@ +Subproject commit 1b53a8eca77783d073ce7cc95991447c3f34b927 From e638efdbcb94ee0965bd09ebd7e672b220842331 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Jun 2015 10:34:34 -0400 Subject: [PATCH 11/20] More thorough .gitignore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 2b57e790..7fdb5211 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,21 @@ CMakeCache.txt CMakeFiles Makefile compile_commands.json +*.cmake libraries/utilities/git_revision.cpp +programs/cli_wallet/cli_wallet +programs/js_operation_serializer/js_operation_serializer +programs/witness_node/witness_node + tests/app_test tests/chain_bench tests/chain_test +tests/intense_test +tests/performance_test /doxygen + +witness_node_data_dir +wallet.json From c6a7cdf5a3b40992b4edc2287e8cff53f779e3ef Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Jun 2015 10:31:19 -0400 Subject: [PATCH 12/20] intense_tests: Implement delegate_groups_mc_test --- tests/intense/block_tests.cpp | 74 +++++++++++++++++++++++++++++++++ tests/tests/operation_tests.cpp | 19 --------- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/tests/intense/block_tests.cpp b/tests/intense/block_tests.cpp index f6bca83e..f603fb74 100644 --- a/tests/intense/block_tests.cpp +++ b/tests/intense/block_tests.cpp @@ -16,6 +16,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include @@ -236,4 +237,77 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture ) } } +/** + * To have a secure random number we need to ensure that the same + * delegate does not get to produce two blocks in a row. There is + * always a chance that the last delegate of one round will be the + * first delegate of the next round. + * + * This means that when we shuffle delegates we need to make sure + * that there is at least N/2 delegates between consecutive turns + * of the same delegate. This means that durring the random + * shuffle we need to restrict the placement of delegates to maintain + * this invariant. + * + * This test checks the requirement using Monte Carlo approach + * (produce lots of blocks and check the invariant holds). + */ +BOOST_FIXTURE_TEST_CASE( delegate_groups_mc_test, database_fixture ) +{ + try { + size_t num_witnesses = db.get_global_properties().active_witnesses.size(); + size_t dmin = num_witnesses >> 1; + + vector< witness_id_type > cur_round; + vector< witness_id_type > full_schedule; + // if we make the maximum witness count testable, + // we'll need to enlarge this. + std::bitset< 0x40 > witness_seen; + size_t total_blocks = 1000000; + + cur_round.reserve( num_witnesses ); + full_schedule.reserve( total_blocks ); + + // we assert so the test doesn't continue, which would + // corrupt memory + assert( num_witnesses <= witness_seen.size() ); + + while( full_schedule.size() < total_blocks ) + { + witness_id_type wid = db.get_scheduled_witness( 1 ).first; + full_schedule.push_back( wid ); + cur_round.push_back( wid ); + if( cur_round.size() == num_witnesses ) + { + wdump( (cur_round) ); + // check that the current round contains exactly 1 copy + // of each witness + witness_seen.reset(); + for( const witness_id_type& w : cur_round ) + { + uint64_t inst = w.instance.value; + BOOST_CHECK( !witness_seen.test( inst ) ); + assert( !witness_seen.test( inst ) ); + witness_seen.set( inst ); + } + cur_round.clear(); + } + generate_block(); + } + + for( size_t i=0,m=full_schedule.size(); iaccumulated_income.value, 0); } FC_LOG_AND_RETHROW() } -/** - * To have a secure random number we need to ensure that the same - * delegate does not get to produce two blocks in a row. There is - * always a chance that the last delegate of one round will be the - * first delegate of the next round. - * - * This means that when we shuffle delegates we need to make sure - * that there is at least N/2 delegates between consecutive turns - * of the same delegate. This means that durring the random - * shuffle we need to restrict the placement of delegates to maintain - * this invariant. - */ -BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES( unimp_delegate_groups_test, 1 ) -BOOST_AUTO_TEST_CASE( unimp_delegate_groups_test ) -{ - BOOST_FAIL( "not implemented" ); -} - - /** * This test should simulate a prediction market which means the following: * From a185f864fc10cc8b49e1e7d36cd750996ba276d8 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 11 Jun 2015 13:54:42 -0400 Subject: [PATCH 13/20] Progress #31: Implement threshold for vesting fees --- libraries/chain/account_object.cpp | 20 +++ libraries/chain/account_operations.cpp | 23 --- libraries/chain/db_balance.cpp | 9 +- libraries/chain/db_maint.cpp | 130 ++++++++-------- libraries/chain/evaluator.cpp | 5 +- .../include/graphene/chain/account_object.hpp | 14 +- .../chain/include/graphene/chain/database.hpp | 2 +- .../graphene/chain/vesting_balance_object.hpp | 85 +++++----- libraries/chain/vesting_balance_object.cpp | 145 ++++++++++-------- tests/common/database_fixture.cpp | 2 +- 10 files changed, 234 insertions(+), 201 deletions(-) delete mode 100644 libraries/chain/account_operations.cpp diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 5775713c..59ba30aa 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -36,4 +36,24 @@ void account_balance_object::adjust_balance(const asset& delta) balance += delta.amount; } +uint16_t account_statistics_object::calculate_bulk_discount_percent(const chain_parameters& params) const +{ + uint64_t bulk_discount_percent = 0; + if( lifetime_fees_paid >= params.bulk_discount_threshold_max ) + bulk_discount_percent = params.max_bulk_discount_percent_of_fee; + else if(params.bulk_discount_threshold_max.value != + params.bulk_discount_threshold_min.value) + { + bulk_discount_percent = + (params.max_bulk_discount_percent_of_fee * + (lifetime_fees_paid.value - + params.bulk_discount_threshold_min.value)) / + (params.bulk_discount_threshold_max.value - + params.bulk_discount_threshold_min.value); + } + assert( bulk_discount_percent <= GRAPHENE_100_PERCENT ); + + return bulk_discount_percent; +} + } } // graphene::chain diff --git a/libraries/chain/account_operations.cpp b/libraries/chain/account_operations.cpp deleted file mode 100644 index efd8e65b..00000000 --- a/libraries/chain/account_operations.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include -#include - -namespace graphene { namespace chain { -} } diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index 2c8736e6..791d8488 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -89,7 +89,7 @@ void database::adjust_core_in_orders( const account_object& acnt, asset delta ) } } -void database::deposit_cashback( const account_object& acct, share_type amount ) +void database::deposit_cashback(const account_object& acct, share_type amount, bool require_vesting) { // If we don't have a VBO, or if it has the wrong maturity // due to a policy change, cut it loose. @@ -112,7 +112,10 @@ void database::deposit_cashback( const account_object& acct, share_type amount ) modify( cashback_vb, [&]( vesting_balance_object& obj ) { - obj.deposit( now, amount ); + if( require_vesting ) + obj.deposit(now, amount); + else + obj.deposit_vested(now, amount); } ); return; } @@ -124,7 +127,7 @@ void database::deposit_cashback( const account_object& acct, share_type amount ) cdd_vesting_policy policy; policy.vesting_seconds = global_vesting_seconds; - policy.coin_seconds_earned = 0; + policy.coin_seconds_earned = require_vesting? 0 : amount.value * policy.vesting_seconds; policy.coin_seconds_earned_last_update = now; obj.policy = policy; diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 84a3597d..0db0b6f6 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -357,86 +357,81 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g process_fees_helper(database& d, const global_property_object& gpo) : d(d), props(gpo) {} + share_type cut_fee(share_type a, uint16_t p)const + { + if( a == 0 || p == 0 ) + return 0; + if( p == GRAPHENE_100_PERCENT ) + return a; + + fc::uint128 r(a.value); + r *= p; + r /= GRAPHENE_100_PERCENT; + return r.to_uint64(); + } + + void pay_out_fees(const account_object& account, share_type core_fee_total, bool require_vesting) + { + share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage); + assert( network_cut <= core_fee_total ); + share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); + share_type accumulated = network_cut - burned; + assert( accumulated + burned == network_cut ); + share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage); + share_type referral = core_fee_total - network_cut - lifetime_cut; + + d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { + d.accumulated_fees += network_cut; + }); + + // Potential optimization: Skip some of this math and object lookups by special casing on the account type. + // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to + // it directly. + share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage); + share_type registrar_cut = referral - referrer_cut; + + d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting); + d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting); + d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting); + + assert( referrer_cut + registrar_cut + accumulated + burned + lifetime_cut == core_fee_total ); + } + void operator()(const account_object& a) { const account_statistics_object& stats = a.statistics(d); - auto cut_fee = [](share_type a, uint16_t p) -> share_type - { - if( a == 0 || p == 0 ) - return 0; - if( p == GRAPHENE_100_PERCENT ) - return a; - - fc::uint128 r(a.value); - r *= p; - r /= GRAPHENE_100_PERCENT; - return r.to_uint64(); - }; - if( stats.pending_fees > 0 ) { - share_type core_fee_subtotal(stats.pending_fees); - share_type bulk_cashback = share_type(0); + share_type vesting_fee_subtotal(stats.pending_fees); + share_type vested_fee_subtotal(stats.pending_vested_fees); + share_type vesting_cashback, vested_cashback; + if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min && a.is_member(d.head_block_time()) ) { - uint64_t bulk_discount_percent = 0; - if( stats.lifetime_fees_paid >= props.parameters.bulk_discount_threshold_max ) - bulk_discount_percent = props.parameters.max_bulk_discount_percent_of_fee; - else if(props.parameters.bulk_discount_threshold_max.value != - props.parameters.bulk_discount_threshold_min.value) - { - bulk_discount_percent = - (props.parameters.max_bulk_discount_percent_of_fee * - (stats.lifetime_fees_paid.value - - props.parameters.bulk_discount_threshold_min.value)) / - (props.parameters.bulk_discount_threshold_max.value - - props.parameters.bulk_discount_threshold_min.value); - } - assert( bulk_discount_percent <= GRAPHENE_100_PERCENT ); - assert( bulk_discount_percent >= 0 ); + auto bulk_discount_rate = stats.calculate_bulk_discount_percent(props.parameters); + vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate); + vesting_fee_subtotal -= vesting_cashback; - bulk_cashback = cut_fee(core_fee_subtotal, bulk_discount_percent); - assert( bulk_cashback <= core_fee_subtotal ); + vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate); + vested_fee_subtotal -= vested_cashback; } - share_type core_fee_total = core_fee_subtotal - bulk_cashback; - share_type network_cut = cut_fee(core_fee_total, a.network_fee_percentage); - assert( network_cut <= core_fee_total ); - share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); - share_type accumulated = network_cut - burned; - assert( accumulated + burned == network_cut ); - share_type lifetime_cut = cut_fee(core_fee_total, a.lifetime_referrer_fee_percentage); - share_type referral = core_fee_total - network_cut - lifetime_cut; + pay_out_fees(a, vesting_fee_subtotal, true); + d.deposit_cashback(a, vesting_cashback, true); + pay_out_fees(a, vested_fee_subtotal, false); + d.deposit_cashback(a, vested_cashback, false); - d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { - d.accumulated_fees += network_cut; - }); - - d.modify(a.statistics(d), [core_fee_total](account_statistics_object& s) { - s.lifetime_fees_paid += core_fee_total; + d.modify(stats, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) { + s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal; s.pending_fees = 0; + s.pending_vested_fees = 0; }); - - d.deposit_cashback( a, bulk_cashback ); - - // Potential optimization: Skip some of this math and object lookups by special casing on the account type. - // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to - // it directly. - share_type referrer_cut = cut_fee(referral, a.referrer_rewards_percentage); - share_type registrar_cut = referral - referrer_cut; - - d.deposit_cashback(d.get(a.lifetime_referrer), lifetime_cut); - d.deposit_cashback(d.get(a.referrer), referrer_cut); - d.deposit_cashback(d.get(a.registrar), registrar_cut); - - idump((referrer_cut)(registrar_cut)(bulk_cashback)(accumulated)(burned)(lifetime_cut)(core_fee_subtotal)); - assert( referrer_cut + registrar_cut + bulk_cashback + accumulated + burned + lifetime_cut == core_fee_subtotal ); - } + } } - } fees_helper(*this, gpo); + } fee_helper(*this, gpo); - perform_account_maintenance(std::tie(tally_helper, fees_helper)); + perform_account_maintenance(std::tie(tally_helper, fee_helper)); struct clear_canary { clear_canary(vector& target): target(target){} @@ -451,9 +446,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g update_active_witnesses(); update_active_delegates(); - const global_property_object& global_properties = get_global_properties(); - if( global_properties.pending_parameters ) - modify(get_global_properties(), [](global_property_object& p) { + if( gpo.pending_parameters ) + modify(gpo, [](global_property_object& p) { p.parameters = std::move(*p.pending_parameters); p.pending_parameters.reset(); }); @@ -472,7 +466,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } auto next_maintenance_time = get(dynamic_global_property_id_type()).next_maintenance_time; - auto maintenance_interval = get_global_properties().parameters.maintenance_interval; + auto maintenance_interval = gpo.parameters.maintenance_interval; if( next_maintenance_time <= next_block.timestamp ) { diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 74cf5c14..46117211 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -67,7 +67,10 @@ namespace graphene { namespace chain { d.fee_pool -= core_fee_paid; }); db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) { - s.pending_fees += core_fee_paid; + if( core_fee_paid > db().get_global_properties().parameters.cashback_vesting_threshold ) + s.pending_fees += core_fee_paid; + else + s.pending_vested_fees += core_fee_paid; }); } FC_CAPTURE_AND_RETHROW() } diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index be74e87d..c41a15fc 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -62,11 +62,20 @@ namespace graphene { namespace chain { /** * Tracks the fees paid by this account which have not been disseminated to the various parties that receive * them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid - * doing massive amounts of uint128 math on each and every operation. + * doing massive amounts of uint128 arithmetic on each and every operation. * - * These fees will be paid out and this counter will reset during the maintenance interval. + *These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance + *interval. */ share_type pending_fees; + /** + * Same as @ref pending_fees, except these fees will be paid out as pre-vested cash-back (immediately + * available for withdrawal) rather than requiring the normal vesting period. + */ + share_type pending_vested_fees; + + /// @brief Calculate the percentage discount this user receives on his fees + uint16_t calculate_bulk_discount_percent(const chain_parameters& params)const; }; /** @@ -290,5 +299,6 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (graphene::chain (most_recent_op) (total_core_in_orders) (lifetime_fees_paid) + (pending_fees)(pending_vested_fees) ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 020ae45d..469a78ad 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -254,7 +254,7 @@ namespace graphene { namespace chain { void adjust_core_in_orders( const account_object& acnt, asset delta ); // helper to handle cashback rewards - void deposit_cashback( const account_object& acct, share_type amount ); + void deposit_cashback(const account_object& acct, share_type amount, bool require_vesting = true); //////////////////// db_debug.cpp //////////////////// diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index f2465573..c0e32347 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -35,9 +35,9 @@ namespace graphene { namespace chain { vesting_policy_context( asset _balance, fc::time_point_sec _now, - asset _amount ) - : balance( _balance ), now( _now ), amount( _amount ) {} - + asset _amount) + : balance(_balance), now(_now), amount(_amount) {} + asset balance; fc::time_point_sec now; asset amount; @@ -53,11 +53,14 @@ namespace graphene { namespace chain { share_type begin_balance; // same asset as balance share_type total_withdrawn; // same asset as balance - asset get_allowed_withdraw( const vesting_policy_context& ctx )const; - bool is_deposit_allowed( const vesting_policy_context& ctx )const; - bool is_withdraw_allowed( const vesting_policy_context& ctx )const; - void on_deposit( const vesting_policy_context& ctx ); - void on_withdraw( const vesting_policy_context& ctx ); + asset get_allowed_withdraw(const vesting_policy_context& ctx)const; + bool is_deposit_allowed(const vesting_policy_context& ctx)const; + bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; } + bool is_withdraw_allowed(const vesting_policy_context& ctx)const; + void on_deposit(const vesting_policy_context& ctx); + void on_deposit_vested(const vesting_policy_context&) + { FC_THROW( "May not deposit vested into a linear vesting balance." ); } + void on_withdraw(const vesting_policy_context& ctx); }; struct cdd_vesting_policy @@ -71,20 +74,22 @@ namespace graphene { namespace chain { * non-destructively figure out how many coin seconds * are available. */ - fc::uint128_t compute_coin_seconds_earned( const vesting_policy_context& ctx )const; + fc::uint128_t compute_coin_seconds_earned(const vesting_policy_context& ctx)const; /** * Update coin_seconds_earned and * coin_seconds_earned_last_update fields; called by both * on_deposit() and on_withdraw(). */ - void update_coin_seconds_earned( const vesting_policy_context& ctx ); + void update_coin_seconds_earned(const vesting_policy_context& ctx); - asset get_allowed_withdraw( const vesting_policy_context& ctx )const; - bool is_deposit_allowed( const vesting_policy_context& ctx )const; - bool is_withdraw_allowed( const vesting_policy_context& ctx )const; - void on_deposit( const vesting_policy_context& ctx ); - void on_withdraw( const vesting_policy_context& ctx ); + asset get_allowed_withdraw(const vesting_policy_context& ctx)const; + bool is_deposit_allowed(const vesting_policy_context& ctx)const; + bool is_deposit_vested_allowed(const vesting_policy_context& ctx)const; + bool is_withdraw_allowed(const vesting_policy_context& ctx)const; + void on_deposit(const vesting_policy_context& ctx); + void on_deposit_vested(const vesting_policy_context& ctx); + void on_withdraw(const vesting_policy_context& ctx); }; typedef fc::static_variant< @@ -102,17 +107,21 @@ namespace graphene { namespace chain { static const uint8_t space_id = protocol_ids; static const uint8_t type_id = vesting_balance_object_type; - account_id_type owner; - asset balance; - vesting_policy policy; + account_id_type owner; + asset balance; + vesting_policy policy; vesting_balance_object() {} /** * Used to increase existing vesting balances. */ - void deposit( const fc::time_point_sec& now, const asset& amount ); - bool is_deposit_allowed( const fc::time_point_sec& now, const asset& amount )const; + void deposit(const fc::time_point_sec& now, const asset& amount); + bool is_deposit_allowed(const fc::time_point_sec& now, const asset& amount)const; + + /// @brief Deposit amount into vesting balance, making the new funds vest immediately + void deposit_vested(const fc::time_point_sec& now, const asset& amount); + bool is_deposit_vested_allowed(const fc::time_point_sec& now, const asset& amount)const; /** * Used to remove a vesting balance from the VBO. As well @@ -122,27 +131,27 @@ namespace graphene { namespace chain { * The money doesn't "go" anywhere; the caller is responsible * for crediting it to the proper account. */ - void withdraw( const fc::time_point_sec& now, const asset& amount ); - bool is_withdraw_allowed( const fc::time_point_sec& now, const asset& amount )const; + void withdraw(const fc::time_point_sec& now, const asset& amount); + bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const; }; } } // graphene::chain -FC_REFLECT( graphene::chain::linear_vesting_policy, - (vesting_seconds) - (begin_date) - (begin_balance) - (total_withdrawn) -) +FC_REFLECT(graphene::chain::linear_vesting_policy, + (vesting_seconds) + (begin_date) + (begin_balance) + (total_withdrawn) + ) -FC_REFLECT( graphene::chain::cdd_vesting_policy, - (vesting_seconds) - (coin_seconds_earned) - (coin_seconds_earned_last_update) -) +FC_REFLECT(graphene::chain::cdd_vesting_policy, + (vesting_seconds) + (coin_seconds_earned) + (coin_seconds_earned_last_update) + ) -FC_REFLECT_DERIVED( graphene::chain::vesting_balance_object, (graphene::db::object), - (owner) - (balance) - (policy) -) +FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object), + (owner) + (balance) + (policy) + ) diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index 022e892d..bfc91de5 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -20,66 +20,64 @@ namespace graphene { namespace chain { -inline bool sum_below_max_shares( const asset& a, const asset& b ) +inline bool sum_below_max_shares(const asset& a, const asset& b) { - assert( GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY ); - return ( a.amount <= GRAPHENE_MAX_SHARE_SUPPLY) + assert(GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY); + return (a.amount <= GRAPHENE_MAX_SHARE_SUPPLY) && ( b.amount <= GRAPHENE_MAX_SHARE_SUPPLY) && ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY) ; } -asset linear_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx ) const +asset linear_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx) const { - if( ctx.now <= begin_date ) - return asset( 0, ctx.balance.asset_id ); - if( vesting_seconds == 0 ) + if(ctx.now <= begin_date) + return asset(0, ctx.balance.asset_id); + if(vesting_seconds == 0) return ctx.balance; int64_t elapsed_seconds = (ctx.now - begin_date).to_seconds(); // if elapsed_seconds <= 0, then ctx.now <= begin_date, // and we should have returned above. - assert( elapsed_seconds > 0 ); + assert(elapsed_seconds > 0); fc::uint128_t total_allowed = begin_balance.value; - total_allowed *= uint64_t( elapsed_seconds ); + total_allowed *= uint64_t(elapsed_seconds); total_allowed /= vesting_seconds; - if( total_allowed <= total_withdrawn.value ) - return asset( 0, ctx.balance.asset_id ); + if(total_allowed <= total_withdrawn.value) + return asset(0, ctx.balance.asset_id); total_allowed -= total_withdrawn.value; - FC_ASSERT( total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY ); - return asset( total_allowed.to_uint64(), ctx.balance.asset_id ); + FC_ASSERT(total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY); + return asset(total_allowed.to_uint64(), ctx.balance.asset_id); } -void linear_vesting_policy::on_deposit( const vesting_policy_context& ctx ) +void linear_vesting_policy::on_deposit(const vesting_policy_context& ctx) { - return; } -bool linear_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const +bool linear_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const { return (ctx.amount.asset_id == ctx.balance.asset_id) - && sum_below_max_shares( ctx.amount, ctx.balance ); + && sum_below_max_shares(ctx.amount, ctx.balance); } -void linear_vesting_policy::on_withdraw( const vesting_policy_context& ctx ) +void linear_vesting_policy::on_withdraw(const vesting_policy_context& ctx) { total_withdrawn += ctx.amount.amount; - return; } -bool linear_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const +bool linear_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const { - return ( ctx.amount <= get_allowed_withdraw( ctx ) ); + return (ctx.amount <= get_allowed_withdraw(ctx)); } -fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_policy_context& ctx )const +fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned(const vesting_policy_context& ctx)const { - assert( ctx.now >= coin_seconds_earned_last_update ); + assert(ctx.now >= coin_seconds_earned_last_update); int64_t delta_seconds = (ctx.now - coin_seconds_earned_last_update).to_seconds(); - assert( delta_seconds >= 0 ); + assert(delta_seconds >= 0); fc::uint128_t delta_coin_seconds = ctx.balance.amount.value; delta_coin_seconds *= delta_seconds; @@ -87,113 +85,132 @@ fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_pol fc::uint128_t coin_seconds_earned_cap = ctx.balance.amount.value; coin_seconds_earned_cap *= vesting_seconds; - return std::min( - coin_seconds_earned + delta_coin_seconds, - coin_seconds_earned_cap - ); + return std::min(coin_seconds_earned + delta_coin_seconds, coin_seconds_earned_cap); } -void cdd_vesting_policy::update_coin_seconds_earned( const vesting_policy_context& ctx ) +void cdd_vesting_policy::update_coin_seconds_earned(const vesting_policy_context& ctx) { - coin_seconds_earned = compute_coin_seconds_earned( ctx ); + coin_seconds_earned = compute_coin_seconds_earned(ctx); coin_seconds_earned_last_update = ctx.now; - return; } -asset cdd_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const +asset cdd_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx)const { - fc::uint128_t cs_earned = compute_coin_seconds_earned( ctx ); + fc::uint128_t cs_earned = compute_coin_seconds_earned(ctx); fc::uint128_t withdraw_available = cs_earned / vesting_seconds; - assert( withdraw_available <= ctx.balance.amount.value ); - return asset( withdraw_available.to_uint64(), ctx.balance.asset_id ); + assert(withdraw_available <= ctx.balance.amount.value); + return asset(withdraw_available.to_uint64(), ctx.balance.asset_id); } -void cdd_vesting_policy::on_deposit( const vesting_policy_context& ctx ) +void cdd_vesting_policy::on_deposit(const vesting_policy_context& ctx) { - update_coin_seconds_earned( ctx ); - return; + update_coin_seconds_earned(ctx); } -void cdd_vesting_policy::on_withdraw( const vesting_policy_context& ctx ) +void cdd_vesting_policy::on_deposit_vested(const vesting_policy_context& ctx) { - update_coin_seconds_earned( ctx ); + on_deposit(ctx); + coin_seconds_earned += ctx.amount.amount.value * vesting_seconds; +} + +void cdd_vesting_policy::on_withdraw(const vesting_policy_context& ctx) +{ + update_coin_seconds_earned(ctx); fc::uint128_t coin_seconds_needed = ctx.amount.amount.value; coin_seconds_needed *= vesting_seconds; // is_withdraw_allowed should forbid any withdrawal that // would trigger this assert - assert( coin_seconds_needed <= coin_seconds_earned ); + assert(coin_seconds_needed <= coin_seconds_earned); coin_seconds_earned -= coin_seconds_needed; - return; } -bool cdd_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const +bool cdd_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const { return (ctx.amount.asset_id == ctx.balance.asset_id) - && sum_below_max_shares( ctx.amount, ctx.balance ); + && sum_below_max_shares(ctx.amount, ctx.balance); } -bool cdd_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const +bool cdd_vesting_policy::is_deposit_vested_allowed(const vesting_policy_context& ctx) const { - return ( ctx.amount <= get_allowed_withdraw( ctx ) ); + return is_deposit_allowed(ctx); } -#define VESTING_VISITOR( NAME, MAYBE_CONST ) \ +bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount <= get_allowed_withdraw(ctx)); +} + +#define VESTING_VISITOR(NAME, MAYBE_CONST) \ struct NAME ## _visitor \ { \ typedef decltype( \ std::declval().NAME( \ std::declval()) \ - ) result_type; \ + ) result_type; \ \ NAME ## _visitor( \ const asset& balance, \ const time_point_sec& now, \ const asset& amount \ - ) \ - : ctx( balance, now, amount ) {} \ + ) \ + : ctx(balance, now, amount) {} \ \ template< typename Policy > \ result_type \ - operator()( MAYBE_CONST Policy& policy ) MAYBE_CONST \ + operator()(MAYBE_CONST Policy& policy) MAYBE_CONST \ { \ - return policy.NAME( ctx ); \ + return policy.NAME(ctx); \ } \ \ vesting_policy_context ctx; \ } -VESTING_VISITOR( on_deposit, ); -VESTING_VISITOR( on_withdraw, ); -VESTING_VISITOR( is_deposit_allowed, const ); -VESTING_VISITOR( is_withdraw_allowed, const ); +VESTING_VISITOR(on_deposit,); +VESTING_VISITOR(on_deposit_vested,); +VESTING_VISITOR(on_withdraw,); +VESTING_VISITOR(is_deposit_allowed, const); +VESTING_VISITOR(is_deposit_vested_allowed, const); +VESTING_VISITOR(is_withdraw_allowed, const); bool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const { - return policy.visit( is_deposit_allowed_visitor( balance, now, amount ) ); + return policy.visit(is_deposit_allowed_visitor(balance, now, amount)); } bool vesting_balance_object::is_withdraw_allowed(const time_point_sec& now, const asset& amount)const { - bool result = policy.visit( is_withdraw_allowed_visitor( balance, now, amount ) ); + bool result = policy.visit(is_withdraw_allowed_visitor(balance, now, amount)); // if some policy allows you to withdraw more than your balance, // there's a programming bug in the policy algorithm - assert( (amount <= balance) || (!result) ); + assert((amount <= balance) || (!result)); return result; } void vesting_balance_object::deposit(const time_point_sec& now, const asset& amount) { - on_deposit_visitor vtor( balance, now, amount ); - policy.visit( vtor ); + on_deposit_visitor vtor(balance, now, amount); + policy.visit(vtor); balance += amount; } +void vesting_balance_object::deposit_vested(const time_point_sec& now, const asset& amount) +{ + on_deposit_vested_visitor vtor(balance, now, amount); + policy.visit(vtor); + balance += amount; +} + +bool vesting_balance_object::is_deposit_vested_allowed(const time_point_sec& now, const asset& amount) const +{ + return policy.visit(is_deposit_vested_allowed_visitor(balance, now, amount)); +} + void vesting_balance_object::withdraw(const time_point_sec& now, const asset& amount) { - assert( amount <= balance ); - on_withdraw_visitor vtor( balance, now, amount ); - policy.visit( vtor ); + assert(amount <= balance); + on_withdraw_visitor vtor(balance, now, amount); + policy.visit(vtor); balance -= amount; } diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 6f74f777..1717a04a 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -119,7 +119,7 @@ void database_fixture::verify_asset_supplies( )const for( const account_statistics_object& a : statistics_index ) { reported_core_in_orders += a.total_core_in_orders; - total_balances[asset_id_type()] += a.pending_fees; + total_balances[asset_id_type()] += a.pending_fees + a.pending_vested_fees; } for( const limit_order_object& o : db.get_index_type().indices() ) { From 93ecffc8a8c18bfd5f2fcbfed102f8dd644ff3c1 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Thu, 11 Jun 2015 14:37:22 -0400 Subject: [PATCH 14/20] When get_account_balances is called with an empty list of assets, return balances for all assets. This fixes the wallet's list_account_balances, which always returned an empty list. --- libraries/app/api.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index aa52ff60..98d25611 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -155,10 +155,22 @@ namespace graphene { namespace app { vector database_api::get_account_balances(account_id_type acnt, const flat_set& assets)const { - vector result; result.reserve(assets.size()); - - std::transform(assets.begin(), assets.end(), std::back_inserter(result), - [this, acnt](asset_id_type id) { return _db.get_balance(acnt, id); }); + vector result; + if (assets.empty()) + { + // if the caller passes in an empty list of assets, return balances for all assets the account owns + const account_balance_index& balance_index = _db.get_index_type(); + auto range = balance_index.indices().get().equal_range(acnt); + for (const account_balance_object& balance : boost::make_iterator_range(range.first, range.second)) + result.push_back(asset(balance.get_balance())); + } + else + { + result.reserve(assets.size()); + + std::transform(assets.begin(), assets.end(), std::back_inserter(result), + [this, acnt](asset_id_type id) { return _db.get_balance(acnt, id); }); + } return result; } From b7a44c6e6af6eda6898752a57b58db1a855d85f9 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Jun 2015 16:33:12 -0400 Subject: [PATCH 15/20] intense_tests: Implement generic_scheduler_mc_test --- tests/intense/block_tests.cpp | 89 +++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tests/intense/block_tests.cpp b/tests/intense/block_tests.cpp index f603fb74..eec68d4e 100644 --- a/tests/intense/block_tests.cpp +++ b/tests/intense/block_tests.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -310,4 +311,92 @@ BOOST_FIXTURE_TEST_CASE( delegate_groups_mc_test, database_fixture ) } } +/** + * To have a secure random number we need to ensure that the same + * delegate does not get to produce two blocks in a row. There is + * always a chance that the last delegate of one round will be the + * first delegate of the next round. + * + * This means that when we shuffle delegates we need to make sure + * that there is at least N/2 delegates between consecutive turns + * of the same delegate. This means that durring the random + * shuffle we need to restrict the placement of delegates to maintain + * this invariant. + * + * This test checks the requirement using Monte Carlo approach + * (produce lots of blocks and check the invariant holds). + */ +BOOST_FIXTURE_TEST_CASE( generic_scheduler_mc_test, database_fixture ) +{ + try { + size_t num_witnesses = db.get_global_properties().active_witnesses.size(); + size_t dmin = num_witnesses >> 1; + witness_scheduler_rng rng( + // - - - - + - - - - 1 - - - - + - - - - 2 - - - - + - - - + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + ); + witness_scheduler scheduler; + vector< witness_id_type > witness_ids; + + witness_ids.reserve( num_witnesses ); + for( size_t i=0; i cur_round; + vector< witness_id_type > full_schedule; + // if we make the maximum witness count testable, + // we'll need to enlarge this. + std::bitset< 0x40 > witness_seen; + size_t total_blocks = 1000000; + + cur_round.reserve( num_witnesses ); + full_schedule.reserve( total_blocks ); + + // we assert so the test doesn't continue, which would + // corrupt memory + assert( num_witnesses <= witness_seen.size() ); + + while( full_schedule.size() < total_blocks ) + { + scheduler.produce_schedule( rng ); + witness_id_type wid = scheduler.consume_schedule(); + full_schedule.push_back( wid ); + cur_round.push_back( wid ); + if( cur_round.size() == num_witnesses ) + { + // check that the current round contains exactly 1 copy + // of each witness + witness_seen.reset(); + for( const witness_id_type& w : cur_round ) + { + uint64_t inst = w.instance.value; + BOOST_CHECK( !witness_seen.test( inst ) ); + assert( !witness_seen.test( inst ) ); + witness_seen.set( inst ); + } + cur_round.clear(); + } + } + + for( size_t i=0,m=full_schedule.size(); i Date: Thu, 11 Jun 2015 17:00:32 -0400 Subject: [PATCH 16/20] db_witness_schedule.cpp: Fix off-by-one translating between DB-level slot_num and scheduler-level slot_num --- libraries/chain/db_witness_schedule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index b19cfefe..92e75afb 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -35,7 +35,7 @@ pair database::get_scheduled_witness(uint32_t slot_num)co // ask the near scheduler who goes in the given slot witness_id_type wid; - bool slot_is_near = wso.scheduler.get_slot(slot_num, wid); + bool slot_is_near = wso.scheduler.get_slot(slot_num-1, wid); if( ! slot_is_near ) { // if the near scheduler doesn't know, we have to extend it to @@ -47,7 +47,7 @@ pair database::get_scheduled_witness(uint32_t slot_num)co far_future_witness_scheduler far_scheduler = far_future_witness_scheduler(wso.scheduler, far_rng); - if( !far_scheduler.get_slot(slot_num, wid) ) + if( !far_scheduler.get_slot(slot_num-1, wid) ) { // no scheduled witness -- somebody set up us the bomb // n.b. this code path is impossible, the present @@ -84,7 +84,7 @@ vector database::get_near_witness_schedule()const vector result; result.reserve(wso.scheduler.size()); - uint32_t slot_num = 1; + uint32_t slot_num = 0; witness_id_type wid; while( wso.scheduler.get_slot(slot_num++, wid) ) From a34cde2178f241c7aa093bd8bd160a7de7d7c9f6 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Jun 2015 17:03:58 -0400 Subject: [PATCH 17/20] db_init.cpp: Fix compiler warning --- libraries/chain/db_init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 1b2e343f..7125932c 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -219,7 +219,7 @@ void database::init_genesis(const genesis_allocation& initial_allocation) _wso.scheduler._min_token_count = init_witnesses.size() / 2; _wso.scheduler.update( init_witness_set ); - for( int i=0; i Date: Thu, 11 Jun 2015 17:12:37 -0400 Subject: [PATCH 18/20] delegate_groups_mc_test: Fix round alignment problem due to generate_block() in database_fixture ctor --- tests/intense/block_tests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/intense/block_tests.cpp b/tests/intense/block_tests.cpp index eec68d4e..b547fa13 100644 --- a/tests/intense/block_tests.cpp +++ b/tests/intense/block_tests.cpp @@ -268,6 +268,7 @@ BOOST_FIXTURE_TEST_CASE( delegate_groups_mc_test, database_fixture ) cur_round.reserve( num_witnesses ); full_schedule.reserve( total_blocks ); + cur_round.push_back( db.get_dynamic_global_properties().current_witness ); // we assert so the test doesn't continue, which would // corrupt memory From 82cce44720fd04b04a139d2d215197e654b9d9aa Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Jun 2015 17:50:43 -0400 Subject: [PATCH 19/20] witness_order_mc_test: Renamed from delegate_groups_mc_test, less verbose logging --- tests/intense/block_tests.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/intense/block_tests.cpp b/tests/intense/block_tests.cpp index b547fa13..993cf074 100644 --- a/tests/intense/block_tests.cpp +++ b/tests/intense/block_tests.cpp @@ -253,7 +253,7 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture ) * This test checks the requirement using Monte Carlo approach * (produce lots of blocks and check the invariant holds). */ -BOOST_FIXTURE_TEST_CASE( delegate_groups_mc_test, database_fixture ) +BOOST_FIXTURE_TEST_CASE( witness_order_mc_test, database_fixture ) { try { size_t num_witnesses = db.get_global_properties().active_witnesses.size(); @@ -276,12 +276,15 @@ BOOST_FIXTURE_TEST_CASE( delegate_groups_mc_test, database_fixture ) while( full_schedule.size() < total_blocks ) { + if( (db.head_block_num() & 0x3FFF) == 0 ) + { + wdump( (db.head_block_num()) ); + } witness_id_type wid = db.get_scheduled_witness( 1 ).first; full_schedule.push_back( wid ); cur_round.push_back( wid ); if( cur_round.size() == num_witnesses ) { - wdump( (cur_round) ); // check that the current round contains exactly 1 copy // of each witness witness_seen.reset(); From dfee44a40827e40becf4d8f21bff5b2bfa1303a2 Mon Sep 17 00:00:00 2001 From: Vikram Rajkumar Date: Thu, 11 Jun 2015 18:21:47 -0400 Subject: [PATCH 20/20] Restore default HTTPS refs for submodules --- .gitmodules | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index c78c21f3..85a89fba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,11 +1,11 @@ [submodule "docs"] path = docs - url = git@github.com:cryptonomex/graphene.wiki.git + url = https://github.com/cryptonomex/graphene.wiki.git [submodule "libraries/fc"] path = libraries/fc - url = git@github.com:cryptonomex/fc.git + url = https://github.com/cryptonomex/fc.git ignore = dirty [submodule "libraries/leveldb"] path = libraries/leveldb - url = git@github.com:bitcoin/leveldb.git + url = https://github.com/bitcoin/leveldb.git ignore = dirty