diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index c807a3f0..2080aa2e 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -344,6 +344,8 @@ namespace graphene { namespace app { break; case impl_budget_record_object_type: break; + case impl_special_authority_object_type: + break; } } return result; diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 876c8c1d..2ce2f99d 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -76,6 +76,7 @@ add_library( graphene_chain withdraw_permission_evaluator.cpp worker_evaluator.cpp confidential_evaluator.cpp + special_authority.cpp account_object.cpp asset_object.cpp diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 28623544..f1c77d10 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include @@ -53,6 +55,12 @@ void verify_authority_accounts( const database& db, const authority& a ) void_result account_create_evaluator::do_evaluate( const account_create_operation& op ) { try { database& d = db(); + if( d.head_block_time() < HARDFORK_516_TIME ) + { + FC_ASSERT( !op.extensions.value.owner_special_authority.valid() ); + FC_ASSERT( !op.extensions.value.active_special_authority.valid() ); + } + FC_ASSERT( d.find_object(op.options.voting_account), "Invalid proxy account specified." ); FC_ASSERT( fee_paying_account->is_lifetime_member(), "Only Lifetime members may register an account." ); FC_ASSERT( op.referrer(d).is_member(d.head_block_time()), "The referrer must be either a lifetime or annual subscriber." ); @@ -68,6 +76,11 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio GRAPHENE_RECODE_EXC( internal_verify_auth_max_auth_exceeded, account_create_max_auth_exceeded ) GRAPHENE_RECODE_EXC( internal_verify_auth_account_not_found, account_create_auth_account_not_found ) + if( op.extensions.value.owner_special_authority.valid() ) + evaluate_special_authority( d, *op.extensions.value.owner_special_authority ); + if( op.extensions.value.active_special_authority.valid() ) + evaluate_special_authority( d, *op.extensions.value.active_special_authority ); + uint32_t max_vote_id = global_props.next_available_vote_id; FC_ASSERT( op.options.num_witness <= chain_params.maximum_witness_count, @@ -136,6 +149,11 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio obj.active = o.active; obj.options = o.options; obj.statistics = db().create([&](account_statistics_object& s){s.owner = obj.id;}).id; + + if( o.extensions.value.owner_special_authority.valid() ) + obj.owner_special_authority = *(o.extensions.value.owner_special_authority); + if( o.extensions.value.active_special_authority.valid() ) + obj.active_special_authority = *(o.extensions.value.active_special_authority); }); if( has_small_percent ) @@ -159,6 +177,15 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio p.parameters.current_fees->get().basic_fee <<= p.parameters.account_fee_scale_bitshifts; }); + if( o.extensions.value.owner_special_authority.valid() + || o.extensions.value.active_special_authority.valid() ) + { + db().create< special_authority_object >( [&]( special_authority_object& sa ) + { + sa.account = new_acnt_object.id; + } ); + } + return new_acnt_object.id; } FC_CAPTURE_AND_RETHROW((o)) } @@ -166,6 +193,11 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio void_result account_update_evaluator::do_evaluate( const account_update_operation& o ) { try { database& d = db(); + if( d.head_block_time() < HARDFORK_516_TIME ) + { + FC_ASSERT( !o.extensions.value.owner_special_authority.valid() ); + FC_ASSERT( !o.extensions.value.active_special_authority.valid() ); + } const auto& chain_params = d.get_global_properties().parameters; @@ -177,6 +209,11 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio GRAPHENE_RECODE_EXC( internal_verify_auth_max_auth_exceeded, account_update_max_auth_exceeded ) GRAPHENE_RECODE_EXC( internal_verify_auth_account_not_found, account_update_auth_account_not_found ) + if( o.extensions.value.owner_special_authority.valid() ) + evaluate_special_authority( d, *o.extensions.value.owner_special_authority ); + if( o.extensions.value.active_special_authority.valid() ) + evaluate_special_authority( d, *o.extensions.value.active_special_authority ); + acnt = &o.account(d); if( o.new_options ) @@ -195,11 +232,35 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio void_result account_update_evaluator::do_apply( const account_update_operation& o ) { try { - db().modify( *acnt, [&](account_object& a){ + database& d = db(); + bool sa_before, sa_after; + d.modify( *acnt, [&](account_object& a){ if( o.owner ) a.owner = *o.owner; if( o.active ) a.active = *o.active; if( o.new_options ) a.options = *o.new_options; + sa_before = a.has_special_authority(); + if( o.extensions.value.owner_special_authority.valid() ) + a.owner_special_authority = *(o.extensions.value.owner_special_authority); + if( o.extensions.value.active_special_authority.valid() ) + a.active_special_authority = *(o.extensions.value.active_special_authority); + sa_after = a.has_special_authority(); }); + + if( sa_before & (!sa_after) ) + { + const auto& sa_idx = d.get_index_type< special_authority_index >().indices().get(); + auto sa_it = sa_idx.find( o.account ); + assert( sa_it != sa_idx.end() ); + d.remove( *sa_it ); + } + else if( (!sa_before) & sa_after ) + { + d.create< special_authority_object >( [&]( special_authority_object& sa ) + { + sa.account = o.account; + } ); + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index cdc4182c..e603b3eb 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -208,6 +209,7 @@ void database::initialize_indexes() add_index< primary_index > >(); add_index< primary_index > >(); add_index< primary_index > >(); + add_index< primary_index< special_authority_index > >(); } void database::init_genesis(const genesis_state_type& genesis_state) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index b473a22a..c00a4226 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -465,6 +466,62 @@ void database::process_budget() FC_CAPTURE_AND_RETHROW() } +template< typename Visitor > +void visit_special_authorities( const database& db, Visitor visit ) +{ + const auto& sa_idx = db.get_index_type< special_authority_index >().indices().get(); + + for( const special_authority_object& sao : sa_idx ) + { + const account_object& acct = sao.account(db); + if( acct.owner_special_authority.which() != special_authority::tag< no_special_authority >::value ) + { + visit( acct, true, acct.owner_special_authority ); + } + if( acct.active_special_authority.which() != special_authority::tag< no_special_authority >::value ) + { + visit( acct, false, acct.active_special_authority ); + } + } +} + +void update_top_n_authorities( database& db ) +{ + visit_special_authorities( db, + [&]( const account_object& acct, bool is_owner, const special_authority& auth ) + { + if( auth.which() == special_authority::tag< top_holders_special_authority >::value ) + { + // use index to grab the top N holders of the asset and vote_counter to obtain the weights + + const top_holders_special_authority& tha = auth.get< top_holders_special_authority >(); + vote_counter vc; + const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_asset_balance >(); + uint8_t num_needed = tha.num_top_holders; + if( num_needed == 0 ) + return; + + // find accounts + const auto range = bal_idx.equal_range( boost::make_tuple( tha.asset ) ); + for( const account_balance_object& bal : boost::make_iterator_range( range.first, range.second ) ) + { + assert( bal.asset_type == tha.asset ); + if( bal.owner == acct.id ) + continue; + vc.add( bal.owner, bal.balance.value ); + --num_needed; + if( num_needed == 0 ) + break; + } + + db.modify( acct, [&]( account_object& a ) + { + vc.finish( is_owner ? a.owner : a.active ); + } ); + } + } ); +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { const auto& gpo = get_global_properties(); @@ -545,7 +602,10 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } } fee_helper(*this, gpo); - perform_account_maintenance(std::tie(tally_helper, fee_helper)); + perform_account_maintenance(std::tie( + tally_helper, + fee_helper + )); struct clear_canary { clear_canary(vector& target): target(target){} @@ -557,6 +617,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g b(_committee_count_histogram_buffer), c(_vote_tally_buffer); + update_top_n_authorities(*this); update_active_witnesses(); update_active_committee_members(); update_worker_votes(); diff --git a/libraries/chain/hardfork.d/516.hf b/libraries/chain/hardfork.d/516.hf new file mode 100644 index 00000000..445e2871 --- /dev/null +++ b/libraries/chain/hardfork.d/516.hf @@ -0,0 +1,4 @@ +// #516 Special authorities +#ifndef HARDFORK_516_TIME +#define HARDFORK_516_TIME (fc::time_point_sec( 1455127200 )) +#endif diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index a3f2b3ab..3039784e 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -204,6 +204,16 @@ namespace graphene { namespace chain { * Vesting balance which receives cashback_reward deposits. */ optional cashback_vb; + + special_authority owner_special_authority = no_special_authority(); + special_authority active_special_authority = no_special_authority(); + + bool has_special_authority()const + { + return (owner_special_authority.which() != special_authority::tag< no_special_authority >::value) + || (active_special_authority.which() != special_authority::tag< no_special_authority >::value); + } + template const vesting_balance_object& cashback_balance(const DB& db)const { @@ -352,7 +362,9 @@ FC_REFLECT_DERIVED( graphene::chain::account_object, (network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage) (name)(owner)(active)(options)(statistics)(whitelisting_accounts)(blacklisting_accounts) (whitelisting_accounts)(blacklisted_accounts) - (cashback_vb) ) + (cashback_vb) + (owner_special_authority)(active_special_authority) + ) FC_REFLECT_DERIVED( graphene::chain::account_balance_object, (graphene::db::object), diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index f178675c..f1bbc3f7 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -143,7 +143,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "GPH2.4" +#define GRAPHENE_CURRENT_DB_VERSION "GPH2.5" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index 30ab0a29..987568b1 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -23,15 +23,18 @@ */ #pragma once #include +#include +#include +#include #include -namespace graphene { namespace chain { +namespace graphene { namespace chain { bool is_valid_name( const string& s ); bool is_cheap_name( const string& n ); /// These are the fields which can be updated by the active authority. - struct account_options + struct account_options { /// 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 @@ -61,7 +64,15 @@ namespace graphene { namespace chain { */ struct account_create_operation : public base_operation { - struct fee_parameters_type { + struct ext + { + optional< void_t > null_ext; + optional< special_authority > owner_special_authority; + optional< special_authority > active_special_authority; + }; + + struct fee_parameters_type + { uint64_t basic_fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account uint64_t premium_fee = 2000*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; @@ -82,7 +93,7 @@ namespace graphene { namespace chain { authority active; account_options options; - extensions_type extensions; + extension< ext > extensions; account_id_type fee_payer()const { return registrar; } void validate()const; @@ -98,8 +109,16 @@ namespace graphene { namespace chain { */ struct account_update_operation : public base_operation { - struct fee_parameters_type { - share_type fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + struct ext + { + optional< void_t > null_ext; + optional< special_authority > owner_special_authority; + optional< special_authority > active_special_authority; + }; + + struct fee_parameters_type + { + share_type fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -114,17 +133,20 @@ namespace graphene { namespace chain { /// New account options optional new_options; - extensions_type extensions; + extension< ext > extensions; account_id_type fee_payer()const { return account; } void validate()const; share_type calculate_fee( const fee_parameters_type& k )const; + bool is_owner_update()const + { return owner || extensions.value.owner_special_authority.valid(); } + void get_required_owner_authorities( flat_set& a )const - { if( owner ) a.insert( account ); } + { if( is_owner_update() ) a.insert( account ); } void get_required_active_authorities( flat_set& a )const - { if( !owner ) a.insert( account ); } + { if( !is_owner_update() ) a.insert( account ); } }; /** @@ -236,16 +258,19 @@ FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listi FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing, (no_listing)(white_listed)(black_listed)(white_and_black_listed)) +FC_REFLECT(graphene::chain::account_create_operation::ext, (null_ext)(owner_special_authority)(active_special_authority) ) FC_REFLECT( graphene::chain::account_create_operation, (fee)(registrar) (referrer)(referrer_percent) (name)(owner)(active)(options)(extensions) ) + +FC_REFLECT(graphene::chain::account_update_operation::ext, (null_ext)(owner_special_authority)(active_special_authority) ) FC_REFLECT( graphene::chain::account_update_operation, (fee)(account)(owner)(active)(new_options)(extensions) ) -FC_REFLECT( graphene::chain::account_upgrade_operation, +FC_REFLECT( graphene::chain::account_upgrade_operation, (fee)(account_to_upgrade)(upgrade_to_lifetime_member)(extensions) ) FC_REFLECT( graphene::chain::account_whitelist_operation, (fee)(authorizing_account)(account_to_list)(new_listing)(extensions)) diff --git a/libraries/chain/include/graphene/chain/protocol/ext.hpp b/libraries/chain/include/graphene/chain/protocol/ext.hpp new file mode 100644 index 00000000..fda4feee --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/ext.hpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +namespace graphene { namespace chain { + +template< typename T > +struct extension +{ + extension() {} + + T value; +}; + +template< typename T > +struct graphene_extension_pack_count_visitor +{ + graphene_extension_pack_count_visitor( const T& v ) : value(v) {} + + template + void operator()( const char* name )const + { + count += ((value.*member).valid()) ? 1 : 0; + } + + const T& value; + mutable uint32_t count = 0; +}; + +template< typename Stream, typename T > +struct graphene_extension_pack_read_visitor +{ + graphene_extension_pack_read_visitor( Stream& s, const T& v ) : stream(s), value(v) {} + + template + void operator()( const char* name )const + { + if( (value.*member).valid() ) + { + fc::raw::pack( stream, unsigned_int( which ) ); + fc::raw::pack( stream, *(value.*member) ); + } + ++which; + } + + Stream& stream; + const T& value; + mutable uint32_t which = 0; +}; + +template< typename Stream, class T > +void operator<<( Stream& stream, const graphene::chain::extension& value ) +{ + graphene_extension_pack_count_visitor count_vtor( value.value ); + fc::reflector::visit( count_vtor ); + fc::raw::pack( stream, unsigned_int( count_vtor.count ) ); + graphene_extension_pack_read_visitor read_vtor( stream, value.value ); + fc::reflector::visit( read_vtor ); +} + +template< typename Stream, typename T > +struct graphene_extension_unpack_visitor +{ + graphene_extension_unpack_visitor( Stream& s, T& v ) : stream(s), value(v) + { + unsigned_int c; + fc::raw::unpack( stream, c ); + count_left = c.value; + maybe_read_next_which(); + } + + void maybe_read_next_which()const + { + if( count_left > 0 ) + { + unsigned_int w; + fc::raw::unpack( stream, w ); + next_which = w.value; + } + } + + template< typename Member, class Class, Member (Class::*member)> + void operator()( const char* name )const + { + if( (count_left > 0) && (which == next_which) ) + { + Member temp; + fc::raw::unpack( stream, temp ); + (value.*member) = temp; + --count_left; + maybe_read_next_which(); + } + else + (value.*member).reset(); + ++which; + } + + mutable uint32_t which = 0; + mutable uint32_t next_which = 0; + mutable uint32_t count_left = 0; + + Stream& stream; + T& value; +}; + +template< typename Stream, typename T > +void operator>>( Stream& s, graphene::chain::extension& value ) +{ + graphene_extension_unpack_visitor vtor( s, value.value ); + fc::reflector::visit( vtor ); + FC_ASSERT( vtor.count_left == 0 ); // unrecognized extension throws here +} + +} } // graphene::chain + +namespace fc { + +template< typename T > +struct graphene_extension_from_variant_visitor +{ + graphene_extension_from_variant_visitor( const variant_object& v, T& val ) + : vo( v ), value( val ) + { + count_left = vo.size(); + } + + template + void operator()( const char* name )const + { + auto it = vo.find(name); + if( it != vo.end() ) + { + from_variant( it->value(), (value.*member) ); + assert( count_left > 0 ); // x.find(k) returns true for n distinct values of k only if x.size() >= n + --count_left; + } + } + + const variant_object& vo; + T& value; + mutable uint32_t count_left = 0; +}; + +template< typename T > +void from_variant( const fc::variant& var, graphene::chain::extension& value ) +{ + graphene_extension_from_variant_visitor vtor( var.get_object(), value.value ); + fc::reflector::visit( vtor ); + FC_ASSERT( vtor.count_left == 0 ); // unrecognized extension throws here +} + +template< typename T > +struct graphene_extension_to_variant_visitor +{ + graphene_extension_to_variant_visitor( const T& v ) : value(v) {} + + template + void operator()( const char* name )const + { + if( (value.*member).valid() ) + mvo[ name ] = (value.*member); + } + + const T& value; + mutable mutable_variant_object mvo; +}; + +template< typename T > +void to_variant( const graphene::chain::extension& value, fc::variant& var ) +{ + graphene_extension_to_variant_visitor vtor( value.value ); + fc::reflector::visit( vtor ); + var = vtor.mvo; +} + +} // fc diff --git a/libraries/chain/include/graphene/chain/protocol/special_authority.hpp b/libraries/chain/include/graphene/chain/protocol/special_authority.hpp new file mode 100644 index 00000000..3ee6f15f --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/special_authority.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +namespace graphene { namespace chain { + +struct no_special_authority {}; + +struct top_holders_special_authority +{ + asset_id_type asset; + uint8_t num_top_holders = 1; +}; + +typedef static_variant< + no_special_authority, + top_holders_special_authority + > special_authority; + +void validate_special_authority( const special_authority& auth ); + +} } // graphene::chain + +FC_REFLECT( graphene::chain::no_special_authority, ) +FC_REFLECT( graphene::chain::top_holders_special_authority, (asset)(num_top_holders) ) +FC_REFLECT_TYPENAME( graphene::chain::special_authority ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 3652ef5e..009a7980 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -152,7 +152,8 @@ namespace graphene { namespace chain { impl_blinded_balance_object_type, impl_chain_property_object_type, impl_witness_schedule_object_type, - impl_budget_record_object_type + impl_budget_record_object_type, + impl_special_authority_object_type }; //typedef fc::unsigned_int object_id_type; @@ -201,6 +202,7 @@ namespace graphene { namespace chain { class chain_property_object; class witness_schedule_object; class budget_record_object; + class special_authority_object; typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; @@ -218,6 +220,7 @@ namespace graphene { namespace chain { typedef object_id< implementation_ids, impl_witness_schedule_object_type, witness_schedule_object> witness_schedule_id_type; typedef object_id< implementation_ids, impl_budget_record_object_type, budget_record_object > budget_record_id_type; typedef object_id< implementation_ids, impl_blinded_balance_object_type, blinded_balance_object > blinded_balance_id_type; + typedef object_id< implementation_ids, impl_special_authority_object_type, special_authority_object > special_authority_id_type; typedef fc::array symbol_type; typedef fc::ripemd160 block_id_type; @@ -347,6 +350,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_chain_property_object_type) (impl_witness_schedule_object_type) (impl_budget_record_object_type) + (impl_special_authority_object_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) @@ -375,6 +379,7 @@ FC_REFLECT_TYPENAME( graphene::chain::transaction_obj_id_type ) FC_REFLECT_TYPENAME( graphene::chain::block_summary_id_type ) FC_REFLECT_TYPENAME( graphene::chain::account_transaction_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::budget_record_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::special_authority_id_type ) FC_REFLECT( graphene::chain::void_t, ) FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, diff --git a/libraries/chain/include/graphene/chain/special_authority.hpp b/libraries/chain/include/graphene/chain/special_authority.hpp new file mode 100644 index 00000000..f091f736 --- /dev/null +++ b/libraries/chain/include/graphene/chain/special_authority.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include + +namespace graphene { namespace chain { + +class database; + +void evaluate_special_authority( const database& db, const special_authority& auth ); + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/special_authority_object.hpp b/libraries/chain/include/graphene/chain/special_authority_object.hpp new file mode 100644 index 00000000..da9ecc5e --- /dev/null +++ b/libraries/chain/include/graphene/chain/special_authority_object.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + +/** + * special_authority_object only exists to help with a specific indexing problem. + * We want to be able to iterate over all accounts that contain a special authority. + * However, accounts which have a special_authority are very rare. So rather + * than indexing account_object by the special_authority fields (requiring additional + * bookkeeping for every account), we instead maintain a special_authority_object + * pointing to each account which has special_authority (requiring additional + * bookkeeping only for every account which has special_authority). + * + * This class is an implementation detail. + */ + +class special_authority_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_special_authority_object_type; + + account_id_type account; +}; + +struct by_account; + +typedef multi_index_container< + special_authority_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, member< special_authority_object, account_id_type, &special_authority_object::account> > + > +> special_authority_multi_index_type; + +typedef generic_index< special_authority_object, special_authority_multi_index_type > special_authority_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( + graphene::chain::special_authority_object, + (graphene::db::object), + (account) +) diff --git a/libraries/chain/include/graphene/chain/vote_count.hpp b/libraries/chain/include/graphene/chain/vote_count.hpp index ad14e22d..bb23b840 100644 --- a/libraries/chain/include/graphene/chain/vote_count.hpp +++ b/libraries/chain/include/graphene/chain/vote_count.hpp @@ -36,6 +36,8 @@ struct vote_counter template< typename Component > void add( Component who, uint64_t votes ) { + if( votes == 0 ) + return; assert( votes <= last_votes ); last_votes = votes; if( bitshift == -1 ) diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index 810328dd..1c3fa007 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -186,6 +186,10 @@ void account_create_operation::validate()const FC_ASSERT( !owner.is_impossible(), "cannot create an account with an imposible owner authority threshold" ); FC_ASSERT( !active.is_impossible(), "cannot create an account with an imposible active authority threshold" ); options.validate(); + if( extensions.value.owner_special_authority.valid() ) + validate_special_authority( *extensions.value.owner_special_authority ); + if( extensions.value.active_special_authority.valid() ) + validate_special_authority( *extensions.value.active_special_authority ); } @@ -204,7 +208,17 @@ void account_update_operation::validate()const FC_ASSERT( account != GRAPHENE_TEMP_ACCOUNT ); FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( account != account_id_type() ); - FC_ASSERT( owner || active || new_options ); + + bool has_action = ( + owner.valid() + || active.valid() + || new_options.valid() + || extensions.value.owner_special_authority.valid() + || extensions.value.active_special_authority.valid() + ); + + FC_ASSERT( has_action ); + if( owner ) { FC_ASSERT( owner->num_auths() != 0 ); @@ -220,9 +234,12 @@ void account_update_operation::validate()const if( new_options ) new_options->validate(); + if( extensions.value.owner_special_authority.valid() ) + validate_special_authority( *extensions.value.owner_special_authority ); + if( extensions.value.active_special_authority.valid() ) + validate_special_authority( *extensions.value.active_special_authority ); } - share_type account_upgrade_operation::calculate_fee(const fee_parameters_type& k) const { if( upgrade_to_lifetime_member ) diff --git a/libraries/chain/special_authority.cpp b/libraries/chain/special_authority.cpp new file mode 100644 index 00000000..ca974f30 --- /dev/null +++ b/libraries/chain/special_authority.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +namespace graphene { namespace chain { + +struct special_authority_validate_visitor +{ + typedef void result_type; + + void operator()( const no_special_authority& a ) {} + + void operator()( const top_holders_special_authority& a ) + { + FC_ASSERT( a.num_top_holders > 0 ); + } +}; + +void validate_special_authority( const special_authority& a ) +{ + special_authority_validate_visitor vtor; + a.visit( vtor ); +} + +struct special_authority_evaluate_visitor +{ + typedef void result_type; + + special_authority_evaluate_visitor( const database& d ) : db(d) {} + + void operator()( const no_special_authority& a ) {} + + void operator()( const top_holders_special_authority& a ) + { + a.asset(db); // require asset to exist + } + + const database& db; +}; + +void evaluate_special_authority( const database& db, const special_authority& a ) +{ + special_authority_evaluate_visitor vtor( db ); + a.visit( vtor ); +} + +} } // graphene::chain diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index e13eda19..7adf3a1c 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -1332,4 +1333,154 @@ BOOST_AUTO_TEST_CASE(zero_second_vbo) // TODO: Write linear VBO tests +BOOST_AUTO_TEST_CASE( top_n_special ) +{ + ACTORS( (alice)(bob)(chloe)(dan)(izzy)(stan) ); + + generate_blocks( HARDFORK_516_TIME ); + + try + { + { + // + // Izzy (issuer) + // Stan (special authority) + // Alice, Bob, Chloe, Dan (ABCD) + // + + asset_id_type topn_id = create_user_issued_asset( "TOPN", izzy_id(db), 0 ).id; + authority stan_owner_auth = stan_id(db).owner; + authority stan_active_auth = stan_id(db).active; + + // set SA, wait for maint interval + // TODO: account_create_operation + // TODO: multiple accounts with different n for same asset + + { + top_holders_special_authority top2, top3; + + top2.num_top_holders = 2; + top2.asset = topn_id; + + top3.num_top_holders = 3; + top3.asset = topn_id; + + account_update_operation op; + op.account = stan_id; + op.extensions.value.active_special_authority = top3; + op.extensions.value.owner_special_authority = top2; + + signed_transaction tx; + tx.operations.push_back( op ); + + set_expiration( db, tx ); + sign( tx, stan_private_key ); + + PUSH_TX( db, tx ); + + // TODO: Check special_authority is properly set + // TODO: Do it in steps + } + + // wait for maint interval + // make sure we don't have any authority as account hasn't gotten distributed yet + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + BOOST_CHECK( stan_id(db).owner == stan_owner_auth ); + BOOST_CHECK( stan_id(db).active == stan_active_auth ); + + // issue some to Alice, make sure she gets control of Stan + + // we need to set_expiration() before issue_uia() because the latter doens't call it #11 + set_expiration( db, trx ); // #11 + issue_uia( alice_id, asset( 1000, topn_id ) ); + + BOOST_CHECK( stan_id(db).owner == stan_owner_auth ); + BOOST_CHECK( stan_id(db).active == stan_active_auth ); + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + /* NOTE - this was an old check from an earlier implementation that only allowed SA for LTM's + // no boost yet, we need to upgrade to LTM before mechanics apply to Stan + BOOST_CHECK( stan_id(db).owner == stan_owner_auth ); + BOOST_CHECK( stan_id(db).active == stan_active_auth ); + + set_expiration( db, trx ); // #11 + upgrade_to_lifetime_member(stan_id); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + */ + + BOOST_CHECK( stan_id(db).owner == authority( 501, alice_id, 1000 ) ); + BOOST_CHECK( stan_id(db).active == authority( 501, alice_id, 1000 ) ); + + // give asset to Stan, make sure owner doesn't change at all + set_expiration( db, trx ); // #11 + transfer( alice_id, stan_id, asset( 1000, topn_id ) ); + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + BOOST_CHECK( stan_id(db).owner == authority( 501, alice_id, 1000 ) ); + BOOST_CHECK( stan_id(db).active == authority( 501, alice_id, 1000 ) ); + + set_expiration( db, trx ); // #11 + issue_uia( chloe_id, asset( 131000, topn_id ) ); + + // now Chloe has 131,000 and Stan has 1k. Make sure change occurs at next maintenance interval. + // NB, 131072 is a power of 2; the number 131000 was chosen so that we need a bitshift, but + // if we put the 1000 from Stan's balance back into play, we need a different bitshift. + + // we use Chloe so she can be displaced by Bob later (showing the tiebreaking logic). + + // Check Alice is still in control, because we're deferred to next maintenance interval + BOOST_CHECK( stan_id(db).owner == authority( 501, alice_id, 1000 ) ); + BOOST_CHECK( stan_id(db).active == authority( 501, alice_id, 1000 ) ); + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + BOOST_CHECK( stan_id(db).owner == authority( 32751, chloe_id, 65500 ) ); + BOOST_CHECK( stan_id(db).active == authority( 32751, chloe_id, 65500 ) ); + + // put Alice's stake back in play + set_expiration( db, trx ); // #11 + transfer( stan_id, alice_id, asset( 1000, topn_id ) ); + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + BOOST_CHECK( stan_id(db).owner == authority( 33001, alice_id, 500, chloe_id, 65500 ) ); + BOOST_CHECK( stan_id(db).active == authority( 33001, alice_id, 500, chloe_id, 65500 ) ); + + // issue 200,000 to Dan to cause another bitshift. + set_expiration( db, trx ); // #11 + issue_uia( dan_id, asset( 200000, topn_id ) ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // 200000 Dan + // 131000 Chloe + // 1000 Alice + + BOOST_CHECK( stan_id(db).owner == authority( 41376, chloe_id, 32750, dan_id, 50000 ) ); + BOOST_CHECK( stan_id(db).active == authority( 41501, alice_id, 250, chloe_id, 32750, dan_id, 50000 ) ); + + // have Alice send all but 1 back to Stan, verify that we clamp Alice at one vote + set_expiration( db, trx ); // #11 + transfer( alice_id, stan_id, asset( 999, topn_id ) ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + BOOST_CHECK( stan_id(db).owner == authority( 41376, chloe_id, 32750, dan_id, 50000 ) ); + BOOST_CHECK( stan_id(db).active == authority( 41376, alice_id, 1, chloe_id, 32750, dan_id, 50000 ) ); + + // send 131k to Bob so he's tied with Chloe, verify he displaces Chloe in top2 + set_expiration( db, trx ); // #11 + issue_uia( bob_id, asset( 131000, topn_id ) ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + BOOST_CHECK( stan_id(db).owner == authority( 41376, bob_id, 32750, dan_id, 50000 ) ); + BOOST_CHECK( stan_id(db).active == authority( 57751, bob_id, 32750, chloe_id, 32750, dan_id, 50000 ) ); + + // TODO more rounding checks + } + + } FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END()