diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8355d7959..9d39a4c12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,10 +19,10 @@ build: - tests/ tags: - builder - + test: stage: test - dependencies: + dependencies: - build script: - ./tests/betting_test @@ -30,7 +30,7 @@ test: - ./tests/cli_test tags: - builder - + code_quality: stage: test image: docker:stable diff --git a/.gitmodules b/.gitmodules index 4a2c72e06..5572259c0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,6 +3,6 @@ url = https://github.com/bitshares/bitshares-core.wiki.git ignore = dirty [submodule "libraries/fc"] - path = libraries/fc - url = https://github.com/PBSA/peerplays-fc.git - ignore = dirty + path = libraries/fc + url = https://github.com/PBSA/peerplays-fc.git + ignore = dirty diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 73861eb8b..833069f8a 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -438,7 +438,12 @@ namespace graphene { namespace app { } case balance_object_type:{ /** these are free from any accounts */ break; - } + } case son_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->son_account ); + break; + } case sport_object_type: case event_group_object_type: case event_object_type: diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index e692f1376..b9ae31b66 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -146,6 +146,12 @@ class database_api_impl : public std::enable_shared_from_this fc::optional get_committee_member_by_account(account_id_type account)const; map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; + // SON members + vector> get_sons(const vector& son_ids)const; + fc::optional get_son_by_account(account_id_type account)const; + map lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const; + uint64_t get_son_count()const; + // Votes vector lookup_vote_ids( const vector& votes )const; @@ -1682,6 +1688,81 @@ map database_api_impl::lookup_committee_member return committee_members_by_account_name; } +////////////////////////////////////////////////////////////////////// +// // +// SON members // +// // +////////////////////////////////////////////////////////////////////// + +vector> database_api::get_sons(const vector& son_ids)const +{ + return my->get_sons( son_ids ); +} + +vector> database_api_impl::get_sons(const vector& son_ids)const +{ + vector> result; result.reserve(son_ids.size()); + std::transform(son_ids.begin(), son_ids.end(), std::back_inserter(result), + [this](son_id_type id) -> optional { + if(auto o = _db.find(id)) + return *o; + return {}; + }); + return result; +} + +fc::optional database_api::get_son_by_account(account_id_type account)const +{ + return my->get_son_by_account( account ); +} + +fc::optional database_api_impl::get_son_by_account(account_id_type account) const +{ + const auto& idx = _db.get_index_type().indices().get(); + auto itr = idx.find(account); + if( itr != idx.end() ) + return *itr; + return {}; +} + +map database_api::lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const +{ + return my->lookup_son_accounts( lower_bound_name, limit ); +} + +map database_api_impl::lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const +{ + FC_ASSERT( limit <= 1000 ); + const auto& sons_by_id = _db.get_index_type().indices().get(); + + // we want to order sons by account name, but that name is in the account object + // so the son_index doesn't have a quick way to access it. + // get all the names and look them all up, sort them, then figure out what + // records to return. This could be optimized, but we expect the + // number of witnesses to be few and the frequency of calls to be rare + std::map sons_by_account_name; + for (const son_object& son : sons_by_id) + if (auto account_iter = _db.find(son.son_account)) + if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name + sons_by_account_name.insert(std::make_pair(account_iter->name, son.id)); + + auto end_iter = sons_by_account_name.begin(); + while (end_iter != sons_by_account_name.end() && limit--) + ++end_iter; + sons_by_account_name.erase(end_iter, sons_by_account_name.end()); + return sons_by_account_name; +} + +uint64_t database_api::get_son_count()const +{ + return my->get_son_count(); +} + +uint64_t database_api_impl::get_son_count()const +{ + return _db.get_index_type().indices().size(); +} + ////////////////////////////////////////////////////////////////////// // // // Votes // @@ -1701,6 +1782,7 @@ vector database_api_impl::lookup_vote_ids( const vector& const auto& committee_idx = _db.get_index_type().indices().get(); const auto& for_worker_idx = _db.get_index_type().indices().get(); const auto& against_worker_idx = _db.get_index_type().indices().get(); + const auto& son_idx = _db.get_index_type().indices().get(); vector result; result.reserve( votes.size() ); @@ -1743,6 +1825,16 @@ vector database_api_impl::lookup_vote_ids( const vector& } break; } + case vote_id_type::son: + { + auto itr = son_idx.find( id ); + if( itr != son_idx.end() ) + result.emplace_back( variant( *itr, 1 ) ); + else + result.emplace_back( variant() ); + break; + } + case vote_id_type::VOTE_TYPE_COUNT: break; // supress unused enum value warnings default: FC_CAPTURE_AND_THROW( fc::out_of_range_exception, (id) ); diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 08253417b..997a3a381 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -298,6 +298,15 @@ struct get_impacted_account_visitor void operator()( const sweeps_vesting_claim_operation& op ) { _impacted.insert( op.account ); } + void operator()( const son_create_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const son_update_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const son_delete_operation& op ){ + _impacted.insert( op.owner_account ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 78a9ca1f9..d597110e7 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -568,6 +569,38 @@ class database_api */ map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; + ///////////////// + // SON members // + ///////////////// + + /** + * @brief Get a list of SONs by ID + * @param son_ids IDs of the SONs to retrieve + * @return The SONs corresponding to the provided IDs + * + * This function has semantics identical to @ref get_objects + */ + vector> get_sons(const vector& son_ids)const; + + /** + * @brief Get the SON owned by a given account + * @param account The ID of the account whose SON should be retrieved + * @return The SON object, or null if the account does not have a SON + */ + fc::optional get_son_by_account(account_id_type account)const; + + /** + * @brief Get names and IDs for registered SONs + * @param lower_bound_name Lower bound of the first name to return + * @param limit Maximum number of results to return -- must not exceed 1000 + * @return Map of SON names to corresponding IDs + */ + map lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const; + + /** + * @brief Get the total number of SONs registered with the blockchain + */ + uint64_t get_son_count()const; /// WORKERS @@ -775,6 +808,12 @@ FC_API(graphene::app::database_api, (get_committee_member_by_account) (lookup_committee_member_accounts) + // SON members + (get_sons) + (get_son_by_account) + (lookup_son_accounts) + (get_son_count) + // workers (get_workers_by_account) // Votes diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index a8d9e5db8..a8969c419 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -113,6 +113,8 @@ add_library( graphene_chain affiliate_payout.cpp + son_evaluator.cpp + ${HEADERS} ${PROTOCOL_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp" diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index b1fa74245..02634c06a 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -49,13 +49,12 @@ #include #include #include - - #include #include #include #include #include +#include #include #include @@ -77,6 +76,7 @@ #include #include #include +#include #include @@ -243,6 +243,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -259,6 +262,7 @@ void database::initialize_indexes() acnt_index->add_secondary_index(); add_index< primary_index >(); // 256 members per chunk + add_index< primary_index >(); // 256 sons per chunk add_index< primary_index >(); // 1024 witnesses per chunk add_index< primary_index >(); add_index< primary_index >(); diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 3ec84d148..765d3c72c 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -253,7 +253,7 @@ void database::update_active_witnesses() void database::update_active_committee_members() { try { assert( _committee_count_histogram_buffer.size() > 0 ); - share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2; + share_type stake_target = (_total_voting_stake-_committee_count_histogram_buffer[0]) / 2; /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) @@ -326,6 +326,96 @@ void database::update_active_committee_members() }); } FC_CAPTURE_AND_RETHROW() } +void database::update_active_sons() +{ try { + assert( _son_count_histogram_buffer.size() > 0 ); + share_type stake_target = (_total_voting_stake-_son_count_histogram_buffer[0]) / 2; + + /// accounts that vote for 0 or 1 son do not get to express an opinion on + /// the number of sons to have (they abstain and are non-voting accounts) + + share_type stake_tally = 0; + + size_t son_count = 0; + if( stake_target > 0 ) + { + while( (son_count < _son_count_histogram_buffer.size() - 1) + && (stake_tally <= stake_target) ) + { + stake_tally += _son_count_histogram_buffer[++son_count]; + } + } + + const chain_property_object& cpo = get_chain_properties(); + auto sons = sort_votable_objects(std::max(son_count*2+1, (size_t)cpo.immutable_parameters.min_son_count)); + + const global_property_object& gpo = get_global_properties(); + + const auto& all_sons = get_index_type().indices(); + + for( const son_object& son : all_sons ) + { + modify( son, [&]( son_object& obj ){ + obj.total_votes = _vote_tally_buffer[son.vote_id]; + }); + } + + // Update SON authority + modify( get(GRAPHENE_SON_ACCOUNT_ID), [&]( account_object& a ) + { + if( head_block_time() < HARDFORK_533_TIME ) + { + uint64_t total_votes = 0; + map weights; + a.active.weight_threshold = 0; + a.active.clear(); + + for( const son_object& son : sons ) + { + weights.emplace(son.son_account, _vote_tally_buffer[son.vote_id]); + total_votes += _vote_tally_buffer[son.vote_id]; + } + + // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, + // then I want to keep the most significant 16 bits of what's left. + int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + for( const auto& weight : weights ) + { + // Ensure that everyone has at least one vote. Zero weights aren't allowed. + uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); + a.active.account_auths[weight.first] += votes; + a.active.weight_threshold += votes; + } + + a.active.weight_threshold /= 2; + a.active.weight_threshold += 1; + } + else + { + vote_counter vc; + for( const son_object& son : sons ) + vc.add( son.son_account, std::max(_vote_tally_buffer[son.vote_id], UINT64_C(1)) ); + vc.finish( a.active ); + } + } ); + + modify(gpo, [&]( global_property_object& gp ){ + gp.active_sons.clear(); + gp.active_sons.reserve(sons.size()); + std::transform(sons.begin(), sons.end(), + std::inserter(gp.active_sons, gp.active_sons.end()), + [](const son_object& s) { + return s.id; + }); + }); + + //const witness_schedule_object& wso = witness_schedule_id_type()(*this); + //modify(wso, [&](witness_schedule_object& _wso) + //{ + // _wso.scheduler.update(gpo.active_witnesses); + //}); +} FC_CAPTURE_AND_RETHROW() } + void database::initialize_budget_record( fc::time_point_sec now, budget_record& rec )const { const dynamic_global_property_object& dpo = get_dynamic_global_properties(); @@ -1239,6 +1329,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g 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._son_count_histogram_buffer.resize(props.parameters.maximum_son_count / 2 + 1); d._total_voting_stake = 0; const vesting_balance_index& vesting_index = d.get_index_type(); @@ -1323,6 +1414,18 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g // same rationale as for witnesses d._committee_count_histogram_buffer[offset] += voting_stake; } + if( opinion_account.options.num_son <= props.parameters.maximum_son_count ) + { + uint16_t offset = std::min(size_t(opinion_account.options.num_son/2), + d._son_count_histogram_buffer.size() - 1); + // votes for a number greater than maximum_son_count + // are turned into votes for maximum_son_count. + // + // in particular, this takes care of the case where a + // member was voting for a high number, then the + // parameter was lowered. + d._son_count_histogram_buffer[offset] += voting_stake; + } d._total_voting_stake += voting_stake; } @@ -1353,11 +1456,13 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g }; clear_canary a(_witness_count_histogram_buffer), b(_committee_count_histogram_buffer), + d(_son_count_histogram_buffer), c(_vote_tally_buffer); update_top_n_authorities(*this); update_active_witnesses(); update_active_committee_members(); + update_active_sons(); update_worker_votes(); modify(gpo, [this](global_property_object& p) { diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 3404989a8..63b0fce8f 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -285,6 +285,15 @@ struct get_impacted_account_visitor void operator()( const sweeps_vesting_claim_operation& op ) { _impacted.insert( op.account ); } + void operator()( const son_create_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_update_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_delete_operation& op ) { + _impacted.insert( op.owner_account ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) @@ -373,6 +382,11 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case balance_object_type:{ /** these are free from any accounts */ break; + } case son_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->son_account ); + break; } } } diff --git a/libraries/chain/hardfork.d/SON.hf b/libraries/chain/hardfork.d/SON.hf new file mode 100644 index 000000000..355c5b96f --- /dev/null +++ b/libraries/chain/hardfork.d/SON.hf @@ -0,0 +1,5 @@ +// SON HARDFORK Monday, September 21, 2020 1:43:11 PM +#ifndef HARDFORK_SON_TIME +#include +#define HARDFORK_SON_TIME (fc::time_point_sec( time(NULL) - (60 * 60) )) +#endif diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 5e4851b90..dd25b6ad3 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -90,8 +90,10 @@ #define GRAPHENE_DEFAULT_MIN_WITNESS_COUNT (11) #define GRAPHENE_DEFAULT_MIN_COMMITTEE_MEMBER_COUNT (11) +#define GRAPHENE_DEFAULT_MIN_SON_COUNT (5) #define GRAPHENE_DEFAULT_MAX_WITNESSES (1001) // SHOULD BE ODD #define GRAPHENE_DEFAULT_MAX_COMMITTEE (1001) // SHOULD BE ODD +#define GRAPHENE_DEFAULT_MAX_SONS (15) #define GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC (60*60*24*7*4) // Four weeks #define GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC (60*60*24*7*2) // Two weeks #define GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) @@ -173,6 +175,8 @@ #define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::chain::account_id_type(5)) /// #define GRAPHENE_RAKE_FEE_ACCOUNT_ID (graphene::chain::account_id_type(6)) +/// +#define GRAPHENE_SON_ACCOUNT_ID (graphene::chain::account_id_type(7)) /// Sentinel value used in the scheduler. #define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0)) ///@} @@ -227,8 +231,6 @@ #define TOURNAMENT_MAX_WHITELIST_LENGTH 1000 #define TOURNAMENT_MAX_START_TIME_IN_FUTURE (60*60*24*7*4) // 1 month #define TOURNAMENT_MAX_START_DELAY (60*60*24*7) // 1 week -#define GPOS_PERIOD (60*60*24*30*6) // 6 months -#define GPOS_SUBPERIOD (60*60*24*30) // 1 month #define MIN_SON_MEMBER_COUNT 15 #define SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE (2*GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 483bfc8a8..9fe285b42 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -519,6 +519,7 @@ 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_committee_members(); + void update_active_sons(); void update_worker_votes(); template @@ -556,6 +557,7 @@ namespace graphene { namespace chain { vector _vote_tally_buffer; vector _witness_count_histogram_buffer; vector _committee_count_histogram_buffer; + vector _son_count_histogram_buffer; uint64_t _total_voting_stake; flat_map _checkpoints; diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index 77f85df68..788ccdafd 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -52,6 +52,8 @@ namespace graphene { namespace chain { vector active_committee_members; // updated once per maintenance interval flat_set active_witnesses; // updated once per maintenance interval // n.b. witness scheduling is done by witness_schedule object + + flat_set active_sons; // updated once per maintenance interval }; /** @@ -148,4 +150,5 @@ FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::obje (next_available_vote_id) (active_committee_members) (active_witnesses) + (active_sons) ) diff --git a/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp b/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp index 0082383c9..ade1a459a 100644 --- a/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp @@ -35,6 +35,7 @@ struct immutable_chain_parameters { uint16_t min_committee_member_count = GRAPHENE_DEFAULT_MIN_COMMITTEE_MEMBER_COUNT; uint16_t min_witness_count = GRAPHENE_DEFAULT_MIN_WITNESS_COUNT; + uint16_t min_son_count = GRAPHENE_DEFAULT_MIN_SON_COUNT; uint32_t num_special_accounts = 0; uint32_t num_special_assets = 0; }; @@ -44,6 +45,7 @@ struct immutable_chain_parameters FC_REFLECT( graphene::chain::immutable_chain_parameters, (min_committee_member_count) (min_witness_count) + (min_son_count) (num_special_accounts) (num_special_assets) ) diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index 6d13a4d3e..496e9067b 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -52,6 +52,9 @@ namespace graphene { namespace chain { /// The number of active committee members this account votes the blockchain should appoint /// Must not exceed the actual number of committee members voted for in @ref votes uint16_t num_committee = 0; + /// The number of active son members this account votes the blockchain should appoint + /// Must not exceed the actual number of son members voted for in @ref votes + uint16_t num_son = 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; diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 90bf70885..2cfedb953 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -37,10 +37,6 @@ namespace graphene { namespace chain { optional< uint16_t > betting_rake_fee_percentage; optional< flat_map > permitted_betting_odds_increments; optional< uint16_t > live_betting_delay_time; - /* gpos parameters */ - optional < uint32_t > gpos_period; - optional < uint32_t > gpos_subperiod; - optional < uint32_t > gpos_period_start; optional < uint16_t > son_count; optional< uint16_t > sweeps_distribution_percentage; optional< asset_id_type > sweeps_distribution_asset; @@ -63,6 +59,7 @@ namespace graphene { namespace chain { uint8_t maximum_asset_feed_publishers = GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS; ///< the maximum number of feed publishers for a given asset 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 committee_members + uint16_t maximum_son_count = GRAPHENE_DEFAULT_MAX_SONS; ///< maximum number of active SONS uint16_t maximum_authority_membership = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP; ///< largest number of keys/accounts an authority can have uint16_t reserve_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 @@ -137,9 +134,6 @@ FC_REFLECT( graphene::chain::parameter_extension, (betting_rake_fee_percentage) (permitted_betting_odds_increments) (live_betting_delay_time) - (gpos_period) - (gpos_subperiod) - (gpos_period_start) (son_count) (sweeps_distribution_percentage) (sweeps_distribution_asset) @@ -160,6 +154,7 @@ FC_REFLECT( graphene::chain::chain_parameters, (maximum_asset_feed_publishers) (maximum_witness_count) (maximum_committee_count) + (maximum_son_count) (maximum_authority_membership) (reserve_percent_of_fee) (network_percent_of_fee) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index bce0e201e..cbad1e05e 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -135,7 +136,10 @@ namespace graphene { namespace chain { ticket_purchase_operation, lottery_reward_operation, lottery_end_operation, - sweeps_vesting_claim_operation + sweeps_vesting_claim_operation, + son_create_operation, + son_update_operation, + son_delete_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/son.hpp b/libraries/chain/include/graphene/chain/protocol/son.hpp new file mode 100644 index 000000000..efea3ef73 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son.hpp @@ -0,0 +1,60 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + struct son_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type owner_account; + std::string url; + vesting_balance_id_type deposit; + public_key_type signing_key; + vesting_balance_id_type pay_vb; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + optional new_url; + optional new_deposit; + optional new_signing_key; + optional new_pay_vb; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_create_operation, (fee)(owner_account)(url)(deposit)(signing_key) + (pay_vb) ) + +FC_REFLECT(graphene::chain::son_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_update_operation, (fee)(son_id)(owner_account)(new_url)(new_deposit) + (new_signing_key)(new_pay_vb) ) + +FC_REFLECT(graphene::chain::son_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_delete_operation, (fee)(son_id)(owner_account) ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index c2c92ca31..707f92743 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -145,6 +145,7 @@ namespace graphene { namespace chain { betting_market_group_object_type, betting_market_object_type, bet_object_type, + son_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -204,6 +205,7 @@ namespace graphene { namespace chain { class betting_market_group_object; class betting_market_object; class bet_object; + class son_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; @@ -230,6 +232,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, betting_market_group_object_type, betting_market_group_object> betting_market_group_id_type; typedef object_id< protocol_ids, betting_market_object_type, betting_market_object> betting_market_id_type; typedef object_id< protocol_ids, bet_object_type, bet_object> bet_id_type; + typedef object_id< protocol_ids, son_object_type, son_object> son_id_type; // implementation types class global_property_object; @@ -410,6 +413,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (betting_market_group_object_type) (betting_market_object_type) (bet_object_type) + (son_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -479,6 +483,8 @@ FC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_id_type ) FC_REFLECT_TYPENAME( graphene::chain::betting_market_position_id_type ) FC_REFLECT_TYPENAME( graphene::chain::global_betting_statistics_id_type ) FC_REFLECT_TYPENAME( graphene::chain::tournament_details_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_id_type ) + FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/chain/include/graphene/chain/protocol/vote.hpp b/libraries/chain/include/graphene/chain/protocol/vote.hpp index 67536f7a3..7ef2c8a14 100644 --- a/libraries/chain/include/graphene/chain/protocol/vote.hpp +++ b/libraries/chain/include/graphene/chain/protocol/vote.hpp @@ -64,6 +64,7 @@ struct vote_id_type committee, witness, worker, + son, VOTE_TYPE_COUNT }; @@ -148,5 +149,5 @@ void from_variant( const fc::variant& var, graphene::chain::vote_id_type& vo, ui FC_REFLECT_TYPENAME( fc::flat_set ) -FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(VOTE_TYPE_COUNT) ) +FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(son)(VOTE_TYPE_COUNT) ) FC_REFLECT( graphene::chain::vote_id_type, (content) ) diff --git a/libraries/chain/include/graphene/chain/son_evaluator.hpp b/libraries/chain/include/graphene/chain/son_evaluator.hpp new file mode 100644 index 000000000..bb6a1820c --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_evaluator.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class create_son_evaluator : public evaluator +{ +public: + typedef son_create_operation operation_type; + + void_result do_evaluate(const son_create_operation& o); + object_id_type do_apply(const son_create_operation& o); +}; + +class update_son_evaluator : public evaluator +{ +public: + typedef son_update_operation operation_type; + + void_result do_evaluate(const son_update_operation& o); + object_id_type do_apply(const son_update_operation& o); +}; + +class delete_son_evaluator : public evaluator +{ +public: + typedef son_delete_operation operation_type; + + void_result do_evaluate(const son_delete_operation& o); + void_result do_apply(const son_delete_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_object.hpp b/libraries/chain/include/graphene/chain/son_object.hpp new file mode 100644 index 000000000..ba84fadb7 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_object.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_object + * @brief tracks information about a SON account. + * @ingroup object + */ + class son_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_object_type; + + account_id_type son_account; + vote_id_type vote_id; + uint64_t total_votes = 0; + string url; + vesting_balance_id_type deposit; + public_key_type signing_key; + vesting_balance_id_type pay_vb; + }; + + struct by_account; + struct by_vote_id; + using son_multi_index_type = multi_index_container< + son_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + > + > + >; + using son_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object), + (son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb) ) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 3a44ca5cb..245e1a53f 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -135,6 +135,18 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_1000_TIME, "event_update_status_operation not allowed yet!" ); } + void operator()(const son_create_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_create_operation not allowed yet!" ); + } + + void operator()(const son_update_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_update_operation not allowed yet!" ); + } + + void operator()(const son_delete_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_delete_operation not allowed yet!" ); + } + // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { for (const op_wrapper &op : v.proposed_ops) diff --git a/libraries/chain/son_evaluator.cpp b/libraries/chain/son_evaluator.cpp new file mode 100644 index 000000000..6d70dc624 --- /dev/null +++ b/libraries/chain/son_evaluator.cpp @@ -0,0 +1,75 @@ +#include + +#include +#include +#include + +namespace graphene { namespace chain { + +void_result create_son_evaluator::do_evaluate(const son_create_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT(db().get(op.owner_account).is_lifetime_member(), "Only Lifetime members may register a SON."); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type create_son_evaluator::do_apply(const son_create_operation& op) +{ try { + vote_id_type vote_id; + db().modify(db().get_global_properties(), [&vote_id](global_property_object& p) { + vote_id = get_next_vote_id(p, vote_id_type::son); + }); + + const auto& new_son_object = db().create( [&]( son_object& obj ){ + obj.son_account = op.owner_account; + obj.vote_id = vote_id; + obj.url = op.url; + obj.deposit = op.deposit; + obj.signing_key = op.signing_key; + obj.pay_vb = op.pay_vb; + }); + return new_son_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result update_son_evaluator::do_evaluate(const son_update_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT(db().get(op.son_id).son_account == op.owner_account); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_id) != idx.end() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type update_son_evaluator::do_apply(const son_update_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + db().modify(*itr, [&op](son_object &so) { + if(op.new_url.valid()) so.url = *op.new_url; + if(op.new_deposit.valid()) so.deposit = *op.new_deposit; + if(op.new_signing_key.valid()) so.signing_key = *op.new_signing_key; + if(op.new_pay_vb.valid()) so.pay_vb = *op.new_pay_vb; + }); + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result delete_son_evaluator::do_evaluate(const son_delete_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON_HARDFORK"); // can be removed after HF date pass + FC_ASSERT(db().get(op.son_id).son_account == op.owner_account); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_id) != idx.end() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result delete_son_evaluator::do_apply(const son_delete_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + db().remove(*idx.find(op.son_id)); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/net/CMakeLists.txt b/libraries/net/CMakeLists.txt index 7aa617d77..f7f549ed5 100644 --- a/libraries/net/CMakeLists.txt +++ b/libraries/net/CMakeLists.txt @@ -10,7 +10,7 @@ set(SOURCES node.cpp add_library( graphene_net ${SOURCES} ${HEADERS} ) target_link_libraries( graphene_net - PUBLIC fc graphene_db ) + PUBLIC graphene_chain ) target_include_directories( graphene_net PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../chain/include" "${CMAKE_CURRENT_BINARY_DIR}/../chain/include" diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt index 916487e3c..931d4f45a 100644 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB HEADERS "include/graphene/peerplays_sidechain_history/*.hpp") +file(GLOB HEADERS "include/graphene/peerplays_sidechain/*.hpp") add_library( peerplays_sidechain peerplays_sidechain_plugin.cpp diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 3059f1794..fd4a3c404 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -188,6 +188,7 @@ struct wallet_data // incomplete account regs map > pending_account_registrations; map pending_witness_registrations; + map pending_son_registrations; key_label_index_type labeled_keys; blind_receipt_index_type blind_receipts; @@ -1283,6 +1284,12 @@ class wallet_api */ map list_committee_members(const string& lowerbound, uint32_t limit); + /** Returns information about the given SON. + * @param owner_account the name or id of the SON account owner, or the id of the SON + * @returns the information about the SON stored in the block chain + */ + son_object get_son(string owner_account); + /** Returns information about the given witness. * @param owner_account the name or id of the witness account owner, or the id of the witness * @returns the information about the witness stored in the block chain @@ -1295,6 +1302,63 @@ class wallet_api */ committee_member_object get_committee_member(string owner_account); + + /** Creates a SON object owned by the given account. + * + * An account can have at most one SON object. + * + * @param owner_account the name or id of the account which is creating the SON + * @param url a URL to include in the SON record in the blockchain. Clients may + * display this when showing a list of SONs. May be blank. + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction registering a SON + */ + signed_transaction create_son(string owner_account, + string url, + bool broadcast = false); + + /** + * Update a SON object owned by the given account. + * + * @param witness The name of the SON's owner account. Also accepts the ID of the owner account or the ID of the SON. + * @param url Same as for create_son. The empty string makes it remain the same. + * @param block_signing_key The new block signing public key. The empty string makes it remain the same. + * @param broadcast true if you wish to broadcast the transaction. + */ + signed_transaction update_son(string owner_account, + string url, + string block_signing_key, + bool broadcast = false); + + + /** Deletes a SON object owned by the given account. + * + * An account can have at most one witness object. + * + * @param owner_account the name or id of the account which is creating the witness + * @param url a URL to include in the witness record in the blockchain. Clients may + * display this when showing a list of witnesses. May be blank. + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction registering a witness + */ + signed_transaction delete_son(string owner_account, + bool broadcast = false); + + /** Lists all SONs in the blockchain. + * This returns a list of all account names that own SON, and the associated SON id, + * sorted by name. This lists SONs whether they are currently voted in or not. + * + * Use the \c lowerbound and limit parameters to page through the list. To retrieve all SONs, + * start by setting \c lowerbound to the empty string \c "", and then each iteration, pass + * the last SON name returned as the \c lowerbound for the next \c list_sons() call. + * + * @param lowerbound the name of the first SON to return. If the named SON does not exist, + * the list will start at the SON that comes after \c lowerbound + * @param limit the maximum number of SON to return (max: 1000) + * @returns a list of SON mapping SON names to SON ids + */ + map list_sons(const string& lowerbound, uint32_t limit); + /** Creates a witness object owned by the given account. * * An account can have at most one witness object. @@ -1402,6 +1466,61 @@ class wallet_api bool approve, bool broadcast = false); + /** Vote for a given SON. + * + * An account can publish a list of all SONs they approve of. This + * command allows you to add or remove SONs from this list. + * Each account's vote is weighted according to the number of shares of the + * core asset owned by that account at the time the votes are tallied. + * + * @note you cannot vote against a SON, you can only vote for the SON + * or not vote for the SON. + * + * @param voting_account the name or id of the account who is voting with their shares + * @param son the name or id of the SONs' owner account + * @param approve true if you wish to vote in favor of that SON, false to + * remove your vote in favor of that SON + * @param broadcast true if you wish to broadcast the transaction + * @return the signed transaction changing your vote for the given SON + */ + signed_transaction vote_for_son(string voting_account, + string son, + bool approve, + bool broadcast = false); + + /** Change your SON votes. + * + * An account can publish a list of all SONs they approve of. + * Each account's vote is weighted according to the number of shares of the + * core asset owned by that account at the time the votes are tallied. + * This command allows you to add or remove one or more SON from this list + * in one call. When you are changing your vote on several SONs, this + * may be easier than multiple `vote_for_sons` and + * `set_desired_witness_and_committee_member_count` calls. + * + * @note you cannot vote against a SON, you can only vote for the SON + * or not vote for the SON. + * + * @param voting_account the name or id of the account who is voting with their shares + * @param sons_to_approve the names or ids of the sons owner accounts you wish + * to approve (these will be added to the list of sons + * you currently approve). This list can be empty. + * @param sons_to_reject the names or ids of the SONs owner accounts you wish + * to reject (these will be removed from the list of SONs + * you currently approve). This list can be empty. + * @param desired_number_of_sons the number of SONs you believe the network + * should have. You must vote for at least this many + * SONs. You can set this to 0 to abstain from + * voting on the number of SONNs. + * @param broadcast true if you wish to broadcast the transaction + * @return the signed transaction changing your vote for the given witnesses + */ + signed_transaction update_son_votes(string voting_account, + std::vector sons_to_approve, + std::vector sons_to_reject, + uint16_t desired_number_of_son, + bool broadcast = false); + /** Vote for a given witness. * * An account can publish a list of all witnesses they approve of. This @@ -1863,7 +1982,7 @@ FC_REFLECT( graphene::wallet::wallet_data, (my_accounts) (cipher_keys) (extra_keys) - (pending_account_registrations)(pending_witness_registrations) + (pending_account_registrations)(pending_witness_registrations)(pending_son_registrations) (labeled_keys) (blind_receipts) (committed_game_moves) @@ -1969,10 +2088,15 @@ FC_API( graphene::wallet::wallet_api, (settle_asset) (whitelist_account) (create_committee_member) + (get_son) (get_witness) (get_committee_member) (list_witnesses) (list_committee_members) + (create_son) + (update_son) + (delete_son) + (list_sons) (create_witness) (update_witness) (create_worker) @@ -1980,6 +2104,8 @@ FC_API( graphene::wallet::wallet_api, (get_vesting_balances) (withdraw_vesting) (vote_for_committee_member) + (vote_for_son) + (update_son_votes) (vote_for_witness) (update_witness_votes) (set_voting_proxy) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 5d5345369..7d6b9ef7f 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -293,6 +293,23 @@ class wallet_api_impl _wallet.pending_account_registrations.erase( it ); } + // after a son registration succeeds, this saves the private key in the wallet permanently + // + void claim_registered_son(const std::string& son_name) + { + auto iter = _wallet.pending_son_registrations.find(son_name); + FC_ASSERT(iter != _wallet.pending_son_registrations.end()); + std::string wif_key = iter->second; + + // get the list key id this key is registered with in the chain + fc::optional son_private_key = wif_to_key(wif_key); + FC_ASSERT(son_private_key); + + auto pub_key = son_private_key->get_public_key(); + _keys[pub_key] = wif_key; + _wallet.pending_son_registrations.erase(iter); + } + // after a witness registration succeeds, this saves the private key in the wallet permanently // void claim_registered_witness(const std::string& witness_name) @@ -354,6 +371,24 @@ class wallet_api_impl claim_registered_witness(optional_account->name); } } + + if (!_wallet.pending_son_registrations.empty()) + { + // make a vector of the owner accounts for sons pending registration + std::vector pending_son_names = boost::copy_range >(boost::adaptors::keys(_wallet.pending_son_registrations)); + + // look up the owners on the blockchain + std::vector> owner_account_objects = _remote_db->lookup_account_names(pending_son_names); + + // if any of them have registered sons, claim them + for( const fc::optional& optional_account : owner_account_objects ) + if (optional_account) + { + fc::optional son_obj = _remote_db->get_son_by_account(optional_account->id); + if (son_obj) + claim_registered_son(optional_account->name); + } + } } // return true if any of my_accounts are players in this tournament @@ -666,6 +701,7 @@ class wallet_api_impl result["participation"] = (100*dynamic_props.recent_slots_filled.popcount()) / 128.0; result["active_witnesses"] = fc::variant(global_props.active_witnesses, GRAPHENE_MAX_NESTED_OBJECTS); result["active_committee_members"] = fc::variant(global_props.active_committee_members, GRAPHENE_MAX_NESTED_OBJECTS); + result["active_sons"] = fc::variant(global_props.active_sons, GRAPHENE_MAX_NESTED_OBJECTS); result["entropy"] = fc::variant(dynamic_props.random, GRAPHENE_MAX_NESTED_OBJECTS); return result; } @@ -1752,6 +1788,41 @@ class wallet_api_impl FC_CAPTURE_AND_RETHROW( (owner_account) ) } + son_object get_son(string owner_account) + { + try + { + fc::optional son_id = maybe_id(owner_account); + if (son_id) + { + std::vector ids_to_get; + ids_to_get.push_back(*son_id); + std::vector> son_objects = _remote_db->get_sons(ids_to_get); + if (son_objects.front()) + return *son_objects.front(); + FC_THROW("No SON is registered for id ${id}", ("id", owner_account)); + } + else + { + // then maybe it's the owner account + try + { + account_id_type owner_account_id = get_account_id(owner_account); + fc::optional son = _remote_db->get_son_by_account(owner_account_id); + if (son) + return *son; + else + FC_THROW("No SON is registered for account ${account}", ("account", owner_account)); + } + catch (const fc::exception&) + { + FC_THROW("No account or SON named ${account}", ("account", owner_account)); + } + } + } + FC_CAPTURE_AND_RETHROW( (owner_account) ) + } + committee_member_object get_committee_member(string owner_account) { try @@ -1787,6 +1858,82 @@ class wallet_api_impl FC_CAPTURE_AND_RETHROW( (owner_account) ) } + signed_transaction create_son(string owner_account, + string url, + bool broadcast /* = false */) + { try { + account_object son_account = get_account(owner_account); + fc::ecc::private_key active_private_key = get_private_key_for_account(son_account); + int son_key_index = find_first_unused_derived_key_index(active_private_key); + fc::ecc::private_key son_private_key = derive_private_key(key_to_wif(active_private_key), son_key_index); + graphene::chain::public_key_type son_public_key = son_private_key.get_public_key(); + + son_create_operation son_create_op; + son_create_op.owner_account = son_account.id; + son_create_op.signing_key = son_public_key; + son_create_op.url = url; + secret_hash_type::encoder enc; + fc::raw::pack(enc, son_private_key); + fc::raw::pack(enc, secret_hash_type()); + + if (_remote_db->get_son_by_account(son_create_op.owner_account)) + FC_THROW("Account ${owner_account} is already a SON", ("owner_account", owner_account)); + + signed_transaction tx; + tx.operations.push_back( son_create_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + _wallet.pending_son_registrations[owner_account] = key_to_wif(son_private_key); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) } + + signed_transaction update_son(string owner_account, + string url, + string block_signing_key, + bool broadcast /* = false */) + { try { + son_object son = get_son(owner_account); + account_object son_account = get_account( son.son_account ); + fc::ecc::private_key active_private_key = get_private_key_for_account(son_account); + + son_update_operation son_update_op; + son_update_op.son_id = son.id; + son_update_op.owner_account = son_account.id; + if( url != "" ) + son_update_op.new_url = url; + if( block_signing_key != "" ) { + son_update_op.new_signing_key = public_key_type( block_signing_key ); + } + + signed_transaction tx; + tx.operations.push_back( son_update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account)(url)(block_signing_key)(broadcast) ) } + + signed_transaction delete_son(string owner_account, + bool broadcast /* = false */) + { try { + son_object son = get_son(owner_account); + account_object son_account = get_account( son.son_account ); + fc::ecc::private_key active_private_key = get_private_key_for_account(son_account); + + son_delete_operation son_delete_op; + son_delete_op.son_id = son.id; + son_delete_op.owner_account = son_account.id; + + signed_transaction tx; + tx.operations.push_back( son_delete_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) } + signed_transaction create_witness(string owner_account, string url, bool broadcast /* = false */) @@ -2055,6 +2202,81 @@ class wallet_api_impl return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (voting_account)(committee_member)(approve)(broadcast) ) } + signed_transaction vote_for_son(string voting_account, + string son, + bool approve, + bool broadcast /* = false */) + { try { + account_object voting_account_object = get_account(voting_account); + account_id_type son_account_id = get_account_id(son); + fc::optional son_obj = _remote_db->get_son_by_account(son_account_id); + if (!son_obj) + FC_THROW("Account ${son} is not registered as a son", ("son", son)); + if (approve) + { + auto insert_result = voting_account_object.options.votes.insert(son_obj->vote_id); + if (!insert_result.second) + FC_THROW("Account ${account} was already voting for son ${son}", ("account", voting_account)("son", son)); + } + else + { + unsigned votes_removed = voting_account_object.options.votes.erase(son_obj->vote_id); + if (!votes_removed) + FC_THROW("Account ${account} is already not voting for son ${son}", ("account", voting_account)("son", son)); + } + account_update_operation account_update_op; + account_update_op.account = voting_account_object.id; + account_update_op.new_options = voting_account_object.options; + + signed_transaction tx; + tx.operations.push_back( account_update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (voting_account)(son)(approve)(broadcast) ) } + + signed_transaction update_son_votes(string voting_account, + std::vector sons_to_approve, + std::vector sons_to_reject, + uint16_t desired_number_of_sons, + bool broadcast /* = false */) + { try { + account_object voting_account_object = get_account(voting_account); + for (const std::string& son : sons_to_approve) + { + account_id_type son_owner_account_id = get_account_id(son); + fc::optional son_obj = _remote_db->get_son_by_account(son_owner_account_id); + if (!son_obj) + FC_THROW("Account ${son} is not registered as a witness", ("son", son)); + auto insert_result = voting_account_object.options.votes.insert(son_obj->vote_id); + if (!insert_result.second) + FC_THROW("Account ${account} was already voting for son ${son}", ("account", voting_account)("son", son)); + } + for (const std::string& son : sons_to_reject) + { + account_id_type son_owner_account_id = get_account_id(son); + fc::optional son_obj = _remote_db->get_son_by_account(son_owner_account_id); + if (!son_obj) + FC_THROW("Account ${son} is not registered as a son", ("son", son)); + unsigned votes_removed = voting_account_object.options.votes.erase(son_obj->vote_id); + if (!votes_removed) + FC_THROW("Account ${account} is already not voting for son ${son}", ("account", voting_account)("son", son)); + } + voting_account_object.options.num_son = desired_number_of_sons; + + account_update_operation account_update_op; + account_update_op.account = voting_account_object.id; + account_update_op.new_options = voting_account_object.options; + + signed_transaction tx; + tx.operations.push_back( account_update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (voting_account)(sons_to_approve)(sons_to_reject)(desired_number_of_sons)(broadcast) ) } + signed_transaction vote_for_witness(string voting_account, string witness, bool approve, @@ -4004,6 +4226,11 @@ map wallet_api::list_committee_members(const st return my->_remote_db->lookup_committee_member_accounts(lowerbound, limit); } +son_object wallet_api::get_son(string owner_account) +{ + return my->get_son(owner_account); +} + witness_object wallet_api::get_witness(string owner_account) { return my->get_witness(owner_account); @@ -4014,6 +4241,32 @@ committee_member_object wallet_api::get_committee_member(string owner_account) return my->get_committee_member(owner_account); } +signed_transaction wallet_api::create_son(string owner_account, + string url, + bool broadcast /* = false */) +{ + return my->create_son(owner_account, url, broadcast); +} + +signed_transaction wallet_api::update_son(string owner_account, + string url, + string block_signing_key, + bool broadcast /* = false */) +{ + return my->update_son(owner_account, url, block_signing_key, broadcast); +} + +signed_transaction wallet_api::delete_son(string owner_account, + bool broadcast /* = false */) +{ + my->delete_son(owner_account, broadcast); +} + +map wallet_api::list_sons(const string& lowerbound, uint32_t limit) +{ + my->_remote_db->lookup_son_accounts(lowerbound, limit); +} + signed_transaction wallet_api::create_witness(string owner_account, string url, bool broadcast /* = false */) @@ -4074,6 +4327,23 @@ signed_transaction wallet_api::vote_for_committee_member(string voting_account, return my->vote_for_committee_member(voting_account, witness, approve, broadcast); } +signed_transaction wallet_api::vote_for_son(string voting_account, + string son, + bool approve, + bool broadcast /* = false */) +{ + return my->vote_for_son(voting_account, son, approve, broadcast); +} + +signed_transaction wallet_api::update_son_votes(string voting_account, + std::vector sons_to_approve, + std::vector sons_to_reject, + uint16_t desired_number_of_sons, + bool broadcast /* = false */) +{ + return my->update_son_votes(voting_account, sons_to_approve, sons_to_reject, desired_number_of_sons, broadcast); +} + signed_transaction wallet_api::vote_for_witness(string voting_account, string witness, bool approve, diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 8994b36b5..a921c0c30 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index ee59f40f0..4aa73a97b 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -479,4 +479,4 @@ BOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture ) edump((e.to_detail_string())); throw; } -} \ No newline at end of file +} diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp new file mode 100644 index 000000000..db9925445 --- /dev/null +++ b/tests/tests/son_operations_tests.cpp @@ -0,0 +1,186 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( son_operation_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( create_son_test ) { + generate_blocks(HARDFORK_SON_TIME); + while (db.head_block_time() <= HARDFORK_SON_TIME) { + generate_block(); + } + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)(bob)); + + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + + transfer( committee_account, alice_id, asset( 100000 ) ); + transfer( committee_account, bob_id, asset( 100000 ) ); + + set_expiration(db, trx); + std::string test_url = "https://create_son_test"; + + // create deposit vesting + vesting_balance_id_type deposit; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(10); + //op.balance_type = vesting_balance_type::unspecified; + + trx.operations.push_back(op); + set_expiration(db, trx); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit = ptx.operation_results[0].get(); + } + // create payment vesting + vesting_balance_id_type payment; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(10); + //op.balance_type = vesting_balance_type::unspecified; + + trx.operations.push_back(op); + set_expiration(db, trx); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment = ptx.operation_results[0].get(); + } + + // alice became son + { + son_create_operation op; + op.owner_account = alice_id; + op.url = test_url; + op.deposit = deposit; + op.pay_vb = payment; + op.signing_key = alice_public_key; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->url == test_url ); + BOOST_CHECK( obj->signing_key == alice_public_key ); + BOOST_CHECK( obj->deposit.instance == deposit.instance.value ); + BOOST_CHECK( obj->pay_vb.instance == payment.instance.value ); +} + +BOOST_AUTO_TEST_CASE( update_son_test ) { + + INVOKE(create_son_test); + GET_ACTOR(alice); + + std::string new_url = "https://anewurl.com"; + + { + son_update_operation op; + op.owner_account = alice_id; + op.new_url = new_url; + op.son_id = son_id_type(0); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->url == new_url ); +} + +BOOST_AUTO_TEST_CASE( delete_son_test ) { + + INVOKE(create_son_test); + GET_ACTOR(alice); + + { + son_delete_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.empty() ); +} + +BOOST_AUTO_TEST_CASE( update_delete_not_own ) { // fee payer needs to be the son object owner +try { + + INVOKE(create_son_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + // bob tries to update a son object he dont own + { + son_update_operation op; + op.owner_account = bob_id; + op.new_url = "whatever"; + op.son_id = son_id_type(0); + + trx.operations.push_back(op); + sign(trx, bob_private_key); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx ), fc::exception); + } + generate_block(); + + set_expiration(db, trx); + trx.clear(); + + const auto& idx = db.get_index_type().indices().get(); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + // not changing + BOOST_CHECK( obj->url == "https://create_son_test" ); + + // bob tries to delete a son object he dont own + { + son_delete_operation op; + op.owner_account = bob_id; + op.son_id = son_id_type(0); + + trx.operations.push_back(op); + sign(trx, bob_private_key); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx ), fc::exception); + + } + generate_block(); + + obj = idx.find( alice_id ); + // not deleting + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->son_account.instance == alice_id.instance); +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} + +} BOOST_AUTO_TEST_SUITE_END()