From b80feab5154848144b97f32689139516dc843861 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Tue, 6 Nov 2018 16:07:46 +0300 Subject: [PATCH 01/16] Distribute Asset Market Fees to Referral Program - initial implementation - unit tests: added asset_rewards_test, modified create_advanced_uia --- libraries/app/database_api.cpp | 27 ++ .../app/include/graphene/app/database_api.hpp | 3 + libraries/chain/asset_evaluator.cpp | 12 + libraries/chain/db_balance.cpp | 35 +++ libraries/chain/db_market.cpp | 74 +++++- libraries/chain/hardfork.d/CORE_1268.hf | 4 + .../chain/include/graphene/chain/database.hpp | 3 + .../graphene/chain/protocol/asset_ops.hpp | 13 +- .../graphene/chain/vesting_balance_object.hpp | 18 +- .../wallet/include/graphene/wallet/wallet.hpp | 8 + libraries/wallet/wallet.cpp | 23 ++ tests/common/database_fixture.cpp | 33 ++- tests/common/database_fixture.hpp | 12 +- tests/tests/market_fee_sharing_tests.cpp | 251 ++++++++++++++++++ tests/tests/uia_tests.cpp | 11 + 15 files changed, 510 insertions(+), 17 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_1268.hf create mode 100644 tests/tests/market_fee_sharing_tests.cpp diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index acb90c8af5..59a46fac79 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -96,6 +96,7 @@ class database_api_impl : public std::enable_shared_from_this vector get_balance_objects( const vector
& addrs )const; vector get_vested_balances( const vector& objs )const; vector get_vesting_balances( const std::string account_id_or_name )const; + vector get_mfs_vesting_balances( const std::string account_id_or_name )const; // Assets vector> get_assets(const vector& asset_ids)const; @@ -1026,6 +1027,11 @@ vector database_api::get_vesting_balances( const std::st return my->get_vesting_balances( account_id_or_name ); } +vector database_api::get_mfs_vesting_balances( const std::string account_id_or_name )const +{ + return my->get_mfs_vesting_balances( account_id_or_name ); +} + vector database_api_impl::get_vesting_balances( const std::string account_id_or_name )const { try @@ -1042,6 +1048,27 @@ vector database_api_impl::get_vesting_balances( const st FC_CAPTURE_AND_RETHROW( (account_id_or_name) ); } +vector database_api_impl::get_mfs_vesting_balances( const std::string account_id_or_name )const +{ + try + { + const account_id_type account_id = get_account_from_string(account_id_or_name)->id; + vector result; + + auto& vesting_balances = _db.get_index_type().indices().get(); + auto key = boost::make_tuple(account_id, vesting_balance_type::market_fee_sharing); + auto mfs_vesting_range = vesting_balances.equal_range(key); + + std::for_each(mfs_vesting_range.first, mfs_vesting_range.second, + [&result](const vesting_balance_object& balance) { + result.emplace_back(balance); + }); + + return result; + } + FC_CAPTURE_AND_RETHROW( (account_id_or_name) ); +} + ////////////////////////////////////////////////////////////////////// // // // Assets // diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 506b427597..601f6d67ed 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -364,6 +364,8 @@ class database_api vector get_vesting_balances( const std::string account_id_or_name )const; + vector get_mfs_vesting_balances( const std::string account_id_or_name )const; + /** * @brief Get the total number of accounts registered with the blockchain */ @@ -784,6 +786,7 @@ FC_API(graphene::app::database_api, (get_balance_objects) (get_vested_balances) (get_vesting_balances) + (get_mfs_vesting_balances) // Assets (get_assets) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9194a02d25..deb838a347 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -45,6 +45,12 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); + if( d.head_block_time() < HARDFORK_1268_TIME ) + { + FC_ASSERT(!op.common_options.additional_options.value.null_ext.valid()); + FC_ASSERT(!op.common_options.additional_options.value.reward_percent.valid()); + } + // Check that all authorities do exist for( auto id : op.common_options.whitelist_authorities ) d.get_object(id); @@ -277,6 +283,12 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } + if ( o.new_options.additional_options.value.reward_percent.valid() ) + { + FC_ASSERT( d.head_block_time() >= HARDFORK_1268_TIME, + "Referrer percent is only available after HARDFORK_1268_TIME!"); + } + if( (d.head_block_time() < HARDFORK_572_TIME) || (a.dynamic_asset_data_id(d).current_supply != 0) ) { // new issuer_permissions must be subset of old issuer permissions diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index cf58cb432d..d28c0f51a0 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -80,6 +81,40 @@ void database::adjust_balance(account_id_type account, asset delta ) } FC_CAPTURE_AND_RETHROW( (account)(delta) ) } +void database::deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta) +{ try { + FC_ASSERT( delta.amount > 0, "Invalid negative value for balance"); + + if( delta.amount == 0 ) + return; + + auto& vesting_balances = get_index_type().indices().get(); + auto market_vesting_balances = vesting_balances.equal_range(boost::make_tuple(account_id, vesting_balance_type::market_fee_sharing)); + auto market_balance = boost::range::find_if(market_vesting_balances, + [&delta](const vesting_balance_object& vbo) { return vbo.balance.asset_id == delta.asset_id;} + ); + + if(market_balance == boost::end(market_vesting_balances) ) + { + create([&](vesting_balance_object &vbo) { + vbo.owner = account_id; + vbo.balance = delta; + vbo.balance_type = vesting_balance_type::market_fee_sharing; + cdd_vesting_policy policy; + policy.vesting_seconds = { 0 }; + policy.coin_seconds_earned = vbo.balance.amount.value; + policy.coin_seconds_earned_last_update = head_block_time(); + vbo.policy = policy; + }); + } else { + modify( *market_balance, [&]( vesting_balance_object& vbo ) + { + vbo.deposit_vested(head_block_time(), delta); + }); + } + +} FC_CAPTURE_AND_RETHROW( (account_id)(delta) ) } + optional< vesting_balance_id_type > database::deposit_lazy_vesting( const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 6b8f67ea1c..e56ae2ad77 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -31,7 +31,17 @@ #include -namespace graphene { namespace chain { +namespace graphene { namespace chain { namespace detail { + + fc::uint128 calculate_percent(const share_type& value, uint16_t percent) + { + fc::uint128 a(value.value); + a *= percent; + a /= GRAPHENE_100_PERCENT; + return a; + } + +} //detail /** * All margin positions are force closed at the swan price @@ -775,7 +785,12 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p const account_object& seller = order.seller(*this); const asset_object& recv_asset = receives.asset_id(*this); - auto issuer_fees = pay_market_fees( recv_asset, receives ); + //auto issuer_fees = pay_market_fees( recv_asset, receives ); + //auto issuer_fees = pay_market_fees(seller, recv_asset, receives); + auto issuer_fees = ( head_block_time() < HARDFORK_1268_TIME ) ? + pay_market_fees(recv_asset, receives) : + pay_market_fees(seller, recv_asset, receives); + pay_order( seller, receives - issuer_fees, pays ); assert( pays.asset_id != receives.asset_id ); @@ -1109,10 +1124,8 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass if( trade_asset.options.market_fee_percent == 0 ) return trade_asset.amount(0); - fc::uint128 a(trade_amount.amount.value); - a *= trade_asset.options.market_fee_percent; - a /= GRAPHENE_100_PERCENT; - asset percent_fee = trade_asset.amount(a.to_uint64()); + auto value = detail::calculate_percent(trade_amount.amount, trade_asset.options.market_fee_percent); + asset percent_fee = trade_asset.amount(value.to_uint64()); if( percent_fee.amount > trade_asset.options.max_market_fee ) percent_fee.amount = trade_asset.options.max_market_fee; @@ -1138,4 +1151,53 @@ asset database::pay_market_fees( const asset_object& recv_asset, const asset& re return issuer_fees; } +asset database::pay_market_fees(const account_object& seller, const asset_object& recv_asset, const asset& receives ) +{ + FC_ASSERT( head_block_time() >= HARDFORK_1268_TIME ); + + const auto issuer_fees = calculate_market_fee( recv_asset, receives ); + + assert(issuer_fees <= receives ); + + //Don't dirty undo state if not actually collecting any fees + if( issuer_fees.amount > 0 ) + { + // calculate and pay rewards + asset reward = recv_asset.amount(0); + + const auto reward_percent = recv_asset.options.additional_options.value.reward_percent; + + if ( reward_percent && *reward_percent ) + { + const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); + + if ( reward_value > 0 ) + { + reward = recv_asset.amount(reward_value.to_uint64()); + + assert( reward < issuer_fees ); + // cut referrer percent from reward + const auto referrer_rewards_percentage = seller.referrer_rewards_percentage; + const auto referrer_rewards_value = detail::calculate_percent(reward.amount, referrer_rewards_percentage); + + auto registrar_reward = reward; + if ( referrer_rewards_value > 0 ) + { + const asset referrer_reward = recv_asset.amount(referrer_rewards_value.to_uint64()); + registrar_reward -= referrer_reward; + deposit_market_fee_vesting_balance(seller.referrer, referrer_reward); + } + deposit_market_fee_vesting_balance(seller.registrar, registrar_reward); + } + } + + const auto& recv_dyn_data = recv_asset.dynamic_asset_data_id(*this); + modify( recv_dyn_data, [&]( asset_dynamic_data_object& obj ){ + obj.accumulated_fees += issuer_fees.amount - reward.amount; + }); + } + + return issuer_fees; +} + } } diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf new file mode 100644 index 0000000000..faae771f56 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1268.hf @@ -0,0 +1,4 @@ +// #1268 Distribute Asset Market Fees to Referral Program +#ifndef HARDFORK_1268_TIME +#define HARDFORK_1268_TIME (fc::time_point_sec( 1530705600 )) // Wednesday, July 4, 2018 12:00:00 PM +#endif \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index f0fb8e11c6..f1d2382ac1 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -303,6 +303,8 @@ namespace graphene { namespace chain { */ void adjust_balance(account_id_type account, asset delta); + void deposit_market_fee_vesting_balance(const account_id_type &account, const asset &delta); + /** * @brief Helper to make lazy deposit to CDD VBO. * @@ -395,6 +397,7 @@ namespace graphene { namespace chain { asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount); asset pay_market_fees( const asset_object& recv_asset, const asset& receives ); + asset pay_market_fees( const account_object& seller, const asset_object& recv_asset, const asset& receives ); ///@{ diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index 3a045a30c9..a36a4e6446 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -27,6 +27,13 @@ namespace graphene { namespace chain { + struct additional_asset_options + { + fc::optional null_ext; + fc::optional reward_percent; + }; + typedef extension additional_asset_options_t; + bool is_valid_symbol( const string& symbol ); /** @@ -75,7 +82,7 @@ namespace graphene { namespace chain { * size of description. */ string description; - extensions_type extensions; + additional_asset_options_t additional_options; /// Perform internal consistency checks. /// @throws fc::exception if any check fails @@ -523,7 +530,7 @@ FC_REFLECT( graphene::chain::asset_options, (whitelist_markets) (blacklist_markets) (description) - (extensions) + (additional_options) ) FC_REFLECT( graphene::chain::bitasset_options, (feed_lifetime_sec) @@ -535,7 +542,7 @@ FC_REFLECT( graphene::chain::bitasset_options, (extensions) ) - +FC_REFLECT( graphene::chain::additional_asset_options, (null_ext)(reward_percent) ) FC_REFLECT( graphene::chain::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_global_settle_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_settle_operation::fee_parameters_type, (fee) ) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 210c6c5870..fc5883994b 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -124,6 +125,9 @@ namespace graphene { namespace chain { cdd_vesting_policy > vesting_policy; + enum class vesting_balance_type { unspecified, + worker, + market_fee_sharing }; /** * Vesting balance object is a balance that is locked by the blockchain for a period of time. */ @@ -140,6 +144,8 @@ namespace graphene { namespace chain { asset balance; /// The vesting policy stores details on when funds vest, and controls when they may be withdrawn vesting_policy policy; + /// type of the vesting balance + vesting_balance_type balance_type = vesting_balance_type::unspecified; vesting_balance_object() {} @@ -171,12 +177,22 @@ namespace graphene { namespace chain { * @ingroup object_index */ struct by_account; + struct by_vesting_type; + typedef multi_index_container< vesting_balance_object, indexed_by< - ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, member< object, object_id_type, &object::id > + >, ordered_non_unique< tag, member + >, + ordered_non_unique< tag, + composite_key< + vesting_balance_object, + member, + member + > > > > vesting_balance_multi_index_type; diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index ca13357f0c..4f059b386d 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1455,6 +1455,13 @@ class wallet_api */ vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ); + /** List account's market fee sharing vesting balances. + * Each account can have multiple market fee sharing vesting balances. + * @param account_name_or_id the name or id of the account whose balances you want + * @returns a list of the given account's market fee sharing vesting balances + */ + vector< vesting_balance_object_with_info > get_mfs_vesting_balances(string account_name_or_id); + /** * Withdraw a vesting balance. * @@ -1799,6 +1806,7 @@ FC_API( graphene::wallet::wallet_api, (create_worker) (update_worker_votes) (get_vesting_balances) + (get_mfs_vesting_balances) (withdraw_vesting) (vote_for_committee_member) (vote_for_witness) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index ac33c180e9..04027083ef 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1765,6 +1765,24 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (account_name) ) } + vector get_mfs_vesting_balances(string account_name_or_id) const + { try { + fc::time_point_sec now = _remote_db->get_dynamic_global_properties().time; + + auto account = get_account(account_name_or_id); + auto always_id = account_id_to_string(account.id); + + vector vbos = _remote_db->get_mfs_vesting_balances(always_id); + + std::vector result; + for (const vesting_balance_object& vbo : vbos) + { + result.emplace_back(vbo, now); + } + return result; + } FC_CAPTURE_AND_RETHROW( (account_name_or_id) ) + } + signed_transaction withdraw_vesting( string witness_name, string amount, @@ -3694,6 +3712,11 @@ vector< vesting_balance_object_with_info > wallet_api::get_vesting_balances( str return my->get_vesting_balances( account_name ); } +vector wallet_api::get_mfs_vesting_balances(string account_name_or_id) +{ + return my->get_mfs_vesting_balances(account_name_or_id); +} + signed_transaction wallet_api::withdraw_vesting( string witness_name, string amount, diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 534c03de33..e50cf19cdb 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -23,6 +23,7 @@ */ #include #include +#include #include #include @@ -55,7 +56,7 @@ namespace graphene { namespace chain { using std::cout; using std::cerr; -database_fixture::database_fixture() +database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) : app(), db( *app.chain_database() ) { try { @@ -75,7 +76,7 @@ database_fixture::database_fixture() boost::program_options::variables_map options; - genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP ); + genesis_state.initial_timestamp = initial_timestamp; genesis_state.initial_active_witnesses = 10; for( unsigned int i = 0; i < genesis_state.initial_active_witnesses; ++i ) @@ -509,8 +510,9 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na return db.get(ptx.operation_results[0].get()); } -const asset_object& database_fixture::create_user_issued_asset( const string& name, const account_object& issuer, uint16_t flags, - const price& core_exchange_rate, uint16_t precision) +const asset_object& database_fixture::create_user_issued_asset( const string& name, const account_object& issuer, + uint16_t flags, const price& core_exchange_rate, + uint16_t precision, uint16_t market_fee_percent) { asset_create_operation creator; creator.issuer = issuer.id; @@ -522,6 +524,7 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; creator.common_options.flags = flags; creator.common_options.issuer_permissions = flags; + creator.common_options.market_fee_percent = market_fee_percent; trx.operations.clear(); trx.operations.push_back(std::move(creator)); set_expiration( db, trx ); @@ -723,6 +726,9 @@ const limit_order_object* database_fixture::create_sell_order( const account_obj const time_point_sec order_expiration, const price& fee_core_exchange_rate ) { + set_expiration( db, trx ); + trx.operations.clear(); + limit_order_create_operation buy_order; buy_order.seller = user.id; buy_order.amount_to_sell = amount; @@ -1120,6 +1126,25 @@ int64_t database_fixture::get_balance( const account_object& account, const asse return db.get_balance(account.get_id(), a.get_id()).amount.value; } +int64_t database_fixture::get_market_fee_reward( account_id_type account, asset_id_type asset_type)const +{ + auto& vesting_balances = db.get_index_type().indices().get(); + auto market_vesting_balances = vesting_balances.equal_range(boost::make_tuple(account, vesting_balance_type::market_fee_sharing)); + auto market_balance = boost::range::find_if(market_vesting_balances, + [&asset_type](const vesting_balance_object& vbo) { return vbo.balance.asset_id == asset_type;} + ); + + FC_ASSERT( market_balance != boost::end(market_vesting_balances) ); + + auto allowed_to_withdraw = market_balance->get_allowed_withdraw(db.head_block_time()); + return allowed_to_withdraw.amount.value; +} + +int64_t database_fixture::get_market_fee_reward( const account_object& account, const asset_object& a )const +{ + return get_market_fee_reward(account.get_id(), a.get_id()); +} + vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const { vector< operation_history_object > result; diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 78f7b65d19..109df46cc8 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -156,7 +156,7 @@ extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP; #define ACTOR(name) \ PREP_ACTOR(name) \ - const auto& name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \ + const auto name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \ graphene::chain::account_id_type name ## _id = name.id; (void)name ## _id; #define GET_ACTOR(name) \ @@ -187,7 +187,8 @@ struct database_fixture { bool skip_key_index_test = false; uint32_t anon_acct_count; - database_fixture(); + database_fixture(const fc::time_point_sec &initial_timestamp = + fc::time_point_sec(GRAPHENE_TESTING_GENESIS_TIMESTAMP)); ~database_fixture(); static fc::ecc::private_key generate_private_key(string seed); @@ -284,7 +285,8 @@ struct database_fixture { const account_object& issuer, uint16_t flags, const price& core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)), - uint16_t precision = 2 /* traditional precision for tests */); + uint16_t precision = 2 /* traditional precision for tests */, + uint16_t market_fee_percent = 0); void issue_uia( const account_object& recipient, asset amount ); void issue_uia( account_id_type recipient_id, asset amount ); @@ -343,6 +345,10 @@ struct database_fixture { void print_joint_market( const string& syma, const string& symb )const; int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; + + int64_t get_market_fee_reward( account_id_type account, asset_id_type asset )const; + int64_t get_market_fee_reward( const account_object& account, const asset_object& asset )const; + vector< operation_history_object > get_operation_history( account_id_type account_id )const; vector< graphene::market_history::order_history_object > get_market_order_history( asset_id_type a, asset_id_type b )const; }; diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp new file mode 100644 index 0000000000..a36f8c7e30 --- /dev/null +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -0,0 +1,251 @@ +#include +//#include +//#include + +#include +#include +#include +#include + + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +namespace fc +{ + template + std::basic_ostream& operator<<(std::basic_ostream& os, safe const& sf) + { + os << sf.value; + return os; + } +} + +struct reward_database_fixture : database_fixture +{ + reward_database_fixture() + : database_fixture(HARDFORK_1268_TIME - 100) + { + } + + void update_asset( const account_id_type& issuer_id, + const fc::ecc::private_key& private_key, + const asset_id_type& asset_id, + uint16_t reward_percent ) + { + asset_update_operation op; + op.issuer = issuer_id; + op.asset_to_update = asset_id; + op.new_options = asset_id(db).options; + op.new_options.additional_options.value.reward_percent = reward_percent; + + signed_transaction tx; + tx.operations.push_back( op ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, private_key ); + PUSH_TX( db, tx ); + } + + void generate_blocks_past_reward_hardfork() + { + database_fixture::generate_blocks( HARDFORK_1268_TIME ); + database_fixture::generate_block(); + } +}; + +BOOST_FIXTURE_TEST_SUITE( reward_tests, reward_database_fixture ) + +BOOST_AUTO_TEST_CASE(asset_rewards_test) +{ + try + { + ACTORS((registrar)(alicereferrer)(bobreferrer)(izzy)(jill)); + + auto register_account = [&](const string& name, const account_object& referrer) -> const account_object& + { + uint8_t referrer_percent = 100; + fc::ecc::private_key _private_key = generate_private_key(name); + public_key_type _public_key = _private_key.get_public_key(); + return create_account(name, registrar, referrer, referrer_percent, _public_key); + }; + + // Izzy issues asset to Alice + // Jill issues asset to Bob + // Alice and Bob trade in the market and pay fees + // Bob's and Alice's referrers can get reward + upgrade_to_lifetime_member(registrar); + upgrade_to_lifetime_member(alicereferrer); + upgrade_to_lifetime_member(bobreferrer); + + auto alice = register_account("alice", alicereferrer); + auto bob = register_account("bob", bobreferrer); + + const share_type core_prec = asset::scaled_precision( asset_id_type()(db).precision ); + + // Return number of core shares (times precision) + auto _core = [&]( int64_t x ) -> asset + { return asset( x*core_prec ); }; + + transfer( committee_account, alice.id, _core(1000000) ); + transfer( committee_account, bob.id, _core(1000000) ); + transfer( committee_account, izzy_id, _core(1000000) ); + transfer( committee_account, jill_id, _core(1000000) ); + + constexpr auto izzycoin_reward_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_reward_percent = 20*GRAPHENE_1_PERCENT; + + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + + asset_id_type izzycoin_id = create_bitasset( "IZZYCOIN", izzy_id, izzycoin_market_percent ).id; + asset_id_type jillcoin_id = create_bitasset( "JILLCOIN", jill_id, jillcoin_market_percent ).id; + + GRAPHENE_REQUIRE_THROW( update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent), fc::exception ); + generate_blocks_past_reward_hardfork(); + update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); + + update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); + + const share_type izzy_prec = asset::scaled_precision( asset_id_type(izzycoin_id)(db).precision ); + const share_type jill_prec = asset::scaled_precision( asset_id_type(jillcoin_id)(db).precision ); + + auto _izzy = [&]( int64_t x ) -> asset + { return asset( x*izzy_prec, izzycoin_id ); }; + auto _jill = [&]( int64_t x ) -> asset + { return asset( x*jill_prec, jillcoin_id ); }; + + update_feed_producers( izzycoin_id(db), { izzy_id } ); + update_feed_producers( jillcoin_id(db), { jill_id } ); + + // Izzycoin is worth 100 BTS + price_feed feed; + feed.settlement_price = price( _izzy(1), _core(100) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( izzycoin_id(db), izzy, feed ); + + // Jillcoin is worth 30 BTS + feed.settlement_price = price( _jill(1), _core(30) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( jillcoin_id(db), jill, feed ); + + enable_fees(); + + // Alice and Bob create some coins + borrow( alice.id, _izzy( 1500), _core( 600000) ); + borrow( bob.id, _jill(2000), _core(180000) ); + + // Alice and Bob place orders which match + create_sell_order( alice.id, _izzy(1000), _jill(1500) ); // Alice is willing to sell her 1000 Izzy's for 1.5 Jill + create_sell_order( bob.id, _jill(1500), _izzy(1000) ); // Bob is buying up to 1500 Izzy's for up to 0.6 Jill + + // 1000 Izzys and 1500 Jills are matched, so the fees should be + // 100 Izzy (10%) and 300 Jill (20%). + // Bob's and Alice's referrers should get rewards + + share_type bob_refereer_reward = get_market_fee_reward( bob.referrer, izzycoin_id ); + share_type alice_refereer_reward = get_market_fee_reward( alice.referrer, jillcoin_id ); + + // Bob's and Alice's registrars should get rewards + share_type bob_rgistrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); + share_type alice_registrar_reward = get_market_fee_reward( alice.registrar, jillcoin_id ); + + auto calculate_percent = [](const share_type& value, uint16_t percent) + { + auto a(value.value); + a *= percent; + a /= GRAPHENE_100_PERCENT; + return a; + }; + + BOOST_CHECK_GT( bob_refereer_reward, 0 ); + BOOST_CHECK_GT( alice_refereer_reward, 0 ); + BOOST_CHECK_GT( bob_rgistrar_reward, 0 ); + BOOST_CHECK_GT( alice_registrar_reward, 0 ); + + const auto izzycoin_market_fee = calculate_percent(_izzy(1000).amount, izzycoin_market_percent); + const auto izzycoin_reward = calculate_percent(izzycoin_market_fee, izzycoin_reward_percent); + BOOST_CHECK_EQUAL( izzycoin_reward, bob_refereer_reward + bob_rgistrar_reward ); + BOOST_CHECK_EQUAL( calculate_percent(izzycoin_reward, bob.referrer_rewards_percentage), bob_refereer_reward ); + + const auto jillcoin_market_fee = calculate_percent(_jill(1500).amount, jillcoin_market_percent); + const auto jillcoin_reward = calculate_percent(jillcoin_market_fee, jillcoin_reward_percent); + BOOST_CHECK_EQUAL( jillcoin_reward, alice_refereer_reward + alice_registrar_reward ); + BOOST_CHECK_EQUAL( calculate_percent(jillcoin_reward, alice.referrer_rewards_percentage), alice_refereer_reward ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(asset_claim_reward_test) +{ + try + { + ACTORS((jill)(izzy)); + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + + upgrade_to_lifetime_member(izzy); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + auto obj = jill_id(db); + const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); + + const share_type core_prec = asset::scaled_precision( asset_id_type()(db).precision ); + + // return number of core shares (times precision) + auto _core = [&core_prec]( int64_t x ) -> asset { return asset( x*core_prec ); }; + + const account_object alice = create_account("alice", izzy, izzy, 50); + const account_object bob = create_account("bob", izzy, izzy, 50); + + // prepare users' balance + issue_uia( alice, jillcoin.amount( 20000000 ) ); + + transfer( committee_account, alice.get_id(), _core(1000) ); + transfer( committee_account, bob.get_id(), _core(1000) ); + transfer( committee_account, izzy.get_id(), _core(1000) ); + + auto claim_reward = [&]( account_object referrer, asset amount_to_claim, fc::ecc::private_key private_key ) + { + vesting_balance_withdraw_operation op; + op.vesting_balance = vesting_balance_id_type(0); + op.owner = referrer.get_id(); + op.amount = amount_to_claim; + + signed_transaction tx; + tx.operations.push_back( op ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, private_key ); + PUSH_TX( db, tx ); + }; + + const int64_t amount_to_claim = 3; + //GRAPHENE_REQUIRE_THROW( claim_reward( izzy, jillcoin.amount(amount_to_claim), izzy_private_key ), fc::exception ); + //GRAPHENE_REQUIRE_THROW( update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent), fc::exception ); + generate_blocks_past_reward_hardfork(); + // update_asset: set referrer percent + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent); + + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), _core(1) ); + create_sell_order( bob, _core(1), jillcoin.amount(100000) ); + + const int64_t izzy_reward = get_market_fee_reward( izzy, jillcoin ); + const int64_t izzy_balance = get_balance( izzy, jillcoin ); + + BOOST_CHECK_GT(izzy_reward, 0); + + + claim_reward( izzy, jillcoin.amount(amount_to_claim), izzy_private_key ); + + BOOST_CHECK_EQUAL(get_balance( izzy, jillcoin ), izzy_balance + amount_to_claim); + BOOST_CHECK_EQUAL(get_market_fee_reward( izzy, jillcoin ), izzy_reward - amount_to_claim); + } + FC_LOG_AND_RETHROW() +} +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index 7b44ef4c04..997cb672b0 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -58,6 +58,12 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) creator.common_options.flags = charge_market_fee|white_list|override_authority|disable_confidential; creator.common_options.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); creator.common_options.whitelist_authorities = creator.common_options.blacklist_authorities = {account_id_type()}; + + if( db.head_block_time() >= HARDFORK_1268_TIME ) + { + creator.common_options.additional_options.value.reward_percent = 12; + } + trx.operations.push_back(std::move(creator)); PUSH_TX( db, trx, ~0 ); @@ -73,6 +79,11 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) BOOST_CHECK(test_asset_dynamic_data.current_supply == 0); BOOST_CHECK(test_asset_dynamic_data.accumulated_fees == 0); BOOST_CHECK(test_asset_dynamic_data.fee_pool == 0); + + if( db.head_block_time() >= HARDFORK_1268_TIME ) + { + BOOST_CHECK(test_asset.options.additional_options.value.reward_percent == 12); + } } catch(fc::exception& e) { edump((e.to_detail_string())); throw; From 558f1ec87191652ed5d8de91db7025ca5bd6f0e6 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Fri, 9 Nov 2018 14:58:00 +0300 Subject: [PATCH 02/16] - prepare vesting balance object for hardfork - fix PR notes --- libraries/chain/asset_evaluator.cpp | 6 ++-- libraries/chain/db_balance.cpp | 18 +++++++--- libraries/chain/db_market.cpp | 26 +++++++------- libraries/chain/hardfork.d/CORE_1268.hf | 2 +- .../chain/include/graphene/chain/config.hpp | 2 +- .../chain/include/graphene/chain/database.hpp | 2 ++ .../graphene/chain/protocol/asset_ops.hpp | 7 ++-- .../graphene/chain/vesting_balance_object.hpp | 12 +++++-- libraries/chain/worker_evaluator.cpp | 1 + tests/tests/market_fee_sharing_tests.cpp | 36 ++++++++----------- tests/tests/uia_tests.cpp | 9 ----- 11 files changed, 60 insertions(+), 61 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index deb838a347..636626e50d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -47,8 +47,8 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o if( d.head_block_time() < HARDFORK_1268_TIME ) { - FC_ASSERT(!op.common_options.additional_options.value.null_ext.valid()); - FC_ASSERT(!op.common_options.additional_options.value.reward_percent.valid()); + FC_ASSERT( !op.common_options.extensions.value.reward_percent.valid(), + "Asset additional options reward percent is only available after HARDFORK_1268_TIME!"); } // Check that all authorities do exist @@ -283,7 +283,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } - if ( o.new_options.additional_options.value.reward_percent.valid() ) + if ( o.new_options.extensions.value.reward_percent.valid() ) { FC_ASSERT( d.head_block_time() >= HARDFORK_1268_TIME, "Referrer percent is only available after HARDFORK_1268_TIME!"); diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index d28c0f51a0..6fa85cf8c7 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -83,7 +83,7 @@ void database::adjust_balance(account_id_type account, asset delta ) void database::deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta) { try { - FC_ASSERT( delta.amount > 0, "Invalid negative value for balance"); + FC_ASSERT( delta.amount >= 0, "Invalid negative value for balance"); if( delta.amount == 0 ) return; @@ -94,22 +94,26 @@ void database::deposit_market_fee_vesting_balance(const account_id_type &account [&delta](const vesting_balance_object& vbo) { return vbo.balance.asset_id == delta.asset_id;} ); + auto block_time = head_block_time(); + if(market_balance == boost::end(market_vesting_balances) ) { - create([&](vesting_balance_object &vbo) { + create([&account_id, &delta, &block_time](vesting_balance_object &vbo) { vbo.owner = account_id; vbo.balance = delta; vbo.balance_type = vesting_balance_type::market_fee_sharing; + cdd_vesting_policy policy; policy.vesting_seconds = { 0 }; policy.coin_seconds_earned = vbo.balance.amount.value; - policy.coin_seconds_earned_last_update = head_block_time(); + policy.coin_seconds_earned_last_update = block_time; + vbo.policy = policy; }); } else { - modify( *market_balance, [&]( vesting_balance_object& vbo ) + modify( *market_balance, [&block_time, &delta]( vesting_balance_object& vbo ) { - vbo.deposit_vested(head_block_time(), delta); + vbo.deposit_vested(block_time, delta); }); } @@ -118,6 +122,7 @@ void database::deposit_market_fee_vesting_balance(const account_id_type &account optional< vesting_balance_id_type > database::deposit_lazy_vesting( const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, + vesting_balance_type balance_type, account_id_type req_owner, bool require_vesting ) { @@ -151,6 +156,7 @@ optional< vesting_balance_id_type > database::deposit_lazy_vesting( { _vbo.owner = req_owner; _vbo.balance = amount; + _vbo.balance_type = balance_type; cdd_vesting_policy policy; policy.vesting_seconds = req_vesting_seconds; @@ -186,6 +192,7 @@ void database::deposit_cashback(const account_object& acct, share_type amount, b acct.cashback_vb, amount, get_global_properties().parameters.cashback_vesting_period_seconds, + vesting_balance_type::cashback, acct.id, require_vesting ); @@ -213,6 +220,7 @@ void database::deposit_witness_pay(const witness_object& wit, share_type amount) wit.pay_vb, amount, get_global_properties().parameters.witness_pay_vesting_seconds, + vesting_balance_type::witness, wit.witness_account, true ); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index e56ae2ad77..9b9d0589a0 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -33,12 +33,12 @@ namespace graphene { namespace chain { namespace detail { - fc::uint128 calculate_percent(const share_type& value, uint16_t percent) + uint64_t calculate_percent(const share_type& value, uint16_t percent) { fc::uint128 a(value.value); a *= percent; a /= GRAPHENE_100_PERCENT; - return a; + return a.to_uint64(); } } //detail @@ -785,8 +785,6 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p const account_object& seller = order.seller(*this); const asset_object& recv_asset = receives.asset_id(*this); - //auto issuer_fees = pay_market_fees( recv_asset, receives ); - //auto issuer_fees = pay_market_fees(seller, recv_asset, receives); auto issuer_fees = ( head_block_time() < HARDFORK_1268_TIME ) ? pay_market_fees(recv_asset, receives) : pay_market_fees(seller, recv_asset, receives); @@ -1125,7 +1123,7 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass return trade_asset.amount(0); auto value = detail::calculate_percent(trade_amount.amount, trade_asset.options.market_fee_percent); - asset percent_fee = trade_asset.amount(value.to_uint64()); + asset percent_fee = trade_asset.amount(value); if( percent_fee.amount > trade_asset.options.max_market_fee ) percent_fee.amount = trade_asset.options.max_market_fee; @@ -1136,7 +1134,7 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass asset database::pay_market_fees( const asset_object& recv_asset, const asset& receives ) { auto issuer_fees = calculate_market_fee( recv_asset, receives ); - assert(issuer_fees <= receives ); + FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); //Don't dirty undo state if not actually collecting any fees if( issuer_fees.amount > 0 ) @@ -1153,11 +1151,9 @@ asset database::pay_market_fees( const asset_object& recv_asset, const asset& re asset database::pay_market_fees(const account_object& seller, const asset_object& recv_asset, const asset& receives ) { - FC_ASSERT( head_block_time() >= HARDFORK_1268_TIME ); - const auto issuer_fees = calculate_market_fee( recv_asset, receives ); - assert(issuer_fees <= receives ); + FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); //Don't dirty undo state if not actually collecting any fees if( issuer_fees.amount > 0 ) @@ -1165,7 +1161,7 @@ asset database::pay_market_fees(const account_object& seller, const asset_object // calculate and pay rewards asset reward = recv_asset.amount(0); - const auto reward_percent = recv_asset.options.additional_options.value.reward_percent; + const auto reward_percent = recv_asset.options.extensions.value.reward_percent; if ( reward_percent && *reward_percent ) { @@ -1173,9 +1169,9 @@ asset database::pay_market_fees(const account_object& seller, const asset_object if ( reward_value > 0 ) { - reward = recv_asset.amount(reward_value.to_uint64()); + reward = recv_asset.amount(reward_value); - assert( reward < issuer_fees ); + FC_ASSERT( reward < issuer_fees, "Market reward should be less than issuer fees"); // cut referrer percent from reward const auto referrer_rewards_percentage = seller.referrer_rewards_percentage; const auto referrer_rewards_value = detail::calculate_percent(reward.amount, referrer_rewards_percentage); @@ -1183,7 +1179,9 @@ asset database::pay_market_fees(const account_object& seller, const asset_object auto registrar_reward = reward; if ( referrer_rewards_value > 0 ) { - const asset referrer_reward = recv_asset.amount(referrer_rewards_value.to_uint64()); + FC_ASSERT ( referrer_rewards_value <= reward.amount, "Referrer reward shouldn't be greater than total reward" ); + + const asset referrer_reward = recv_asset.amount(referrer_rewards_value); registrar_reward -= referrer_reward; deposit_market_fee_vesting_balance(seller.referrer, referrer_reward); } @@ -1192,7 +1190,7 @@ asset database::pay_market_fees(const account_object& seller, const asset_object } const auto& recv_dyn_data = recv_asset.dynamic_asset_data_id(*this); - modify( recv_dyn_data, [&]( asset_dynamic_data_object& obj ){ + modify( recv_dyn_data, [&issuer_fees, &reward]( asset_dynamic_data_object& obj ){ obj.accumulated_fees += issuer_fees.amount - reward.amount; }); } diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf index faae771f56..6872db2fb2 100644 --- a/libraries/chain/hardfork.d/CORE_1268.hf +++ b/libraries/chain/hardfork.d/CORE_1268.hf @@ -1,4 +1,4 @@ // #1268 Distribute Asset Market Fees to Referral Program #ifndef HARDFORK_1268_TIME #define HARDFORK_1268_TIME (fc::time_point_sec( 1530705600 )) // Wednesday, July 4, 2018 12:00:00 PM -#endif \ No newline at end of file +#endif diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 1ef28a0c67..3ab5f47f19 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -121,7 +121,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "BTS2.18" +#define GRAPHENE_CURRENT_DB_VERSION "BTS2.19" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index f1d2382ac1..4e2915a14b 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -47,6 +47,7 @@ namespace graphene { namespace chain { class transaction_evaluation_state; struct budget_record; + enum class vesting_balance_type; /** * @class database @@ -322,6 +323,7 @@ namespace graphene { namespace chain { const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, + vesting_balance_type balance_type, account_id_type req_owner, bool require_vesting ); diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index a36a4e6446..716c14a458 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -29,7 +29,6 @@ namespace graphene { namespace chain { struct additional_asset_options { - fc::optional null_ext; fc::optional reward_percent; }; typedef extension additional_asset_options_t; @@ -82,7 +81,7 @@ namespace graphene { namespace chain { * size of description. */ string description; - additional_asset_options_t additional_options; + additional_asset_options_t extensions; /// Perform internal consistency checks. /// @throws fc::exception if any check fails @@ -530,7 +529,7 @@ FC_REFLECT( graphene::chain::asset_options, (whitelist_markets) (blacklist_markets) (description) - (additional_options) + (extensions) ) FC_REFLECT( graphene::chain::bitasset_options, (feed_lifetime_sec) @@ -542,7 +541,7 @@ FC_REFLECT( graphene::chain::bitasset_options, (extensions) ) -FC_REFLECT( graphene::chain::additional_asset_options, (null_ext)(reward_percent) ) +FC_REFLECT( graphene::chain::additional_asset_options, (reward_percent) ) FC_REFLECT( graphene::chain::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_global_settle_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_settle_operation::fee_parameters_type, (fee) ) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index fc5883994b..afe7aeb75a 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -125,9 +125,11 @@ namespace graphene { namespace chain { cdd_vesting_policy > vesting_policy; - enum class vesting_balance_type { unspecified, - worker, - market_fee_sharing }; + enum class vesting_balance_type { unspecified, + cashback, + worker, + witness, + market_fee_sharing }; /** * Vesting balance object is a balance that is locked by the blockchain for a period of time. */ @@ -223,4 +225,8 @@ FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::objec (owner) (balance) (policy) + (balance_type) ) + +FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (unspecified)(cashback)(worker)(witness)(market_fee_sharing) ) + diff --git a/libraries/chain/worker_evaluator.cpp b/libraries/chain/worker_evaluator.cpp index b5aea8f3b4..240f9723fa 100644 --- a/libraries/chain/worker_evaluator.cpp +++ b/libraries/chain/worker_evaluator.cpp @@ -58,6 +58,7 @@ struct worker_init_visitor w.balance = db.create([&](vesting_balance_object& b) { b.owner = worker.worker_account; b.balance = asset(0); + b.balance_type = vesting_balance_type::worker; cdd_vesting_policy policy; policy.vesting_seconds = fc::days(i.pay_vesting_period_days).to_seconds(); diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index a36f8c7e30..e7fc05e078 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -1,6 +1,4 @@ #include -//#include -//#include #include #include @@ -39,7 +37,7 @@ struct reward_database_fixture : database_fixture op.issuer = issuer_id; op.asset_to_update = asset_id; op.new_options = asset_id(db).options; - op.new_options.additional_options.value.reward_percent = reward_percent; + op.new_options.extensions.value.reward_percent = reward_percent; signed_transaction tx; tx.operations.push_back( op ); @@ -146,7 +144,6 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) // 1000 Izzys and 1500 Jills are matched, so the fees should be // 100 Izzy (10%) and 300 Jill (20%). // Bob's and Alice's referrers should get rewards - share_type bob_refereer_reward = get_market_fee_reward( bob.referrer, izzycoin_id ); share_type alice_refereer_reward = get_market_fee_reward( alice.referrer, jillcoin_id ); @@ -209,6 +206,19 @@ BOOST_AUTO_TEST_CASE(asset_claim_reward_test) transfer( committee_account, bob.get_id(), _core(1000) ); transfer( committee_account, izzy.get_id(), _core(1000) ); + generate_blocks_past_reward_hardfork(); + // update_asset: set referrer percent + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent); + + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), _core(1) ); + create_sell_order( bob, _core(1), jillcoin.amount(100000) ); + + const int64_t izzy_reward = get_market_fee_reward( izzy, jillcoin ); + const int64_t izzy_balance = get_balance( izzy, jillcoin ); + + BOOST_CHECK_GT(izzy_reward, 0); + auto claim_reward = [&]( account_object referrer, asset amount_to_claim, fc::ecc::private_key private_key ) { vesting_balance_withdraw_operation op; @@ -225,22 +235,6 @@ BOOST_AUTO_TEST_CASE(asset_claim_reward_test) }; const int64_t amount_to_claim = 3; - //GRAPHENE_REQUIRE_THROW( claim_reward( izzy, jillcoin.amount(amount_to_claim), izzy_private_key ), fc::exception ); - //GRAPHENE_REQUIRE_THROW( update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent), fc::exception ); - generate_blocks_past_reward_hardfork(); - // update_asset: set referrer percent - update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent); - - // Alice and Bob place orders which match - create_sell_order( alice, jillcoin.amount(200000), _core(1) ); - create_sell_order( bob, _core(1), jillcoin.amount(100000) ); - - const int64_t izzy_reward = get_market_fee_reward( izzy, jillcoin ); - const int64_t izzy_balance = get_balance( izzy, jillcoin ); - - BOOST_CHECK_GT(izzy_reward, 0); - - claim_reward( izzy, jillcoin.amount(amount_to_claim), izzy_private_key ); BOOST_CHECK_EQUAL(get_balance( izzy, jillcoin ), izzy_balance + amount_to_claim); @@ -248,4 +242,4 @@ BOOST_AUTO_TEST_CASE(asset_claim_reward_test) } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index 997cb672b0..95b4fdf4ec 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -59,11 +59,6 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) creator.common_options.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); creator.common_options.whitelist_authorities = creator.common_options.blacklist_authorities = {account_id_type()}; - if( db.head_block_time() >= HARDFORK_1268_TIME ) - { - creator.common_options.additional_options.value.reward_percent = 12; - } - trx.operations.push_back(std::move(creator)); PUSH_TX( db, trx, ~0 ); @@ -80,10 +75,6 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) BOOST_CHECK(test_asset_dynamic_data.accumulated_fees == 0); BOOST_CHECK(test_asset_dynamic_data.fee_pool == 0); - if( db.head_block_time() >= HARDFORK_1268_TIME ) - { - BOOST_CHECK(test_asset.options.additional_options.value.reward_percent == 12); - } } catch(fc::exception& e) { edump((e.to_detail_string())); throw; From 45e11592839366ba905ff25c67f48b04c9c7d140 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Mon, 12 Nov 2018 15:46:51 +0300 Subject: [PATCH 03/16] Unit tests for asset - added market_sharing_whitelist option - added tests for asset extensions before and after the hardfork 1268 --- .../graphene/chain/protocol/asset_ops.hpp | 5 +- tests/common/database_fixture.cpp | 4 +- tests/common/database_fixture.hpp | 5 +- tests/tests/market_fee_sharing_tests.cpp | 101 +++++++++++++++++- 4 files changed, 107 insertions(+), 8 deletions(-) diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index 716c14a458..9c6fca3c9c 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -29,7 +29,8 @@ namespace graphene { namespace chain { struct additional_asset_options { - fc::optional reward_percent; + fc::optional reward_percent; + fc::optional> whitelist_market_fee_sharing; }; typedef extension additional_asset_options_t; @@ -541,7 +542,7 @@ FC_REFLECT( graphene::chain::bitasset_options, (extensions) ) -FC_REFLECT( graphene::chain::additional_asset_options, (reward_percent) ) +FC_REFLECT( graphene::chain::additional_asset_options, (reward_percent)(whitelist_market_fee_sharing) ) FC_REFLECT( graphene::chain::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_global_settle_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_settle_operation::fee_parameters_type, (fee) ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index e50cf19cdb..6c633c4729 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -512,7 +512,8 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na const asset_object& database_fixture::create_user_issued_asset( const string& name, const account_object& issuer, uint16_t flags, const price& core_exchange_rate, - uint16_t precision, uint16_t market_fee_percent) + uint8_t precision, uint16_t market_fee_percent, + additional_asset_options_t additional_options) { asset_create_operation creator; creator.issuer = issuer.id; @@ -525,6 +526,7 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na creator.common_options.flags = flags; creator.common_options.issuer_permissions = flags; creator.common_options.market_fee_percent = market_fee_percent; + creator.common_options.extensions = std::move(additional_options); trx.operations.clear(); trx.operations.push_back(std::move(creator)); set_expiration( db, trx ); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 109df46cc8..651d5f29cc 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -285,8 +285,9 @@ struct database_fixture { const account_object& issuer, uint16_t flags, const price& core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)), - uint16_t precision = 2 /* traditional precision for tests */, - uint16_t market_fee_percent = 0); + uint8_t precision = 2 /* traditional precision for tests */, + uint16_t market_fee_percent = 0, + additional_asset_options_t options = additional_asset_options_t()); void issue_uia( const account_object& recipient, asset amount ); void issue_uia( account_id_type recipient_id, asset amount ); diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index e7fc05e078..a04862fc71 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -31,13 +31,15 @@ struct reward_database_fixture : database_fixture void update_asset( const account_id_type& issuer_id, const fc::ecc::private_key& private_key, const asset_id_type& asset_id, - uint16_t reward_percent ) + uint16_t reward_percent, + flat_set whitelist_market_fee_sharing = flat_set()) { asset_update_operation op; op.issuer = issuer_id; op.asset_to_update = asset_id; op.new_options = asset_id(db).options; op.new_options.extensions.value.reward_percent = reward_percent; + op.new_options.extensions.value.whitelist_market_fee_sharing = whitelist_market_fee_sharing; signed_transaction tx; tx.operations.push_back( op ); @@ -56,6 +58,100 @@ struct reward_database_fixture : database_fixture BOOST_FIXTURE_TEST_SUITE( reward_tests, reward_database_fixture ) +BOOST_AUTO_TEST_CASE(cannot_create_asset_with_additional_options_before_hf) +{ + try + { + ACTOR(issuer); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 100; + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + + GRAPHENE_CHECK_THROW(create_user_issued_asset("USD", + issuer, + charge_market_fee, + price, + 2, + market_fee_percent, + options), + fc::assert_exception); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(create_asset_with_additional_options_after_hf) +{ + try + { + ACTOR(issuer); + + generate_blocks_past_reward_hardfork(); + + uint16_t reward_percent = 100; + flat_set whitelist = {issuer_id}; + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 100; + + additional_asset_options_t options; + options.value.reward_percent = reward_percent; + options.value.whitelist_market_fee_sharing = whitelist; + + asset_object usd_asset = create_user_issued_asset("USD", + issuer, + charge_market_fee, + price, + 2, + market_fee_percent, + options); + + additional_asset_options usd_options = usd_asset.options.extensions.value; + BOOST_CHECK_EQUAL(reward_percent, *usd_options.reward_percent); + BOOST_CHECK(whitelist == *usd_options.whitelist_market_fee_sharing); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(cannot_update_additional_options_before_hf) +{ + try + { + ACTOR(issuer); + + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + GRAPHENE_CHECK_THROW( + update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), 40, {issuer_id}), + fc::assert_exception ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(update_additional_options_after_hf) +{ + try + { + ACTOR(issuer); + + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + generate_blocks_past_reward_hardfork(); + + uint16_t reward_percent = 40; + flat_set whitelist = {issuer_id}; + update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), reward_percent, whitelist); + + asset_object updated_asset = usd_asset.get_id()(db); + additional_asset_options options = updated_asset.options.extensions.value; + BOOST_CHECK_EQUAL(reward_percent, *options.reward_percent); + BOOST_CHECK(whitelist == *options.whitelist_market_fee_sharing); + } + FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_CASE(asset_rewards_test) { try @@ -101,10 +197,9 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) asset_id_type izzycoin_id = create_bitasset( "IZZYCOIN", izzy_id, izzycoin_market_percent ).id; asset_id_type jillcoin_id = create_bitasset( "JILLCOIN", jill_id, jillcoin_market_percent ).id; - GRAPHENE_REQUIRE_THROW( update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent), fc::exception ); generate_blocks_past_reward_hardfork(); - update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); + update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); const share_type izzy_prec = asset::scaled_precision( asset_id_type(izzycoin_id)(db).precision ); From 44049b0f3462e73e7ab80e0362567ca51f102dc4 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Fri, 16 Nov 2018 15:08:15 +0300 Subject: [PATCH 04/16] Apply market fee sharing whitelist --- libraries/chain/db_market.cpp | 48 ++++--- tests/tests/market_fee_sharing_tests.cpp | 169 +++++++++++++++++++---- 2 files changed, 168 insertions(+), 49 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 9b9d0589a0..08ebad0ade 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1152,40 +1152,42 @@ asset database::pay_market_fees( const asset_object& recv_asset, const asset& re asset database::pay_market_fees(const account_object& seller, const asset_object& recv_asset, const asset& receives ) { const auto issuer_fees = calculate_market_fee( recv_asset, receives ); - FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); - //Don't dirty undo state if not actually collecting any fees - if( issuer_fees.amount > 0 ) + if ( issuer_fees.amount > 0 ) { // calculate and pay rewards asset reward = recv_asset.amount(0); - const auto reward_percent = recv_asset.options.extensions.value.reward_percent; + auto is_rewards_allowed = [&recv_asset, &seller]() { + const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing; + return ( !white_list || (*white_list).empty() || ( (*white_list).find(seller.referrer) != (*white_list).end() ) ); + }; - if ( reward_percent && *reward_percent ) + if ( is_rewards_allowed() ) { - const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); - - if ( reward_value > 0 ) + const auto reward_percent = recv_asset.options.extensions.value.reward_percent; + if ( reward_percent && *reward_percent ) { - reward = recv_asset.amount(reward_value); - - FC_ASSERT( reward < issuer_fees, "Market reward should be less than issuer fees"); - // cut referrer percent from reward - const auto referrer_rewards_percentage = seller.referrer_rewards_percentage; - const auto referrer_rewards_value = detail::calculate_percent(reward.amount, referrer_rewards_percentage); - - auto registrar_reward = reward; - if ( referrer_rewards_value > 0 ) + const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); + if ( reward_value > 0 ) { - FC_ASSERT ( referrer_rewards_value <= reward.amount, "Referrer reward shouldn't be greater than total reward" ); - - const asset referrer_reward = recv_asset.amount(referrer_rewards_value); - registrar_reward -= referrer_reward; - deposit_market_fee_vesting_balance(seller.referrer, referrer_reward); + reward = recv_asset.amount(reward_value); + FC_ASSERT( reward < issuer_fees, "Market reward should be less than issuer fees"); + // cut referrer percent from reward + const auto referrer_rewards_percentage = seller.referrer_rewards_percentage; + const auto referrer_rewards_value = detail::calculate_percent(reward.amount, referrer_rewards_percentage); + auto registrar_reward = reward; + + if ( referrer_rewards_value > 0 ) + { + FC_ASSERT ( referrer_rewards_value <= reward.amount, "Referrer reward shouldn't be greater than total reward" ); + const asset referrer_reward = recv_asset.amount(referrer_rewards_value); + registrar_reward -= referrer_reward; + deposit_market_fee_vesting_balance(seller.referrer, referrer_reward); + } + deposit_market_fee_vesting_balance(seller.registrar, registrar_reward); } - deposit_market_fee_vesting_balance(seller.registrar, registrar_reward); } } diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index a04862fc71..4b5d984d25 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -23,6 +23,8 @@ namespace fc struct reward_database_fixture : database_fixture { + using whitelist_market_fee_sharing_t = fc::optional>; + reward_database_fixture() : database_fixture(HARDFORK_1268_TIME - 100) { @@ -32,7 +34,7 @@ struct reward_database_fixture : database_fixture const fc::ecc::private_key& private_key, const asset_id_type& asset_id, uint16_t reward_percent, - flat_set whitelist_market_fee_sharing = flat_set()) + const whitelist_market_fee_sharing_t &whitelist_market_fee_sharing = whitelist_market_fee_sharing_t{}) { asset_update_operation op; op.issuer = issuer_id; @@ -54,6 +56,13 @@ struct reward_database_fixture : database_fixture database_fixture::generate_blocks( HARDFORK_1268_TIME ); database_fixture::generate_block(); } + + asset core_asset(int64_t x ) + { + return asset( x*core_precision ); + }; + + const share_type core_precision = asset::scaled_precision( asset_id_type()(db).precision ); }; BOOST_FIXTURE_TEST_SUITE( reward_tests, reward_database_fixture ) @@ -123,8 +132,9 @@ BOOST_AUTO_TEST_CASE(cannot_update_additional_options_before_hf) asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + flat_set whitelist = {issuer_id}; GRAPHENE_CHECK_THROW( - update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), 40, {issuer_id}), + update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), 40, whitelist), fc::assert_exception ); } FC_LOG_AND_RETHROW() @@ -177,16 +187,10 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) auto alice = register_account("alice", alicereferrer); auto bob = register_account("bob", bobreferrer); - const share_type core_prec = asset::scaled_precision( asset_id_type()(db).precision ); - - // Return number of core shares (times precision) - auto _core = [&]( int64_t x ) -> asset - { return asset( x*core_prec ); }; - - transfer( committee_account, alice.id, _core(1000000) ); - transfer( committee_account, bob.id, _core(1000000) ); - transfer( committee_account, izzy_id, _core(1000000) ); - transfer( committee_account, jill_id, _core(1000000) ); + transfer( committee_account, alice.id, core_asset(1000000) ); + transfer( committee_account, bob.id, core_asset(1000000) ); + transfer( committee_account, izzy_id, core_asset(1000000) ); + transfer( committee_account, jill_id, core_asset(1000000) ); constexpr auto izzycoin_reward_percent = 10*GRAPHENE_1_PERCENT; constexpr auto jillcoin_reward_percent = 20*GRAPHENE_1_PERCENT; @@ -215,13 +219,13 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) // Izzycoin is worth 100 BTS price_feed feed; - feed.settlement_price = price( _izzy(1), _core(100) ); + feed.settlement_price = price( _izzy(1), core_asset(100) ); feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; publish_feed( izzycoin_id(db), izzy, feed ); // Jillcoin is worth 30 BTS - feed.settlement_price = price( _jill(1), _core(30) ); + feed.settlement_price = price( _jill(1), core_asset(30) ); feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; publish_feed( jillcoin_id(db), jill, feed ); @@ -229,8 +233,8 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) enable_fees(); // Alice and Bob create some coins - borrow( alice.id, _izzy( 1500), _core( 600000) ); - borrow( bob.id, _jill(2000), _core(180000) ); + borrow( alice.id, _izzy( 1500), core_asset( 600000) ); + borrow( bob.id, _jill(2000), core_asset(180000) ); // Alice and Bob place orders which match create_sell_order( alice.id, _izzy(1000), _jill(1500) ); // Alice is willing to sell her 1000 Izzy's for 1.5 Jill @@ -286,28 +290,23 @@ BOOST_AUTO_TEST_CASE(asset_claim_reward_test) auto obj = jill_id(db); const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); - const share_type core_prec = asset::scaled_precision( asset_id_type()(db).precision ); - - // return number of core shares (times precision) - auto _core = [&core_prec]( int64_t x ) -> asset { return asset( x*core_prec ); }; - const account_object alice = create_account("alice", izzy, izzy, 50); const account_object bob = create_account("bob", izzy, izzy, 50); // prepare users' balance issue_uia( alice, jillcoin.amount( 20000000 ) ); - transfer( committee_account, alice.get_id(), _core(1000) ); - transfer( committee_account, bob.get_id(), _core(1000) ); - transfer( committee_account, izzy.get_id(), _core(1000) ); + transfer( committee_account, alice.get_id(), core_asset(1000) ); + transfer( committee_account, bob.get_id(), core_asset(1000) ); + transfer( committee_account, izzy.get_id(), core_asset(1000) ); generate_blocks_past_reward_hardfork(); // update_asset: set referrer percent update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent); // Alice and Bob place orders which match - create_sell_order( alice, jillcoin.amount(200000), _core(1) ); - create_sell_order( bob, _core(1), jillcoin.amount(100000) ); + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); const int64_t izzy_reward = get_market_fee_reward( izzy, jillcoin ); const int64_t izzy_balance = get_balance( izzy, jillcoin ); @@ -337,4 +336,122 @@ BOOST_AUTO_TEST_CASE(asset_claim_reward_test) } FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(create_actors) +{ + try + { + ACTORS((jill)(izzy)); + + upgrade_to_lifetime_member(izzy); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + auto obj = jill_id(db); + const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); + + const account_object alice = create_account("alice", izzy, izzy, 50); + const account_object bob = create_account("bob", izzy, izzy, 50); + + // prepare users' balance + issue_uia( alice, jillcoin.amount( 20000000 ) ); + + transfer( committee_account, alice.get_id(), core_asset(1000) ); + transfer( committee_account, bob.get_id(), core_asset(1000) ); + transfer( committee_account, izzy.get_id(), core_asset(1000) ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_is_empty_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_reward_hardfork(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + flat_set whitelist; + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + GET_ACTOR(izzy); + BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const auto izzy_reward = get_market_fee_reward( izzy, jillcoin ); + BOOST_CHECK_GT(izzy_reward , 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_contains_registrar_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_reward_hardfork(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(izzy); + flat_set whitelist = {jill_id, izzy_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const auto izzy_reward = get_market_fee_reward( izzy, jillcoin ); + BOOST_CHECK_GT(izzy_reward , 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_reward_hardfork(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(alice); + flat_set whitelist = {jill_id, alice_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + GET_ACTOR(izzy); + BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + } + FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END() From b6a813a4889c7dd7c20cd6a6c71a243b4d245cac Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Fri, 16 Nov 2018 15:10:38 +0300 Subject: [PATCH 05/16] Add instant_vesting_policy --- libraries/chain/db_balance.cpp | 8 +-- .../graphene/chain/protocol/vesting.hpp | 10 ++- .../graphene/chain/vesting_balance_object.hpp | 22 ++++++- libraries/chain/vesting_balance_evaluator.cpp | 6 ++ libraries/chain/vesting_balance_object.cpp | 30 +++++++++ tests/tests/operation_tests.cpp | 64 +++++++++++++++++++ 6 files changed, 131 insertions(+), 9 deletions(-) diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index 6fa85cf8c7..610ba8fae0 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -102,13 +102,7 @@ void database::deposit_market_fee_vesting_balance(const account_id_type &account vbo.owner = account_id; vbo.balance = delta; vbo.balance_type = vesting_balance_type::market_fee_sharing; - - cdd_vesting_policy policy; - policy.vesting_seconds = { 0 }; - policy.coin_seconds_earned = vbo.balance.amount.value; - policy.coin_seconds_earned_last_update = block_time; - - vbo.policy = policy; + vbo.policy = instant_vesting_policy{}; }); } else { modify( *market_balance, [&block_time, &delta]( vesting_balance_object& vbo ) diff --git a/libraries/chain/include/graphene/chain/protocol/vesting.hpp b/libraries/chain/include/graphene/chain/protocol/vesting.hpp index 4915b62ec6..b5d03585de 100644 --- a/libraries/chain/include/graphene/chain/protocol/vesting.hpp +++ b/libraries/chain/include/graphene/chain/protocol/vesting.hpp @@ -42,8 +42,15 @@ namespace graphene { namespace chain { cdd_vesting_policy_initializer( uint32_t vest_sec = 0, fc::time_point_sec sc = fc::time_point_sec() ):start_claim(sc),vesting_seconds(vest_sec){} }; - typedef fc::static_variant vesting_policy_initializer; + struct instant_vesting_policy_initializer + { + }; + typedef fc::static_variant< + linear_vesting_policy_initializer, + cdd_vesting_policy_initializer, + instant_vesting_policy_initializer + > vesting_policy_initializer; /** @@ -117,4 +124,5 @@ FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_b FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) ) FC_REFLECT(graphene::chain::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) ) +FC_REFLECT_EMPTY( graphene::chain::instant_vesting_policy_initializer ) FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer ) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index afe7aeb75a..1fba587dff 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -120,9 +120,27 @@ namespace graphene { namespace chain { void on_withdraw(const vesting_policy_context& ctx); }; + /** + * @brief instant vesting policy + * + * This policy allows to withdraw everything that is on a balance immediately + * + */ + struct instant_vesting_policy + { + asset get_allowed_withdraw(const vesting_policy_context& ctx)const; + bool is_deposit_allowed(const vesting_policy_context& ctx)const; + bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; } + bool is_withdraw_allowed(const vesting_policy_context& ctx)const; + void on_deposit(const vesting_policy_context& ctx); + void on_deposit_vested(const vesting_policy_context&); + void on_withdraw(const vesting_policy_context& ctx); + }; + typedef fc::static_variant< linear_vesting_policy, - cdd_vesting_policy + cdd_vesting_policy, + instant_vesting_policy > vesting_policy; enum class vesting_balance_type { unspecified, @@ -219,6 +237,8 @@ FC_REFLECT(graphene::chain::cdd_vesting_policy, (coin_seconds_earned_last_update) ) +FC_REFLECT_EMPTY( graphene::chain::instant_vesting_policy ) + FC_REFLECT_TYPENAME( graphene::chain::vesting_policy ) FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object), diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index ee918fd16d..b0bf34f12e 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -76,6 +76,12 @@ struct init_policy_visitor policy.coin_seconds_earned_last_update = now; p = policy; } + + void operator()( const instant_vesting_policy_initializer& i )const + { + p = instant_vesting_policy{}; + } + }; object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op ) diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index 73448e04c8..8735a674f5 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -157,6 +157,36 @@ bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)c return (ctx.amount <= get_allowed_withdraw(ctx)); } +asset instant_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const +{ + return ctx.balance; +} + +void instant_vesting_policy::on_deposit(const vesting_policy_context& ctx) +{ +} + +void instant_vesting_policy::on_deposit_vested(const vesting_policy_context&) +{ + +} + +bool instant_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount.asset_id == ctx.balance.asset_id) + && sum_below_max_shares(ctx.amount, ctx.balance); +} + +void instant_vesting_policy::on_withdraw(const vesting_policy_context& ctx) +{ +} + +bool instant_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount.asset_id == ctx.balance.asset_id) + && (ctx.amount <= get_allowed_withdraw(ctx)); +} + #define VESTING_VISITOR(NAME, MAYBE_CONST) \ struct NAME ## _visitor \ { \ diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 5e4168562d..2afab2e489 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2359,4 +2359,68 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test ) // TODO: Write linear VBO tests +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_test ) +{ try { + ACTOR(alice); + + INVOKE( create_uia ); + + const asset_object& core = asset_id_type()(db); + const asset_object& test_asset = get_asset(UIA_TEST_SYMBOL); + + vesting_balance_create_operation op; + op.fee = core.amount( 0 ); + op.creator = account_id_type(); + op.owner = account_id_type(); + op.amount = test_asset.amount( 100 ); + op.policy = instant_vesting_policy_initializer{}; + + // Fee must be non-negative + REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(1) ); + REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(0) ); + REQUIRE_OP_VALIDATION_FAILURE( op, fee, core.amount(-1) ); + + // Amount must be positive + REQUIRE_OP_VALIDATION_SUCCESS( op, amount, core.amount(1) ); + REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount(0) ); + REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount(-1) ); + + transfer(committee_account(db), alice, core.amount(100000)); + + op.creator = alice_id; + op.owner = alice_id; + + account_id_type nobody = account_id_type(1234); + + trx.operations.push_back(op); + // Invalid account_id's + REQUIRE_THROW_WITH_VALUE( op, creator, nobody ); + REQUIRE_THROW_WITH_VALUE( op, owner, nobody ); + + // Insufficient funds + REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(999999999) ); + // Alice can fund a bond to herself or to Bob + op.amount = core.amount( 1000 ); + + trx.operations.back() = op; + set_expiration( db, trx ); + sign(trx, alice_private_key); + + processed_transaction ptx = PUSH_TX( db, trx, ~0 ); + const vesting_balance_id_type& vbid = ptx.operation_results.back().get(); + + { + vesting_balance_withdraw_operation withdraw_op; + withdraw_op.vesting_balance = vbid; + withdraw_op.owner = alice_id; + withdraw_op.amount = op.amount; + + signed_transaction withdraw_tx; + withdraw_tx.operations.push_back( withdraw_op ); + set_expiration( db, withdraw_tx ); + sign(withdraw_tx, alice_private_key); + PUSH_TX( db, withdraw_tx ); + } +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 2429cb64cb98d7dc71bf30d211f27c823a272f49 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Fri, 16 Nov 2018 15:16:54 +0300 Subject: [PATCH 06/16] Add check for hf 1268 to proposal_create_evaluator --- libraries/chain/asset_evaluator.cpp | 25 +++--- libraries/chain/proposal_evaluator.cpp | 9 ++ tests/tests/market_fee_sharing_tests.cpp | 104 +++++++++++++++++++++++ 3 files changed, 128 insertions(+), 10 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 636626e50d..aa6688f6eb 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -35,6 +35,19 @@ #include namespace graphene { namespace chain { +namespace detail { + void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options) + { + if( block_time < HARDFORK_1268_TIME ) + { + FC_ASSERT( !options.extensions.value.reward_percent.valid(), + "Asset extension reward percent is only available after HARDFORK_1268_TIME!"); + + FC_ASSERT( !options.extensions.value.whitelist_market_fee_sharing.valid(), + "Asset extension whitelist_market_fee_sharing is only available after HARDFORK_1268_TIME!"); + } + } +} void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) { try { @@ -45,11 +58,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); - if( d.head_block_time() < HARDFORK_1268_TIME ) - { - FC_ASSERT( !op.common_options.extensions.value.reward_percent.valid(), - "Asset additional options reward percent is only available after HARDFORK_1268_TIME!"); - } + detail::check_asset_options_hf_1268(d.head_block_time(), op.common_options); // Check that all authorities do exist for( auto id : op.common_options.whitelist_authorities ) @@ -283,11 +292,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } - if ( o.new_options.extensions.value.reward_percent.valid() ) - { - FC_ASSERT( d.head_block_time() >= HARDFORK_1268_TIME, - "Referrer percent is only available after HARDFORK_1268_TIME!"); - } + detail::check_asset_options_hf_1268(d.head_block_time(), o.new_options); if( (d.head_block_time() < HARDFORK_572_TIME) || (a.dynamic_asset_data_id(d).current_supply != 0) ) { diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 8124cc4fda..99dd87916a 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -30,6 +30,9 @@ namespace graphene { namespace chain { +namespace detail { + void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options); +} struct proposal_operation_hardfork_visitor { @@ -57,6 +60,12 @@ struct proposal_operation_hardfork_visitor static const std::locale &loc = std::locale::classic(); FC_ASSERT(isalpha(v.symbol.back(), loc), "Asset ${s} must end with alpha character before hardfork 620", ("s", v.symbol)); } + + detail::check_asset_options_hf_1268(block_time, v.common_options); + } + // hf_1268 + void operator()(const graphene::chain::asset_update_operation &v) const { + detail::check_asset_options_hf_1268(block_time, v.new_options); } // hf_199 void operator()(const graphene::chain::asset_update_issuer_operation &v) const { diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index 4b5d984d25..80d72cc414 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -454,4 +454,108 @@ BOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test) FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(create_asset_via_proposal_test) +{ + try + { + ACTOR(issuer); + price core_exchange_rate(asset(1, asset_id_type(1)), asset(1)); + + asset_create_operation create_op; + create_op.issuer = issuer.id; + create_op.fee = asset(); + create_op.symbol = "ASSET"; + create_op.common_options.max_supply = 0; + create_op.precision = 2; + create_op.common_options.core_exchange_rate = core_exchange_rate; + create_op.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + create_op.common_options.flags = charge_market_fee; + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + create_op.common_options.extensions = std::move(options);; + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = issuer_id; + prop.proposed_ops.emplace_back( create_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_reward_hardfork(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(update_asset_via_proposal_test) +{ + try + { + ACTOR(issuer); + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + + asset_update_operation update_op; + update_op.issuer = issuer_id; + update_op.asset_to_update = usd_asset.get_id(); + asset_options new_options; + update_op.new_options = usd_asset.options; + update_op.new_options.extensions = std::move(options); + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = issuer_id; + prop.proposed_ops.emplace_back( update_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_reward_hardfork(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + } + FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END() From c7d8a5b155824f68d9db955823196de3c03319cc Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Tue, 20 Nov 2018 13:11:16 +0300 Subject: [PATCH 07/16] Check accumulated fees before/after HF_1268 --- tests/tests/market_fee_sharing_tests.cpp | 109 +++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index 80d72cc414..df8c425eee 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -558,4 +558,113 @@ BOOST_AUTO_TEST_CASE(update_asset_via_proposal_test) FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(issue_asset){ + try + { + ACTORS((alice)(bob)(izzy)(jill)); + // Izzy issues asset to Alice (Izzycoin market percent - 10%) + // Jill issues asset to Bob (Jillcoin market percent - 20%) + + fund( alice, core_asset(1000000) ); + fund( bob, core_asset(1000000) ); + fund( izzy, core_asset(1000000) ); + fund( jill, core_asset(1000000) ); + + price price(asset(1, asset_id_type(1)), asset(1)); + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + asset_object izzycoin = create_user_issued_asset( "IZZYCOIN", izzy, charge_market_fee, price, 2, izzycoin_market_percent ); + + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + asset_object jillcoin = create_user_issued_asset( "JILLCOIN", jill, charge_market_fee, price, 2, jillcoin_market_percent ); + + // Alice and Bob create some coins + issue_uia( alice, izzycoin.amount( 100000 ) ); + issue_uia( bob, jillcoin.amount( 100000 ) ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_before_hf_test) +{ + try + { + INVOKE(issue_asset); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_after_hf_test) +{ + try + { + INVOKE(issue_asset); + + generate_blocks_past_reward_hardfork(); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_with_additional_options_after_hf_test) +{ + try + { + INVOKE(issue_asset); + + generate_blocks_past_reward_hardfork(); + + GET_ACTOR(jill); + GET_ACTOR(izzy); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + uint16_t reward_percent = 0; + update_asset(jill_id, jill_private_key, jillcoin.get_id(), reward_percent); + update_asset(izzy_id, izzy_private_key, izzycoin.get_id(), reward_percent); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END() From b52ea7123a0d96bbfe66dd2519178d9f386f7e5e Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Tue, 20 Nov 2018 13:16:31 +0300 Subject: [PATCH 08/16] Add hardfork protection to prevent vesting_balance_create operation with instant_vesting_policy_initializer --- libraries/chain/proposal_evaluator.cpp | 5 + libraries/chain/vesting_balance_evaluator.cpp | 13 ++ tests/tests/market_fee_sharing_tests.cpp | 138 ++++++++++++++++-- tests/tests/operation_tests.cpp | 64 -------- 4 files changed, 143 insertions(+), 77 deletions(-) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 99dd87916a..975d14cb54 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -32,6 +32,7 @@ namespace graphene { namespace chain { namespace detail { void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options); + void check_vesting_balance_policy_hf_1268(const fc::time_point_sec& block_time, const vesting_policy_initializer& policy); } struct proposal_operation_hardfork_visitor @@ -67,6 +68,10 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::asset_update_operation &v) const { detail::check_asset_options_hf_1268(block_time, v.new_options); } + // hf_1268 + void operator()(const graphene::chain::vesting_balance_create_operation &v) const { + detail::check_vesting_balance_policy_hf_1268(block_time, v.policy); + } // hf_199 void operator()(const graphene::chain::asset_update_issuer_operation &v) const { if (block_time < HARDFORK_CORE_199_TIME) { diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index b0bf34f12e..6f05343c24 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -26,8 +26,19 @@ #include #include #include +#include namespace graphene { namespace chain { +namespace detail { + void check_vesting_balance_policy_hf_1268(const fc::time_point_sec& block_time, const vesting_policy_initializer& policy) + { + if( block_time < HARDFORK_1268_TIME ) + { + FC_ASSERT( policy.which() != vesting_policy_initializer::tag::value, + "Instant vesting policy is only available after HARDFORK_1268_TIME!"); + } + } +} void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance_create_operation& op ) { try { @@ -42,6 +53,8 @@ void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance FC_ASSERT( d.get_balance( creator_account.id, op.amount.asset_id ) >= op.amount ); FC_ASSERT( !op.amount.asset_id(d).is_transfer_restricted() ); + detail::check_vesting_balance_policy_hf_1268(d.head_block_time(), op.policy); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index df8c425eee..d0544b64ae 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -51,7 +51,7 @@ struct reward_database_fixture : database_fixture PUSH_TX( db, tx ); } - void generate_blocks_past_reward_hardfork() + void generate_blocks_past_hf1268() { database_fixture::generate_blocks( HARDFORK_1268_TIME ); database_fixture::generate_block(); @@ -65,7 +65,7 @@ struct reward_database_fixture : database_fixture const share_type core_precision = asset::scaled_precision( asset_id_type()(db).precision ); }; -BOOST_FIXTURE_TEST_SUITE( reward_tests, reward_database_fixture ) +BOOST_FIXTURE_TEST_SUITE( fee_sharing_tests, reward_database_fixture ) BOOST_AUTO_TEST_CASE(cannot_create_asset_with_additional_options_before_hf) { @@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(create_asset_with_additional_options_after_hf) { ACTOR(issuer); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); uint16_t reward_percent = 100; flat_set whitelist = {issuer_id}; @@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(update_additional_options_after_hf) asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); uint16_t reward_percent = 40; flat_set whitelist = {issuer_id}; @@ -201,7 +201,7 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) asset_id_type izzycoin_id = create_bitasset( "IZZYCOIN", izzy_id, izzycoin_market_percent ).id; asset_id_type jillcoin_id = create_bitasset( "JILLCOIN", jill_id, jillcoin_market_percent ).id; - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); @@ -300,7 +300,7 @@ BOOST_AUTO_TEST_CASE(asset_claim_reward_test) transfer( committee_account, bob.get_id(), core_asset(1000) ); transfer( committee_account, izzy.get_id(), core_asset(1000) ); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); // update_asset: set referrer percent update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent); @@ -369,7 +369,7 @@ BOOST_AUTO_TEST_CASE(white_list_is_empty_test) { INVOKE(create_actors); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); GET_ACTOR(jill); constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; @@ -399,7 +399,7 @@ BOOST_AUTO_TEST_CASE(white_list_contains_registrar_test) { INVOKE(create_actors); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); GET_ACTOR(jill); constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; @@ -430,7 +430,7 @@ BOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test) { INVOKE(create_actors); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); GET_ACTOR(jill); constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; @@ -493,7 +493,7 @@ BOOST_AUTO_TEST_CASE(create_asset_via_proposal_test) GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); } - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); { prop.expiration_time = db.head_block_time() + fc::days(1); @@ -543,7 +543,7 @@ BOOST_AUTO_TEST_CASE(update_asset_via_proposal_test) GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); } - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); { prop.expiration_time = db.head_block_time() + fc::days(1); @@ -614,7 +614,7 @@ BOOST_AUTO_TEST_CASE(accumulated_fees_after_hf_test) { INVOKE(issue_asset); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); const asset_object &jillcoin = get_asset("JILLCOIN"); const asset_object &izzycoin = get_asset("IZZYCOIN"); @@ -640,7 +640,7 @@ BOOST_AUTO_TEST_CASE(accumulated_fees_with_additional_options_after_hf_test) { INVOKE(issue_asset); - generate_blocks_past_reward_hardfork(); + generate_blocks_past_hf1268(); GET_ACTOR(jill); GET_ACTOR(izzy); @@ -667,4 +667,116 @@ BOOST_AUTO_TEST_CASE(accumulated_fees_with_additional_options_after_hf_test) FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_before_hf1268_test ) +{ try { + + ACTOR(alice); + fund(alice); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation op; + op.fee = core.amount( 0 ); + op.creator = alice_id; + op.owner = alice_id; + op.amount = core.amount( 100 ); + op.policy = instant_vesting_policy_initializer{}; + + trx.operations.push_back(op); + set_expiration( db, trx ); + sign(trx, alice_private_key); + + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_after_hf1268_test ) +{ try { + + ACTOR(alice); + fund(alice); + + generate_blocks_past_hf1268(); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation op; + op.fee = core.amount( 0 ); + op.creator = alice_id; + op.owner = alice_id; + op.amount = core.amount( 100 ); + op.policy = instant_vesting_policy_initializer{}; + + trx.operations.push_back(op); + set_expiration( db, trx ); + + processed_transaction ptx = PUSH_TX( db, trx, ~0 ); + const vesting_balance_id_type& vbid = ptx.operation_results.back().get(); + + auto withdraw = [&](const asset& amount) { + vesting_balance_withdraw_operation withdraw_op; + withdraw_op.vesting_balance = vbid; + withdraw_op.owner = alice_id; + withdraw_op.amount = amount; + + signed_transaction withdraw_tx; + withdraw_tx.operations.push_back( withdraw_op ); + set_expiration( db, withdraw_tx ); + sign(withdraw_tx, alice_private_key); + PUSH_TX( db, withdraw_tx ); + }; + // try to withdraw more then it is on the balance + GRAPHENE_REQUIRE_THROW(withdraw(op.amount.amount + 1), fc::exception); + //to withdraw all that is on the balance + withdraw(op.amount); + // try to withdraw more then it is on the balance + GRAPHENE_REQUIRE_THROW(withdraw( core.amount(1) ), fc::exception); +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_via_proposal_test ) +{ try { + + ACTOR(actor); + fund(actor); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation create_op; + create_op.fee = core.amount( 0 ); + create_op.creator = actor_id; + create_op.owner = actor_id; + create_op.amount = core.amount( 100 ); + create_op.policy = instant_vesting_policy_initializer{}; + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = actor_id; + prop.proposed_ops.emplace_back( create_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, actor_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_hf1268(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, actor_private_key ); + PUSH_TX( db, tx ); + } +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 2afab2e489..5e4168562d 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2359,68 +2359,4 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test ) // TODO: Write linear VBO tests -BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_test ) -{ try { - ACTOR(alice); - - INVOKE( create_uia ); - - const asset_object& core = asset_id_type()(db); - const asset_object& test_asset = get_asset(UIA_TEST_SYMBOL); - - vesting_balance_create_operation op; - op.fee = core.amount( 0 ); - op.creator = account_id_type(); - op.owner = account_id_type(); - op.amount = test_asset.amount( 100 ); - op.policy = instant_vesting_policy_initializer{}; - - // Fee must be non-negative - REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(1) ); - REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(0) ); - REQUIRE_OP_VALIDATION_FAILURE( op, fee, core.amount(-1) ); - - // Amount must be positive - REQUIRE_OP_VALIDATION_SUCCESS( op, amount, core.amount(1) ); - REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount(0) ); - REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount(-1) ); - - transfer(committee_account(db), alice, core.amount(100000)); - - op.creator = alice_id; - op.owner = alice_id; - - account_id_type nobody = account_id_type(1234); - - trx.operations.push_back(op); - // Invalid account_id's - REQUIRE_THROW_WITH_VALUE( op, creator, nobody ); - REQUIRE_THROW_WITH_VALUE( op, owner, nobody ); - - // Insufficient funds - REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(999999999) ); - // Alice can fund a bond to herself or to Bob - op.amount = core.amount( 1000 ); - - trx.operations.back() = op; - set_expiration( db, trx ); - sign(trx, alice_private_key); - - processed_transaction ptx = PUSH_TX( db, trx, ~0 ); - const vesting_balance_id_type& vbid = ptx.operation_results.back().get(); - - { - vesting_balance_withdraw_operation withdraw_op; - withdraw_op.vesting_balance = vbid; - withdraw_op.owner = alice_id; - withdraw_op.amount = op.amount; - - signed_transaction withdraw_tx; - withdraw_tx.operations.push_back( withdraw_op ); - set_expiration( db, withdraw_tx ); - sign(withdraw_tx, alice_private_key); - PUSH_TX( db, withdraw_tx ); - } -} FC_LOG_AND_RETHROW() } - BOOST_AUTO_TEST_SUITE_END() From 7a528039a753d883ebc3d35c2e5f733367e7a7b6 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Tue, 27 Nov 2018 16:39:50 +0300 Subject: [PATCH 09/16] Remove function [get_mfs_vesting_balances] --- libraries/app/database_api.cpp | 27 ------------------- .../app/include/graphene/app/database_api.hpp | 3 --- .../wallet/include/graphene/wallet/wallet.hpp | 8 ------ libraries/wallet/wallet.cpp | 23 ---------------- 4 files changed, 61 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 59a46fac79..acb90c8af5 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -96,7 +96,6 @@ class database_api_impl : public std::enable_shared_from_this vector get_balance_objects( const vector
& addrs )const; vector get_vested_balances( const vector& objs )const; vector get_vesting_balances( const std::string account_id_or_name )const; - vector get_mfs_vesting_balances( const std::string account_id_or_name )const; // Assets vector> get_assets(const vector& asset_ids)const; @@ -1027,11 +1026,6 @@ vector database_api::get_vesting_balances( const std::st return my->get_vesting_balances( account_id_or_name ); } -vector database_api::get_mfs_vesting_balances( const std::string account_id_or_name )const -{ - return my->get_mfs_vesting_balances( account_id_or_name ); -} - vector database_api_impl::get_vesting_balances( const std::string account_id_or_name )const { try @@ -1048,27 +1042,6 @@ vector database_api_impl::get_vesting_balances( const st FC_CAPTURE_AND_RETHROW( (account_id_or_name) ); } -vector database_api_impl::get_mfs_vesting_balances( const std::string account_id_or_name )const -{ - try - { - const account_id_type account_id = get_account_from_string(account_id_or_name)->id; - vector result; - - auto& vesting_balances = _db.get_index_type().indices().get(); - auto key = boost::make_tuple(account_id, vesting_balance_type::market_fee_sharing); - auto mfs_vesting_range = vesting_balances.equal_range(key); - - std::for_each(mfs_vesting_range.first, mfs_vesting_range.second, - [&result](const vesting_balance_object& balance) { - result.emplace_back(balance); - }); - - return result; - } - FC_CAPTURE_AND_RETHROW( (account_id_or_name) ); -} - ////////////////////////////////////////////////////////////////////// // // // Assets // diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 601f6d67ed..506b427597 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -364,8 +364,6 @@ class database_api vector get_vesting_balances( const std::string account_id_or_name )const; - vector get_mfs_vesting_balances( const std::string account_id_or_name )const; - /** * @brief Get the total number of accounts registered with the blockchain */ @@ -786,7 +784,6 @@ FC_API(graphene::app::database_api, (get_balance_objects) (get_vested_balances) (get_vesting_balances) - (get_mfs_vesting_balances) // Assets (get_assets) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 4f059b386d..ca13357f0c 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1455,13 +1455,6 @@ class wallet_api */ vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ); - /** List account's market fee sharing vesting balances. - * Each account can have multiple market fee sharing vesting balances. - * @param account_name_or_id the name or id of the account whose balances you want - * @returns a list of the given account's market fee sharing vesting balances - */ - vector< vesting_balance_object_with_info > get_mfs_vesting_balances(string account_name_or_id); - /** * Withdraw a vesting balance. * @@ -1806,7 +1799,6 @@ FC_API( graphene::wallet::wallet_api, (create_worker) (update_worker_votes) (get_vesting_balances) - (get_mfs_vesting_balances) (withdraw_vesting) (vote_for_committee_member) (vote_for_witness) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 04027083ef..ac33c180e9 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1765,24 +1765,6 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (account_name) ) } - vector get_mfs_vesting_balances(string account_name_or_id) const - { try { - fc::time_point_sec now = _remote_db->get_dynamic_global_properties().time; - - auto account = get_account(account_name_or_id); - auto always_id = account_id_to_string(account.id); - - vector vbos = _remote_db->get_mfs_vesting_balances(always_id); - - std::vector result; - for (const vesting_balance_object& vbo : vbos) - { - result.emplace_back(vbo, now); - } - return result; - } FC_CAPTURE_AND_RETHROW( (account_name_or_id) ) - } - signed_transaction withdraw_vesting( string witness_name, string amount, @@ -3712,11 +3694,6 @@ vector< vesting_balance_object_with_info > wallet_api::get_vesting_balances( str return my->get_vesting_balances( account_name ); } -vector wallet_api::get_mfs_vesting_balances(string account_name_or_id) -{ - return my->get_mfs_vesting_balances(account_name_or_id); -} - signed_transaction wallet_api::withdraw_vesting( string witness_name, string amount, From 59b41905530aa3ae2efd9fbb89b0be769b0c555b Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Tue, 27 Nov 2018 16:42:15 +0300 Subject: [PATCH 10/16] Check asset restrictions - added check asset restrictions while pay reward - added unit test --- libraries/chain/db_market.cpp | 7 +- tests/common/database_fixture.cpp | 8 +- tests/common/database_fixture.hpp | 6 +- tests/tests/market_fee_sharing_tests.cpp | 213 ++++++++++++++++++++--- 4 files changed, 196 insertions(+), 38 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 08ebad0ade..8feae4e0ef 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -1161,7 +1162,7 @@ asset database::pay_market_fees(const account_object& seller, const asset_object auto is_rewards_allowed = [&recv_asset, &seller]() { const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing; - return ( !white_list || (*white_list).empty() || ( (*white_list).find(seller.referrer) != (*white_list).end() ) ); + return ( !white_list || (*white_list).empty() || ( (*white_list).find(seller.registrar) != (*white_list).end() ) ); }; if ( is_rewards_allowed() ) @@ -1170,7 +1171,7 @@ asset database::pay_market_fees(const account_object& seller, const asset_object if ( reward_percent && *reward_percent ) { const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); - if ( reward_value > 0 ) + if ( reward_value > 0 && is_authorized_asset(*this, seller.registrar(*this), recv_asset) ) { reward = recv_asset.amount(reward_value); FC_ASSERT( reward < issuer_fees, "Market reward should be less than issuer fees"); @@ -1179,7 +1180,7 @@ asset database::pay_market_fees(const account_object& seller, const asset_object const auto referrer_rewards_value = detail::calculate_percent(reward.amount, referrer_rewards_percentage); auto registrar_reward = reward; - if ( referrer_rewards_value > 0 ) + if ( referrer_rewards_value > 0 && is_authorized_asset(*this, seller.referrer(*this), recv_asset)) { FC_ASSERT ( referrer_rewards_value <= reward.amount, "Referrer reward shouldn't be greater than total reward" ); const asset referrer_reward = recv_asset.amount(referrer_rewards_value); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 6c633c4729..7413867d07 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -376,7 +376,7 @@ account_create_operation database_fixture::make_account( const std::string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent /* = 100 */, + uint16_t referrer_percent /* = 100 */, public_key_type key /* = public_key_type() */ ) { @@ -600,7 +600,7 @@ const account_object& database_fixture::create_account( const string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent /* = 100 */, + uint16_t referrer_percent /* = 100 (1%)*/, const public_key_type& key /*= public_key_type()*/ ) { @@ -622,7 +622,7 @@ const account_object& database_fixture::create_account( const private_key_type& key, const account_id_type& registrar_id /* = account_id_type() */, const account_id_type& referrer_id /* = account_id_type() */, - uint8_t referrer_percent /* = 100 */ + uint16_t referrer_percent /* = 100 (1%)*/ ) { try @@ -632,6 +632,8 @@ const account_object& database_fixture::create_account( account_create_operation account_create_op; account_create_op.registrar = registrar_id; + account_create_op.referrer = referrer_id; + account_create_op.referrer_percent = referrer_percent; account_create_op.name = name; account_create_op.owner = authority(1234, public_key_type(key.get_public_key()), 1234); account_create_op.active = authority(5678, public_key_type(key.get_public_key()), 5678); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 651d5f29cc..13e4ead075 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -221,7 +221,7 @@ struct database_fixture { const std::string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent = 100, + uint16_t referrer_percent = 100, public_key_type key = public_key_type() ); @@ -300,7 +300,7 @@ struct database_fixture { const string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent = 100, + uint16_t referrer_percent = 100, const public_key_type& key = public_key_type() ); @@ -309,7 +309,7 @@ struct database_fixture { const private_key_type& key, const account_id_type& registrar_id = account_id_type(), const account_id_type& referrer_id = account_id_type(), - uint8_t referrer_percent = 100 + uint16_t referrer_percent = 100 ); const committee_member_object& create_committee_member( const account_object& owner ); diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index d0544b64ae..143f2a78d6 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../common/database_fixture.hpp" @@ -31,10 +32,11 @@ struct reward_database_fixture : database_fixture } void update_asset( const account_id_type& issuer_id, - const fc::ecc::private_key& private_key, - const asset_id_type& asset_id, - uint16_t reward_percent, - const whitelist_market_fee_sharing_t &whitelist_market_fee_sharing = whitelist_market_fee_sharing_t{}) + const fc::ecc::private_key& private_key, + const asset_id_type& asset_id, + uint16_t reward_percent, + const whitelist_market_fee_sharing_t &whitelist_market_fee_sharing = whitelist_market_fee_sharing_t{}, + const flat_set &blacklist = flat_set()) { asset_update_operation op; op.issuer = issuer_id; @@ -42,6 +44,7 @@ struct reward_database_fixture : database_fixture op.new_options = asset_id(db).options; op.new_options.extensions.value.reward_percent = reward_percent; op.new_options.extensions.value.whitelist_market_fee_sharing = whitelist_market_fee_sharing; + op.new_options.blacklist_authorities = blacklist; signed_transaction tx; tx.operations.push_back( op ); @@ -51,6 +54,42 @@ struct reward_database_fixture : database_fixture PUSH_TX( db, tx ); } + void asset_update_blacklist_authority(const account_id_type& issuer_id, + const asset_id_type& asset_id, + const account_id_type& authority_account_id, + const fc::ecc::private_key& issuer_private_key) + { + asset_update_operation uop; + uop.issuer = issuer_id; + uop.asset_to_update = asset_id; + uop.new_options = asset_id(db).options; + uop.new_options.blacklist_authorities.insert(authority_account_id); + + signed_transaction tx; + tx.operations.push_back( uop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + + void add_account_to_blacklist(const account_id_type& authorizing_account_id, + const account_id_type& blacklisted_account_id, + const fc::ecc::private_key& authorizing_account_private_key) + { + account_whitelist_operation wop; + wop.authorizing_account = authorizing_account_id; + wop.account_to_list = blacklisted_account_id; + wop.new_listing = account_whitelist_operation::black_listed; + + signed_transaction tx; + tx.operations.push_back( wop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, authorizing_account_private_key ); + PUSH_TX( db, tx); + } + void generate_blocks_past_hf1268() { database_fixture::generate_blocks( HARDFORK_1268_TIME ); @@ -170,7 +209,7 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) auto register_account = [&](const string& name, const account_object& referrer) -> const account_object& { - uint8_t referrer_percent = 100; + uint16_t referrer_percent = GRAPHENE_1_PERCENT; fc::ecc::private_key _private_key = generate_private_key(name); public_key_type _public_key = _private_key.get_public_key(); return create_account(name, registrar, referrer, referrer_percent, _public_key); @@ -247,7 +286,7 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) share_type alice_refereer_reward = get_market_fee_reward( alice.referrer, jillcoin_id ); // Bob's and Alice's registrars should get rewards - share_type bob_rgistrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); + share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); share_type alice_registrar_reward = get_market_fee_reward( alice.registrar, jillcoin_id ); auto calculate_percent = [](const share_type& value, uint16_t percent) @@ -260,12 +299,12 @@ BOOST_AUTO_TEST_CASE(asset_rewards_test) BOOST_CHECK_GT( bob_refereer_reward, 0 ); BOOST_CHECK_GT( alice_refereer_reward, 0 ); - BOOST_CHECK_GT( bob_rgistrar_reward, 0 ); + BOOST_CHECK_GT( bob_registrar_reward, 0 ); BOOST_CHECK_GT( alice_registrar_reward, 0 ); const auto izzycoin_market_fee = calculate_percent(_izzy(1000).amount, izzycoin_market_percent); const auto izzycoin_reward = calculate_percent(izzycoin_market_fee, izzycoin_reward_percent); - BOOST_CHECK_EQUAL( izzycoin_reward, bob_refereer_reward + bob_rgistrar_reward ); + BOOST_CHECK_EQUAL( izzycoin_reward, bob_refereer_reward + bob_registrar_reward ); BOOST_CHECK_EQUAL( calculate_percent(izzycoin_reward, bob.referrer_rewards_percentage), bob_refereer_reward ); const auto jillcoin_market_fee = calculate_percent(_jill(1500).amount, jillcoin_market_percent); @@ -287,11 +326,10 @@ BOOST_AUTO_TEST_CASE(asset_claim_reward_test) price price(asset(1, asset_id_type(1)), asset(1)); uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; - auto obj = jill_id(db); const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); - const account_object alice = create_account("alice", izzy, izzy, 50); - const account_object bob = create_account("bob", izzy, izzy, 50); + const account_object alice = create_account("alice", izzy, izzy, 50/*0.5%*/); + const account_object bob = create_account("bob", izzy, izzy, 50/*0.5%*/); // prepare users' balance issue_uia( alice, jillcoin.amount( 20000000 ) ); @@ -341,24 +379,26 @@ BOOST_AUTO_TEST_CASE(create_actors) { try { - ACTORS((jill)(izzy)); + ACTORS((jill)(izzyregistrar)(izzyreferrer)); - upgrade_to_lifetime_member(izzy); + upgrade_to_lifetime_member(izzyregistrar); + upgrade_to_lifetime_member(izzyreferrer); price price(asset(1, asset_id_type(1)), asset(1)); uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; auto obj = jill_id(db); const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); - const account_object alice = create_account("alice", izzy, izzy, 50); - const account_object bob = create_account("bob", izzy, izzy, 50); + const account_object alice = create_account("alice", izzyregistrar, izzyreferrer, 50/*0.5%*/); + const account_object bob = create_account("bob", izzyregistrar, izzyreferrer, 50/*0.5%*/); // prepare users' balance issue_uia( alice, jillcoin.amount( 20000000 ) ); transfer( committee_account, alice.get_id(), core_asset(1000) ); transfer( committee_account, bob.get_id(), core_asset(1000) ); - transfer( committee_account, izzy.get_id(), core_asset(1000) ); + transfer( committee_account, izzyregistrar.get_id(), core_asset(1000) ); + transfer( committee_account, izzyreferrer.get_id(), core_asset(1000) ); } FC_LOG_AND_RETHROW() } @@ -378,8 +418,10 @@ BOOST_AUTO_TEST_CASE(white_list_is_empty_test) flat_set whitelist; update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); - GET_ACTOR(izzy); - BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); GET_ACTOR(alice); GET_ACTOR(bob); @@ -387,8 +429,10 @@ BOOST_AUTO_TEST_CASE(white_list_is_empty_test) create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); - const auto izzy_reward = get_market_fee_reward( izzy, jillcoin ); - BOOST_CHECK_GT(izzy_reward , 0); + const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin ); + const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin ); + BOOST_CHECK_GT(izzyregistrar_reward , 0); + BOOST_CHECK_GT(izzyreferrer_reward , 0); } FC_LOG_AND_RETHROW() } @@ -405,12 +449,49 @@ BOOST_AUTO_TEST_CASE(white_list_contains_registrar_test) constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; const asset_object &jillcoin = get_asset("JCOIN"); - GET_ACTOR(izzy); - flat_set whitelist = {jill_id, izzy_id}; + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + flat_set whitelist = {jill_id, izzyregistrar_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin ); + const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin ); + BOOST_CHECK_GT(izzyregistrar_reward , 0); + BOOST_CHECK_GT(izzyreferrer_reward , 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_contains_referrer_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + flat_set whitelist = {jill_id, izzyreferrer_id}; update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); - BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); GET_ACTOR(alice); GET_ACTOR(bob); @@ -418,8 +499,8 @@ BOOST_AUTO_TEST_CASE(white_list_contains_registrar_test) create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); - const auto izzy_reward = get_market_fee_reward( izzy, jillcoin ); - BOOST_CHECK_GT(izzy_reward , 0); + BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); } FC_LOG_AND_RETHROW() } @@ -441,15 +522,18 @@ BOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test) update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); - GET_ACTOR(izzy); - BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); GET_ACTOR(bob); // Alice and Bob place orders which match create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); - BOOST_CHECK_THROW( get_market_fee_reward( izzy, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); } FC_LOG_AND_RETHROW() } @@ -733,7 +817,6 @@ BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_after_h GRAPHENE_REQUIRE_THROW(withdraw( core.amount(1) ), fc::exception); } FC_LOG_AND_RETHROW() } - BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_via_proposal_test ) { try { @@ -779,4 +862,76 @@ BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_via_pro } } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(white_list_asset_rewards_test) +{ + try + { + ACTORS((aliceregistrar)(bobregistrar)(alicereferrer)(bobreferrer)(izzy)(jill)); + + // Izzy issues white_list asset to Alice + // Jill issues white_list asset to Bob + // Bobreferrer added to blacklist for izzycoin asset + // Aliceregistrar added to blacklist for jillcoin asset + // Alice and Bob trade in the market and pay fees + // Check registrar/referrer rewards + upgrade_to_lifetime_member(aliceregistrar); + upgrade_to_lifetime_member(alicereferrer); + upgrade_to_lifetime_member(bobregistrar); + upgrade_to_lifetime_member(bobreferrer); + upgrade_to_lifetime_member(izzy); + upgrade_to_lifetime_member(jill); + + const account_object alice = create_account("alice", aliceregistrar, alicereferrer, 20*GRAPHENE_1_PERCENT); + const account_object bob = create_account("bob", bobregistrar, bobreferrer, 20*GRAPHENE_1_PERCENT); + + fund( alice, core_asset(1000000) ); + fund( bob, core_asset(1000000) ); + fund( izzy, core_asset(1000000) ); + fund( jill, core_asset(1000000) ); + + price price(asset(1, asset_id_type(1)), asset(1)); + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + const asset_id_type izzycoin_id = create_user_issued_asset( "IZZYCOIN", izzy, charge_market_fee|white_list, price, 0, izzycoin_market_percent ).id; + const asset_id_type jillcoin_id = create_user_issued_asset( "JILLCOIN", jill, charge_market_fee|white_list, price, 0, jillcoin_market_percent ).id; + + // Alice and Bob create some coins + issue_uia( alice, izzycoin_id(db).amount( 200000 ) ); + issue_uia( bob, jillcoin_id(db).amount( 200000 ) ); + + generate_blocks_past_hf1268(); + + constexpr auto izzycoin_reward_percent = 50*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_reward_percent = 50*GRAPHENE_1_PERCENT; + + update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); + update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); + + BOOST_TEST_MESSAGE( "Attempting to blacklist bobreferrer for izzycoin asset" ); + asset_update_blacklist_authority(izzy_id, izzycoin_id, izzy_id, izzy_private_key); + add_account_to_blacklist(izzy_id, bobreferrer_id, izzy_private_key); + BOOST_CHECK( !(is_authorized_asset( db, bobreferrer_id(db), izzycoin_id(db) )) ); + + BOOST_TEST_MESSAGE( "Attempting to blacklist aliceregistrar for jillcoin asset" ); + asset_update_blacklist_authority(jill_id, jillcoin_id, jill_id, jill_private_key); + add_account_to_blacklist(jill_id, aliceregistrar_id, jill_private_key); + BOOST_CHECK( !(is_authorized_asset( db, aliceregistrar_id(db), jillcoin_id(db) )) ); + + // Alice and Bob place orders which match + create_sell_order( alice.id, izzycoin_id(db).amount(1000), jillcoin_id(db).amount(1500) ); // Alice is willing to sell her 1000 Izzy's for 1.5 Jill + create_sell_order( bob.id, jillcoin_id(db).amount(1500), izzycoin_id(db).amount(1000) ); // Bob is buying up to 1500 Izzy's for up to 0.6 Jill + + // 1000 Izzys and 1500 Jills are matched, so the fees should be + // 100 Izzy (10%) and 300 Jill (20%). + + // Only Bob's registrar should get rewards + share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); + BOOST_CHECK_GT( bob_registrar_reward, 0 ); + BOOST_CHECK_THROW( get_market_fee_reward( bob.referrer, izzycoin_id ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( alice.registrar, jillcoin_id ), fc::exception ); + BOOST_CHECK_THROW( get_market_fee_reward( alice.referrer, jillcoin_id ), fc::exception ); + } + FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END() From 312300366af1a7e09b937a263bd919c4d8f90cde Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Tue, 27 Nov 2018 16:43:47 +0300 Subject: [PATCH 11/16] Optimize vesting_balance_index --- libraries/chain/db_balance.cpp | 53 ++++++++++++-- .../chain/include/graphene/chain/database.hpp | 9 ++- .../graphene/chain/vesting_balance_object.hpp | 69 +++++++++++++++++-- tests/common/database_fixture.cpp | 17 ++--- tests/tests/market_fee_sharing_tests.cpp | 68 ++++++++++++++---- 5 files changed, 174 insertions(+), 42 deletions(-) diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index 610ba8fae0..9aa8bbaf3b 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -81,6 +81,48 @@ void database::adjust_balance(account_id_type account, asset delta ) } FC_CAPTURE_AND_RETHROW( (account)(delta) ) } +namespace detail { + + /** + * Used as a key to search vesting_balance_object in the index + */ + struct vbo_mfs_key + { + account_id_type account_id; + asset_id_type asset_id; + + vbo_mfs_key(const account_id_type& account, const asset_id_type& asset): + account_id(account), + asset_id(asset) + {} + + bool operator()(const vbo_mfs_key& k, const vesting_balance_object& vbo)const + { + return ( vbo.balance_type == vesting_balance_type::market_fee_sharing ) && + ( k.asset_id == vbo.balance.asset_id ) && + ( k.account_id == vbo.owner ); + } + + uint64_t operator()(const vbo_mfs_key& k)const + { + return vbo_mfs_hash(k.account_id, k.asset_id); + } + }; +} //detail + +asset database::get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id) +{ + auto& vesting_balances = get_index_type().indices().get(); + const auto& key = detail::vbo_mfs_key{account_id, asset_id}; + auto vbo_it = vesting_balances.find(key, key, key); + + if( vbo_it == vesting_balances.end() ) + { + return asset(0, asset_id); + } + return vbo_it->balance; +} + void database::deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta) { try { FC_ASSERT( delta.amount >= 0, "Invalid negative value for balance"); @@ -89,14 +131,12 @@ void database::deposit_market_fee_vesting_balance(const account_id_type &account return; auto& vesting_balances = get_index_type().indices().get(); - auto market_vesting_balances = vesting_balances.equal_range(boost::make_tuple(account_id, vesting_balance_type::market_fee_sharing)); - auto market_balance = boost::range::find_if(market_vesting_balances, - [&delta](const vesting_balance_object& vbo) { return vbo.balance.asset_id == delta.asset_id;} - ); + const auto& key = detail::vbo_mfs_key{account_id, delta.asset_id}; + auto vbo_it = vesting_balances.find(key, key, key); auto block_time = head_block_time(); - if(market_balance == boost::end(market_vesting_balances) ) + if( vbo_it == vesting_balances.end() ) { create([&account_id, &delta, &block_time](vesting_balance_object &vbo) { vbo.owner = account_id; @@ -105,12 +145,11 @@ void database::deposit_market_fee_vesting_balance(const account_id_type &account vbo.policy = instant_vesting_policy{}; }); } else { - modify( *market_balance, [&block_time, &delta]( vesting_balance_object& vbo ) + modify( *vbo_it, [&block_time, &delta]( vesting_balance_object& vbo ) { vbo.deposit_vested(block_time, delta); }); } - } FC_CAPTURE_AND_RETHROW( (account_id)(delta) ) } optional< vesting_balance_id_type > database::deposit_lazy_vesting( diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 4e2915a14b..52b73075c1 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -304,7 +304,14 @@ namespace graphene { namespace chain { */ void adjust_balance(account_id_type account, asset delta); - void deposit_market_fee_vesting_balance(const account_id_type &account, const asset &delta); + void deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta); + /** + * @brief Retrieve a particular account's market fee vesting balance in a given asset + * @param owner Account whose balance should be retrieved + * @param asset_id ID of the asset to get balance in + * @return owner's balance in asset + */ + asset get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id); /** * @brief Helper to make lazy deposit to CDD VBO. diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 1fba587dff..226dbf0c0a 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -199,6 +201,63 @@ namespace graphene { namespace chain { struct by_account; struct by_vesting_type; +namespace detail { + + /** + Calculate a hash for account_id_type and asset_id. + Use object_id.hash_value function to calculate sum of hashes from account_id_type and asset_id. + then subducts a result from std::numeric_limits::max(). + object_id.hash_value returns uint64_t but only 48 bits are significant + so std::numeric_limits::max() - (48 bits + 48 bits) always correct from point of natural numbers + */ + inline uint64_t vbo_mfs_hash(const account_id_type& account_id, const asset_id_type& asset_id) + { + return std::numeric_limits::max() - ( hash_value(account_id) + hash_value(asset_id) ); + } + + /** + * Used as CompatibleHash + Calculate a hash vesting_balance_object + if vesting_balance_object.balance_type is market_fee_sharing + calculate has as vbo_mfs_hash(vesting_balance_object.owner, hash(vbo.balance.asset_id) (see vbo_mfs_hash) + otherwise: hash_value(vesting_balance_object.id); + */ + struct vesting_balance_object_hash + { + uint64_t operator()(const vesting_balance_object& vbo) const + { + if ( vbo.balance_type == vesting_balance_type::market_fee_sharing ) + { + return vbo_mfs_hash(vbo.owner, vbo.balance.asset_id); + } + return hash_value(vbo.id); + } + }; + + /** + * Used as CompatiblePred + * Compares two vesting_balance_objects + * if vesting_balance_object.balance_type is a market_fee_sharing + * compare owners' ids and assets' ids + * otherwise: vesting_balance_object.id + */ + struct vesting_balance_object_equal + { + bool operator() (const vesting_balance_object& lhs, const vesting_balance_object& rhs) const + { + if ( ( lhs.balance_type == vesting_balance_type::market_fee_sharing ) && + ( lhs.balance_type == rhs.balance_type ) && + ( lhs.owner == rhs.owner ) && + ( lhs.balance.asset_id == rhs.balance.asset_id) + ) + { + return true; + } + return ( lhs.id == rhs.id ); + } + }; +} // detail + typedef multi_index_container< vesting_balance_object, indexed_by< @@ -207,12 +266,10 @@ namespace graphene { namespace chain { ordered_non_unique< tag, member >, - ordered_non_unique< tag, - composite_key< - vesting_balance_object, - member, - member - > + hashed_unique< tag, + identity, + detail::vesting_balance_object_hash, + detail::vesting_balance_object_equal > > > vesting_balance_multi_index_type; diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 7413867d07..806bc0144f 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1130,23 +1130,14 @@ int64_t database_fixture::get_balance( const account_object& account, const asse return db.get_balance(account.get_id(), a.get_id()).amount.value; } -int64_t database_fixture::get_market_fee_reward( account_id_type account, asset_id_type asset_type)const +int64_t database_fixture::get_market_fee_reward( account_id_type account_id, asset_id_type asset_id)const { - auto& vesting_balances = db.get_index_type().indices().get(); - auto market_vesting_balances = vesting_balances.equal_range(boost::make_tuple(account, vesting_balance_type::market_fee_sharing)); - auto market_balance = boost::range::find_if(market_vesting_balances, - [&asset_type](const vesting_balance_object& vbo) { return vbo.balance.asset_id == asset_type;} - ); - - FC_ASSERT( market_balance != boost::end(market_vesting_balances) ); - - auto allowed_to_withdraw = market_balance->get_allowed_withdraw(db.head_block_time()); - return allowed_to_withdraw.amount.value; + return db.get_market_fee_vesting_balance(account_id, asset_id).amount.value; } -int64_t database_fixture::get_market_fee_reward( const account_object& account, const asset_object& a )const +int64_t database_fixture::get_market_fee_reward( const account_object& account, const asset_object& asset )const { - return get_market_fee_reward(account.get_id(), a.get_id()); + return get_market_fee_reward(account.get_id(), asset.get_id()); } vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index 143f2a78d6..c8df1f56ac 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -102,6 +102,16 @@ struct reward_database_fixture : database_fixture }; const share_type core_precision = asset::scaled_precision( asset_id_type()(db).precision ); + + void create_vesting_balance_object(const account_id_type& account_id, vesting_balance_type balance_type ) + { + auto block_time = db.head_block_time(); + + db.create([&account_id, &block_time, balance_type] (vesting_balance_object &vbo) { + vbo.owner = account_id; + vbo.balance_type = balance_type; + }); + }; }; BOOST_FIXTURE_TEST_SUITE( fee_sharing_tests, reward_database_fixture ) @@ -420,8 +430,8 @@ BOOST_AUTO_TEST_CASE(white_list_is_empty_test) GET_ACTOR(izzyregistrar); GET_ACTOR(izzyreferrer); - BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); GET_ACTOR(alice); GET_ACTOR(bob); @@ -455,8 +465,8 @@ BOOST_AUTO_TEST_CASE(white_list_contains_registrar_test) update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); - BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); GET_ACTOR(alice); GET_ACTOR(bob); @@ -490,8 +500,8 @@ BOOST_AUTO_TEST_CASE(white_list_contains_referrer_test) update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); - BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); GET_ACTOR(alice); GET_ACTOR(bob); @@ -499,8 +509,8 @@ BOOST_AUTO_TEST_CASE(white_list_contains_referrer_test) create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); } FC_LOG_AND_RETHROW() } @@ -524,16 +534,16 @@ BOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test) GET_ACTOR(izzyregistrar); GET_ACTOR(izzyreferrer); - BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); GET_ACTOR(bob); // Alice and Bob place orders which match create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyregistrar, jillcoin ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( izzyreferrer, jillcoin ), fc::exception ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0); } FC_LOG_AND_RETHROW() } @@ -927,11 +937,39 @@ BOOST_AUTO_TEST_CASE(white_list_asset_rewards_test) // Only Bob's registrar should get rewards share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); BOOST_CHECK_GT( bob_registrar_reward, 0 ); - BOOST_CHECK_THROW( get_market_fee_reward( bob.referrer, izzycoin_id ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( alice.registrar, jillcoin_id ), fc::exception ); - BOOST_CHECK_THROW( get_market_fee_reward( alice.referrer, jillcoin_id ), fc::exception ); + BOOST_CHECK_EQUAL( get_market_fee_reward( bob.referrer, izzycoin_id ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( alice.registrar, jillcoin_id ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( alice.referrer, jillcoin_id ), 0 ); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( create_vesting_balance_object_test ) +{ + /** + * Test checks that an account could have duplicates VBO (with the same asset_type) + * for any type of vesting_balance_type + * except vesting_balance_type::market_fee_sharing + */ + try { + + ACTOR(actor); + + create_vesting_balance_object(actor_id, vesting_balance_type::unspecified); + create_vesting_balance_object(actor_id, vesting_balance_type::unspecified); + + create_vesting_balance_object(actor_id, vesting_balance_type::cashback); + create_vesting_balance_object(actor_id, vesting_balance_type::cashback); + + create_vesting_balance_object(actor_id, vesting_balance_type::witness); + create_vesting_balance_object(actor_id, vesting_balance_type::witness); + + create_vesting_balance_object(actor_id, vesting_balance_type::worker); + create_vesting_balance_object(actor_id, vesting_balance_type::worker); + + create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing); + GRAPHENE_CHECK_THROW(create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing), fc::exception); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From c5a6835826001c62f7971557a9b5e5521950660e Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Thu, 6 Dec 2018 12:27:33 +0300 Subject: [PATCH 12/16] Add perfomance benchmark --- .../performance/market_fee_sharing_tests.cpp | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/performance/market_fee_sharing_tests.cpp diff --git a/tests/performance/market_fee_sharing_tests.cpp b/tests/performance/market_fee_sharing_tests.cpp new file mode 100644 index 0000000000..5c431595ea --- /dev/null +++ b/tests/performance/market_fee_sharing_tests.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 Bitshares Foundation, 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 +#include +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; + +BOOST_FIXTURE_TEST_CASE(mfs_performance_test, database_fixture) +{ + try + { + ACTORS((issuer)); + + const unsigned int accounts = 3000; + const unsigned int iterations = 20; + + std::vector registrators; + for (unsigned int i = 0; i < accounts; ++i) + { + auto account = create_account("registrar" + std::to_string(i)); + transfer(committee_account, account.get_id(), asset(1000000)); + upgrade_to_lifetime_member(account); + + registrators.push_back(std::move(account)); + } + + generate_blocks(HARDFORK_1268_TIME); + generate_block(); + + additional_asset_options_t options; + options.value.reward_percent = 2 * GRAPHENE_1_PERCENT; + + const auto usd = create_user_issued_asset( + "USD", + issuer, + charge_market_fee, + price(asset(1, asset_id_type(1)), asset(1)), + 1, + 20 * GRAPHENE_1_PERCENT, + options); + + issue_uia(issuer, usd.amount(iterations * accounts * 2000)); + + std::vector traders; + for (unsigned int i = 0; i < accounts; ++i) + { + std::string name = "account" + std::to_string(i); + auto account = create_account(name, registrators[i], registrators[i], GRAPHENE_1_PERCENT); + transfer(committee_account, account.get_id(), asset(1000000)); + transfer(issuer, account, usd.amount(iterations * 2000)); + + traders.push_back(std::move(account)); + } + + using namespace std::chrono; + + const auto start = high_resolution_clock::now(); + + for (unsigned int i = 0; i < iterations; ++i) + { + for (unsigned int j = 0; j < accounts; ++j) + { + create_sell_order(traders[j], usd.amount(2000), asset(1)); + create_sell_order(traders[accounts - j - 1], asset(1), usd.amount(1000)); + } + } + + const auto end = high_resolution_clock::now(); + + const auto elapsed = duration_cast(end - start); + wlog("Elapsed: ${c} ms", ("c", elapsed.count())); + + for (unsigned int i = 0; i < accounts; ++i) + { + const auto reward = get_market_fee_reward(registrators[i], usd); + BOOST_CHECK_GT(reward, 0); + } + } + FC_LOG_AND_RETHROW() +} From 3e1e5b679a5a1fba2cbf5a9d57a2d22c9992ce08 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Wed, 23 Jan 2019 16:41:47 +0300 Subject: [PATCH 13/16] Move hardfork timestamp into the far future Fix notes --- libraries/chain/asset_evaluator.cpp | 1 + libraries/chain/hardfork.d/CORE_1268.hf | 2 +- libraries/chain/vesting_balance_evaluator.cpp | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index aa6688f6eb..f6d0f23062 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -36,6 +36,7 @@ namespace graphene { namespace chain { namespace detail { + // TODO review and remove code below and links to it after hf_1268 void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options) { if( block_time < HARDFORK_1268_TIME ) diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf index 6872db2fb2..352463c043 100644 --- a/libraries/chain/hardfork.d/CORE_1268.hf +++ b/libraries/chain/hardfork.d/CORE_1268.hf @@ -1,4 +1,4 @@ // #1268 Distribute Asset Market Fees to Referral Program #ifndef HARDFORK_1268_TIME -#define HARDFORK_1268_TIME (fc::time_point_sec( 1530705600 )) // Wednesday, July 4, 2018 12:00:00 PM +#define HARDFORK_1268_TIME (fc::time_point_sec( 1577880000 )) // Wednesday, January 1, 2020 12:00:00 PM #endif diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index 6f05343c24..586045e4e6 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -30,6 +30,7 @@ namespace graphene { namespace chain { namespace detail { + // TODO review and remove code below and links to it after hf_1268 void check_vesting_balance_policy_hf_1268(const fc::time_point_sec& block_time, const vesting_policy_initializer& policy) { if( block_time < HARDFORK_1268_TIME ) From 1b9f6c9e14090c2bf02e49bbc1427589a384a680 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Wed, 23 Jan 2019 16:48:07 +0300 Subject: [PATCH 14/16] Merge with latest hardfork branch --- .travis.yml | 5 +- README.md | 2 + libraries/app/api.cpp | 13 +- libraries/app/application.cpp | 55 +- libraries/app/application_impl.hxx | 4 + libraries/app/database_api.cpp | 42 +- libraries/app/include/graphene/app/api.hpp | 6 +- libraries/chain/CMakeLists.txt | 3 +- libraries/chain/account_evaluator.cpp | 12 +- libraries/chain/account_object.cpp | 56 +- .../chain/committee_member_evaluator.cpp | 4 + libraries/chain/db_balance.cpp | 19 +- libraries/chain/db_block.cpp | 108 ++- libraries/chain/db_init.cpp | 31 +- libraries/chain/db_maint.cpp | 64 +- libraries/chain/db_management.cpp | 116 ++-- libraries/chain/db_notify.cpp | 30 +- libraries/chain/db_update.cpp | 19 + libraries/chain/fork_database.cpp | 74 +- libraries/chain/hardfork.d/CORE_1268.hf | 2 +- libraries/chain/hardfork.d/CORE_1479.hf | 4 + libraries/chain/hardfork.d/core-1468.hf | 4 + libraries/chain/htlc_evaluator.cpp | 126 ++++ .../include/graphene/chain/account_object.hpp | 61 +- .../graphene/chain/budget_record_object.hpp | 90 +++ .../chain/include/graphene/chain/config.hpp | 2 +- .../chain/include/graphene/chain/database.hpp | 36 +- .../chain/include/graphene/chain/db_with.hpp | 14 +- .../include/graphene/chain/fork_database.hpp | 3 - .../include/graphene/chain/htlc_evaluator.hpp | 59 ++ .../include/graphene/chain/htlc_object.hpp | 82 +++ .../graphene/chain/proposal_evaluator.hpp | 21 + .../graphene/chain/protocol/account.hpp | 2 + .../include/graphene/chain/protocol/base.hpp | 3 + .../include/graphene/chain/protocol/block.hpp | 20 +- .../chain/protocol/chain_parameters.hpp | 25 +- .../graphene/chain/protocol/fee_schedule.hpp | 46 ++ .../include/graphene/chain/protocol/htlc.hpp | 211 ++++++ .../graphene/chain/protocol/operations.hpp | 8 +- .../graphene/chain/protocol/transaction.hpp | 57 +- .../include/graphene/chain/protocol/types.hpp | 36 +- libraries/chain/proposal_evaluator.cpp | 53 ++ libraries/chain/protocol/block.cpp | 64 +- libraries/chain/protocol/htlc.cpp | 67 ++ libraries/chain/protocol/operations.cpp | 6 + libraries/chain/protocol/transaction.cpp | 50 +- libraries/db/include/graphene/db/index.hpp | 141 +++- libraries/db/object_database.cpp | 17 +- .../include/graphene/net/core_messages.hpp | 4 +- libraries/net/node.cpp | 2 +- .../graphene/debug_witness/debug_witness.hpp | 2 +- .../delayed_node/delayed_node_plugin.cpp | 4 +- .../elasticsearch/elasticsearch_plugin.cpp | 23 +- libraries/plugins/es_objects/es_objects.cpp | 195 +----- .../graphene/es_objects/es_objects.hpp | 155 ++--- .../include/graphene/witness/witness.hpp | 2 +- .../wallet/include/graphene/wallet/wallet.hpp | 49 ++ libraries/wallet/wallet.cpp | 218 +++++- programs/build_helpers/member_enumerator.cpp | 1 + programs/js_operation_serializer/main.cpp | 1 + tests/app/main.cpp | 2 +- tests/cli/main.cpp | 199 +++++- tests/common/database_fixture.cpp | 72 +- tests/common/database_fixture.hpp | 10 + tests/tests/authority_tests.cpp | 114 +++- tests/tests/basic_tests.cpp | 11 +- tests/tests/bitasset_tests.cpp | 3 +- tests/tests/block_tests.cpp | 54 +- tests/tests/confidential_tests.cpp | 8 +- tests/tests/database_tests.cpp | 85 +++ tests/tests/fee_tests.cpp | 5 - tests/tests/htlc_tests.cpp | 639 ++++++++++++++++++ tests/tests/operation_tests.cpp | 12 +- tests/tests/operation_tests2.cpp | 51 +- tests/tests/swan_tests.cpp | 24 + tests/tests/uia_tests.cpp | 4 +- tests/tests/voting_tests.cpp | 125 ++++ 77 files changed, 3253 insertions(+), 794 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_1479.hf create mode 100644 libraries/chain/hardfork.d/core-1468.hf create mode 100644 libraries/chain/htlc_evaluator.cpp create mode 100644 libraries/chain/include/graphene/chain/budget_record_object.hpp create mode 100644 libraries/chain/include/graphene/chain/htlc_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/htlc_object.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/htlc.hpp create mode 100644 libraries/chain/protocol/htlc.cpp create mode 100644 tests/tests/htlc_tests.cpp diff --git a/.travis.yml b/.travis.yml index 9e21993904..236fa6ef78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,8 +34,9 @@ script: - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DBoost_USE_STATIC_LIBS=OFF -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON . - 'which build-wrapper-linux-x86-64 && build-wrapper-linux-x86-64 --out-dir bw-output make -j 2 cli_wallet witness_node chain_test cli_test || make -j 2 cli_wallet witness_node chain_test cli_test' - set -o pipefail + - '[ $((`date +%s` - `cat _start_time`)) -gt $((42 * 60)) ] && touch _empty_cache || true' - '[ -r _empty_cache ] || tests/chain_test 2>&1 | cat' - '[ -r _empty_cache ] || tests/cli_test 2>&1 | cat' - 'find libraries/[acdenptuw]*/CMakeFiles/*.dir programs/[cdgjsw]*/CMakeFiles/*.dir -type d | while read d; do gcov -o "$d" "${d/CMakeFiles*.dir//}"/*.cpp; done >/dev/null' - - '( [ -r _empty_cache -o $((`date +%s` - `cat _start_time`)) -gt $((42 * 60)) ] && echo "WARNING! Skipping sonar scanner due to time constraints!" ) || ( which sonar-scanner && sonar-scanner || true )' - - '[ ! -r _empty_cache ] || ( echo "Please restart with populated cache" && false )' + - '[ -r _empty_cache ] || ( which sonar-scanner && sonar-scanner || true )' + - '[ ! -r _empty_cache ] || ( echo "WARNING! Skipped some tests due to compile time! Please restart with populated cache." && false )' diff --git a/README.md b/README.md index a516790596..e5147f0112 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Visit [BitShares.org](https://bitshares.org/) to learn about BitShares and join Information for developers can be found in the [Bitshares Developer Portal](https://dev.bitshares.works/). Users interested in how bitshares works can go to the [BitShares Documentation](https://how.bitshares.works/) site. +For security issues and bug bounty program please visit [Hack the DEX](https://hackthedex.io). + Getting Started --------------- Build instructions and additional documentation are available in the diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index dbe6043e50..f3cffa70ff 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -159,18 +159,18 @@ namespace graphene { namespace app { } } - void network_broadcast_api::broadcast_transaction(const signed_transaction& trx) + void network_broadcast_api::broadcast_transaction(const precomputable_transaction& trx) { - trx.validate(); + _app.chain_database()->precompute_parallel( trx ).wait(); _app.chain_database()->push_transaction(trx); if( _app.p2p_node() != nullptr ) _app.p2p_node()->broadcast_transaction(trx); } - fc::variant network_broadcast_api::broadcast_transaction_synchronous(const signed_transaction& trx) + fc::variant network_broadcast_api::broadcast_transaction_synchronous(const precomputable_transaction& trx) { fc::promise::ptr prom( new fc::promise() ); - broadcast_transaction_with_callback( [=]( const fc::variant& v ){ + broadcast_transaction_with_callback( [prom]( const fc::variant& v ){ prom->set_value(v); }, trx ); @@ -179,14 +179,15 @@ namespace graphene { namespace app { void network_broadcast_api::broadcast_block( const signed_block& b ) { + _app.chain_database()->precompute_parallel( b ).wait(); _app.chain_database()->push_block(b); if( _app.p2p_node() != nullptr ) _app.p2p_node()->broadcast( net::block_message( b )); } - void network_broadcast_api::broadcast_transaction_with_callback(confirmation_callback cb, const signed_transaction& trx) + void network_broadcast_api::broadcast_transaction_with_callback(confirmation_callback cb, const precomputable_transaction& trx) { - trx.validate(); + _app.chain_database()->precompute_parallel( trx ).wait(); _callbacks[trx.id()] = cb; _app.chain_database()->push_transaction(trx); if( _app.p2p_node() != nullptr ) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index f3b9c2f5db..cc7828034f 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -169,8 +170,8 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) "seed01.liondani.com:1776", // liondani (GERMANY) "104.236.144.84:1777", // puppies (USA) "128.199.143.47:2015", // Harvey (Singapore) - "23.92.53.182:1776", // sahkan (USA) - "192.121.166.162:1776", // sahkan (UK) + "209.105.239.13:1776", // sahkan (USA) + "45.35.12.22:1776", // sahkan (USA) "51.15.61.160:1776", // lafona (France) "bts-seed1.abit-more.com:62015", // abit (China) "node.blckchnd.com:4243", // blckchnd (Germany) @@ -394,12 +395,34 @@ void application_impl::startup() _chain_db->enable_standby_votes_tracking( _options->at("enable-standby-votes-tracking").as() ); } - if( _options->count("replay-blockchain") ) + if( _options->count("replay-blockchain") || _options->count("revalidate-blockchain") ) _chain_db->wipe( _data_dir / "blockchain", false ); try { - _chain_db->open( _data_dir / "blockchain", initial_state, GRAPHENE_CURRENT_DB_VERSION ); + // these flags are used in open() only, i. e. during replay + uint32_t skip; + if( _options->count("revalidate-blockchain") ) // see also handle_block() + { + if( !loaded_checkpoints.empty() ) + wlog( "Warning - revalidate will not validate before last checkpoint" ); + if( _options->count("force-validate") ) + skip = graphene::chain::database::skip_nothing; + else + skip = graphene::chain::database::skip_transaction_signatures; + } + else // no revalidate, skip most checks + skip = graphene::chain::database::skip_witness_signature | + graphene::chain::database::skip_block_size_check | + graphene::chain::database::skip_merkle_check | + graphene::chain::database::skip_transaction_signatures | + graphene::chain::database::skip_transaction_dupe_check | + graphene::chain::database::skip_tapos_check | + graphene::chain::database::skip_witness_schedule_check; + + graphene::chain::detail::with_skip_flags( *_chain_db, skip, [this,&initial_state] () { + _chain_db->open( _data_dir / "blockchain", initial_state, GRAPHENE_CURRENT_DB_VERSION ); + }); } catch( const fc::exception& e ) { @@ -517,13 +540,17 @@ bool application_impl::handle_block(const graphene::net::block_message& blk_msg, FC_ASSERT( (latency.count()/1000) > -5000, "Rejecting block with timestamp in the future" ); try { - // TODO: in the case where this block is valid but on a fork that's too old for us to switch to, - // you can help the network code out by throwing a block_older_than_undo_history exception. - // when the net code sees that, it will stop trying to push blocks from that chain, but - // leave that peer connected so that they can get sync blocks from us - bool result = _chain_db->push_block( blk_msg.block, - (_is_block_producer | _force_validate) ? - database::skip_nothing : database::skip_transaction_signatures ); + const uint32_t skip = (_is_block_producer | _force_validate) ? + database::skip_nothing : database::skip_transaction_signatures; + bool result = valve.do_serial( [this,&blk_msg,skip] () { + _chain_db->precompute_parallel( blk_msg.block, skip ).wait(); + }, [this,&blk_msg,skip] () { + // TODO: in the case where this block is valid but on a fork that's too old for us to switch to, + // you can help the network code out by throwing a block_older_than_undo_history exception. + // when the net code sees that, it will stop trying to push blocks from that chain, but + // leave that peer connected so that they can get sync blocks from us + return _chain_db->push_block( blk_msg.block, skip ); + }); // the block was accepted, so we now know all of the transactions contained in the block if (!sync_mode) @@ -573,6 +600,7 @@ void application_impl::handle_transaction(const graphene::net::trx_message& tran trx_count = 0; } + _chain_db->precompute_parallel( transaction_message.trx ).wait(); _chain_db->push_transaction( transaction_message.trx ); } FC_CAPTURE_AND_RETHROW( (transaction_message) ) } @@ -961,9 +989,10 @@ void application::set_program_options(boost::program_options::options_descriptio "Path to create a Genesis State at. If a well-formed JSON file exists at the path, it will be parsed and any " "missing fields in a Genesis State will be added, and any unknown fields will be removed. If no file or an " "invalid file is found, it will be replaced with an example Genesis State.") - ("replay-blockchain", "Rebuild object graph by replaying all blocks") + ("replay-blockchain", "Rebuild object graph by replaying all blocks without validation") + ("revalidate-blockchain", "Rebuild object graph by replaying all blocks with full validation") ("resync-blockchain", "Delete all blocks and re-sync with network from scratch") - ("force-validate", "Force validation of all transactions") + ("force-validate", "Force validation of all transactions during normal operation") ("genesis-timestamp", bpo::value(), "Replace timestamp from genesis.json with current time plus this many seconds (experts only!)") ; diff --git a/libraries/app/application_impl.hxx b/libraries/app/application_impl.hxx index 5b0c543728..9f601bce79 100644 --- a/libraries/app/application_impl.hxx +++ b/libraries/app/application_impl.hxx @@ -1,6 +1,8 @@ #pragma once #include +#include + #include #include #include @@ -194,6 +196,8 @@ class application_impl : public net::node_delegate std::map> _available_plugins; bool _is_finished_syncing = false; + private: + fc::serial_valve valve; }; }}} // namespace graphene namespace app namespace detail diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index acb90c8af5..f067a101e8 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -547,6 +547,10 @@ vector> database_api::get_key_references( vector> database_api_impl::get_key_references( vector keys )const { + const auto& idx = _db.get_index_type(); + const auto& aidx = dynamic_cast(idx); + const auto& refs = aidx.get_secondary_index(); + vector< vector > final_result; final_result.reserve(keys.size()); @@ -566,10 +570,6 @@ vector> database_api_impl::get_key_references( vector(); - const auto& aidx = dynamic_cast&>(idx); - const auto& refs = aidx.get_secondary_index(); - auto itr = refs.account_to_key_memberships.find(key); vector result; for( auto& a : {a1,a2,a3,a4,a5} ) @@ -577,7 +577,7 @@ vector> database_api_impl::get_key_references( vectorsecond.size() ); + result.reserve( result.size() + itr->second.size() ); for( auto item : itr->second ) { result.push_back(item); @@ -585,9 +585,10 @@ vector> database_api_impl::get_key_references( vectorsecond.size() ); + result.reserve( result.size() + itr->second.size() ); for( auto item : itr->second ) result.push_back(item); } final_result.emplace_back( std::move(result) ); @@ -620,7 +621,7 @@ bool database_api_impl::is_public_key_registered(string public_key) const return false; } const auto& idx = _db.get_index_type(); - const auto& aidx = dynamic_cast&>(idx); + const auto& aidx = dynamic_cast(idx); const auto& refs = aidx.get_secondary_index(); auto itr = refs.account_to_key_memberships.find(key); bool is_known = itr != refs.account_to_key_memberships.end(); @@ -755,6 +756,10 @@ std::map database_api::get_full_accounts( const vector database_api_impl::get_full_accounts( const vector& names_or_ids, bool subscribe) { + const auto& proposal_idx = _db.get_index_type(); + const auto& pidx = dynamic_cast(proposal_idx); + const auto& proposals_by_account = pidx.get_secondary_index(); + std::map results; for (const std::string& account_name_or_id : names_or_ids) @@ -784,9 +789,6 @@ std::map database_api_impl::get_full_accounts( const acnt.cashback_balance = account->cashback_balance(_db); } // Add the account's proposals - const auto& proposal_idx = _db.get_index_type(); - const auto& pidx = dynamic_cast&>(proposal_idx); - const auto& proposals_by_account = pidx.get_secondary_index(); auto required_approvals_itr = proposals_by_account._account_to_proposals.find( account->id ); if( required_approvals_itr != proposals_by_account._account_to_proposals.end() ) { @@ -797,11 +799,9 @@ std::map database_api_impl::get_full_accounts( const // Add the account's balances - auto balance_range = _db.get_index_type().indices().get().equal_range(boost::make_tuple(account->id)); - std::for_each(balance_range.first, balance_range.second, - [&acnt](const account_balance_object& balance) { - acnt.balances.emplace_back(balance); - }); + const auto& balances = _db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >().get_account_balances( account->id ); + for( const auto balance : balances ) + acnt.balances.emplace_back( *balance.second ); // Add the account's vesting balances auto vesting_range = _db.get_index_type().indices().get().equal_range(account->id); @@ -869,7 +869,7 @@ vector database_api::get_account_references( const std::string vector database_api_impl::get_account_references( const std::string account_id_or_name )const { const auto& idx = _db.get_index_type(); - const auto& aidx = dynamic_cast&>(idx); + const auto& aidx = dynamic_cast(idx); const auto& refs = aidx.get_secondary_index(); const account_id_type account_id = get_account_from_string(account_id_or_name)->id; auto itr = refs.account_to_account_memberships.find(account_id); @@ -953,10 +953,10 @@ vector database_api_impl::get_account_balances(const std::string& account if (assets.empty()) { // if the caller passes in an empty list of assets, return balances for all assets the account owns - const account_balance_index& balance_index = _db.get_index_type(); - auto range = balance_index.indices().get().equal_range(boost::make_tuple(acnt)); - for (const account_balance_object& balance : boost::make_iterator_range(range.first, range.second)) - result.push_back(asset(balance.get_balance())); + const auto& balance_index = _db.get_index_type< primary_index< account_balance_index > >(); + const auto& balances = balance_index.get_secondary_index< balances_by_account_index >().get_account_balances( acnt ); + for( const auto balance : balances ) + result.push_back( balance.second->get_balance() ); } else { @@ -2448,7 +2448,7 @@ void database_api_impl::on_applied_block() } if( market.valid() && _market_subscriptions.count(*market) ) // FIXME this may cause fill_order_operation be pushed before order creation - subscribed_markets_ops[*market].emplace_back( std::move( std::make_pair( op.op, op.result ) ) ); + subscribed_markets_ops[*market].emplace_back(std::make_pair(op.op, op.result)); } /// we need to ensure the database_api is not deleted for the life of the async operation auto capture_this = shared_from_this(); diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index aea35be08b..af34bbaf73 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -266,19 +266,19 @@ namespace graphene { namespace app { * The transaction will be checked for validity in the local database prior to broadcasting. If it fails to * apply locally, an error will be thrown and the transaction will not be broadcast. */ - void broadcast_transaction(const signed_transaction& trx); + void broadcast_transaction(const precomputable_transaction& trx); /** this version of broadcast transaction registers a callback method that will be called when the transaction is * included into a block. The callback method includes the transaction id, block number, and transaction number in the * block. */ - void broadcast_transaction_with_callback( confirmation_callback cb, const signed_transaction& trx); + void broadcast_transaction_with_callback( confirmation_callback cb, const precomputable_transaction& trx); /** this version of broadcast transaction registers a callback method that will be called when the transaction is * included into a block. The callback method includes the transaction id, block number, and transaction number in the * block. */ - fc::variant broadcast_transaction_synchronous(const signed_transaction& trx); + fc::variant broadcast_transaction_synchronous(const precomputable_transaction& trx); /** * @brief Broadcast a signed block to the network diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 6fe3f47883..71d2fd2d2e 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -58,7 +58,7 @@ add_library( graphene_chain protocol/fee_schedule.cpp protocol/confidential.cpp protocol/vote.cpp - + protocol/htlc.cpp genesis_state.cpp get_config.cpp @@ -77,6 +77,7 @@ add_library( graphene_chain vesting_balance_evaluator.cpp withdraw_permission_evaluator.cpp worker_evaluator.cpp + htlc_evaluator.cpp confidential_evaluator.cpp special_authority.cpp buyback.cpp diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index d550007c76..086953dcfb 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -119,7 +119,6 @@ void verify_account_votes( const database& db, const account_options& options ) } } - void_result account_create_evaluator::do_evaluate( const account_create_operation& op ) { try { database& d = db(); @@ -318,11 +317,16 @@ void_result account_update_evaluator::do_apply( const account_update_operation& bool sa_before = acnt->has_special_authority(); // update account statistics - if( o.new_options.valid() && o.new_options->is_voting() != acnt->options.is_voting() ) + if( o.new_options.valid() ) { - d.modify( acnt->statistics( d ), []( account_statistics_object& aso ) + d.modify( acnt->statistics( d ), [&]( account_statistics_object& aso ) { - aso.is_voting = !aso.is_voting; + if(o.new_options->is_voting() != acnt->options.is_voting()) + aso.is_voting = !aso.is_voting; + + if((o.new_options->votes != acnt->options.votes || + o.new_options->voting_account != acnt->options.voting_account)) + aso.last_vote_time = d.head_block_time(); } ); } diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 952a4c7c2c..7acaf10b21 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -121,9 +121,9 @@ set account_member_index::get_account_members(const account_obj result.insert(auth.first); return result; } -set account_member_index::get_key_members(const account_object& a)const +set account_member_index::get_key_members(const account_object& a)const { - set result; + set result; for( auto auth : a.owner.key_auths ) result.insert(auth.first); for( auto auth : a.active.key_auths ) @@ -215,7 +215,7 @@ void account_member_index::object_modified(const object& after) { - set after_key_members = get_key_members(a); + set after_key_members = get_key_members(a); vector removed; removed.reserve(before_key_members.size()); std::set_difference(before_key_members.begin(), before_key_members.end(), @@ -269,4 +269,54 @@ void account_referrer_index::object_modified( const object& after ) { } +const uint8_t balances_by_account_index::bits = 20; +const uint64_t balances_by_account_index::mask = (1ULL << balances_by_account_index::bits) - 1; + +void balances_by_account_index::object_inserted( const object& obj ) +{ + const auto& abo = dynamic_cast< const account_balance_object& >( obj ); + while( balances.size() < (abo.owner.instance.value >> bits) + 1 ) + { + balances.reserve( (abo.owner.instance.value >> bits) + 1 ); + balances.resize( balances.size() + 1 ); + balances.back().resize( 1ULL << bits ); + } + balances[abo.owner.instance.value >> bits][abo.owner.instance.value & mask][abo.asset_type] = &abo; +} + +void balances_by_account_index::object_removed( const object& obj ) +{ + const auto& abo = dynamic_cast< const account_balance_object& >( obj ); + if( balances.size() < (abo.owner.instance.value >> bits) + 1 ) return; + balances[abo.owner.instance.value >> bits][abo.owner.instance.value & mask].erase( abo.asset_type ); +} + +void balances_by_account_index::about_to_modify( const object& before ) +{ + ids_being_modified.emplace( before.id ); +} + +void balances_by_account_index::object_modified( const object& after ) +{ + FC_ASSERT( ids_being_modified.top() == after.id, "Modification of ID is not supported!"); + ids_being_modified.pop(); +} + +const map< asset_id_type, const account_balance_object* >& balances_by_account_index::get_account_balances( const account_id_type& acct )const +{ + static const map< asset_id_type, const account_balance_object* > _empty; + + if( balances.size() < (acct.instance.value >> bits) + 1 ) return _empty; + return balances[acct.instance.value >> bits][acct.instance.value & mask]; +} + +const account_balance_object* balances_by_account_index::get_account_balance( const account_id_type& acct, const asset_id_type& asset )const +{ + if( balances.size() < (acct.instance.value >> bits) + 1 ) return nullptr; + const auto& mine = balances[acct.instance.value >> bits][acct.instance.value & mask]; + const auto itr = mine.find( asset ); + if( mine.end() == itr ) return nullptr; + return itr->second; +} + } } // graphene::chain diff --git a/libraries/chain/committee_member_evaluator.cpp b/libraries/chain/committee_member_evaluator.cpp index 4e7eb827e5..9a258e6543 100644 --- a/libraries/chain/committee_member_evaluator.cpp +++ b/libraries/chain/committee_member_evaluator.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,9 @@ void_result committee_member_update_global_parameters_evaluator::do_evaluate(con { try { FC_ASSERT(trx_state->_is_proposed_trx); + FC_ASSERT( db().head_block_time() > HARDFORK_CORE_1468_TIME || !o.new_parameters.extensions.value.updatable_htlc_options.valid(), + "Unable to set HTLC parameters until hardfork." ); + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index 9aa8bbaf3b..2a48679e3e 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -34,11 +34,11 @@ namespace graphene { namespace chain { asset database::get_balance(account_id_type owner, asset_id_type asset_id) const { - auto& index = get_index_type().indices().get(); - auto itr = index.find(boost::make_tuple(owner, asset_id)); - if( itr == index.end() ) + auto& index = get_index_type< primary_index< account_balance_index > >().get_secondary_index(); + auto abo = index.get_account_balance( owner, asset_id ); + if( !abo ) return asset(0, asset_id); - return itr->get_balance(); + return abo->get_balance(); } asset database::get_balance(const account_object& owner, const asset_object& asset_obj) const @@ -56,9 +56,9 @@ void database::adjust_balance(account_id_type account, asset delta ) if( delta.amount == 0 ) return; - auto& index = get_index_type().indices().get(); - auto itr = index.find(boost::make_tuple(account, delta.asset_id)); - if(itr == index.end()) + auto& index = get_index_type< primary_index< account_balance_index > >().get_secondary_index(); + auto abo = index.get_account_balance( account, delta.asset_id ); + if( !abo ) { FC_ASSERT( delta.amount > 0, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", ("a",account(*this).name) @@ -73,8 +73,9 @@ void database::adjust_balance(account_id_type account, asset delta ) }); } else { if( delta.amount < 0 ) - FC_ASSERT( itr->get_balance() >= -delta, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", ("a",account(*this).name)("b",to_pretty_string(itr->get_balance()))("r",to_pretty_string(-delta))); - modify(*itr, [delta](account_balance_object& b) { + FC_ASSERT( abo->get_balance() >= -delta, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", + ("a",account(*this).name)("b",to_pretty_string(abo->get_balance()))("r",to_pretty_string(-delta))); + modify(*abo, [delta](account_balance_object& b) { b.adjust_balance(delta); }); } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index efc5562a89..24618dbd34 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -37,6 +37,7 @@ #include #include +#include #include namespace graphene { namespace chain { @@ -227,7 +228,7 @@ bool database::_push_block(const signed_block& new_block) * queues full as well, it will be kept in the queue to be propagated later when a new block flushes out the pending * queues. */ -processed_transaction database::push_transaction( const signed_transaction& trx, uint32_t skip ) +processed_transaction database::push_transaction( const precomputable_transaction& trx, uint32_t skip ) { try { processed_transaction result; detail::with_skip_flags( *this, skip, [&]() @@ -237,7 +238,7 @@ processed_transaction database::push_transaction( const signed_transaction& trx, return result; } FC_CAPTURE_AND_RETHROW( (trx) ) } -processed_transaction database::_push_transaction( const signed_transaction& trx ) +processed_transaction database::_push_transaction( const precomputable_transaction& trx ) { // If this is the first transaction pushed after applying a block, start a new undo session. // This allows us to quickly rewind to the clean state of the head block, in case a new block arrives. @@ -465,15 +466,17 @@ signed_block database::_generate_block( void database::pop_block() { try { _pending_tx_session.reset(); - auto head_id = head_block_id(); - optional head_block = fetch_block_by_id( head_id ); - GRAPHENE_ASSERT( head_block.valid(), pop_empty_chain, "there are no blocks to pop" ); - - _fork_db.pop_block(); + auto fork_db_head = _fork_db.head(); + FC_ASSERT( fork_db_head, "Trying to pop() from empty fork database!?" ); + if( fork_db_head->id == head_block_id() ) + _fork_db.pop_block(); + else + { + fork_db_head = _fork_db.fetch_block( head_block_id() ); + FC_ASSERT( fork_db_head, "Trying to pop() block that's not in fork database!?" ); + } pop_undo(); - - _popped_tx.insert( _popped_tx.begin(), head_block->transactions.begin(), head_block->transactions.end() ); - + _popped_tx.insert( _popped_tx.begin(), fork_db_head->data.transactions.begin(), fork_db_head->data.transactions.end() ); } FC_CAPTURE_AND_RETHROW() } void database::clear_pending() @@ -584,6 +587,7 @@ void database::_apply_block( const signed_block& next_block ) clear_expired_transactions(); clear_expired_proposals(); clear_expired_orders(); + clear_expired_htlcs(); update_expired_feeds(); // this will update expired feeds and some core exchange rates update_core_exchange_rates(); // this will update remaining core exchange rates update_withdraw_permissions(); @@ -621,22 +625,17 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx { try { uint32_t skip = get_node_properties().skip_flags; - if( true || !(skip&skip_validate) ) /* issue #505 explains why this skip_flag is disabled */ - trx.validate(); + trx.validate(); auto& trx_idx = get_mutable_index_type(); const chain_id_type& chain_id = get_chain_id(); - transaction_id_type trx_id; if( !(skip & skip_transaction_dupe_check) ) - { - trx_id = trx.id(); - FC_ASSERT( trx_idx.indices().get().find(trx_id) == trx_idx.indices().get().end() ); - } + FC_ASSERT( trx_idx.indices().get().find(trx.id()) == trx_idx.indices().get().end() ); transaction_evaluation_state eval_state(this); const chain_parameters& chain_parameters = get_global_properties().parameters; eval_state._trx = &trx; - if( !(skip & (skip_transaction_signatures | skip_authority_check) ) ) + if( !(skip & skip_transaction_signatures) ) { auto get_active = [&]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; }; @@ -665,8 +664,8 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx //Insert transaction into unique transactions database. if( !(skip & skip_transaction_dupe_check) ) { - create([&trx_id,&trx](transaction_object& transaction) { - transaction.trx_id = trx_id; + create([&trx](transaction_object& transaction) { + transaction.trx_id = trx.id(); transaction.trx = trx; }); } @@ -683,14 +682,6 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx } ptrx.operation_results = std::move(eval_state.operation_results); - if( head_block_time() < HARDFORK_CORE_1040_TIME ) // TODO totally remove this code block after hard fork - { - //Make sure the temp account has no non-zero balances - const auto& index = get_index_type().indices().get(); - auto range = index.equal_range( boost::make_tuple( GRAPHENE_TEMP_ACCOUNT ) ); - std::for_each(range.first, range.second, [](const account_balance_object& b) { FC_ASSERT(b.balance == 0); }); - } - return ptrx; } FC_CAPTURE_AND_RETHROW( (trx) ) } @@ -750,4 +741,65 @@ bool database::before_last_checkpoint()const return (_checkpoints.size() > 0) && (_checkpoints.rbegin()->first >= head_block_num()); } + +static const uint32_t skip_expensive = database::skip_transaction_signatures | database::skip_witness_signature + | database::skip_merkle_check | database::skip_transaction_dupe_check; + +template +void database::_precompute_parallel( const Trx* trx, const size_t count, const uint32_t skip )const +{ + for( size_t i = 0; i < count; ++i, ++trx ) + { + trx->validate(); // TODO - parallelize wrt confidential operations + if( !(skip&skip_transaction_dupe_check) ) + trx->id(); + if( !(skip&skip_transaction_signatures) ) + trx->get_signature_keys( get_chain_id() ); + } +} + +fc::future database::precompute_parallel( const signed_block& block, const uint32_t skip )const +{ try { + std::vector> workers; + if( !block.transactions.empty() ) + { + if( (skip & skip_expensive) == skip_expensive ) + _precompute_parallel( &block.transactions[0], block.transactions.size(), skip ); + else + { + uint32_t chunks = fc::asio::default_io_service_scope::get_num_threads(); + uint32_t chunk_size = ( block.transactions.size() + chunks - 1 ) / chunks; + workers.reserve( chunks + 1 ); + for( size_t base = 0; base < block.transactions.size(); base += chunk_size ) + workers.push_back( fc::do_parallel( [this,&block,base,chunk_size,skip] () { + _precompute_parallel( &block.transactions[base], + base + chunk_size < block.transactions.size() ? chunk_size : block.transactions.size() - base, + skip ); + }) ); + } + } + + if( !(skip&skip_witness_signature) ) + workers.push_back( fc::do_parallel( [&block] () { block.signee(); } ) ); + if( !(skip&skip_merkle_check) ) + block.calculate_merkle_root(); + block.id(); + + if( workers.empty() ) + return fc::future< void >( fc::promise< void >::ptr( new fc::promise< void >( true ) ) ); + + auto first = workers.begin(); + auto worker = first; + while( ++worker != workers.end() ) + worker->wait(); + return *first; +} FC_LOG_AND_RETHROW() } + +fc::future database::precompute_parallel( const precomputable_transaction& trx )const +{ + return fc::do_parallel([this,&trx] () { + _precompute_parallel( &trx, 1, skip_nothing ); + }); +} + } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 263c5df3e5..eb0c52f42e 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,7 @@ #include #include #include +#include #include #include @@ -60,6 +62,7 @@ #include #include #include +#include #include @@ -125,6 +128,9 @@ const uint8_t witness_object::type_id; const uint8_t worker_object::space_id; const uint8_t worker_object::type_id; +const uint8_t htlc_object::space_id; +const uint8_t htlc_object::type_id; + void database::initialize_evaluators() { @@ -173,6 +179,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -181,15 +190,15 @@ void database::initialize_indexes() _undo_db.set_max_size( GRAPHENE_MIN_UNDO_HISTORY ); //Protocol object indexes - add_index< primary_index >(); + add_index< primary_index >(); // 8192 assets per chunk add_index< primary_index >(); - auto acnt_index = add_index< primary_index >(); + auto acnt_index = add_index< primary_index >(); // ~1 million accounts per chunk acnt_index->add_secondary_index(); acnt_index->add_secondary_index(); - add_index< primary_index >(); - add_index< primary_index >(); + add_index< primary_index >(); // 256 members per chunk + add_index< primary_index >(); // 1024 witnesses per chunk add_index< primary_index >(); add_index< primary_index >(); @@ -201,22 +210,26 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index< htlc_index> >(); //Implementation object indexes add_index< primary_index >(); - add_index< primary_index >(); - add_index< primary_index >(); + + auto bal_idx = add_index< primary_index >(); + bal_idx->add_secondary_index(); + + add_index< primary_index >(); // 8192 add_index< primary_index> >(); add_index< primary_index> >(); - add_index< primary_index >(); + add_index< primary_index >(); // 1 Mi add_index< primary_index> >(); add_index< primary_index> >(); add_index< primary_index > >(); add_index< primary_index > >(); + add_index< primary_index > >(); add_index< primary_index< special_authority_index > >(); add_index< primary_index< buyback_index > >(); add_index< primary_index >(); - add_index< primary_index< simple_index< fba_accumulator_object > > >(); } @@ -233,7 +246,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) _undo_db.disable(); struct auth_inhibitor { auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags) - { db.node_properties().skip_flags |= skip_authority_check; } + { db.node_properties().skip_flags |= skip_transaction_signatures; } ~auth_inhibitor() { db.node_properties().skip_flags = old_flags; } private: diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 7f1a8a4c6c..4ee7cb2825 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -131,33 +132,6 @@ struct worker_pay_visitor } }; -/// @brief A budget record struct to be used in initialize_budget_record and process_budget -struct budget_record -{ - uint64_t time_since_last_budget = 0; - - // sources of budget - share_type from_initial_reserve = 0; - share_type from_accumulated_fees = 0; - share_type from_unused_witness_budget = 0; - - // witness budget requested by the committee - share_type requested_witness_budget = 0; - - // funds that can be released from reserve at maximum rate - share_type total_budget = 0; - - // sinks of budget, should sum up to total_budget - share_type witness_budget = 0; - share_type worker_budget = 0; - - // unused budget - share_type leftover_worker_funds = 0; - - // change in supply due to budget operations - share_type supply_delta = 0; -}; - void database::update_worker_votes() { const auto& idx = get_index_type().indices().get(); @@ -560,6 +534,12 @@ void database::process_budget() _dpo.last_budget_time = now; }); + create< budget_record_object >( [&]( budget_record_object& _rec ) + { + _rec.time = head_block_time(); + _rec.record = rec; + }); + // available_funds is money we could spend, but don't want to. // we simply let it evaporate back into the reserve. } @@ -713,7 +693,7 @@ void distribute_fba_balances( database& db ) void create_buyback_orders( database& db ) { const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get(); - const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_account_asset >(); + const auto& bal_idx = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >(); for( const buyback_object& bbo : bbo_idx ) { @@ -721,7 +701,6 @@ void create_buyback_orders( database& db ) assert( asset_to_buy.buyback_account.valid() ); const account_object& buyback_account = (*(asset_to_buy.buyback_account))(db); - asset_id_type next_asset = asset_id_type(); if( !buyback_account.allowed_assets.valid() ) { @@ -729,16 +708,11 @@ void create_buyback_orders( database& db ) continue; } - while( true ) + for( const auto& entry : bal_idx.get_account_balances( buyback_account.id ) ) { - auto it = bal_idx.lower_bound( boost::make_tuple( buyback_account.id, next_asset ) ); - if( it == bal_idx.end() ) - break; - if( it->owner != buyback_account.id ) - break; + const auto* it = entry.second; asset_id_type asset_to_sell = it->asset_type; share_type amount_to_sell = it->balance; - next_asset = asset_to_sell + 1; if( asset_to_sell == asset_to_buy.id ) continue; if( amount_to_sell == 0 ) @@ -834,11 +808,14 @@ void database::process_bids( const asset_bitasset_data_object& bad ) while( covered < bdd.current_supply && itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == to_revive_id ) { const collateral_bid_object& bid = *itr; - asset total_collateral = bid.inv_swan_price.quote * bad.settlement_price; + asset debt_in_bid = bid.inv_swan_price.quote; + if( debt_in_bid.amount > bdd.current_supply ) + debt_in_bid.amount = bdd.current_supply; + asset total_collateral = debt_in_bid * bad.settlement_price; total_collateral += bid.inv_swan_price.base; - price call_price = price::call_price( bid.inv_swan_price.quote, total_collateral, bad.current_feed.maintenance_collateral_ratio ); + price call_price = price::call_price( debt_in_bid, total_collateral, bad.current_feed.maintenance_collateral_ratio ); if( ~call_price >= bad.current_feed.settlement_price ) break; - covered += bid.inv_swan_price.quote.amount; + covered += debt_in_bid.amount; ++itr; } if( covered < bdd.current_supply ) return; @@ -850,9 +827,12 @@ void database::process_bids( const asset_bitasset_data_object& bad ) { const collateral_bid_object& bid = *itr; ++itr; - share_type debt = bid.inv_swan_price.quote.amount; - share_type collateral = (bid.inv_swan_price.quote * bad.settlement_price).amount; - if( bid.inv_swan_price.quote.amount >= to_cover ) + asset debt_in_bid = bid.inv_swan_price.quote; + if( debt_in_bid.amount > bdd.current_supply ) + debt_in_bid.amount = bdd.current_supply; + share_type debt = debt_in_bid.amount; + share_type collateral = (debt_in_bid * bad.settlement_price).amount; + if( debt >= to_cover ) { debt = to_cover; collateral = remaining_fund; diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 6f627adb55..9167768f48 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include namespace graphene { namespace chain { @@ -74,68 +76,78 @@ void database::reindex( fc::path data_dir ) else _undo_db.disable(); - uint32_t skip = skip_witness_signature | - skip_block_size_check | - skip_merkle_check | - skip_transaction_signatures | - skip_transaction_dupe_check | - skip_tapos_check | - skip_witness_schedule_check | - skip_authority_check; + uint32_t skip = node_properties().skip_flags; - size_t total_processed_block_size; size_t total_block_size = _block_id_to_block.total_block_size(); const auto& gpo = get_global_properties(); - for( uint32_t i = head_block_num() + 1; i <= last_block_num; ++i ) + std::queue< std::tuple< size_t, signed_block, fc::future< void > > > blocks; + uint32_t next_block_num = head_block_num() + 1; + uint32_t i = next_block_num; + while( next_block_num <= last_block_num || !blocks.empty() ) { - if( i % 10000 == 0 ) + if( next_block_num <= last_block_num && blocks.size() < 20 ) { - total_processed_block_size = _block_id_to_block.blocks_current_position(); - - ilog( - " [by size: ${size}% ${processed} of ${total}] [by num: ${num}% ${i} of ${last}]", - ("size", double(total_processed_block_size) / total_block_size * 100) - ("processed", total_processed_block_size) - ("total", total_block_size) - ("num", double(i*100)/last_block_num) - ("i", i) - ("last", last_block_num) - ); - } - if( i == flush_point ) - { - ilog( "Writing database to disk at block ${i}", ("i",i) ); - flush(); - ilog( "Done" ); - } - if( head_block_time() >= last_block->timestamp - gpo.parameters.maximum_time_until_expiration ) - skip &= ~skip_transaction_dupe_check; - fc::optional< signed_block > block = _block_id_to_block.fetch_by_number(i); - if( !block.valid() ) - { - wlog( "Reindexing terminated due to gap: Block ${i} does not exist!", ("i", i) ); - uint32_t dropped_count = 0; - while( true ) + const size_t processed_block_size = _block_id_to_block.blocks_current_position(); + fc::optional< signed_block > block = _block_id_to_block.fetch_by_number( next_block_num++ ); + if( block.valid() ) { - fc::optional< block_id_type > last_id = _block_id_to_block.last_id(); - // this can trigger if we attempt to e.g. read a file that has block #2 but no block #1 - if( !last_id.valid() ) - break; - // we've caught up to the gap - if( block_header::num_from_id( *last_id ) <= i ) - break; - _block_id_to_block.remove( *last_id ); - dropped_count++; + if( block->timestamp >= last_block->timestamp - gpo.parameters.maximum_time_until_expiration ) + skip &= ~skip_transaction_dupe_check; + blocks.emplace( processed_block_size, std::move(*block), fc::future() ); + std::get<2>(blocks.back()) = precompute_parallel( std::get<1>(blocks.back()), skip ); + } + else + { + wlog( "Reindexing terminated due to gap: Block ${i} does not exist!", ("i", i) ); + uint32_t dropped_count = 0; + while( true ) + { + fc::optional< block_id_type > last_id = _block_id_to_block.last_id(); + // this can trigger if we attempt to e.g. read a file that has block #2 but no block #1 + if( !last_id.valid() ) + break; + // we've caught up to the gap + if( block_header::num_from_id( *last_id ) <= i ) + break; + _block_id_to_block.remove( *last_id ); + dropped_count++; + } + wlog( "Dropped ${n} blocks from after the gap", ("n", dropped_count) ); + next_block_num = last_block_num + 1; // don't load more blocks } - wlog( "Dropped ${n} blocks from after the gap", ("n", dropped_count) ); - break; } - if( i < undo_point ) - apply_block( *block, skip ); else { - _undo_db.enable(); - push_block( *block, skip ); + std::get<2>(blocks.front()).wait(); + const signed_block& block = std::get<1>(blocks.front()); + + if( i % 10000 == 0 ) + { + ilog( + " [by size: ${size}% ${processed} of ${total}] [by num: ${num}% ${i} of ${last}]", + ("size", double(std::get<0>(blocks.front())) / total_block_size * 100) + ("processed", std::get<0>(blocks.front())) + ("total", total_block_size) + ("num", double(i*100)/last_block_num) + ("i", i) + ("last", last_block_num) + ); + } + if( i == flush_point ) + { + ilog( "Writing database to disk at block ${i}", ("i",i) ); + flush(); + ilog( "Done" ); + } + if( i < undo_point ) + apply_block( block, skip ); + else + { + _undo_db.enable(); + push_block( block, skip ); + } + blocks.pop(); + i++; } } _undo_db.enable(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index ff44a177df..fafaa3bf00 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -256,6 +257,27 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // account_id } + void operator()( const htlc_create_operation& op ) + { + _impacted.insert( op.fee_payer() ); + _impacted.insert( op.to ); + } + void operator()( const htlc_redeem_operation& op ) + { + _impacted.insert( op.fee_payer() ); + } + void operator()( const htlc_redeemed_operation& op ) + { + _impacted.insert( op.from ); + } + void operator()( const htlc_extend_operation& op ) + { + _impacted.insert( op.fee_payer() ); + } + void operator()( const htlc_refund_operation& op ) + { + _impacted.insert( op.fee_payer() ); + } }; void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result ) @@ -344,6 +366,12 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case balance_object_type:{ /** these are free from any accounts */ break; + } case htlc_object_type:{ + const auto& htlc_obj = dynamic_cast(obj); + FC_ASSERT( htlc_obj != nullptr ); + accounts.insert( htlc_obj->from ); + accounts.insert( htlc_obj->to ); + break; } } } @@ -393,7 +421,7 @@ void get_relevant_accounts( const object* obj, flat_set& accoun break; case impl_witness_schedule_object_type: break; - case impl_reserved1_object_type: + case impl_budget_record_object_type: break; case impl_special_authority_object_type: break; diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 9a5bcad1eb..05aae9d058 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -558,4 +559,22 @@ void database::update_withdraw_permissions() remove(*permit_index.begin()); } +void database::clear_expired_htlcs() +{ + const auto& htlc_idx = get_index_type().indices().get(); + while ( htlc_idx.begin() != htlc_idx.end() + && htlc_idx.begin()->expiration <= head_block_time() ) + { + const htlc_object& obj = *htlc_idx.begin(); + adjust_balance( obj.from, obj.amount ); + // virtual op + htlc_refund_operation vop( obj.id, obj.from ); + vop.htlc_id = htlc_idx.begin()->id; + push_applied_operation( vop ); + + // remove the db object + remove( *htlc_idx.begin() ); + } +} + } } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 39e9d3b83f..71da27629b 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace graphene { namespace chain { fork_database::fork_database() { @@ -39,7 +41,7 @@ void fork_database::pop_block() FC_ASSERT( _head, "no block to pop" ); auto prev = _head->prev.lock(); FC_ASSERT( prev, "popping block would leave head block null" ); - _head = prev; + _head = prev; } void fork_database::start_block(signed_block b) @@ -50,7 +52,7 @@ void fork_database::start_block(signed_block b) } /** - * Pushes the block into the fork database and caches it if it doesn't link + * Pushes the block into the fork database * */ shared_ptr fork_database::push_block(const signed_block& b) @@ -64,7 +66,6 @@ shared_ptr fork_database::push_block(const signed_block& b) wlog( "Pushing block to fork database that failed to link: ${id}, ${num}", ("id",b.id())("num",b.block_num()) ); wlog( "Head: ${num}, ${id}", ("num",_head->data.block_num())("id",_head->data.id()) ); throw; - _unlinked_index.insert( item ); } return _head; } @@ -92,35 +93,10 @@ void fork_database::_push_block(const item_ptr& item) { _head = item; uint32_t min_num = _head->num - std::min( _max_size, _head->num ); -// ilog( "min block in fork DB ${n}, max_size: ${m}", ("n",min_num)("m",_max_size) ); auto& num_idx = _index.get(); while( num_idx.size() && (*num_idx.begin())->num < min_num ) num_idx.erase( num_idx.begin() ); - - _unlinked_index.get().erase(_head->num - _max_size); } - //_push_next( item ); -} - -/** - * Iterate through the unlinked cache and insert anything that - * links to the newly inserted item. This will start a recursive - * set of calls performing a depth-first insertion of pending blocks as - * _push_next(..) calls _push_block(...) which will in turn call _push_next - */ -void fork_database::_push_next( const item_ptr& new_item ) -{ - auto& prev_idx = _unlinked_index.get(); - - auto itr = prev_idx.find( new_item->id ); - while( itr != prev_idx.end() ) - { - auto tmp = *itr; - prev_idx.erase( itr ); - _push_block( tmp ); - - itr = prev_idx.find( new_item->id ); - } } void fork_database::set_max_size( uint32_t s ) @@ -128,29 +104,15 @@ void fork_database::set_max_size( uint32_t s ) _max_size = s; if( !_head ) return; - { /// index - auto& by_num_idx = _index.get(); - auto itr = by_num_idx.begin(); - while( itr != by_num_idx.end() ) - { - if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) ) - by_num_idx.erase(itr); - else - break; - itr = by_num_idx.begin(); - } - } - { /// unlinked_index - auto& by_num_idx = _unlinked_index.get(); - auto itr = by_num_idx.begin(); - while( itr != by_num_idx.end() ) - { - if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) ) - by_num_idx.erase(itr); - else - break; - itr = by_num_idx.begin(); - } + auto& by_num_idx = _index.get(); + auto itr = by_num_idx.begin(); + while( itr != by_num_idx.end() ) + { + if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) ) + by_num_idx.erase(itr); + else + break; + itr = by_num_idx.begin(); } } @@ -158,11 +120,7 @@ bool fork_database::is_known_block(const block_id_type& id)const { auto& index = _index.get(); auto itr = index.find(id); - if( itr != index.end() ) - return true; - auto& unlinked_index = _unlinked_index.get(); - auto unlinked_itr = unlinked_index.find(id); - return unlinked_itr != unlinked_index.end(); + return itr != index.end(); } item_ptr fork_database::fetch_block(const block_id_type& id)const @@ -171,10 +129,6 @@ item_ptr fork_database::fetch_block(const block_id_type& id)const auto itr = index.find(id); if( itr != index.end() ) return *itr; - auto& unlinked_index = _unlinked_index.get(); - auto unlinked_itr = unlinked_index.find(id); - if( unlinked_itr != unlinked_index.end() ) - return *unlinked_itr; return item_ptr(); } diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf index 352463c043..6872db2fb2 100644 --- a/libraries/chain/hardfork.d/CORE_1268.hf +++ b/libraries/chain/hardfork.d/CORE_1268.hf @@ -1,4 +1,4 @@ // #1268 Distribute Asset Market Fees to Referral Program #ifndef HARDFORK_1268_TIME -#define HARDFORK_1268_TIME (fc::time_point_sec( 1577880000 )) // Wednesday, January 1, 2020 12:00:00 PM +#define HARDFORK_1268_TIME (fc::time_point_sec( 1530705600 )) // Wednesday, July 4, 2018 12:00:00 PM #endif diff --git a/libraries/chain/hardfork.d/CORE_1479.hf b/libraries/chain/hardfork.d/CORE_1479.hf new file mode 100644 index 0000000000..2f8ee807e3 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1479.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #1479 nodes crashing on self-approving proposal +#ifndef HARDFORK_CORE_1479_TIME +#define HARDFORK_CORE_1479_TIME (fc::time_point_sec( 1545436800 )) // 2018-12-22T00:00:00Z +#endif diff --git a/libraries/chain/hardfork.d/core-1468.hf b/libraries/chain/hardfork.d/core-1468.hf new file mode 100644 index 0000000000..a669d57c84 --- /dev/null +++ b/libraries/chain/hardfork.d/core-1468.hf @@ -0,0 +1,4 @@ +// HTLC implementation +#ifndef HARDFORK_CORE_1468_TIME +#define HARDFORK_CORE_1468_TIME (fc::time_point_sec( 1600000000 ) ) +#endif diff --git a/libraries/chain/htlc_evaluator.cpp b/libraries/chain/htlc_evaluator.cpp new file mode 100644 index 0000000000..218558a98d --- /dev/null +++ b/libraries/chain/htlc_evaluator.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018 jmjatlanta 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 +#include +#include + +namespace graphene { + namespace chain { + + optional get_committee_htlc_options(graphene::chain::database& db) + { + return db.get_global_properties().parameters.extensions.value.updatable_htlc_options; + } + + void_result htlc_create_evaluator::do_evaluate(const htlc_create_operation& o) + { + optional htlc_options = get_committee_htlc_options(db()); + + FC_ASSERT(htlc_options, "HTLC Committee options are not set."); + + // make sure the expiration is reasonable + FC_ASSERT( o.claim_period_seconds <= htlc_options->max_timeout_secs, "HTLC Timeout exceeds allowed length" ); + // make sure the preimage length is reasonable + FC_ASSERT( o.preimage_size <= htlc_options->max_preimage_size, "HTLC preimage length exceeds allowed length" ); + // make sure the sender has the funds for the HTLC + FC_ASSERT( db().get_balance( o.from, o.amount.asset_id) >= (o.amount), "Insufficient funds") ; + return void_result(); + } + + object_id_type htlc_create_evaluator::do_apply(const htlc_create_operation& o) + { + try { + graphene::chain::database& dbase = db(); + dbase.adjust_balance( o.from, -o.amount ); + + const htlc_object& esc = db().create([&dbase,&o]( htlc_object& esc ) { + esc.from = o.from; + esc.to = o.to; + esc.amount = o.amount; + esc.preimage_hash = o.preimage_hash; + esc.preimage_size = o.preimage_size; + esc.expiration = dbase.head_block_time() + o.claim_period_seconds; + }); + return esc.id; + + } FC_CAPTURE_AND_RETHROW( (o) ) + } + + class htlc_redeem_visitor + { + //private: + const std::vector& data; + public: + typedef bool result_type; + + htlc_redeem_visitor( const std::vector& preimage ) + : data( preimage ) {} + + template + bool operator()( const T& preimage_hash )const + { + return T::hash( (const char*)data.data(), (uint32_t) data.size() ) == preimage_hash; + } + }; + + void_result htlc_redeem_evaluator::do_evaluate(const htlc_redeem_operation& o) + { + htlc_obj = &db().get(o.htlc_id); + + FC_ASSERT(o.preimage.size() == htlc_obj->preimage_size, "Preimage size mismatch."); + + const htlc_redeem_visitor vtor( o.preimage ); + FC_ASSERT( htlc_obj->preimage_hash.visit( vtor ), "Provided preimage does not generate correct hash."); + + return void_result(); + } + + void_result htlc_redeem_evaluator::do_apply(const htlc_redeem_operation& o) + { + db().adjust_balance(htlc_obj->to, htlc_obj->amount); + // notify related parties + htlc_redeemed_operation virt_op( htlc_obj->id, htlc_obj->from, htlc_obj->to, htlc_obj->amount ); + db().push_applied_operation( virt_op ); + db().remove(*htlc_obj); + return void_result(); + } + + void_result htlc_extend_evaluator::do_evaluate(const htlc_extend_operation& o) + { + htlc_obj = &db().get(o.htlc_id); + return void_result(); + } + + void_result htlc_extend_evaluator::do_apply(const htlc_extend_operation& o) + { + db().modify(*htlc_obj, [&o](htlc_object& db_obj) { + db_obj.expiration += o.seconds_to_add; + }); + + return void_result(); + } + + } // namespace chain +} // namespace graphene diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index cae9d35984..cb52552eae 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -70,6 +70,8 @@ namespace graphene { namespace chain { bool is_voting = false; ///< redundately store whether this account is voting for better maintenance performance + time_point_sec last_vote_time; // add last time voted + /// Whether this account owns some CORE asset and is voting inline bool has_some_core_voting() const { @@ -292,14 +294,6 @@ namespace graphene { namespace chain { */ class account_member_index : public secondary_index { - class key_compare { - public: - inline bool operator()( const public_key_type& a, const public_key_type& b )const - { - return a.key_data < b.key_data; - } - }; - public: virtual void object_inserted( const object& obj ) override; virtual void object_removed( const object& obj ) override; @@ -308,20 +302,20 @@ namespace graphene { namespace chain { /** given an account or key, map it to the set of accounts that reference it in an active or owner authority */ - map< account_id_type, set > account_to_account_memberships; - map< public_key_type, set, key_compare > account_to_key_memberships; + map< account_id_type, set > account_to_account_memberships; + map< public_key_type, set, pubkey_comparator > account_to_key_memberships; /** some accounts use address authorities in the genesis block */ - map< address, set > account_to_address_memberships; + map< address, set > account_to_address_memberships; protected: - set get_account_members( const account_object& a )const; - set get_key_members( const account_object& a )const; - set
get_address_members( const account_object& a )const; + set get_account_members( const account_object& a )const; + set get_key_members( const account_object& a )const; + set
get_address_members( const account_object& a )const; - set before_account_members; - set before_key_members; - set
before_address_members; + set before_account_members; + set before_key_members; + set
before_address_members; }; @@ -341,7 +335,30 @@ namespace graphene { namespace chain { map< account_id_type, set > referred_by; }; - struct by_account_asset; + /** + * @brief This secondary index will allow fast access to the balance objects + * that belonging to an account. + */ + class balances_by_account_index : public secondary_index + { + public: + virtual void object_inserted( const object& obj ) override; + virtual void object_removed( const object& obj ) override; + virtual void about_to_modify( const object& before ) override; + virtual void object_modified( const object& after ) override; + + const map< asset_id_type, const account_balance_object* >& get_account_balances( const account_id_type& acct )const; + const account_balance_object* get_account_balance( const account_id_type& acct, const asset_id_type& asset )const; + + private: + static const uint8_t bits; + static const uint64_t mask; + + /** Maps each account to its balance objects */ + vector< vector< map< asset_id_type, const account_balance_object* > > > balances; + std::stack< object_id_type > ids_being_modified; + }; + struct by_asset_balance; struct by_maintenance_flag; /** @@ -353,13 +370,6 @@ namespace graphene { namespace chain { ordered_unique< tag, member< object, object_id_type, &object::id > >, ordered_non_unique< tag, member< account_balance_object, bool, &account_balance_object::maintenance_flag > >, - ordered_unique< tag, - composite_key< - account_balance_object, - member, - member - > - >, ordered_unique< tag, composite_key< account_balance_object, @@ -453,6 +463,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (core_in_balance) (has_cashback_vb) (is_voting) + (last_vote_time) (lifetime_fees_paid) (pending_fees)(pending_vested_fees) ) diff --git a/libraries/chain/include/graphene/chain/budget_record_object.hpp b/libraries/chain/include/graphene/chain/budget_record_object.hpp new file mode 100644 index 0000000000..49544793a6 --- /dev/null +++ b/libraries/chain/include/graphene/chain/budget_record_object.hpp @@ -0,0 +1,90 @@ +/* + * 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 { + +struct budget_record +{ + uint64_t time_since_last_budget = 0; + + // sources of budget + share_type from_initial_reserve = 0; + share_type from_accumulated_fees = 0; + share_type from_unused_witness_budget = 0; + + // witness budget requested by the committee + share_type requested_witness_budget = 0; + + // funds that can be released from reserve at maximum rate + share_type total_budget = 0; + + // sinks of budget, should sum up to total_budget + share_type witness_budget = 0; + share_type worker_budget = 0; + + // unused budget + share_type leftover_worker_funds = 0; + + // change in supply due to budget operations + share_type supply_delta = 0; +}; + +class budget_record_object; + +class budget_record_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_budget_record_object_type; + + fc::time_point_sec time; + budget_record record; +}; + +} } + +FC_REFLECT( + graphene::chain::budget_record, + (time_since_last_budget) + (from_initial_reserve) + (from_accumulated_fees) + (from_unused_witness_budget) + (requested_witness_budget) + (total_budget) + (witness_budget) + (worker_budget) + (leftover_worker_funds) + (supply_delta) +) + +FC_REFLECT_DERIVED( + graphene::chain::budget_record_object, + (graphene::db::object), + (time) + (record) +) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 3ab5f47f19..e97bacc032 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -121,7 +121,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "BTS2.19" +#define GRAPHENE_CURRENT_DB_VERSION "BTS2.181221" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 52b73075c1..9bc3c67d3a 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -70,12 +70,11 @@ namespace graphene { namespace chain { skip_fork_db = 1 << 3, ///< used while reindexing skip_block_size_check = 1 << 4, ///< used when applying locally generated transactions skip_tapos_check = 1 << 5, ///< used while reindexing -- note this skips expiration check as well - skip_authority_check = 1 << 6, ///< used while reindexing -- disables any checking of authority on transactions + // skip_authority_check = 1 << 6, ///< removed because effectively identical to skip_transaction_signatures skip_merkle_check = 1 << 7, ///< used while reindexing skip_assert_evaluation = 1 << 8, ///< used while reindexing skip_undo_history_check = 1 << 9, ///< used while reindexing - skip_witness_schedule_check = 1 << 10, ///< used while reindexing - skip_validate = 1 << 11 ///< used prior to checkpoint, skips validate() call on transaction + skip_witness_schedule_check = 1 << 10 ///< used while reindexing }; /** @@ -137,9 +136,9 @@ namespace graphene { namespace chain { bool before_last_checkpoint()const; bool push_block( const signed_block& b, uint32_t skip = skip_nothing ); - processed_transaction push_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing ); + processed_transaction push_transaction( const precomputable_transaction& trx, uint32_t skip = skip_nothing ); bool _push_block( const signed_block& b ); - processed_transaction _push_transaction( const signed_transaction& trx ); + processed_transaction _push_transaction( const precomputable_transaction& trx ); ///@throws fc::exception if the proposed transaction fails to apply. processed_transaction push_proposal( const proposal_object& proposal ); @@ -419,7 +418,7 @@ namespace graphene { namespace chain { /** when popping a block, the transactions that were removed get cached here so they * can be reapplied at the proper time */ - std::deque< signed_transaction > _popped_tx; + std::deque< precomputable_transaction > _popped_tx; /** * @} @@ -428,6 +427,29 @@ namespace graphene { namespace chain { /// Enable or disable tracking of votes of standby witnesses and committee members inline void enable_standby_votes_tracking(bool enable) { _track_standby_votes = enable; } + /** Precomputes digests, signatures and operation validations depending + * on skip flags. "Expensive" computations may be done in a parallel + * thread. + * + * @param block the block to preprocess + * @param skip indicates which computations can be skipped + * @return a future that will resolve to the input block with + * precomputations applied + */ + fc::future precompute_parallel( const signed_block& block, const uint32_t skip = skip_nothing )const; + + /** Precomputes digests, signatures and operation validations. + * "Expensive" computations may be done in a parallel thread. + * + * @param trx the transaction to preprocess + * @return a future that will resolve to the input transaction with + * precomputations applied + */ + fc::future precompute_parallel( const precomputable_transaction& trx )const; + private: + template + void _precompute_parallel( const Trx* trx, const size_t count, const uint32_t skip )const; + protected: //Mark pop_undo() as protected -- we do not want outside calling pop_undo(); it should call pop_block() instead void pop_undo() { object_database::pop_undo(); } @@ -449,6 +471,7 @@ namespace graphene { namespace chain { void apply_block( const signed_block& next_block, uint32_t skip = skip_nothing ); processed_transaction apply_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing ); operation_result apply_operation( transaction_evaluation_state& eval_state, const operation& op ); + private: void _apply_block( const signed_block& next_block ); processed_transaction _apply_transaction( const signed_transaction& trx ); @@ -478,6 +501,7 @@ namespace graphene { namespace chain { void update_withdraw_permissions(); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, const asset_bitasset_data_object* bitasset_ptr = nullptr ); + void clear_expired_htlcs(); ///Steps performed only at maintenance intervals ///@{ diff --git a/libraries/chain/include/graphene/chain/db_with.hpp b/libraries/chain/include/graphene/chain/db_with.hpp index de93bb15f5..7ae189216f 100644 --- a/libraries/chain/include/graphene/chain/db_with.hpp +++ b/libraries/chain/include/graphene/chain/db_with.hpp @@ -80,11 +80,9 @@ struct pending_transactions_restorer { try { if( !_db.is_known_transaction( tx.id() ) ) { - // since push_transaction() takes a signed_transaction, - // the operation_results field will be ignored. _db._push_transaction( tx ); } - } catch ( const fc::exception& ) { + } catch ( const fc::exception& ) { // ignore invalid transactions } } _db._popped_tx.clear(); @@ -93,17 +91,11 @@ struct pending_transactions_restorer try { if( !_db.is_known_transaction( tx.id() ) ) { - // since push_transaction() takes a signed_transaction, - // the operation_results field will be ignored. _db._push_transaction( tx ); } } - catch( const fc::exception& e ) - { - /* - wlog( "Pending transaction became invalid after switching to block ${b} ${t}", ("b", _db.head_block_id())("t",_db.head_block_time()) ); - wlog( "The invalid pending transaction caused exception ${e}", ("e", e.to_detail_string() ) ); - */ + catch( const fc::exception& ) + { // ignore invalid transactions } } } diff --git a/libraries/chain/include/graphene/chain/fork_database.hpp b/libraries/chain/include/graphene/chain/fork_database.hpp index be3991ed80..363a21f2ce 100644 --- a/libraries/chain/include/graphene/chain/fork_database.hpp +++ b/libraries/chain/include/graphene/chain/fork_database.hpp @@ -93,12 +93,10 @@ namespace graphene { namespace chain { struct block_id; struct block_num; - struct by_previous; typedef multi_index_container< item_ptr, indexed_by< hashed_unique, member, std::hash>, - hashed_non_unique, const_mem_fun, std::hash>, ordered_non_unique, member> > > fork_multi_index_type; @@ -112,7 +110,6 @@ namespace graphene { namespace chain { uint32_t _max_size = 1024; - fork_multi_index_type _unlinked_index; fork_multi_index_type _index; shared_ptr _head; }; diff --git a/libraries/chain/include/graphene/chain/htlc_evaluator.hpp b/libraries/chain/include/graphene/chain/htlc_evaluator.hpp new file mode 100644 index 0000000000..08a24b2bdc --- /dev/null +++ b/libraries/chain/include/graphene/chain/htlc_evaluator.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 jmjatlanta 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 htlc_create_evaluator : public evaluator + { + public: + typedef htlc_create_operation operation_type; + + void_result do_evaluate( const htlc_create_operation& o); + object_id_type do_apply( const htlc_create_operation& o); + }; + + class htlc_redeem_evaluator : public evaluator + { + public: + typedef htlc_redeem_operation operation_type; + + void_result do_evaluate( const htlc_redeem_operation& o); + void_result do_apply( const htlc_redeem_operation& o); + const htlc_object* htlc_obj = nullptr; + }; + + class htlc_extend_evaluator : public evaluator + { + public: + typedef htlc_extend_operation operation_type; + + void_result do_evaluate( const htlc_extend_operation& o); + void_result do_apply( const htlc_extend_operation& o); + const htlc_object* htlc_obj = nullptr; + }; + } // namespace graphene +} // namespace graphene diff --git a/libraries/chain/include/graphene/chain/htlc_object.hpp b/libraries/chain/include/graphene/chain/htlc_object.hpp new file mode 100644 index 0000000000..e9c4515042 --- /dev/null +++ b/libraries/chain/include/graphene/chain/htlc_object.hpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 jmjatlanta 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 +#include +#include +#include +#include + +namespace graphene { namespace chain { + + /** + * @brief database object to store HTLCs + * + * This object is stored in the database while an HTLC is active. The HTLC will + * become inactive at expiration or when unlocked via the preimage. + */ + class htlc_object : public graphene::db::abstract_object { + public: + // uniquely identify this object in the database + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = htlc_object_type; + + account_id_type from; + account_id_type to; + asset amount; + fc::time_point_sec expiration; + htlc_hash preimage_hash; + uint16_t preimage_size; + }; + + struct by_from_id; + struct by_expiration; + typedef multi_index_container< + htlc_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + + ordered_unique< tag< by_expiration >, + composite_key< htlc_object, + member< htlc_object, fc::time_point_sec, &htlc_object::expiration >, + member< object, object_id_type, &object::id > > >, + + ordered_unique< tag< by_from_id >, + composite_key< htlc_object, + member< htlc_object, account_id_type, &htlc_object::from >, + member< object, object_id_type, &object::id > > > + > + + > htlc_object_index_type; + + typedef generic_index< htlc_object, htlc_object_index_type > htlc_index; + +} } // namespace graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::htlc_object, (graphene::db::object), + (from)(to)(amount)(expiration) + (preimage_hash)(preimage_size) ); diff --git a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp index 36f99ccfbe..04b5c62d22 100644 --- a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp @@ -28,6 +28,25 @@ namespace graphene { namespace chain { + class hardfork_visitor_1479 + { + public: + typedef void result_type; + + uint64_t max_update_instance = 0; + uint64_t nested_update_count = 0; + + template + void operator()(const T &v) const {} + + void operator()(const proposal_update_operation &v); + + void operator()(const proposal_delete_operation &v); + + // loop and self visit in proposals + void operator()(const graphene::chain::proposal_create_operation &v); + }; + class proposal_create_evaluator : public evaluator { public: @@ -37,6 +56,8 @@ namespace graphene { namespace chain { object_id_type do_apply( const proposal_create_operation& o ); transaction _proposed_trx; + + hardfork_visitor_1479 vtor_1479; }; class proposal_update_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index f6178d3e24..f2be53837b 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -274,6 +274,7 @@ 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)(buyback_options) ) +FC_REFLECT_TYPENAME(graphene::chain::extension) FC_REFLECT( graphene::chain::account_create_operation, (fee)(registrar) (referrer)(referrer_percent) @@ -281,6 +282,7 @@ FC_REFLECT( graphene::chain::account_create_operation, ) FC_REFLECT(graphene::chain::account_update_operation::ext, (null_ext)(owner_special_authority)(active_special_authority) ) +FC_REFLECT_TYPENAME(graphene::chain::extension) FC_REFLECT( graphene::chain::account_update_operation, (fee)(account)(owner)(active)(new_options)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/base.hpp b/libraries/chain/include/graphene/chain/protocol/base.hpp index 52240b934a..73209a1861 100644 --- a/libraries/chain/include/graphene/chain/protocol/base.hpp +++ b/libraries/chain/include/graphene/chain/protocol/base.hpp @@ -27,6 +27,8 @@ #include #include +#include + namespace graphene { namespace chain { /** @@ -94,6 +96,7 @@ namespace graphene { namespace chain { void get_required_active_authorities( flat_set& )const{} void get_required_owner_authorities( flat_set& )const{} void validate()const{} + fc::optional< fc::future > validate_parallel( uint32_t skip )const; static uint64_t calculate_data_fee( uint64_t bytes, uint64_t price_per_kbyte ); }; diff --git a/libraries/chain/include/graphene/chain/protocol/block.hpp b/libraries/chain/include/graphene/chain/protocol/block.hpp index 98e627c928..aa8c46052f 100644 --- a/libraries/chain/include/graphene/chain/protocol/block.hpp +++ b/libraries/chain/include/graphene/chain/protocol/block.hpp @@ -26,8 +26,9 @@ namespace graphene { namespace chain { - struct block_header + class block_header { + public: digest_type digest()const; block_id_type previous; uint32_t block_num()const { return num_from_id(previous) + 1; } @@ -41,20 +42,27 @@ namespace graphene { namespace chain { static uint32_t num_from_id(const block_id_type& id); }; - struct signed_block_header : public block_header + class signed_block_header : public block_header { - block_id_type id()const; - fc::ecc::public_key signee()const; + public: + const block_id_type& id()const; + const fc::ecc::public_key& signee()const; void sign( const fc::ecc::private_key& signer ); bool validate_signee( const fc::ecc::public_key& expected_signee )const; signature_type witness_signature; + protected: + mutable fc::ecc::public_key _signee; + mutable block_id_type _block_id; }; - struct signed_block : public signed_block_header + class signed_block : public signed_block_header { - checksum_type calculate_merkle_root()const; + public: + const checksum_type& calculate_merkle_root()const; vector transactions; + protected: + mutable checksum_type _calculated_merkle_root; }; } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 695d9541ee..dba8281305 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -30,7 +30,12 @@ namespace graphene { namespace chain { struct fee_schedule; } } namespace graphene { namespace chain { - typedef static_variant<> parameter_extension; + struct htlc_options + { + uint32_t max_timeout_secs; + uint32_t max_preimage_size; + }; + struct chain_parameters { /** using a smart ref breaks the circular dependency created between operations and the fee schedule */ @@ -63,14 +68,30 @@ namespace graphene { namespace chain { uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH; - extensions_type extensions; + + struct ext + { + optional< htlc_options > updatable_htlc_options; + }; + + extension extensions; /** defined in fee_schedule.cpp */ void validate()const; + }; } } // graphene::chain +FC_REFLECT( graphene::chain::htlc_options, + (max_timeout_secs) + (max_preimage_size) +) + +FC_REFLECT( graphene::chain::chain_parameters::ext, + (updatable_htlc_options) +) + FC_REFLECT( graphene::chain::chain_parameters, (current_fees) (block_interval) diff --git a/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp index a08ee98a2c..e578f1d9f4 100644 --- a/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp +++ b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp @@ -107,6 +107,46 @@ namespace graphene { namespace chain { } }; + template<> + class fee_helper { + public: + const htlc_create_operation::fee_parameters_type& cget(const flat_set& parameters)const + { + auto itr = parameters.find( htlc_create_operation::fee_parameters_type() ); + if ( itr != parameters.end() ) + return itr->get(); + + static htlc_create_operation::fee_parameters_type htlc_create_operation_fee_dummy; + return htlc_create_operation_fee_dummy; + } + }; + + template<> + class fee_helper { + public: + const htlc_redeem_operation::fee_parameters_type& cget(const flat_set& parameters)const + { + auto itr = parameters.find( htlc_redeem_operation::fee_parameters_type() ); + if ( itr != parameters.end() ) + return itr->get(); + + static htlc_redeem_operation::fee_parameters_type htlc_redeem_operation_fee_dummy; + return htlc_redeem_operation_fee_dummy; + } + }; + template<> + class fee_helper { + public: + const htlc_extend_operation::fee_parameters_type& cget(const flat_set& parameters)const + { + auto itr = parameters.find( htlc_extend_operation::fee_parameters_type() ); + if ( itr != parameters.end() ) + return itr->get(); + + static htlc_extend_operation::fee_parameters_type htlc_extend_operation_fee_dummy; + return htlc_extend_operation_fee_dummy; + } + }; /** * @brief contains all of the parameters necessary to calculate the fee for any operation */ @@ -140,6 +180,12 @@ namespace graphene { namespace chain { { return fee_helper().get(parameters); } + template + const bool exists()const + { + auto itr = parameters.find(typename Operation::fee_parameters_type()); + return itr != parameters.end(); + } /** * @note must be sorted by fee_parameters.which() and have no duplicates diff --git a/libraries/chain/include/graphene/chain/protocol/htlc.hpp b/libraries/chain/include/graphene/chain/protocol/htlc.hpp new file mode 100644 index 0000000000..ef5f2b1469 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/htlc.hpp @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2018 jmjatlanta 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 +#include +#include +#include +#include +#include // std::max + +namespace graphene { + namespace chain { + + typedef fc::ripemd160 htlc_algo_ripemd160; + typedef fc::sha1 htlc_algo_sha1; + typedef fc::sha256 htlc_algo_sha256; + + typedef fc::static_variant< + htlc_algo_ripemd160, + htlc_algo_sha1, + htlc_algo_sha256 + > htlc_hash; + + struct htlc_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + // paid to network + asset fee; + // where the held monies are to come from + account_id_type from; + // where the held monies will go if the preimage is provided + account_id_type to; + // the amount to hold + asset amount; + // the (typed) hash of the preimage + htlc_hash preimage_hash; + // the size of the preimage + uint16_t preimage_size; + // The time the funds will be returned to the source if not claimed + uint32_t claim_period_seconds; + // for future expansion + extensions_type extensions; + + /*** + * @brief Does simple validation of this object + */ + void validate()const; + + /** + * @brief who will pay the fee + */ + account_id_type fee_payer()const { return from; } + + /**** + * @brief calculates the fee to be paid for this operation + */ + share_type calculate_fee(const fee_parameters_type& fee_params)const; + }; + + struct htlc_redeem_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee_per_kb = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + // paid to network + asset fee; + // the object we are attempting to update + htlc_id_type htlc_id; + // who is attempting to update the transaction + account_id_type redeemer; + // the preimage (not used if after epoch timeout) + std::vector preimage; + // for future expansion + extensions_type extensions; + + /*** + * @brief Perform obvious checks to validate this object + */ + void validate()const; + + /** + * @brief Who is to pay the fee + */ + account_id_type fee_payer()const { return redeemer; } + + /**** + * @brief calculates the fee to be paid for this operation + */ + share_type calculate_fee(const fee_parameters_type& fee_params)const; + }; + + /** + * virtual op to assist with notifying related parties + */ + struct htlc_redeemed_operation : public base_operation + { + struct fee_parameters_type {}; + + htlc_redeemed_operation() {} + htlc_redeemed_operation( htlc_id_type htlc_id, account_id_type from, account_id_type to, asset amount) : + htlc_id(htlc_id), from(from), to(to), amount(amount) {} + + account_id_type fee_payer()const { return to; } + void validate()const { FC_ASSERT( !"virtual operation" ); } + + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + + htlc_id_type htlc_id; + account_id_type from, to; + asset amount; + asset fee; + }; + + struct htlc_extend_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + // paid to network + asset fee; + // the object we are attempting to update + htlc_id_type htlc_id; + // who is attempting to update the transaction + account_id_type update_issuer; + // how much to add + uint32_t seconds_to_add; + // for future expansion + extensions_type extensions; + + /*** + * @brief Perform obvious checks to validate this object + */ + void validate()const; + + /** + * @brief Who is to pay the fee + */ + account_id_type fee_payer()const { return update_issuer; } + + /**** + * @brief calculates the fee to be paid for this operation + */ + share_type calculate_fee(const fee_parameters_type& fee_params)const; + }; + + struct htlc_refund_operation : public base_operation + { + struct fee_parameters_type {}; + + htlc_refund_operation(){} + htlc_refund_operation( const htlc_id_type& htlc_id, const account_id_type& to ) + : htlc_id(htlc_id), to(to) {} + + account_id_type fee_payer()const { return to; } + void validate()const { FC_ASSERT( !"virtual operation" ); } + + /// This is a virtual operation; there is no fee + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + + htlc_id_type htlc_id; + account_id_type to; + asset fee; + }; + } +} + +FC_REFLECT_TYPENAME( graphene::chain::htlc_hash ) + +FC_REFLECT( graphene::chain::htlc_create_operation::fee_parameters_type, (fee) (fee_per_day) ) +FC_REFLECT( graphene::chain::htlc_redeem_operation::fee_parameters_type, (fee) (fee_per_kb) ) +FC_REFLECT( graphene::chain::htlc_redeemed_operation::fee_parameters_type, ) // VIRTUAL +FC_REFLECT( graphene::chain::htlc_extend_operation::fee_parameters_type, (fee) (fee_per_day)) +FC_REFLECT( graphene::chain::htlc_refund_operation::fee_parameters_type, ) // VIRTUAL + +FC_REFLECT( graphene::chain::htlc_create_operation, + (fee)(from)(to)(amount)(preimage_hash)(preimage_size)(claim_period_seconds)(extensions)) +FC_REFLECT( graphene::chain::htlc_redeem_operation, (fee)(htlc_id)(redeemer)(preimage)(extensions)) +FC_REFLECT( graphene::chain::htlc_redeemed_operation, (fee)(htlc_id)(from)(to)(amount) ) +FC_REFLECT( graphene::chain::htlc_extend_operation, (fee)(htlc_id)(update_issuer)(seconds_to_add)(extensions)) +FC_REFLECT( graphene::chain::htlc_refund_operation, (fee)(htlc_id)(to)) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index de2cfa7fd9..9b5ffabb37 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -38,6 +38,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -95,7 +96,12 @@ namespace graphene { namespace chain { bid_collateral_operation, execute_bid_operation, // VIRTUAL asset_claim_pool_operation, - asset_update_issuer_operation + asset_update_issuer_operation, + htlc_create_operation, + htlc_redeem_operation, + htlc_redeemed_operation, // VIRTUAL + htlc_extend_operation, + htlc_refund_operation // VIRTUAL > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/transaction.hpp b/libraries/chain/include/graphene/chain/protocol/transaction.hpp index 95c3996135..84234afb9e 100644 --- a/libraries/chain/include/graphene/chain/protocol/transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transaction.hpp @@ -62,8 +62,9 @@ namespace graphene { namespace chain { /** * @brief groups operations that should be applied atomically */ - struct transaction + class transaction { + public: /** * Least significant 16 bits from the reference block number. If @ref relative_expiration is zero, this field * must be zero as well. @@ -85,11 +86,9 @@ namespace graphene { namespace chain { extensions_type extensions; /// Calculate the digest for a transaction - digest_type digest()const; - transaction_id_type id()const; - void validate() const; - /// Calculate the digest used for signature validation - digest_type sig_digest( const chain_id_type& chain_id )const; + digest_type digest()const; + virtual const transaction_id_type& id()const; + virtual void validate() const; void set_expiration( fc::time_point_sec expiration_time ); void set_reference_block( const block_id_type& reference_block ); @@ -113,13 +112,19 @@ namespace graphene { namespace chain { } void get_required_authorities( flat_set& active, flat_set& owner, vector& other )const; + + protected: + // Calculate the digest used for signature validation + digest_type sig_digest( const chain_id_type& chain_id )const; + mutable transaction_id_type _tx_id_buffer; }; /** * @brief adds a signature to a transaction */ - struct signed_transaction : public transaction + class signed_transaction : public transaction { + public: signed_transaction( const transaction& trx = transaction() ) : transaction(trx){} @@ -176,19 +181,36 @@ namespace graphene { namespace chain { * otherwise, the @ref chain_id parameter will be ignored, and * @ref signees will be returned directly. */ - const flat_set& get_signature_keys( const chain_id_type& chain_id )const; + virtual const flat_set& get_signature_keys( const chain_id_type& chain_id )const; /** Signatures */ vector signatures; - /** Public keys extracted from signatures */ - mutable flat_set signees; + /** Removes all operations and signatures */ + void clear() { operations.clear(); signatures.clear(); } - /// Removes all operations, signatures and signees - void clear() { operations.clear(); signatures.clear(); signees.clear(); } + /** Removes all signatures */ + void clear_signatures() { signatures.clear(); } + protected: + /** Public keys extracted from signatures */ + mutable flat_set _signees; + }; - /// Removes all signatures and signees - void clear_signatures() { signatures.clear(); signees.clear(); } + /** This represents a signed transaction that will never have its operations, + * signatures etc. modified again, after initial creation. It is therefore + * safe to cache results from various calls. + */ + class precomputable_transaction : public signed_transaction { + public: + precomputable_transaction() {} + precomputable_transaction( const signed_transaction& tx ) : signed_transaction(tx) {}; + precomputable_transaction( signed_transaction&& tx ) : signed_transaction( std::move(tx) ) {}; + + virtual const transaction_id_type& id()const override; + virtual void validate()const override; + virtual const flat_set& get_signature_keys( const chain_id_type& chain_id )const override; + protected: + mutable bool _validated = false; }; void verify_authority( const vector& ops, const flat_set& sigs, @@ -212,10 +234,10 @@ namespace graphene { namespace chain { * If an operation did not create any new object IDs then 0 * should be returned. */ - struct processed_transaction : public signed_transaction + struct processed_transaction : public precomputable_transaction { processed_transaction( const signed_transaction& trx = signed_transaction() ) - : signed_transaction(trx){} + : precomputable_transaction(trx){} vector operation_results; @@ -229,4 +251,5 @@ namespace graphene { namespace chain { FC_REFLECT( graphene::chain::transaction, (ref_block_num)(ref_block_prefix)(expiration)(operations)(extensions) ) // Note: not reflecting signees field for backward compatibility; in addition, it should not be in p2p messages FC_REFLECT_DERIVED( graphene::chain::signed_transaction, (graphene::chain::transaction), (signatures) ) -FC_REFLECT_DERIVED( graphene::chain::processed_transaction, (graphene::chain::signed_transaction), (operation_results) ) +FC_REFLECT_DERIVED( graphene::chain::precomputable_transaction, (graphene::chain::signed_transaction), ) +FC_REFLECT_DERIVED( graphene::chain::processed_transaction, (graphene::chain::precomputable_transaction), (operation_results) ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 06ab7ff291..87536f42ee 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -36,6 +36,22 @@ #include +// TODO: move this to fc +#include +namespace fc { namespace raw { + template + inline void pack( T& ds, const fc::sha1& ep, uint32_t _max_depth = 1 ) { + ds << ep; + } + + template + inline void unpack( T& ds, sha1& ep, uint32_t _max_depth = 1 ) { + ds >> ep; + } + +} } +// /TODO: move to fc + #include #include #include @@ -140,6 +156,7 @@ namespace graphene { namespace chain { vesting_balance_object_type, worker_object_type, balance_object_type, + htlc_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -158,7 +175,7 @@ namespace graphene { namespace chain { impl_blinded_balance_object_type, impl_chain_property_object_type, impl_witness_schedule_object_type, - impl_reserved1_object_type, // formerly impl_budget_record_object_type, TODO: delete me + impl_budget_record_object_type, impl_special_authority_object_type, impl_buyback_object_type, impl_fba_accumulator_object_type, @@ -182,6 +199,7 @@ namespace graphene { namespace chain { class worker_object; class balance_object; class blinded_balance_object; + class htlc_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; @@ -197,6 +215,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; + typedef object_id< protocol_ids, htlc_object_type, htlc_object> htlc_id_type; // implementation types class global_property_object; @@ -210,6 +229,7 @@ namespace graphene { namespace chain { class account_transaction_history_object; class chain_property_object; class witness_schedule_object; + class budget_record_object; class special_authority_object; class buyback_object; class fba_accumulator_object; @@ -229,6 +249,7 @@ namespace graphene { namespace chain { account_transaction_history_object> account_transaction_history_id_type; typedef object_id< implementation_ids, impl_chain_property_object_type, chain_property_object> chain_property_id_type; 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 object_id< implementation_ids, impl_buyback_object_type, buyback_object > buyback_id_type; @@ -264,6 +285,14 @@ namespace graphene { namespace chain { friend bool operator != ( const public_key_type& p1, const public_key_type& p2); }; + class pubkey_comparator { + public: + inline bool operator()( const public_key_type& a, const public_key_type& b )const + { + return a.key_data < b.key_data; + } + }; + struct extended_public_key_type { struct binary_key @@ -343,6 +372,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (vesting_balance_object_type) (worker_object_type) (balance_object_type) + (htlc_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -359,7 +389,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_blinded_balance_object_type) (impl_chain_property_object_type) (impl_witness_schedule_object_type) - (impl_reserved1_object_type) + (impl_budget_record_object_type) (impl_special_authority_object_type) (impl_buyback_object_type) (impl_fba_accumulator_object_type) @@ -391,10 +421,12 @@ FC_REFLECT_TYPENAME( graphene::chain::account_statistics_id_type ) 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_TYPENAME( graphene::chain::buyback_id_type ) FC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_id_type ) FC_REFLECT_TYPENAME( graphene::chain::collateral_bid_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::htlc_id_type ) FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 975d14cb54..348688d605 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -108,6 +108,23 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!"Virtual operation"); } } + void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const { + if (block_time < HARDFORK_CORE_1468_TIME) { + FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), "Unable to set HTLC options before hardfork 1468"); + FC_ASSERT(!op.new_parameters.current_fees->exists()); + FC_ASSERT(!op.new_parameters.current_fees->exists()); + FC_ASSERT(!op.new_parameters.current_fees->exists()); + } + } + void operator()(const graphene::chain::htlc_create_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + } + void operator()(const graphene::chain::htlc_redeem_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + } + void operator()(const graphene::chain::htlc_extend_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { for (const op_wrapper &op : v.proposed_ops) @@ -127,6 +144,27 @@ struct hardfork_visitor_214 // non-recursive proposal visitor } }; +void hardfork_visitor_1479::operator()(const proposal_update_operation &v) +{ + if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance ) + max_update_instance = v.proposal.instance.value; + nested_update_count++; +} + +void hardfork_visitor_1479::operator()(const proposal_delete_operation &v) +{ + if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance ) + max_update_instance = v.proposal.instance.value; + nested_update_count++; +} + +// loop and self visit in proposals +void hardfork_visitor_1479::operator()(const graphene::chain::proposal_create_operation &v) +{ + for (const op_wrapper &op : v.proposed_ops) + op.op.visit(*this); +} + void_result proposal_create_evaluator::do_evaluate(const proposal_create_operation& o) { try { const database& d = db(); @@ -142,6 +180,7 @@ void_result proposal_create_evaluator::do_evaluate(const proposal_create_operati for (const op_wrapper &op : o.proposed_ops) op.op.visit( hf214 ); } + vtor_1479( o ); const auto& global_parameters = d.get_global_properties().parameters; @@ -213,6 +252,20 @@ object_id_type proposal_create_evaluator::do_apply(const proposal_create_operati std::set_difference(required_active.begin(), required_active.end(), proposal.required_owner_approvals.begin(), proposal.required_owner_approvals.end(), std::inserter(proposal.required_active_approvals, proposal.required_active_approvals.begin())); + + if( d.head_block_time() > HARDFORK_CORE_1479_TIME ) + FC_ASSERT( vtor_1479.nested_update_count == 0 || proposal.id.instance() > vtor_1479.max_update_instance, + "Cannot update/delete a proposal with a future id!" ); + else if( vtor_1479.nested_update_count > 0 && proposal.id.instance() <= vtor_1479.max_update_instance ) + { + // prevent approval + transfer_operation top; + top.from = GRAPHENE_NULL_ACCOUNT; + top.to = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT; + top.amount = asset( GRAPHENE_MAX_SHARE_SUPPLY ); + proposal.proposed_transaction.operations.emplace_back( top ); + wlog( "Issue 1479: ${p}", ("p",proposal) ); + } }); return proposal.id; diff --git a/libraries/chain/protocol/block.cpp b/libraries/chain/protocol/block.cpp index d32365dd08..9fdf4707eb 100644 --- a/libraries/chain/protocol/block.cpp +++ b/libraries/chain/protocol/block.cpp @@ -37,19 +37,23 @@ namespace graphene { namespace chain { return fc::endian_reverse_u32(id._hash[0]); } - block_id_type signed_block_header::id()const + const block_id_type& signed_block_header::id()const { - auto tmp = fc::sha224::hash( *this ); - tmp._hash[0] = fc::endian_reverse_u32(block_num()); // store the block num in the ID, 160 bits is plenty for the hash - static_assert( sizeof(tmp._hash[0]) == 4, "should be 4 bytes" ); - block_id_type result; - memcpy(result._hash, tmp._hash, std::min(sizeof(result), sizeof(tmp))); - return result; + if( !_block_id._hash[0] ) + { + auto tmp = fc::sha224::hash( *this ); + tmp._hash[0] = fc::endian_reverse_u32(block_num()); // store the block num in the ID, 160 bits is plenty for the hash + static_assert( sizeof(tmp._hash[0]) == 4, "should be 4 bytes" ); + memcpy(_block_id._hash, tmp._hash, std::min(sizeof(_block_id), sizeof(tmp))); + } + return _block_id; } - fc::ecc::public_key signed_block_header::signee()const + const fc::ecc::public_key& signed_block_header::signee()const { - return fc::ecc::public_key( witness_signature, digest(), true/*enforce canonical*/ ); + if( !_signee.valid() ) + _signee = fc::ecc::public_key( witness_signature, digest(), true/*enforce canonical*/ ); + return _signee; } void signed_block_header::sign( const fc::ecc::private_key& signer ) @@ -62,31 +66,35 @@ namespace graphene { namespace chain { return signee() == expected_signee; } - checksum_type signed_block::calculate_merkle_root()const + const checksum_type& signed_block::calculate_merkle_root()const { + static const checksum_type empty_checksum; if( transactions.size() == 0 ) - return checksum_type(); - - vector ids; - ids.resize( transactions.size() ); - for( uint32_t i = 0; i < transactions.size(); ++i ) - ids[i] = transactions[i].merkle_digest(); + return empty_checksum; - vector::size_type current_number_of_hashes = ids.size(); - while( current_number_of_hashes > 1 ) + if( !_calculated_merkle_root._hash[0] ) { - // hash ID's in pairs - uint32_t i_max = current_number_of_hashes - (current_number_of_hashes&1); - uint32_t k = 0; + vector ids; + ids.resize( transactions.size() ); + for( uint32_t i = 0; i < transactions.size(); ++i ) + ids[i] = transactions[i].merkle_digest(); - for( uint32_t i = 0; i < i_max; i += 2 ) - ids[k++] = digest_type::hash( std::make_pair( ids[i], ids[i+1] ) ); + vector::size_type current_number_of_hashes = ids.size(); + while( current_number_of_hashes > 1 ) + { + // hash ID's in pairs + uint32_t i_max = current_number_of_hashes - (current_number_of_hashes&1); + uint32_t k = 0; - if( current_number_of_hashes&1 ) - ids[k++] = ids[i_max]; - current_number_of_hashes = k; + for( uint32_t i = 0; i < i_max; i += 2 ) + ids[k++] = digest_type::hash( std::make_pair( ids[i], ids[i+1] ) ); + + if( current_number_of_hashes&1 ) + ids[k++] = ids[i_max]; + current_number_of_hashes = k; + } + _calculated_merkle_root = checksum_type::hash( ids[0] ); } - return checksum_type::hash( ids[0] ); + return _calculated_merkle_root; } - } } diff --git a/libraries/chain/protocol/htlc.cpp b/libraries/chain/protocol/htlc.cpp new file mode 100644 index 0000000000..645feb6dbf --- /dev/null +++ b/libraries/chain/protocol/htlc.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 jmjatlanta 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 + +#define SECONDS_PER_DAY (60 * 60 * 24) + +namespace graphene { namespace chain { + + void htlc_create_operation::validate()const { + FC_ASSERT( fee.amount >= 0, "Fee amount should not be negative" ); + FC_ASSERT( amount.amount > 0, "HTLC amount should be greater than zero" ); + } + + share_type htlc_create_operation::calculate_fee( const fee_parameters_type& fee_params )const + { + uint64_t days = ( claim_period_seconds + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; + // multiply with overflow check + uint64_t per_day_fee = fee_params.fee_per_day * days; + FC_ASSERT( days == 0 || per_day_fee / days == fee_params.fee_per_day, "Fee calculation overflow" ); + return fee_params.fee + per_day_fee; + } + + void htlc_redeem_operation::validate()const { + FC_ASSERT( fee.amount >= 0, "Fee amount should not be negative" ); + } + + share_type htlc_redeem_operation::calculate_fee( const fee_parameters_type& fee_params )const + { + uint64_t kb = ( preimage.size() + 1023 ) / 1024; + uint64_t product = kb * fee_params.fee_per_kb; + FC_ASSERT( kb == 0 || product / kb == fee_params.fee_per_kb, "Fee calculation overflow"); + return fee_params.fee + product; + } + + void htlc_extend_operation::validate()const { + FC_ASSERT( fee.amount >= 0 , "Fee amount should not be negative"); + } + + share_type htlc_extend_operation::calculate_fee( const fee_parameters_type& fee_params )const + { + uint32_t days = ( seconds_to_add + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; + uint64_t per_day_fee = fee_params.fee_per_day * days; + FC_ASSERT( days == 0 || per_day_fee / days == fee_params.fee_per_day, "Fee calculation overflow" ); + return fee_params.fee + per_day_fee; + } +} } diff --git a/libraries/chain/protocol/operations.cpp b/libraries/chain/protocol/operations.cpp index 40a37eba3a..48a65f6fed 100644 --- a/libraries/chain/protocol/operations.cpp +++ b/libraries/chain/protocol/operations.cpp @@ -32,6 +32,12 @@ uint64_t base_operation::calculate_data_fee( uint64_t bytes, uint64_t price_per_ return result.to_uint64(); } +fc::optional< fc::future > base_operation::validate_parallel( uint32_t skip )const +{ + validate(); + return fc::optional< fc::future >(); +} + void balance_claim_operation::validate()const { FC_ASSERT( fee == asset() ); diff --git a/libraries/chain/protocol/transaction.cpp b/libraries/chain/protocol/transaction.cpp index 4bb7ce8378..1a1293ca76 100644 --- a/libraries/chain/protocol/transaction.cpp +++ b/libraries/chain/protocol/transaction.cpp @@ -60,19 +60,17 @@ void transaction::validate() const operation_validate(op); } -graphene::chain::transaction_id_type graphene::chain::transaction::id() const +const transaction_id_type& transaction::id() const { auto h = digest(); - transaction_id_type result; - memcpy(result._hash, h._hash, std::min(sizeof(result), sizeof(h))); - return result; + memcpy(_tx_id_buffer._hash, h._hash, std::min(sizeof(_tx_id_buffer), sizeof(h))); + return _tx_id_buffer; } const signature_type& graphene::chain::signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id) { digest_type h = sig_digest( chain_id ); signatures.push_back(key.sign_compact(h)); - signees.clear(); // Clear signees since it may be inconsistent after added a new signature return signatures.back(); } @@ -305,22 +303,17 @@ void verify_authority( const vector& ops, const flat_set& signed_transaction::get_signature_keys( const chain_id_type& chain_id )const { try { - // Strictly we should check whether the given chain ID is same as the one used to initialize the `signees` field. - // However, we don't pass in another chain ID so far, for better performance, we skip the check. - if( signees.empty() && !signatures.empty() ) + auto d = sig_digest( chain_id ); + flat_set result; + for( const auto& sig : signatures ) { - auto d = sig_digest( chain_id ); - flat_set result; - for( const auto& sig : signatures ) - { - GRAPHENE_ASSERT( - result.insert( fc::ecc::public_key(sig,d) ).second, + GRAPHENE_ASSERT( + result.insert( fc::ecc::public_key(sig,d) ).second, tx_duplicate_sig, "Duplicate Signature detected" ); - } - signees = std::move( result ); } - return signees; + _signees = std::move( result ); + return _signees; } FC_CAPTURE_AND_RETHROW() } @@ -386,6 +379,29 @@ set signed_transaction::minimize_required_signatures( return set( result.begin(), result.end() ); } +const transaction_id_type& precomputable_transaction::id()const +{ + if( !_tx_id_buffer._hash[0] ) + transaction::id(); + return _tx_id_buffer; +} + +void precomputable_transaction::validate() const +{ + if( _validated ) return; + transaction::validate(); + _validated = true; +} + +const flat_set& precomputable_transaction::get_signature_keys( const chain_id_type& chain_id )const +{ + // Strictly we should check whether the given chain ID is same as the one used to initialize the `signees` field. + // However, we don't pass in another chain ID so far, for better performance, we skip the check. + if( _signees.empty() ) + signed_transaction::get_signature_keys( chain_id ); + return _signees; +} + void signed_transaction::verify_authority( const chain_id_type& chain_id, const std::function& get_active, diff --git a/libraries/db/include/graphene/db/index.hpp b/libraries/db/include/graphene/db/index.hpp index bcf9b24f13..4b54862899 100644 --- a/libraries/db/include/graphene/db/index.hpp +++ b/libraries/db/include/graphene/db/index.hpp @@ -23,11 +23,14 @@ */ #pragma once #include + #include #include #include #include + #include +#include namespace graphene { namespace db { class object_database; @@ -190,6 +193,111 @@ namespace graphene { namespace db { object_database& _db; }; + /** @class direct_index + * @brief A secondary index that tracks objects in vectors indexed by object + * id. It is meant for fully (or almost fully) populated indexes only (will + * fail when loading an object_database with large gaps). + * + * WARNING! If any of the methods called on insertion, removal or + * modification throws, subsequent behaviour is undefined! Such exceptions + * indicate that this index type is not appropriate for the use-case. + */ + template + class direct_index : public secondary_index + { + static_assert( chunkbits < 64, "Do you really want arrays with more than 2^63 elements???" ); + + // private + static const size_t MAX_HOLE = 100; + static const size_t _mask = ((1 << chunkbits) - 1); + size_t next = 0; + vector< vector< const Object* > > content; + std::stack< object_id_type > ids_being_modified; + + public: + direct_index() { + FC_ASSERT( (1ULL << chunkbits) > MAX_HOLE, "Small chunkbits is inefficient." ); + } + + virtual ~direct_index(){} + + virtual void object_inserted( const object& obj ) + { + uint64_t instance = obj.id.instance(); + if( instance == next ) + { + if( !(next & _mask) ) + { + content.resize((next >> chunkbits) + 1); + content[next >> chunkbits].resize( 1 << chunkbits, nullptr ); + } + next++; + } + else if( instance < next ) + FC_ASSERT( !content[instance >> chunkbits][instance & _mask], "Overwriting insert at {id}!", ("id",obj.id) ); + else // instance > next, allow small "holes" + { + FC_ASSERT( instance <= next + MAX_HOLE, "Out-of-order insert: {id} > {next}!", ("id",obj.id)("next",next) ); + if( !(next & _mask) || (next & (~_mask)) != (instance & (~_mask)) ) + { + content.resize((instance >> chunkbits) + 1); + content[instance >> chunkbits].resize( 1 << chunkbits, nullptr ); + } + while( next <= instance ) + { + content[next >> chunkbits][next & _mask] = nullptr; + next++; + } + } + FC_ASSERT( nullptr != dynamic_cast(&obj), "Wrong object type!" ); + content[instance >> chunkbits][instance & _mask] = static_cast( &obj ); + } + + virtual void object_removed( const object& obj ) + { + FC_ASSERT( nullptr != dynamic_cast(&obj), "Wrong object type!" ); + uint64_t instance = obj.id.instance(); + FC_ASSERT( instance < next, "Removing out-of-range object: {id} > {next}!", ("id",obj.id)("next",next) ); + FC_ASSERT( content[instance >> chunkbits][instance & _mask], "Removing non-existent object {id}!", ("id",obj.id) ); + content[instance >> chunkbits][instance & _mask] = nullptr; + } + + virtual void about_to_modify( const object& before ) + { + ids_being_modified.emplace( before.id ); + } + + virtual void object_modified( const object& after ) + { + FC_ASSERT( ids_being_modified.top() == after.id, "Modification of ID is not supported!"); + ids_being_modified.pop(); + } + + template< typename object_id > + const Object* find( const object_id& id )const + { + static_assert( object_id::space_id == Object::space_id, "Space ID mismatch!" ); + static_assert( object_id::type_id == Object::type_id, "Type_ID mismatch!" ); + if( id.instance >= next ) return nullptr; + return content[id.instance.value >> chunkbits][id.instance.value & _mask]; + }; + + template< typename object_id > + const Object& get( const object_id& id )const + { + const Object* ptr = find( id ); + FC_ASSERT( ptr != nullptr, "Object not found!" ); + return *ptr; + }; + + const Object* find( const object_id_type& id )const + { + FC_ASSERT( id.space() == Object::space_id, "Space ID mismatch!" ); + FC_ASSERT( id.type() == Object::type_id, "Type_ID mismatch!" ); + if( id.instance() >= next ) return nullptr; + return content[id.instance() >> chunkbits][id.instance() & ((1 << chunkbits) - 1)]; + }; + }; /** * @class primary_index @@ -198,14 +306,18 @@ namespace graphene { namespace db { * * @see http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern */ - template + template class primary_index : public DerivedIndex, public base_primary_index { public: typedef typename DerivedIndex::object_type object_type; primary_index( object_database& db ) - :base_primary_index(db),_next_id(object_type::space_id,object_type::type_id,0) {} + :base_primary_index(db),_next_id(object_type::space_id,object_type::type_id,0) + { + if( DirectBits > 0 ) + _direct_by_id = add_secondary_index< direct_index< object_type, DirectBits > >(); + } virtual uint8_t object_space_id()const override { return object_type::space_id; } @@ -217,6 +329,14 @@ namespace graphene { namespace db { virtual void use_next_id()override { ++_next_id.number; } virtual void set_next_id( object_id_type id )override { _next_id = id; } + /** @return the object with id or nullptr if not found */ + virtual const object* find( object_id_type id )const override + { + if( DirectBits > 0 ) + return _direct_by_id->find( id ); + return DerivedIndex::find( id ); + } + fc::sha256 get_object_version()const { std::string desc = "1.0";//get_type_description(); @@ -234,14 +354,12 @@ namespace graphene { namespace db { fc::raw::unpack(ds, _next_id); fc::raw::unpack(ds, open_ver); FC_ASSERT( open_ver == get_object_version(), "Incompatible Version, the serialization of objects in this index has changed" ); - try { - vector tmp; - while( true ) - { - fc::raw::unpack( ds, tmp ); - load( tmp ); - } - } catch ( const fc::exception& ){} + vector tmp; + while( ds.remaining() > 0 ) + { + fc::raw::unpack( ds, tmp ); + load( tmp ); + } } virtual void save( const path& db ) override @@ -329,7 +447,8 @@ namespace graphene { namespace db { } private: - object_id_type _next_id; + object_id_type _next_id; + const direct_index< object_type, DirectBits >* _direct_by_id = nullptr; }; } } // graphene::db diff --git a/libraries/db/object_database.cpp b/libraries/db/object_database.cpp index fdde0fed6b..5b026c08cf 100644 --- a/libraries/db/object_database.cpp +++ b/libraries/db/object_database.cpp @@ -25,6 +25,7 @@ #include #include +#include #include namespace graphene { namespace db { @@ -72,14 +73,20 @@ void object_database::flush() { // ilog("Save object_database in ${d}", ("d", _data_dir)); fc::create_directories( _data_dir / "object_database.tmp" / "lock" ); + std::vector> tasks; + tasks.reserve(200); for( uint32_t space = 0; space < _index.size(); ++space ) { fc::create_directories( _data_dir / "object_database.tmp" / fc::to_string(space) ); const auto types = _index[space].size(); for( uint32_t type = 0; type < types; ++type ) if( _index[space][type] ) - _index[space][type]->save( _data_dir / "object_database.tmp" / fc::to_string(space)/fc::to_string(type) ); + tasks.push_back( fc::do_parallel( [this,space,type] () { + _index[space][type]->save( _data_dir / "object_database.tmp" / fc::to_string(space)/fc::to_string(type) ); + } ) ); } + for( auto& task : tasks ) + task.wait(); fc::remove_all( _data_dir / "object_database.tmp" / "lock" ); if( fc::exists( _data_dir / "object_database" ) ) fc::rename( _data_dir / "object_database", _data_dir / "object_database.old" ); @@ -103,11 +110,17 @@ void object_database::open(const fc::path& data_dir) wlog("Ignoring locked object_database"); return; } + std::vector> tasks; + tasks.reserve(200); ilog("Opening object database from ${d} ...", ("d", data_dir)); for( uint32_t space = 0; space < _index.size(); ++space ) for( uint32_t type = 0; type < _index[space].size(); ++type ) if( _index[space][type] ) - _index[space][type]->open( _data_dir / "object_database" / fc::to_string(space)/fc::to_string(type) ); + tasks.push_back( fc::do_parallel( [this,space,type] () { + _index[space][type]->open( _data_dir / "object_database" / fc::to_string(space)/fc::to_string(type) ); + } ) ); + for( auto& task : tasks ) + task.wait(); ilog( "Done opening object database." ); } FC_CAPTURE_AND_RETHROW( (data_dir) ) } diff --git a/libraries/net/include/graphene/net/core_messages.hpp b/libraries/net/include/graphene/net/core_messages.hpp index 8af0c3443c..76f74bd253 100644 --- a/libraries/net/include/graphene/net/core_messages.hpp +++ b/libraries/net/include/graphene/net/core_messages.hpp @@ -95,9 +95,9 @@ namespace graphene { namespace net { { static const core_message_type_enum type; - signed_transaction trx; + graphene::chain::precomputable_transaction trx; trx_message() {} - trx_message(signed_transaction transaction) : + trx_message(graphene::chain::signed_transaction transaction) : trx(std::move(transaction)) {} }; diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index b2eb5185cb..612529f93c 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -317,7 +317,7 @@ namespace graphene { namespace net { namespace detail { _maximum_blocks_per_peer_during_syncing(GRAPHENE_NET_MAX_BLOCKS_PER_PEER_DURING_SYNCING) { _rate_limiter.set_actual_rate_time_constant(fc::seconds(2)); - fc::rand_pseudo_bytes(&_node_id.data[0], (int)_node_id.size()); + fc::rand_bytes(&_node_id.data[0], (int)_node_id.size()); } node_impl::~node_impl() diff --git a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp index 907d26ae9a..22c71236b5 100644 --- a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp +++ b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp @@ -58,7 +58,7 @@ class debug_witness_plugin : public graphene::app::plugin { boost::program_options::variables_map _options; - std::map _private_keys; + std::map _private_keys; std::shared_ptr< std::ofstream > _json_object_stream; boost::signals2::scoped_connection _applied_block_conn; diff --git a/libraries/plugins/delayed_node/delayed_node_plugin.cpp b/libraries/plugins/delayed_node/delayed_node_plugin.cpp index 24a46cc066..0329d018b2 100644 --- a/libraries/plugins/delayed_node/delayed_node_plugin.cpp +++ b/libraries/plugins/delayed_node/delayed_node_plugin.cpp @@ -58,7 +58,7 @@ delayed_node_plugin::~delayed_node_plugin() void delayed_node_plugin::plugin_set_program_options(bpo::options_description& cli, bpo::options_description& cfg) { cli.add_options() - ("trusted-node", boost::program_options::value(), "RPC endpoint of a trusted validating node (required)") + ("trusted-node", boost::program_options::value(), "RPC endpoint of a trusted validating node (required for delayed_node)") ; cfg.add(cli); } @@ -103,8 +103,10 @@ void delayed_node_plugin::sync_with_trusted_node() while( remote_dpo.last_irreversible_block_num > db.head_block_num() ) { fc::optional block = my->database_api->get_block( db.head_block_num()+1 ); + // TODO: during sync, decouple requesting blocks from preprocessing + applying them FC_ASSERT(block, "Trusted node claims it has blocks it doesn't actually have."); ilog("Pushing block #${n}", ("n", block->block_num())); + db.precompute_parallel( *block, graphene::chain::database::skip_nothing ).wait(); db.push_block(*block); synced_blocks++; } diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index dcffaea5f9..6af2df19ac 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -59,6 +59,7 @@ class elasticsearch_plugin_impl std::string _elasticsearch_basic_auth = ""; std::string _elasticsearch_index_prefix = "bitshares-"; bool _elasticsearch_operation_object = false; + uint32_t _elasticsearch_start_es_after_block = 0; // disabled CURL *curl; // curl handler vector bulk_lines; // vector of op lines vector prepare; @@ -74,7 +75,7 @@ class elasticsearch_plugin_impl std::string index_name; bool is_sync = false; private: - bool add_elasticsearch( const account_id_type account_id, const optional& oho ); + bool add_elasticsearch( const account_id_type account_id, const optional& oho, const uint32_t block_number ); const account_transaction_history_object& addNewEntry(const account_statistics_object& stats_obj, const account_id_type& account_id, const optional & oho); @@ -163,7 +164,7 @@ bool elasticsearch_plugin_impl::update_account_histories( const signed_block& b for( auto& account_id : impacted ) { - if(!add_elasticsearch( account_id, oho )) + if(!add_elasticsearch( account_id, oho, b.block_num() )) return false; } } @@ -276,13 +277,16 @@ void elasticsearch_plugin_impl::doVisitor(const optional & oho) + const optional & oho, + const uint32_t block_number) { const auto &stats_obj = getStatsObject(account_id); const auto &ath = addNewEntry(stats_obj, account_id, oho); growStats(stats_obj, ath); - createBulkLine(ath); - prepareBulk(ath.id); + if(_elasticsearch_start_es_after_block == 0 || block_number > _elasticsearch_start_es_after_block) { + createBulkLine(ath); + prepareBulk(ath.id); + } cleanObjects(ath.id, account_id); if (curl && bulk_lines.size() >= limit_documents) { // we are in bulk time, ready to add data to elasticsearech @@ -428,6 +432,7 @@ void elasticsearch_plugin::plugin_set_program_options( ("elasticsearch-basic-auth", boost::program_options::value(), "Pass basic auth to elasticsearch database('')") ("elasticsearch-index-prefix", boost::program_options::value(), "Add a prefix to the index(bitshares-)") ("elasticsearch-operation-object", boost::program_options::value(), "Save operation as object(false)") + ("elasticsearch-start-es-after-block", boost::program_options::value(), "Start doing ES job after block(0)") ; cfg.add(cli); } @@ -435,11 +440,10 @@ void elasticsearch_plugin::plugin_set_program_options( void elasticsearch_plugin::plugin_initialize(const boost::program_options::variables_map& options) { database().applied_block.connect( [&]( const signed_block& b) { - if(!my->update_account_histories(b)) - { + if (!my->update_account_histories(b)) FC_THROW_EXCEPTION(graphene::chain::plugin_exception, "Error populating ES database, we are going to keep trying."); - } } ); + my->_oho_index = database().add_index< primary_index< operation_history_index > >(); database().add_index< primary_index< account_transaction_history_index > >(); @@ -464,6 +468,9 @@ void elasticsearch_plugin::plugin_initialize(const boost::program_options::varia if (options.count("elasticsearch-operation-object")) { my->_elasticsearch_operation_object = options["elasticsearch-operation-object"].as(); } + if (options.count("elasticsearch-start-es-after-block")) { + my->_elasticsearch_start_es_after_block = options["elasticsearch-start-es-after-block"].as(); + } } void elasticsearch_plugin::plugin_startup() diff --git a/libraries/plugins/es_objects/es_objects.cpp b/libraries/plugins/es_objects/es_objects.cpp index d0c35010ce..5695064325 100644 --- a/libraries/plugins/es_objects/es_objects.cpp +++ b/libraries/plugins/es_objects/es_objects.cpp @@ -30,10 +30,11 @@ #include #include #include +#include +#include #include - namespace graphene { namespace es_objects { namespace detail @@ -72,12 +73,8 @@ class es_objects_plugin_impl fc::time_point_sec block_time; private: - void prepare_proposal(const proposal_object& proposal_object); - void prepare_account(const account_object& account_object); - void prepare_asset(const asset_object& asset_object); - void prepare_balance(const account_balance_object& account_balance_object); - void prepare_limit(const limit_order_object& limit_object); - void prepare_bitasset(const asset_bitasset_data_object& bitasset_object); + template + void prepareTemplate(T blockchain_object, string index_name); }; bool es_objects_plugin_impl::index_database( const vector& ids, std::string action) @@ -102,7 +99,7 @@ bool es_objects_plugin_impl::index_database( const vector& ids, if(action == "delete") remove_from_database(p->id, "proposal"); else - prepare_proposal(*p); + prepareTemplate(*p, "proposal"); } } else if(value.is() && _es_objects_accounts) { @@ -112,7 +109,7 @@ bool es_objects_plugin_impl::index_database( const vector& ids, if(action == "delete") remove_from_database(a->id, "account"); else - prepare_account(*a); + prepareTemplate(*a, "account"); } } else if(value.is() && _es_objects_assets) { @@ -122,7 +119,7 @@ bool es_objects_plugin_impl::index_database( const vector& ids, if(action == "delete") remove_from_database(a->id, "asset"); else - prepare_asset(*a); + prepareTemplate(*a, "asset"); } } else if(value.is() && _es_objects_balances) { @@ -132,7 +129,7 @@ bool es_objects_plugin_impl::index_database( const vector& ids, if(action == "delete") remove_from_database(b->id, "balance"); else - prepare_balance(*b); + prepareTemplate(*b, "balance"); } } else if(value.is() && _es_objects_limit_orders) { @@ -142,7 +139,7 @@ bool es_objects_plugin_impl::index_database( const vector& ids, if(action == "delete") remove_from_database(l->id, "limitorder"); else - prepare_limit(*l); + prepareTemplate(*l, "limitorder"); } } else if(value.is() && _es_objects_asset_bitasset) { @@ -152,7 +149,7 @@ bool es_objects_plugin_impl::index_database( const vector& ids, if(action == "delete") remove_from_database(ba->id, "bitasset"); else - prepare_bitasset(*ba); + prepareTemplate(*ba, "bitasset"); } } } @@ -190,181 +187,33 @@ void es_objects_plugin_impl::remove_from_database( object_id_type id, std::strin } } -void es_objects_plugin_impl::prepare_proposal(const proposal_object& proposal_object) -{ - proposal_struct prop; - prop.object_id = proposal_object.id; - prop.block_time = block_time; - prop.block_number = block_number; - prop.expiration_time = proposal_object.expiration_time; - prop.review_period_time = proposal_object.review_period_time; - prop.proposed_transaction = fc::json::to_string(proposal_object.proposed_transaction); - prop.required_owner_approvals = fc::json::to_string(proposal_object.required_owner_approvals); - prop.available_owner_approvals = fc::json::to_string(proposal_object.available_owner_approvals); - prop.required_active_approvals = fc::json::to_string(proposal_object.required_active_approvals); - prop.available_key_approvals = fc::json::to_string(proposal_object.available_key_approvals); - prop.proposer = proposal_object.proposer; - - std::string data = fc::json::to_string(prop); - - fc::mutable_variant_object bulk_header; - bulk_header["_index"] = _es_objects_index_prefix + "proposal"; - bulk_header["_type"] = "data"; - if(_es_objects_keep_only_current) - { - bulk_header["_id"] = string(prop.object_id); - } - - prepare = graphene::utilities::createBulk(bulk_header, std::move(data)); - std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk)); - prepare.clear(); -} - -void es_objects_plugin_impl::prepare_account(const account_object& account_object) -{ - account_struct acct; - acct.object_id = account_object.id; - acct.block_time = block_time; - acct.block_number = block_number; - acct.membership_expiration_date = account_object.membership_expiration_date; - acct.registrar = account_object.registrar; - acct.referrer = account_object.referrer; - acct.lifetime_referrer = account_object.lifetime_referrer; - acct.network_fee_percentage = account_object.network_fee_percentage; - acct.lifetime_referrer_fee_percentage = account_object.lifetime_referrer_fee_percentage; - acct.referrer_rewards_percentage = account_object.referrer_rewards_percentage; - acct.name = account_object.name; - acct.owner_account_auths = fc::json::to_string(account_object.owner.account_auths); - acct.owner_key_auths = fc::json::to_string(account_object.owner.key_auths); - acct.owner_address_auths = fc::json::to_string(account_object.owner.address_auths); - acct.active_account_auths = fc::json::to_string(account_object.active.account_auths); - acct.active_key_auths = fc::json::to_string(account_object.active.key_auths); - acct.active_address_auths = fc::json::to_string(account_object.active.address_auths); - acct.voting_account = account_object.options.voting_account; - acct.votes = fc::json::to_string(account_object.options.votes); - - std::string data = fc::json::to_string(acct); - - fc::mutable_variant_object bulk_header; - bulk_header["_index"] = _es_objects_index_prefix + "account"; - bulk_header["_type"] = "data"; - if(_es_objects_keep_only_current) - { - bulk_header["_id"] = string(acct.object_id); - } - - prepare = graphene::utilities::createBulk(bulk_header, std::move(data)); - std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk)); - prepare.clear(); -} - -void es_objects_plugin_impl::prepare_asset(const asset_object& asset_object) -{ - asset_struct asset; - asset.object_id = asset_object.id; - asset.block_time = block_time; - asset.block_number = block_number; - asset.symbol = asset_object.symbol; - asset.issuer = asset_object.issuer; - asset.is_market_issued = asset_object.is_market_issued(); - asset.dynamic_asset_data_id = asset_object.dynamic_asset_data_id; - asset.bitasset_data_id = asset_object.bitasset_data_id; - - std::string data = fc::json::to_string(asset); - - fc::mutable_variant_object bulk_header; - bulk_header["_index"] = _es_objects_index_prefix + "asset"; - bulk_header["_type"] = "data"; - if(_es_objects_keep_only_current) - { - bulk_header["_id"] = string(asset.object_id); - } - - prepare = graphene::utilities::createBulk(bulk_header, std::move(data)); - std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk)); - prepare.clear(); -} - -void es_objects_plugin_impl::prepare_balance(const account_balance_object& account_balance_object) +template +void es_objects_plugin_impl::prepareTemplate(T blockchain_object, string index_name) { - balance_struct balance; - balance.object_id = account_balance_object.id; - balance.block_time = block_time; - balance.block_number = block_number; - balance.owner = account_balance_object.owner; - balance.asset_type = account_balance_object.asset_type; - balance.balance = account_balance_object.balance; - balance.maintenance_flag = account_balance_object.maintenance_flag; - - std::string data = fc::json::to_string(balance); - fc::mutable_variant_object bulk_header; - bulk_header["_index"] = _es_objects_index_prefix + "balance"; + bulk_header["_index"] = _es_objects_index_prefix + index_name; bulk_header["_type"] = "data"; if(_es_objects_keep_only_current) { - bulk_header["_id"] = string(balance.object_id); + bulk_header["_id"] = string(blockchain_object.id); } - prepare = graphene::utilities::createBulk(bulk_header, std::move(data)); - std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk)); - prepare.clear(); -} + adaptor_struct adaptor; + fc::variant blockchain_object_variant; + fc::to_variant( blockchain_object, blockchain_object_variant, GRAPHENE_NET_MAX_NESTED_OBJECTS ); + fc::mutable_variant_object o = adaptor.adapt(blockchain_object_variant.get_object()); -void es_objects_plugin_impl::prepare_limit(const limit_order_object& limit_object) -{ - limit_order_struct limit; - limit.object_id = limit_object.id; - limit.block_time = block_time; - limit.block_number = block_number; - limit.expiration = limit_object.expiration; - limit.seller = limit_object.seller; - limit.for_sale = limit_object.for_sale; - limit.sell_price = limit_object.sell_price; - limit.deferred_fee = limit_object.deferred_fee; - - std::string data = fc::json::to_string(limit); + o["object_id"] = string(blockchain_object.id); + o["block_time"] = block_time; + o["block_number"] = block_number; - fc::mutable_variant_object bulk_header; - bulk_header["_index"] = _es_objects_index_prefix + "limitorder"; - bulk_header["_type"] = "data"; - if(_es_objects_keep_only_current) - { - bulk_header["_id"] = string(limit.object_id); - } + string data = fc::json::to_string(o, fc::json::legacy_generator); prepare = graphene::utilities::createBulk(bulk_header, std::move(data)); std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk)); prepare.clear(); } -void es_objects_plugin_impl::prepare_bitasset(const asset_bitasset_data_object& bitasset_object) -{ - if(!bitasset_object.is_prediction_market) { - - bitasset_struct bitasset; - bitasset.object_id = bitasset_object.id; - bitasset.block_time = block_time; - bitasset.block_number = block_number; - bitasset.current_feed = fc::json::to_string(bitasset_object.current_feed); - bitasset.current_feed_publication_time = bitasset_object.current_feed_publication_time; - - std::string data = fc::json::to_string(bitasset); - - fc::mutable_variant_object bulk_header; - bulk_header["_index"] = _es_objects_index_prefix + "bitasset"; - bulk_header["_type"] = "data"; - if(_es_objects_keep_only_current) - { - bulk_header["_id"] = string(bitasset.object_id); - } - - prepare = graphene::utilities::createBulk(bulk_header, std::move(data)); - std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk)); - prepare.clear(); - } -} - es_objects_plugin_impl::~es_objects_plugin_impl() { return; diff --git a/libraries/plugins/es_objects/include/graphene/es_objects/es_objects.hpp b/libraries/plugins/es_objects/include/graphene/es_objects/es_objects.hpp index 54ecbdc90d..fa91e3bde4 100644 --- a/libraries/plugins/es_objects/include/graphene/es_objects/es_objects.hpp +++ b/libraries/plugins/es_objects/include/graphene/es_objects/es_objects.hpp @@ -30,7 +30,6 @@ namespace graphene { namespace es_objects { using namespace chain; - namespace detail { class es_objects_plugin_impl; @@ -54,105 +53,61 @@ class es_objects_plugin : public graphene::app::plugin std::unique_ptr my; }; -struct proposal_struct { - object_id_type object_id; - fc::time_point_sec block_time; - uint32_t block_number; - time_point_sec expiration_time; - optional review_period_time; - string proposed_transaction; - string required_active_approvals; - string available_active_approvals; - string required_owner_approvals; - string available_owner_approvals; - string available_key_approvals; - account_id_type proposer; -}; -struct account_struct { - object_id_type object_id; - fc::time_point_sec block_time; - uint32_t block_number; - time_point_sec membership_expiration_date; - account_id_type registrar; - account_id_type referrer; - account_id_type lifetime_referrer; - uint16_t network_fee_percentage; - uint16_t lifetime_referrer_fee_percentage; - uint16_t referrer_rewards_percentage; - string name; - string owner_account_auths; - string owner_key_auths; - string owner_address_auths; - string active_account_auths; - string active_key_auths; - string active_address_auths; - account_id_type voting_account; - string votes; -}; -struct asset_struct { - object_id_type object_id; - fc::time_point_sec block_time; - uint32_t block_number; - string symbol; - account_id_type issuer; - bool is_market_issued; - asset_dynamic_data_id_type dynamic_asset_data_id; - optional bitasset_data_id; -}; -struct balance_struct { - object_id_type object_id; - fc::time_point_sec block_time; - uint32_t block_number; - account_id_type owner; - asset_id_type asset_type; - share_type balance; - bool maintenance_flag; -}; -struct limit_order_struct { - object_id_type object_id; - fc::time_point_sec block_time; - uint32_t block_number; - time_point_sec expiration; - account_id_type seller; - share_type for_sale; - price sell_price; - share_type deferred_fee; -}; -struct bitasset_struct { - object_id_type object_id; - fc::time_point_sec block_time; - uint32_t block_number; - string current_feed; - time_point_sec current_feed_publication_time; - time_point_sec feed_expiration_time; +struct adaptor_struct { + fc::mutable_variant_object adapt(const variant_object &obj) { + fc::mutable_variant_object o(obj); + vector keys_to_rename; + for (auto i = o.begin(); i != o.end(); ++i) { + auto &element = (*i).value(); + if (element.is_object()) { + const string &name = (*i).key(); + auto &vo = element.get_object(); + if (vo.contains(name.c_str())) + keys_to_rename.emplace_back(name); + element = adapt(vo); + } else if (element.is_array()) + adapt(element.get_array()); + } + for (const auto &i : keys_to_rename) { + string new_name = i + "_"; + o[new_name] = variant(o[i]); + o.erase(i); + } + if (o.find("owner") != o.end() && o["owner"].is_string()) + { + o["owner_"] = o["owner"].as_string(); + o.erase("owner"); + } + if (o.find("active_special_authority") != o.end()) + { + o["active_special_authority"] = fc::json::to_string(o["active_special_authority"]); + } + if (o.find("owner_special_authority") != o.end()) + { + o["owner_special_authority"] = fc::json::to_string(o["owner_special_authority"]); + } + if (o.find("feeds") != o.end()) + { + o["feeds"] = fc::json::to_string(o["feeds"]); + } + if (o.find("operations") != o.end()) + { + o["operations"] = fc::json::to_string(o["operations"]); + } + + return o; + } + + void adapt(fc::variants &v) { + for (auto &array_element : v) { + if (array_element.is_object()) + array_element = adapt(array_element.get_object()); + else if (array_element.is_array()) + adapt(array_element.get_array()); + else + array_element = array_element.as_string(); + } + } }; } } //graphene::es_objects - -FC_REFLECT( - graphene::es_objects::proposal_struct, - (object_id)(block_time)(block_number)(expiration_time)(review_period_time)(proposed_transaction)(required_active_approvals) - (available_active_approvals)(required_owner_approvals)(available_owner_approvals)(available_key_approvals)(proposer) -) -FC_REFLECT( - graphene::es_objects::account_struct, - (object_id)(block_time)(block_number)(membership_expiration_date)(registrar)(referrer)(lifetime_referrer) - (network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage)(name)(owner_account_auths) - (owner_key_auths)(owner_address_auths)(active_account_auths)(active_key_auths)(active_address_auths)(voting_account)(votes) -) -FC_REFLECT( - graphene::es_objects::asset_struct, - (object_id)(block_time)(block_number)(symbol)(issuer)(is_market_issued)(dynamic_asset_data_id)(bitasset_data_id) -) -FC_REFLECT( - graphene::es_objects::balance_struct, - (object_id)(block_time)(block_number)(owner)(asset_type)(balance)(maintenance_flag) -) -FC_REFLECT( - graphene::es_objects::limit_order_struct, - (object_id)(block_time)(block_number)(expiration)(seller)(for_sale)(sell_price)(deferred_fee) -) -FC_REFLECT( - graphene::es_objects::bitasset_struct, - (object_id)(block_time)(block_number)(current_feed)(current_feed_publication_time) -) \ No newline at end of file diff --git a/libraries/plugins/witness/include/graphene/witness/witness.hpp b/libraries/plugins/witness/include/graphene/witness/witness.hpp index 9292b55e9f..0d2eab27ac 100644 --- a/libraries/plugins/witness/include/graphene/witness/witness.hpp +++ b/libraries/plugins/witness/include/graphene/witness/witness.hpp @@ -81,7 +81,7 @@ class witness_plugin : public graphene::app::plugin { uint32_t _required_witness_participation = 33 * GRAPHENE_1_PERCENT; uint32_t _production_skip_flags = graphene::chain::database::skip_nothing; - std::map _private_keys; + std::map _private_keys; std::set _witnesses; fc::future _block_production_task; diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index ca13357f0c..e069fbcdc3 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -490,6 +490,13 @@ class wallet_api */ asset_bitasset_data_object get_bitasset_data(string asset_name_or_id)const; + /** + * Returns information about the given HTLC object. + * @param htlc_id the id of the HTLC object. + * @returns the information about the HTLC object + */ + variant get_htlc(string htlc_id) const; + /** Lookup the id of a named account. * @param account_name_or_id the name of the account to look up * @returns the id of the named account @@ -1448,6 +1455,44 @@ class wallet_api bool broadcast = false ); + /** + * Create a hashed time lock contract + * + * @param source The account that will reserve the funds (and pay the fee) + * @param destination The account that will receive the funds if the preimage is presented + * @param amount the amount of the asset that is to be traded + * @param asset_symbol The asset that is to be traded + * @param hash_algorithm the algorithm used to generate the hash from the preimage. Can be RIPEMD160, SHA1 or SHA256. + * @param preimage_hash the hash of the preimage + * @param preimage_size the size of the preimage in bytes + * @param claim_period_seconds how long after creation until the lock expires + * @param broadcast true if you wish to broadcast the transaction + */ + signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, + string hash_algorithm, const std::string& preimage_hash, size_t preimage_size, + const uint32_t claim_period_seconds, bool broadcast = false ); + + /**** + * Update a hashed time lock contract + * + * @param htlc_id The object identifier of the HTLC on the blockchain + * @param issuer Who is performing this operation (and paying the fee) + * @param preimage the preimage that should evaluate to the preimage_hash + */ + signed_transaction htlc_redeem( string htlc_id, string issuer, const std::string& preimage, + bool broadcast = false ); + + /***** + * Increase the timelock on an existing HTLC + * + * @param htlc_id The object identifier of the HTLC on the blockchain + * @param issuer Who is performing this operation (and paying the fee) + * @param seconds_to_add how many seconds to add to the existing timelock + * @param broadcast true to broadcast to the network + */ + signed_transaction htlc_extend(string htlc_id, string issuer, const uint32_t seconds_to_add, + bool broadcast = false); + /** * Get information about a vesting balance object. * @@ -1777,6 +1822,7 @@ FC_API( graphene::wallet::wallet_api, (update_asset) (update_asset_issuer) (update_bitasset) + (get_htlc) (update_asset_feed_producers) (publish_asset_feed) (issue_asset) @@ -1798,6 +1844,9 @@ FC_API( graphene::wallet::wallet_api, (update_witness) (create_worker) (update_worker_votes) + (htlc_create) + (htlc_redeem) + (htlc_extend) (get_vesting_balances) (withdraw_vesting) (vote_for_committee_member) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index ac33c180e9..0eabbe35d1 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -60,10 +60,12 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -114,14 +116,16 @@ struct operation_printer ostream& out; const wallet_api_impl& wallet; operation_result result; + operation_history_object hist; std::string fee(const asset& a) const; public: - operation_printer( ostream& out, const wallet_api_impl& wallet, const operation_result& r = operation_result() ) + operation_printer( ostream& out, const wallet_api_impl& wallet, const operation_history_object& obj ) : out(out), wallet(wallet), - result(r) + result(obj.result), + hist(obj) {} typedef std::string result_type; @@ -134,6 +138,8 @@ struct operation_printer std::string operator()(const account_create_operation& op)const; std::string operator()(const account_update_operation& op)const; std::string operator()(const asset_create_operation& op)const; + std::string operator()(const htlc_create_operation& op)const; + std::string operator()(const htlc_redeem_operation& op)const; }; template @@ -259,6 +265,27 @@ struct op_prototype_visitor } }; +class htlc_hash_to_string_visitor +{ +public: + typedef string result_type; + + result_type operator()( const fc::ripemd160& hash )const + { + return "RIPEMD160 " + hash.str(); + } + + result_type operator()( const fc::sha1& hash )const + { + return "SHA1 " + hash.str(); + } + + result_type operator()( const fc::sha256& hash )const + { + return "SHA256 " + hash.str(); + } +}; + class wallet_api_impl { public: @@ -645,6 +672,15 @@ class wallet_api_impl return *opt; } + htlc_object get_htlc(string htlc_id) const + { + htlc_id_type id; + fc::from_variant(htlc_id, id); + auto obj = _remote_db->get_objects( { id }).front(); + htlc_object htlc = obj.template as(GRAPHENE_MAX_NESTED_OBJECTS); + return htlc; + } + asset_id_type get_asset_id(string asset_symbol_or_id) const { FC_ASSERT( asset_symbol_or_id.size() > 0 ); @@ -1735,6 +1771,95 @@ class wallet_api_impl return sign_transaction( tx, broadcast ); } + static htlc_hash do_hash( const string& algorithm, const std::string& hash ) + { + string name_upper; + std::transform( algorithm.begin(), algorithm.end(), std::back_inserter(name_upper), ::toupper); + if( name_upper == "RIPEMD160" ) + return fc::ripemd160( hash ); + if( name_upper == "SHA256" ) + return fc::sha256( hash ); + if( name_upper == "SHA1" ) + return fc::sha1( hash ); + FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unknown algorithm '${a}'", ("a",algorithm) ); + } + + signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, + string hash_algorithm, const std::string& preimage_hash, size_t preimage_size, + const uint32_t claim_period_seconds, bool broadcast = false ) + { + try + { + FC_ASSERT( !self.is_locked() ); + fc::optional asset_obj = get_asset(asset_symbol); + FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol)); + + htlc_create_operation create_op; + create_op.from = get_account(source).id; + create_op.to = get_account(destination).id; + create_op.amount = asset_obj->amount_from_string(amount); + create_op.claim_period_seconds = claim_period_seconds; + create_op.preimage_hash = do_hash( hash_algorithm, preimage_hash ); + create_op.preimage_size = preimage_size; + + signed_transaction tx; + tx.operations.push_back(create_op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction(tx, broadcast); + } FC_CAPTURE_AND_RETHROW( (source)(destination)(amount)(asset_symbol)(hash_algorithm) + (preimage_hash)(preimage_size)(claim_period_seconds)(broadcast) ) + } + + signed_transaction htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, bool broadcast ) + { + try + { + FC_ASSERT( !self.is_locked() ); + fc::optional htlc_obj = get_htlc(htlc_id); + FC_ASSERT(htlc_obj, "Could not find HTLC matching ${htlc}", ("htlc", htlc_id)); + + account_object issuer_obj = get_account(issuer); + + htlc_redeem_operation update_op; + update_op.htlc_id = htlc_obj->id; + update_op.redeemer = issuer_obj.id; + update_op.preimage = preimage; + + signed_transaction tx; + tx.operations.push_back(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( (htlc_id)(issuer)(preimage)(broadcast) ) + } + + signed_transaction htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, bool broadcast) + { + try + { + FC_ASSERT( !self.is_locked() ); + fc::optional htlc_obj = get_htlc(htlc_id); + FC_ASSERT(htlc_obj, "Could not find HTLC matching ${htlc}", ("htlc", htlc_id)); + + account_object issuer_obj = get_account(issuer); + + htlc_extend_operation update_op; + update_op.htlc_id = htlc_obj->id; + update_op.update_issuer = issuer_obj.id; + update_op.seconds_to_add = seconds_to_add; + + signed_transaction tx; + tx.operations.push_back(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( (htlc_id)(issuer)(seconds_to_add)(broadcast) ) + } + vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) { try { fc::optional vbid = maybe_id( account_name ); @@ -2225,7 +2350,7 @@ class wallet_api_impl auto b = _remote_db->get_block_header(i.block_num); FC_ASSERT(b); ss << b->timestamp.to_iso_string() << " "; - i.op.visit(operation_printer(ss, *this, i.result)); + i.op.visit(operation_printer(ss, *this, i)); ss << " \n"; } @@ -2242,7 +2367,7 @@ class wallet_api_impl auto b = _remote_db->get_block_header(i.block_num); FC_ASSERT(b); ss << b->timestamp.to_iso_string() << " "; - i.op.visit(operation_printer(ss, *this, i.result)); + i.op.visit(operation_printer(ss, *this, i)); ss << " \n"; } @@ -2263,7 +2388,7 @@ class wallet_api_impl auto b = _remote_db->get_block_header(i.block_num); FC_ASSERT(b); ss << b->timestamp.to_iso_string() << " "; - i.op.visit(operation_printer(ss, *this, i.result)); + i.op.visit(operation_printer(ss, *this, i)); ss << " transaction_id : "; ss << d.transaction_id.str(); ss << " \n"; @@ -2305,7 +2430,7 @@ class wallet_api_impl { auto r = result.as( GRAPHENE_MAX_NESTED_OBJECTS ); std::stringstream ss; - r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); + r.trx.operations[0].visit( operation_printer( ss, *this, operation_history_object() ) ); ss << "\n"; for( const auto& out : r.outputs ) { @@ -2318,7 +2443,7 @@ class wallet_api_impl { auto r = result.as( GRAPHENE_MAX_NESTED_OBJECTS ); std::stringstream ss; - r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); + r.trx.operations[0].visit( operation_printer( ss, *this, operation_history_object() ) ); ss << "\n"; for( const auto& out : r.outputs ) { @@ -2882,6 +3007,41 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons return fee(op.fee); } +std::string operation_printer::operator()(const htlc_redeem_operation& op) const +{ + out << "Redeem HTLC with database id " + << std::to_string(op.htlc_id.space_id) + << "." << std::to_string(op.htlc_id.type_id) + << "." << std::to_string((uint64_t)op.htlc_id.instance) + << " with preimage \""; + for (unsigned char c : op.preimage) + out << c; + out << "\""; + return fee(op.fee); +} + +std::string operation_printer::operator()(const htlc_create_operation& op) const +{ + static htlc_hash_to_string_visitor vtor; + + auto fee_asset = wallet.get_asset( op.fee.asset_id ); + auto to = wallet.get_account( op.to ); + operation_result_printer rprinter(wallet); + std::string database_id = result.visit(rprinter); + + out << "Create HTLC to " << to.name + << " with id " << database_id + << " preimage hash: [" + << op.preimage_hash.visit( vtor ) + << "] (Fee: " << fee_asset.amount_to_pretty_string( op.fee ) << ")"; + // determine if the block that the HTLC is in is before or after LIB + int32_t pending_blocks = hist.block_num - wallet.get_dynamic_global_properties().last_irreversible_block_num; + if (pending_blocks > 0) + out << " (pending " << std::to_string(pending_blocks) << " blocks)"; + + return ""; +} + std::string operation_result_printer::operator()(const void_result& x) const { return ""; @@ -3012,6 +3172,44 @@ uint64_t wallet_api::get_asset_count()const return my->_remote_db->get_asset_count(); } +signed_transaction wallet_api::htlc_create( string source, string destination, string amount, string asset_symbol, + string hash_algorithm, const std::string& preimage_hash, size_t preimage_size, + const uint32_t claim_period_seconds, bool broadcast) +{ + return my->htlc_create(source, destination, amount, asset_symbol, hash_algorithm, preimage_hash, preimage_size, + claim_period_seconds, broadcast); +} + +variant wallet_api::get_htlc(std::string htlc_id) const +{ + static detail::htlc_hash_to_string_visitor vtor; + + graphene::chain::htlc_object obj = my->get_htlc(htlc_id); + fc::mutable_variant_object ret_val; + ret_val["database_id"] = (std::string)obj.id; + ret_val["from"] = (std::string)((graphene::db::object_id_type)obj.from); + ret_val["to"] = (std::string)((graphene::db::object_id_type)obj.to); + ret_val["amount"] = obj.amount.amount.value; + ret_val["asset"] = (std::string)((graphene::db::object_id_type)obj.amount.asset_id); + ret_val["expiration"] = fc::get_approximate_relative_time_string(obj.expiration); + ret_val["preimage_hash"] = obj.preimage_hash.visit( vtor ); + ret_val["preimage_size"] = obj.preimage_size; + return ret_val; +} + +signed_transaction wallet_api::htlc_redeem( std::string htlc_id, std::string issuer, const std::string& preimage, + bool broadcast) +{ + + return my->htlc_redeem(htlc_id, issuer, std::vector(preimage.begin(), preimage.end()), broadcast); +} + +signed_transaction wallet_api::htlc_extend ( std::string htlc_id, std::string issuer, const uint32_t seconds_to_add, + bool broadcast) +{ + return my->htlc_extend(htlc_id, issuer, seconds_to_add, broadcast); +} + vector wallet_api::get_account_history(string name, int limit)const { vector result; @@ -3060,7 +3258,7 @@ vector wallet_api::get_account_history(string name, int limit) } } std::stringstream ss; - auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result)); + auto memo = o.op.visit(detail::operation_printer(ss, *my, o)); result.push_back( operation_detail{ memo, ss.str(), o } ); } @@ -3108,7 +3306,7 @@ vector wallet_api::get_relative_account_history( start); for (auto &o : current) { std::stringstream ss; - auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result)); + auto memo = o.op.visit(detail::operation_printer(ss, *my, o)); result.push_back(operation_detail{memo, ss.str(), o}); } if (current.size() < std::min(100, limit)) @@ -3152,7 +3350,7 @@ account_history_operation_detail wallet_api::get_account_history_by_operations( auto current = my->_remote_hist->get_account_history_by_operations(always_id, operation_types, start, min_limit); for (auto& obj : current.operation_history_objs) { std::stringstream ss; - auto memo = obj.op.visit(detail::operation_printer(ss, *my, obj.result)); + auto memo = obj.op.visit(detail::operation_printer(ss, *my, obj)); transaction_id_type transaction_id; auto block = get_block(obj.block_num); diff --git a/programs/build_helpers/member_enumerator.cpp b/programs/build_helpers/member_enumerator.cpp index 16452c5bf8..5dcc6156a3 100644 --- a/programs/build_helpers/member_enumerator.cpp +++ b/programs/build_helpers/member_enumerator.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index e955f8d7fa..24a4065e64 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/app/main.cpp b/tests/app/main.cpp index 848d60d664..b68cdf78a0 100644 --- a/tests/app/main.cpp +++ b/tests/app/main.cpp @@ -267,7 +267,7 @@ BOOST_AUTO_TEST_CASE( two_node_network ) BOOST_CHECK_EQUAL( db2->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 0 ); BOOST_TEST_MESSAGE( "Creating transfer tx" ); - graphene::chain::signed_transaction trx; + graphene::chain::precomputable_transaction trx; { account_id_type nathan_id = db2->get_index_type().indices().get().find( "nathan" )->id; fc::ecc::private_key nathan_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 393dce4d83..7968f75349 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #ifdef _WIN32 #ifndef _WIN32_WINNT @@ -49,6 +50,7 @@ #include #include #endif +#include #include @@ -143,13 +145,15 @@ std::shared_ptr start_application(fc::temp_directory /////////// /// Send a block to the db /// @param app the application +/// @param returned_block the signed block /// @returns true on success /////////// -bool generate_block(std::shared_ptr app) { +bool generate_block(std::shared_ptr app, graphene::chain::signed_block& returned_block) +{ try { fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); auto db = app->chain_database(); - auto block_1 = db->generate_block( db->get_slot_time(1), + returned_block = db->generate_block( db->get_slot_time(1), db->get_scheduled_witness(1), committee_key, database::skip_nothing ); @@ -159,6 +163,12 @@ bool generate_block(std::shared_ptr app) { } } +bool generate_block(std::shared_ptr app) +{ + graphene::chain::signed_block returned_block; + return generate_block(app, returned_block); +} + /////////// /// @brief Skip intermediate blocks, and generate a maintenance block /// @param app the application @@ -566,3 +576,188 @@ BOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture ) throw; } } + +/////////////////////// +// Start a server and connect using the same calls as the CLI +// Create an HTLC +/////////////////////// +BOOST_AUTO_TEST_CASE( cli_create_htlc ) +{ + using namespace graphene::chain; + using namespace graphene::app; + std::shared_ptr app1; + try { + fc::temp_directory app_dir( graphene::utilities::temp_directory_path() ); + + int server_port_number = 0; + app1 = start_application(app_dir, server_port_number); + // set committee parameters + app1->chain_database()->modify(app1->chain_database()->get_global_properties(), [](global_property_object& p) { + graphene::chain::htlc_options params; + params.max_preimage_size = 1024; + params.max_timeout_secs = 60 * 60 * 24 * 28; + p.parameters.extensions.value.updatable_htlc_options = params; + }); + + // connect to the server + client_connection con(app1, app_dir, server_port_number); + + BOOST_TEST_MESSAGE("Setting wallet password"); + con.wallet_api_ptr->set_password("supersecret"); + con.wallet_api_ptr->unlock("supersecret"); + + // import Nathan account + BOOST_TEST_MESSAGE("Importing nathan key"); + std::vector nathan_keys{"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"}; + BOOST_CHECK_EQUAL(nathan_keys[0], "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); + BOOST_CHECK(con.wallet_api_ptr->import_key("nathan", nathan_keys[0])); + + BOOST_TEST_MESSAGE("Importing nathan's balance"); + std::vector import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + account_object nathan_acct_before_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // upgrade nathan + BOOST_TEST_MESSAGE("Upgrading Nathan to LTM"); + signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); + account_object nathan_acct_after_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // verify that the upgrade was successful + BOOST_CHECK_PREDICATE( std::not_equal_to(), (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch()) + (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) ); + BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member()); + + // Create new asset called BOBCOIN + try + { + graphene::chain::asset_options asset_ops; + asset_ops.max_supply = 1000000; + asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); + fc::optional bit_opts; + con.wallet_api_ptr->create_asset("nathan", "BOBCOIN", 5, asset_ops, bit_opts, true); + } + catch(exception& e) + { + BOOST_FAIL(e.what()); + } + catch(...) + { + BOOST_FAIL("Unknown exception creating BOBCOIN"); + } + + // create a new account for Alice + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "alice", + "nathan", "nathan", true); + // save the private key for this new account in the wallet file + BOOST_CHECK(con.wallet_api_ptr->import_key("alice", bki.wif_priv_key)); + con.wallet_api_ptr->save_wallet_file(con.wallet_filename); + // attempt to give alice some bitsahres + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to alice"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "alice", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + } + + // create a new account for Bob + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "bob", + "nathan", "nathan", true); + // save the private key for this new account in the wallet file + BOOST_CHECK(con.wallet_api_ptr->import_key("bob", bki.wif_priv_key)); + con.wallet_api_ptr->save_wallet_file(con.wallet_filename); + // attempt to give bob some bitsahres + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to Bob"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "bob", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + con.wallet_api_ptr->issue_asset("bob", "5", "BOBCOIN", "Here are your BOBCOINs", true); + } + + + BOOST_TEST_MESSAGE("Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC"); + // create an HTLC + std::string preimage_string = "My Secret"; + fc::sha256 preimage_md = fc::sha256::hash(preimage_string); + std::stringstream ss; + for(int i = 0; i < preimage_md.data_size(); i++) + { + char d = preimage_md.data()[i]; + unsigned char uc = static_cast(d); + ss << std::setfill('0') << std::setw(2) << std::hex << (int)uc; + } + std::string hash_str = ss.str(); + BOOST_TEST_MESSAGE("Secret is " + preimage_string + " and hash is " + hash_str); + uint32_t timelock = fc::days(1).to_seconds(); + graphene::chain::signed_transaction result_tx + = con.wallet_api_ptr->htlc_create("alice", "bob", + "3", "1.3.0", "SHA256", hash_str, preimage_string.size(), timelock, true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string alice_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1].operation_results[0].get(); + alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Alice shares the HTLC ID with Bob. The HTLC ID is: " + alice_htlc_id_as_string); + } + + // Bob can now look over Alice's HTLC, to see if it is what was agreed to. + BOOST_TEST_MESSAGE("Bob retrieves the HTLC Object by ID to examine it."); + auto alice_htlc = con.wallet_api_ptr->get_htlc(alice_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(alice_htlc)); + + // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC + con.wallet_api_ptr->htlc_create("bob", "alice", + "3", "BOBCOIN", "SHA256", hash_str, preimage_string.size(), timelock, true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string bob_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1].operation_results[0].get(); + bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Bob shares the HTLC ID with Alice. The HTLC ID is: " + bob_htlc_id_as_string); + } + + // Alice can now look over Bob's HTLC, to see if it is what was agreed to: + BOOST_TEST_MESSAGE("Alice retrieves the HTLC Object by ID to examine it."); + auto bob_htlc = con.wallet_api_ptr->get_htlc(bob_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(bob_htlc)); + + // Alice likes what she sees, so uses her preimage to get her BOBCOIN + { + BOOST_TEST_MESSAGE("Alice uses her preimage to retrieve the BOBCOIN"); + std::string secret = "My Secret"; + con.wallet_api_ptr->htlc_redeem(bob_htlc_id_as_string, "alice", secret, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // TODO: Bob can look at Alice's history to see her preimage + // Bob can use the preimage to retrieve his BTS + { + BOOST_TEST_MESSAGE("Bob uses Alice's preimage to retrieve the BOBCOIN"); + std::string secret = "My Secret"; + con.wallet_api_ptr->htlc_redeem(alice_htlc_id_as_string, "bob", secret, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // wait for everything to finish up + fc::usleep(fc::seconds(1)); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } + app1->shutdown(); +} diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 806bc0144f..d1af826d39 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -31,12 +31,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include @@ -56,6 +58,13 @@ namespace graphene { namespace chain { using std::cout; using std::cerr; +void clearable_block::clear() +{ + _calculated_merkle_root = checksum_type(); + _signee = fc::ecc::public_key(); + _block_id = block_id_type(); +} + database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) : app(), db( *app.chain_database() ) { @@ -228,7 +237,7 @@ void database_fixture::verify_asset_supplies( const database& db ) BOOST_CHECK(core_asset_data.fee_pool == 0); const auto& statistics_index = db.get_index_type().indices(); - const auto& balance_index = db.get_index_type().indices(); + const auto& acct_balance_index = db.get_index_type().indices(); const auto& settle_index = db.get_index_type().indices(); const auto& bids = db.get_index_type().indices(); map total_balances; @@ -236,7 +245,7 @@ void database_fixture::verify_asset_supplies( const database& db ) share_type core_in_orders; share_type reported_core_in_orders; - for( const account_balance_object& b : balance_index ) + for( const account_balance_object& b : acct_balance_index ) total_balances[b.asset_type] += b.balance; for( const force_settlement_object& s : settle_index ) total_balances[s.balance.asset_id] += s.balance.amount; @@ -278,6 +287,8 @@ void database_fixture::verify_asset_supplies( const database& db ) total_balances[ vbo.balance.asset_id ] += vbo.balance.amount; for( const fba_accumulator_object& fba : db.get_index_type< simple_index< fba_accumulator_object > >() ) total_balances[ asset_id_type() ] += fba.accumulated_fba_fees; + for( const balance_object& bo : db.get_index_type< balance_index >().indices() ) + total_balances[ bo.balance.asset_id ] += bo.balance.amount; total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; @@ -286,6 +297,13 @@ void database_fixture::verify_asset_supplies( const database& db ) BOOST_CHECK_EQUAL(item.first(db).dynamic_asset_data_id(db).current_supply.value, item.second.value); } + // htlc + const auto& htlc_idx = db.get_index_type< htlc_index >().indices().get< by_id >(); + for( auto itr = htlc_idx.begin(); itr != htlc_idx.end(); ++itr ) + { + total_balances[itr->amount.asset_id] += itr->amount.amount; + } + for( const asset_object& asset_obj : db.get_index_type().indices() ) { BOOST_CHECK_EQUAL(total_balances[asset_obj.id].value, asset_obj.dynamic_asset_data_id(db).current_supply.value); @@ -454,7 +472,7 @@ const asset_object& database_fixture::create_bitasset( creator.bitasset_opts->short_backing_asset = backing_asset; trx.operations.push_back(std::move(creator)); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); trx.operations.clear(); return db.get(ptx.operation_results[0].get()); } FC_CAPTURE_AND_RETHROW( (name)(flags) ) } @@ -485,7 +503,7 @@ const asset_object& database_fixture::create_prediction_market( creator.is_prediction_market = true; trx.operations.push_back(std::move(creator)); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); trx.operations.clear(); return db.get(ptx.operation_results[0].get()); } FC_CAPTURE_AND_RETHROW( (name)(flags) ) } @@ -505,7 +523,7 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na creator.common_options.issuer_permissions = charge_market_fee; trx.operations.push_back(std::move(creator)); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); trx.operations.clear(); return db.get(ptx.operation_results[0].get()); } @@ -531,7 +549,7 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na trx.operations.push_back(std::move(creator)); set_expiration( db, trx ); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); trx.operations.clear(); return db.get(ptx.operation_results[0].get()); } @@ -544,7 +562,7 @@ void database_fixture::issue_uia( const account_object& recipient, asset amount op.asset_to_issue = amount; op.issue_to_account = recipient.id; trx.operations.push_back(op); - db.push_transaction( trx, ~0 ); + PUSH_TX( db, trx, ~0 ); trx.operations.clear(); } @@ -590,7 +608,7 @@ const account_object& database_fixture::create_account( { trx.operations.push_back(make_account(name, key)); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); auto& result = db.get(ptx.operation_results[0].get()); trx.operations.clear(); return result; @@ -609,7 +627,7 @@ const account_object& database_fixture::create_account( trx.operations.resize(1); trx.operations.back() = (make_account(name, registrar, referrer, referrer_percent, key)); trx.validate(); - auto r = db.push_transaction(trx, ~0); + auto r = PUSH_TX(db, trx, ~0); const auto& result = db.get(r.operation_results[0].get()); trx.operations.clear(); return result; @@ -643,7 +661,7 @@ const account_object& database_fixture::create_account( trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); const account_object& result = db.get(ptx.operation_results[0].get()); trx.operations.clear(); return result; @@ -657,7 +675,7 @@ const committee_member_object& database_fixture::create_committee_member( const op.committee_member_account = owner.id; trx.operations.push_back(op); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); trx.operations.clear(); return db.get(ptx.operation_results[0].get()); } @@ -678,7 +696,7 @@ const witness_object& database_fixture::create_witness( const account_object& ow op.block_signing_key = signing_private_key.get_public_key(); trx.operations.push_back(op); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, skip_flags ); + processed_transaction ptx = PUSH_TX(db, trx, skip_flags ); trx.clear(); return db.get(ptx.operation_results[0].get()); } FC_CAPTURE_AND_RETHROW() } @@ -693,7 +711,7 @@ const worker_object& database_fixture::create_worker( const account_id_type owne op.work_end_date = op.work_begin_date + duration; trx.operations.push_back(op); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); trx.clear(); return db.get(ptx.operation_results[0].get()); } FC_CAPTURE_AND_RETHROW() } @@ -741,7 +759,7 @@ const limit_order_object* database_fixture::create_sell_order( const account_obj trx.operations.push_back(buy_order); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate); trx.validate(); - auto processed = db.push_transaction(trx, ~0); + auto processed = PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); return db.find( processed.operation_results[0].get() ); @@ -755,7 +773,7 @@ asset database_fixture::cancel_limit_order( const limit_order_object& order ) trx.operations.push_back(cancel_order); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - auto processed = db.push_transaction(trx, ~0); + auto processed = PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); return processed.operation_results[0].get(); @@ -791,7 +809,7 @@ void database_fixture::transfer( for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); } trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); verify_asset_supplies(db); trx.operations.clear(); } FC_CAPTURE_AND_RETHROW( (from.id)(to.id)(amount)(fee) ) @@ -809,7 +827,7 @@ void database_fixture::update_feed_producers( const asset_object& mia, flat_set< for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); } FC_CAPTURE_AND_RETHROW( (mia)(producers) ) } @@ -829,7 +847,7 @@ void database_fixture::publish_feed( const asset_object& mia, const account_obje for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); } @@ -877,7 +895,7 @@ void database_fixture::force_global_settle( const asset_object& what, const pric trx.operations.push_back(sop); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); } FC_CAPTURE_AND_RETHROW( (what)(p) ) } @@ -892,7 +910,7 @@ operation_result database_fixture::force_settle( const account_object& who, asse trx.operations.push_back(sop); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - processed_transaction ptx = db.push_transaction(trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); const operation_result& op_result = ptx.operation_results.front(); trx.operations.clear(); verify_asset_supplies(db); @@ -912,7 +930,7 @@ const call_order_object* database_fixture::borrow( const account_object& who, as trx.operations.push_back(update); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); @@ -937,7 +955,7 @@ void database_fixture::cover(const account_object& who, asset what, asset collat trx.operations.push_back(update); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); } FC_CAPTURE_AND_RETHROW( (who.name)(what)(collateral)(target_cr) ) } @@ -953,7 +971,7 @@ void database_fixture::bid_collateral(const account_object& who, const asset& to trx.operations.push_back(bid); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); } FC_CAPTURE_AND_RETHROW( (who.name)(to_bid)(to_cover) ) } @@ -969,7 +987,7 @@ void database_fixture::fund_fee_pool( const account_object& from, const asset_ob for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); set_expiration( db, trx ); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); verify_asset_supplies(db); } @@ -996,7 +1014,7 @@ void database_fixture::upgrade_to_lifetime_member( const account_object& account op.upgrade_to_lifetime_member = true; op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); trx.operations = {op}; - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); FC_ASSERT( op.account_to_upgrade(db).is_lifetime_member() ); trx.clear(); verify_asset_supplies(db); @@ -1016,7 +1034,7 @@ void database_fixture::upgrade_to_annual_member(const account_object& account) op.account_to_upgrade = account.get_id(); op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); trx.operations = {op}; - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); FC_ASSERT( op.account_to_upgrade(db).is_member(db.head_block_time()) ); trx.clear(); verify_asset_supplies(db); @@ -1193,7 +1211,7 @@ bool _push_block( database& db, const signed_block& b, uint32_t skip_flags /* = processed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags /* = 0 */ ) { try { - auto pt = db.push_transaction( tx, skip_flags ); + auto pt = db.push_transaction( precomputable_transaction(tx), skip_flags ); database_fixture::verify_asset_supplies(db); return pt; } FC_CAPTURE_AND_RETHROW((tx)) } diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 13e4ead075..fee840cad6 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -170,6 +170,12 @@ extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP; namespace graphene { namespace chain { +class clearable_block : public signed_block { +public: + /** @brief Clears internal cached values like ID, signing key, Merkle root etc. */ + void clear(); +}; + struct database_fixture { // the reason we use an app is to exercise the indexes of built-in // plugins @@ -333,6 +339,10 @@ struct database_fixture { void transfer( account_id_type from, account_id_type to, const asset& amount, const asset& fee = asset() ); void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() ); void fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount ); + /** + * NOTE: This modifies the database directly. You will probably have to call this each time you + * finish creating a block + */ void enable_fees(); void change_fees( const flat_set< fee_parameters >& new_params, uint32_t new_scale = 0 ); void upgrade_to_lifetime_member( account_id_type account ); diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index 813cff42d4..cf9df59142 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -498,7 +498,7 @@ BOOST_AUTO_TEST_CASE( committee_authority ) */ trx.operations.push_back(uop); sign( trx, committee_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_CHECK_EQUAL(get_balance(nathan, asset_id_type()(db)), 0); BOOST_CHECK(db.get(prop.id).is_authorized_to_execute(db)); @@ -1057,7 +1057,6 @@ BOOST_FIXTURE_TEST_CASE( bogus_signature, database_fixture ) PUSH_TX( db, trx, skip ); trx.operations.push_back( xfer_op ); - trx.signees.clear(); // signees should be invalidated BOOST_TEST_MESSAGE( "Invalidating Alices Signature" ); // Alice's signature is now invalid GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, skip ), fc::exception ); @@ -1169,7 +1168,7 @@ BOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture ) op.owner = auth; tx.operations.push_back( op ); set_expiration( db, tx ); - PUSH_TX( db, tx, database::skip_transaction_signatures | database::skip_authority_check ); + PUSH_TX( db, tx, database::skip_transaction_signatures ); } ; auto get_active = [&]( @@ -1283,7 +1282,7 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) op.owner = auth; tx.operations.push_back( op ); set_expiration( db, tx ); - PUSH_TX( db, tx, database::skip_transaction_signatures | database::skip_authority_check ); + PUSH_TX( db, tx, database::skip_transaction_signatures ); } ; auto get_active = [&]( @@ -1370,7 +1369,7 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) op.owner = owner; tx.operations.push_back( op ); set_expiration( db, tx ); - PUSH_TX( db, tx, database::skip_transaction_signatures | database::skip_authority_check ); + PUSH_TX( db, tx, database::skip_transaction_signatures ); } ; auto set_auth = [&]( @@ -1462,7 +1461,7 @@ BOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture ) op.owner = owner; tx.operations.push_back( op ); set_expiration( db, tx ); - PUSH_TX( db, tx, database::skip_transaction_signatures | database::skip_authority_check ); + PUSH_TX( db, tx, database::skip_transaction_signatures ); } ; auto get_active = [&]( @@ -1677,4 +1676,107 @@ BOOST_AUTO_TEST_CASE( issue_214 ) BOOST_CHECK_EQUAL( top.amount.amount.value, get_balance( bob_id, top.amount.asset_id ) ); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( irrelevant_signatures ) +{ try { + ACTORS( (alice)(bob) ); + fund( alice ); + + // PK: BTS4vsFgTXJcGQMKCFayF2hrNRfYcKjNZ6Mzk8aw9M4zuWfscPhzE, A: BTSGfxPKKLj6tdTUB7i3mHsd2m7QvPLPy2YA + const fc::ecc::private_key test2 = fc::ecc::private_key::regenerate( fc::sha256::hash( std::string( "test-2" ) ) ); + const public_key_type test2_pub( test2.get_public_key() ); + + // PK: BTS7FXC7S9UH7HEH8QiuJ8Xv1NRJJZd1GomALLm9ffjtH95Tb2ZQB, A: BTSBajRqmdrXqmDpZhJ8sgkGagdeXneHFVeM + const fc::ecc::private_key test3 = fc::ecc::private_key::regenerate( fc::sha256::hash( std::string( "test-3" ) ) ); + const public_key_type test3_pub( test3.get_public_key() ); + + BOOST_REQUIRE( test2_pub.key_data < test3_pub.key_data ); + BOOST_REQUIRE( address( test3_pub ) < address( test2_pub ) ); + + account_update_operation auo; + auo.account = alice_id; + auo.active = authority( 2, test2_pub, 2, test3_pub, 1 ); + + trx.clear(); + set_expiration( db, trx ); + trx.operations.push_back( auo ); + sign( trx, alice_private_key ); + PUSH_TX( db, trx ); + trx.clear(); + + transfer_operation to; + to.amount = asset( 1 ); + to.from = alice_id; + to.to = bob_id; + trx.operations.push_back( to ); + sign( trx, test2 ); + sign( trx, test3 ); + PUSH_TX( db, trx ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( self_approving_proposal ) +{ try { + ACTORS( (alice) ); + fund( alice ); + + generate_blocks( HARDFORK_CORE_1479_TIME ); + trx.clear(); + set_expiration( db, trx ); + + proposal_update_operation pup; + pup.fee_paying_account = alice_id; + pup.proposal = proposal_id_type(0); + pup.active_approvals_to_add.insert( alice_id ); + + proposal_create_operation pop; + pop.proposed_ops.emplace_back(pup); + pop.fee_paying_account = alice_id; + pop.expiration_time = db.head_block_time() + fc::days(1); + trx.operations.push_back(pop); + const proposal_id_type pid1 = PUSH_TX( db, trx, ~0 ).operation_results[0].get(); + trx.clear(); + BOOST_REQUIRE_EQUAL( 0, pid1.instance.value ); + db.get(pid1); + + trx.operations.push_back(pup); + PUSH_TX( db, trx, ~0 ); + + // Proposal failed and still exists + db.get(pid1); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( self_deleting_proposal ) +{ try { + ACTORS( (alice) ); + fund( alice ); + + generate_blocks( HARDFORK_CORE_1479_TIME ); + trx.clear(); + set_expiration( db, trx ); + + proposal_delete_operation pdo; + pdo.fee_paying_account = alice_id; + pdo.proposal = proposal_id_type(0); + pdo.using_owner_authority = false; + + proposal_create_operation pop; + pop.proposed_ops.emplace_back( pdo ); + pop.fee_paying_account = alice_id; + pop.expiration_time = db.head_block_time() + fc::days(1); + trx.operations.push_back( pop ); + const proposal_id_type pid1 = PUSH_TX( db, trx, ~0 ).operation_results[0].get(); + trx.clear(); + BOOST_REQUIRE_EQUAL( 0, pid1.instance.value ); + db.get(pid1); + + proposal_update_operation pup; + pup.fee_paying_account = alice_id; + pup.proposal = proposal_id_type(0); + pup.active_approvals_to_add.insert( alice_id ); + trx.operations.push_back(pup); + PUSH_TX( db, trx, ~0 ); + + // Proposal failed and still exists + db.get(pid1); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 2157dde75a..97d03ed41b 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -408,7 +408,7 @@ BOOST_AUTO_TEST_CASE( scaled_precision ) BOOST_AUTO_TEST_CASE( merkle_root ) { - signed_block block; + clearable_block block; vector tx; vector t; const uint32_t num_tx = 10; @@ -444,6 +444,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dA = d(t[0], t[1]); block.transactions.push_back( tx[1] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dA) ); /* @@ -458,6 +459,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dI = d(dA, dB); block.transactions.push_back( tx[2] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dI) ); /* @@ -472,6 +474,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dI = d(dA, dB); block.transactions.push_back( tx[3] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dI) ); /* @@ -489,6 +492,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dM = d(dI, dJ); block.transactions.push_back( tx[4] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dM) ); /* @@ -506,6 +510,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dM = d(dI, dJ); block.transactions.push_back( tx[5] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dM) ); /* @@ -523,6 +528,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dM = d(dI, dJ); block.transactions.push_back( tx[6] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dM) ); /* @@ -540,6 +546,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dM = d(dI, dJ); block.transactions.push_back( tx[7] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dM) ); /* @@ -560,6 +567,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dO = d(dM, dN); block.transactions.push_back( tx[8] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dO) ); /* @@ -580,6 +588,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) dO = d(dM, dN); block.transactions.push_back( tx[9] ); + block.clear(); BOOST_CHECK( block.calculate_merkle_root() == c(dO) ); } diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 759ea8744e..4fc6097650 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -447,7 +448,6 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; generate_blocks(HARDFORK_615_TIME, true, skip); // get around Graphene issue #615 feed expiration bug @@ -917,7 +917,6 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; generate_blocks( HARDFORK_615_TIME, true, skip ); // get around Graphene issue #615 feed expiration bug diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index e71642726b..76d9c0b75c 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -80,11 +80,12 @@ BOOST_AUTO_TEST_CASE( block_database_test ) FC_ASSERT( !bdb.is_open() ); bdb.open( data_dir.path() ); - signed_block b; + clearable_block b; for( uint32_t i = 0; i < 5; ++i ) { if( i > 0 ) b.previous = b.id(); b.witness = witness_id_type(i+1); + b.clear(); bdb.store( b.id(), b ); auto fetch = bdb.fetch_by_number( b.block_num() ); @@ -601,11 +602,11 @@ BOOST_AUTO_TEST_CASE( undo_pending ) t.to = nathan_id; t.amount = asset(5000); trx.operations.push_back(t); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.clear(); set_expiration( db, trx ); trx.operations.push_back(t); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); BOOST_CHECK(db.get_balance(nathan_id, asset_id_type()).amount == 10000); db.clear_pending(); @@ -687,7 +688,7 @@ BOOST_AUTO_TEST_CASE( duplicate_transactions ) db2.open(dir2.path(), make_genesis, "TEST"); BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() ); - auto skip_sigs = database::skip_transaction_signatures | database::skip_authority_check; + auto skip_sigs = database::skip_transaction_signatures; auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")) ); public_key_type init_account_pub_key = init_account_priv_key.get_public_key(); @@ -756,7 +757,7 @@ BOOST_AUTO_TEST_CASE( tapos ) cop.active = cop.owner; trx.operations.push_back(cop); trx.sign( init_account_priv_key, db1.get_chain_id() ); - db1.push_transaction(trx); + PUSH_TX(db1, trx); b = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing); trx.clear(); @@ -766,11 +767,11 @@ BOOST_AUTO_TEST_CASE( tapos ) trx.operations.push_back(t); trx.sign( init_account_priv_key, db1.get_chain_id() ); //relative_expiration is 1, but ref block is 2 blocks old, so this should fail. - GRAPHENE_REQUIRE_THROW(PUSH_TX( db1, trx, database::skip_transaction_signatures | database::skip_authority_check ), fc::exception); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db1, trx, database::skip_transaction_signatures ), fc::exception); set_expiration( db1, trx ); trx.clear_signatures(); trx.sign( init_account_priv_key, db1.get_chain_id() ); - db1.push_transaction(trx, database::skip_transaction_signatures | database::skip_authority_check); + PUSH_TX( db1, trx, database::skip_transaction_signatures ); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; @@ -951,7 +952,7 @@ BOOST_FIXTURE_TEST_CASE( double_sign_check, database_fixture ) for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); - db.push_transaction(trx, ~0); + PUSH_TX(db, trx, ~0); trx.operations.clear(); t.from = bob.id; @@ -962,22 +963,21 @@ BOOST_FIXTURE_TEST_CASE( double_sign_check, database_fixture ) trx.validate(); BOOST_TEST_MESSAGE( "Verify that not-signing causes an exception" ); - GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, 0), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, 0), fc::exception ); BOOST_TEST_MESSAGE( "Verify that double-signing causes an exception" ); sign( trx, bob_private_key ); sign( trx, bob_private_key ); - GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, 0), tx_duplicate_sig ); + GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, 0), tx_duplicate_sig ); BOOST_TEST_MESSAGE( "Verify that signing with an extra, unused key fails" ); trx.signatures.pop_back(); sign( trx, generate_private_key("bogus" )); - GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, 0), tx_irrelevant_sig ); + GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, 0), tx_irrelevant_sig ); BOOST_TEST_MESSAGE( "Verify that signing once with the proper key passes" ); trx.signatures.pop_back(); - trx.signees.clear(); // signees should be invalidated - db.push_transaction(trx, 0); + PUSH_TX(db, trx, 0); } FC_LOG_AND_RETHROW() } @@ -998,7 +998,7 @@ BOOST_FIXTURE_TEST_CASE( change_block_interval, database_fixture ) uop.new_parameters.block_interval = 1; cop.proposed_ops.emplace_back(uop); trx.operations.push_back(cop); - db.push_transaction(trx); + PUSH_TX(db, trx); } BOOST_TEST_MESSAGE( "Updating proposal by signing with the committee_member private key" ); { @@ -1019,7 +1019,7 @@ BOOST_FIXTURE_TEST_CASE( change_block_interval, database_fixture ) sign( trx, get_account("init6" ).active.get_keys().front(),init_account_priv_key); sign( trx, get_account("init7" ).active.get_keys().front(),init_account_priv_key); */ - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_CHECK(proposal_id_type()(db).is_authorized_to_execute(db)); } BOOST_TEST_MESSAGE( "Verifying that the interval didn't change immediately" ); @@ -1056,7 +1056,6 @@ BOOST_FIXTURE_TEST_CASE( pop_block_twice, database_fixture ) uint32_t skip_flags = ( database::skip_witness_signature | database::skip_transaction_signatures - | database::skip_authority_check ); const asset_object& core = asset_id_type()(db); @@ -1231,8 +1230,8 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture ) }; // tx's created by ACTORS() have bogus authority, so we need to - // skip_authority_check in the block where they're included - signed_block b1 = generate_block(db, database::skip_authority_check); + // skip_transaction_signatures in the block where they're included + signed_block b1 = generate_block(db, database::skip_transaction_signatures); fc::temp_directory data_dir2( graphene::utilities::temp_directory_path() ); @@ -1244,7 +1243,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture ) { optional< signed_block > b = db.fetch_block_by_number( db2.head_block_num()+1 ); db2.push_block(*b, database::skip_witness_signature - |database::skip_authority_check ); + |database::skip_transaction_signatures ); } BOOST_CHECK( db2.get( alice_id ).name == "alice" ); BOOST_CHECK( db2.get( bob_id ).name == "bob" ); @@ -1253,7 +1252,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture ) transfer( account_id_type(), alice_id, asset( 1000 ) ); transfer( account_id_type(), bob_id, asset( 1000 ) ); // need to skip authority check here as well for same reason as above - db2.push_block(generate_block(db, database::skip_authority_check), database::skip_authority_check); + db2.push_block(generate_block(db, database::skip_transaction_signatures), database::skip_transaction_signatures); BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 1000); BOOST_CHECK_EQUAL(db.get_balance( bob_id, asset_id_type()).amount.value, 1000); @@ -1478,7 +1477,6 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture ) database::skip_transaction_dupe_check | database::skip_witness_signature | database::skip_transaction_signatures - | database::skip_authority_check ; // Sam is the creator of accounts @@ -1604,10 +1602,9 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture ) trx.operations.push_back( create_op ); // trx.sign( sam_key ); - processed_transaction ptx_create = db.push_transaction( trx, + processed_transaction ptx_create = PUSH_TX( db, trx, database::skip_transaction_dupe_check | - database::skip_transaction_signatures | - database::skip_authority_check + database::skip_transaction_signatures ); account_id_type alice_account_id = ptx_create.operation_results[0] @@ -1642,11 +1639,11 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture ) sign( trx, *owner_privkey[i] ); if( i < int(create_op.owner.weight_threshold-1) ) { - GRAPHENE_REQUIRE_THROW(db.push_transaction(trx), fc::exception); + GRAPHENE_REQUIRE_THROW(PUSH_TX(db, trx), fc::exception); } else { - db.push_transaction( trx, + PUSH_TX( db, trx, database::skip_transaction_dupe_check | database::skip_transaction_signatures ); } @@ -1830,11 +1827,6 @@ BOOST_FIXTURE_TEST_CASE( temp_account_balance, database_fixture ) top.to = GRAPHENE_COMMITTEE_ACCOUNT; trx.operations.push_back( top ); - sign( trx, alice_private_key ); - BOOST_CHECK_THROW( PUSH_TX( db, trx ), fc::assert_exception ); - - generate_blocks( HARDFORK_CORE_1040_TIME ); - set_expiration( db, trx ); trx.clear_signatures(); sign( trx, alice_private_key ); diff --git a/tests/tests/confidential_tests.cpp b/tests/tests/confidential_tests.cpp index bef651db5c..b5046dcdfe 100644 --- a/tests/tests/confidential_tests.cpp +++ b/tests/tests/confidential_tests.cpp @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE( confidential_test ) trx.operations = {to_blind}; sign( trx, dan_private_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); trx.clear_signatures(); BOOST_TEST_MESSAGE( "Transfering from blind to blind with change address" ); @@ -97,7 +97,7 @@ BOOST_AUTO_TEST_CASE( confidential_test ) blind_tr.validate(); trx.operations = {blind_tr}; sign( trx, owner2_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_TEST_MESSAGE( "Attempting to double spend the same commitments" ); blind_tr.fee = core.amount(11); @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE( confidential_test ) out4.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 750-300-11 ); blind_tr.outputs = {out4,out3}; trx.operations = {blind_tr}; - BOOST_REQUIRE_THROW( db.push_transaction(trx, ~0), graphene::chain::blind_transfer_unknown_commitment ); + BOOST_REQUIRE_THROW( PUSH_TX(db, trx, ~0), graphene::chain::blind_transfer_unknown_commitment ); BOOST_TEST_MESSAGE( "Transfering from blind to nathan public" ); @@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE( confidential_test ) from_blind.inputs.push_back( {out4.commitment, out4.owner} ); trx.operations = {from_blind}; trx.clear_signatures(); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_REQUIRE_EQUAL( get_balance( nathan, core ), 750-300-10-10 ); diff --git a/tests/tests/database_tests.cpp b/tests/tests/database_tests.cpp index 6b5ea8b96b..6158c2eb05 100644 --- a/tests/tests/database_tests.cpp +++ b/tests/tests/database_tests.cpp @@ -137,4 +137,89 @@ BOOST_AUTO_TEST_CASE( merge_test ) } } +BOOST_AUTO_TEST_CASE( direct_index_test ) +{ try { + try { + const graphene::db::primary_index< account_index, 6 > small_chunkbits( db ); + BOOST_FAIL( "Expected assertion failure!" ); + } catch( const fc::assert_exception& expected ) {} + + graphene::db::primary_index< account_index, 8 > my_accounts( db ); + const auto& direct = my_accounts.get_secondary_index>(); + BOOST_CHECK_EQUAL( 0, my_accounts.indices().size() ); + BOOST_CHECK( nullptr == direct.find( account_id_type( 1 ) ) ); + // BOOST_CHECK_THROW( direct.find( asset_id_type( 1 ) ), fc::assert_exception ); // compile-time error + BOOST_CHECK_THROW( direct.find( object_id_type( asset_id_type( 1 ) ) ), fc::assert_exception ); + BOOST_CHECK_THROW( direct.get( account_id_type( 1 ) ), fc::assert_exception ); + + account_object test_account; + test_account.id = account_id_type(1); + test_account.name = "account1"; + + my_accounts.load( fc::raw::pack( test_account ) ); + + BOOST_CHECK_EQUAL( 1, my_accounts.indices().size() ); + BOOST_CHECK( nullptr == direct.find( account_id_type( 0 ) ) ); + BOOST_CHECK( nullptr == direct.find( account_id_type( 2 ) ) ); + BOOST_CHECK( nullptr != direct.find( account_id_type( 1 ) ) ); + BOOST_CHECK_EQUAL( test_account.name, direct.get( test_account.id ).name ); + + // The following assumes that MAX_HOLE = 100 + test_account.id = account_id_type(102); + test_account.name = "account102"; + // highest insert was 1, direct.next is 2 => 102 is highest allowed instance + my_accounts.load( fc::raw::pack( test_account ) ); + BOOST_CHECK_EQUAL( test_account.name, direct.get( test_account.id ).name ); + + // direct.next is now 103, but index sequence counter is 0 + my_accounts.create( [] ( object& o ) { + account_object& acct = dynamic_cast< account_object& >( o ); + BOOST_CHECK_EQUAL( 0, acct.id.instance() ); + acct.name = "account0"; + } ); + + test_account.id = account_id_type(50); + test_account.name = "account50"; + my_accounts.load( fc::raw::pack( test_account ) ); + + // can handle nested modification + my_accounts.modify( direct.get( account_id_type(0) ), [&direct,&my_accounts] ( object& outer ) { + account_object& _outer = dynamic_cast< account_object& >( outer ); + my_accounts.modify( direct.get( account_id_type(50) ), [] ( object& inner ) { + account_object& _inner = dynamic_cast< account_object& >( inner ); + _inner.referrer = account_id_type(102); + }); + _outer.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT; + }); + + // direct.next is still 103, so 204 is not allowed + test_account.id = account_id_type(204); + test_account.name = "account204"; + GRAPHENE_REQUIRE_THROW( my_accounts.load( fc::raw::pack( test_account ) ), fc::assert_exception ); + // This is actually undefined behaviour. The object has been inserted into + // the primary index, but the secondary has refused to insert it! + BOOST_CHECK_EQUAL( 5, my_accounts.indices().size() ); + + uint32_t count = 0; + for( uint32_t i = 0; i < 250; i++ ) + { + const account_object* aptr = dynamic_cast< const account_object* >( my_accounts.find( account_id_type( i ) ) ); + if( aptr ) + { + count++; + BOOST_CHECK( aptr->id.instance() == 0 || aptr->id.instance() == 1 + || aptr->id.instance() == 50 || aptr->id.instance() == 102 ); + BOOST_CHECK_EQUAL( i, aptr->id.instance() ); + BOOST_CHECK_EQUAL( "account" + std::to_string( i ), aptr->name ); + } + } + BOOST_CHECK_EQUAL( count, my_accounts.indices().size() - 1 ); + + GRAPHENE_REQUIRE_THROW( my_accounts.modify( direct.get( account_id_type( 1 ) ), [] ( object& acct ) { + acct.id = account_id_type(2); + }), fc::assert_exception ); + // This is actually undefined behaviour. The object has been modified, but + // but the secondary has not updated its representation +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index 503c9ebc6e..587814815c 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -759,7 +759,6 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; @@ -897,7 +896,6 @@ BOOST_AUTO_TEST_CASE( non_core_fee_refund_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; @@ -1284,7 +1282,6 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; @@ -1791,7 +1788,6 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; @@ -2349,7 +2345,6 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; diff --git a/tests/tests/htlc_tests.cpp b/tests/tests/htlc_tests.cpp new file mode 100644 index 0000000000..98518f6484 --- /dev/null +++ b/tests/tests/htlc_tests.cpp @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2018 jmjatlanta 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. + */ + +// below are for random bytes for htlc +#include +#include +#include +#include +#include +// for htlc timeout +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( htlc_tests, database_fixture ) + +void generate_random_preimage(uint16_t key_size, std::vector& vec) +{ + std::independent_bits_engine rbe; + std::generate(begin(vec), end(vec), std::ref(rbe)); + return; +} + +/**** + * Hash the preimage and put it in a vector + * @param preimage the preimage + * @returns a vector that cointains the sha256 hash of the preimage + */ +template +H hash_it(std::vector preimage) +{ + return H::hash( (char*)preimage.data(), preimage.size() ); +} + +flat_map< uint64_t, graphene::chain::fee_parameters > get_htlc_fee_parameters() +{ + flat_map ret_val; + + htlc_create_operation::fee_parameters_type create_param; + create_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + create_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + ret_val[((operation)htlc_create_operation()).which()] = create_param; + + htlc_redeem_operation::fee_parameters_type redeem_param; + redeem_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + redeem_param.fee_per_kb = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + ret_val[((operation)htlc_redeem_operation()).which()] = redeem_param; + + htlc_extend_operation::fee_parameters_type extend_param; + extend_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + extend_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + ret_val[((operation)htlc_extend_operation()).which()] = extend_param; + + return ret_val; +} + +/**** + * @brief push through a proposal that sets htlc parameters and fees + * @param db_fixture the database connection + */ +void set_committee_parameters(database_fixture* db_fixture) +{ + // htlc fees + // get existing fee_schedule + const chain_parameters& existing_params = db_fixture->db.get_global_properties().parameters; + const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees); + // create a new fee_shedule + fee_schedule_type new_fee_schedule; + new_fee_schedule.scale = GRAPHENE_100_PERCENT; + // replace the old with the new + flat_map params_map = get_htlc_fee_parameters(); + for(auto param : existing_fee_schedule.parameters) + { + auto itr = params_map.find(param.which()); + if (itr == params_map.end()) + new_fee_schedule.parameters.insert(param); + else + { + new_fee_schedule.parameters.insert( (*itr).second); + } + } + // htlc parameters + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db_fixture->db.get_global_properties().parameters, db_fixture->db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db_fixture->db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation uop; + graphene::chain::htlc_options new_params; + new_params.max_preimage_size = 19200; + new_params.max_timeout_secs = 60 * 60 * 24 * 28; + uop.new_parameters.extensions.value.updatable_htlc_options = new_params; + uop.new_parameters.current_fees = new_fee_schedule; + cop.proposed_ops.emplace_back(uop); + + db_fixture->trx.operations.push_back(cop); + graphene::chain::processed_transaction proc_trx =db_fixture->db.push_transaction(db_fixture->trx); + proposal_id_type good_proposal_id = proc_trx.operation_results[0].get(); + + proposal_update_operation puo; + puo.proposal = good_proposal_id; + puo.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + puo.active_approvals_to_add = { + db_fixture->get_account("init0").get_id(), db_fixture->get_account("init1").get_id(), + db_fixture->get_account("init2").get_id(), db_fixture->get_account("init3").get_id(), + db_fixture->get_account("init4").get_id(), db_fixture->get_account("init5").get_id(), + db_fixture->get_account("init6").get_id(), db_fixture->get_account("init7").get_id()}; + db_fixture->trx.operations.push_back(puo); + db_fixture->sign( db_fixture->trx, db_fixture->init_account_priv_key ); + db_fixture->db.push_transaction(db_fixture->trx); + db_fixture->trx.clear(); + + db_fixture->generate_blocks( good_proposal_id( db_fixture->db ).expiration_time + 5 ); + db_fixture->generate_blocks( db_fixture->db.get_dynamic_global_properties().next_maintenance_time ); + db_fixture->generate_block(); // get the maintenance skip slots out of the way + +} + +void advance_past_hardfork(database_fixture* db_fixture) +{ + db_fixture->generate_blocks(HARDFORK_CORE_1468_TIME); + set_expiration(db_fixture->db, db_fixture->trx); + set_committee_parameters(db_fixture); + set_expiration(db_fixture->db, db_fixture->trx); +} + +BOOST_AUTO_TEST_CASE( htlc_expires ) +{ +try { + ACTORS((alice)(bob)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + + advance_past_hardfork(this); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // cler everything out + generate_block(); + trx.clear(); + // Alice puts a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + BOOST_TEST_MESSAGE("Alice (who has 100 coins, is transferring 2 coins to Bob"); + create_operation.amount = graphene::chain::asset( 3 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 60; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + graphene::chain::signed_block blk = generate_block(); + processed_transaction alice_trx = blk.transactions[0]; + alice_htlc_id = alice_trx.operation_results[0].get(); + generate_block(); + } + + // verify funds on hold... 100 - 3 = 97, minus the 4 coin fee = 93 + BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 93 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + // make sure Bob (or anyone) can see the details of the transaction + graphene::app::database_api db_api(db); + auto obj = db_api.get_objects( {alice_htlc_id }).front(); + graphene::chain::htlc_object htlc = obj.template as(GRAPHENE_MAX_NESTED_OBJECTS); + + // let it expire (wait for timeout) + generate_blocks( db.head_block_time() + fc::seconds(120) ); + // verify funds return (minus the fees) + BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 96 * GRAPHENE_BLOCKCHAIN_PRECISION ); + // verify Bob cannot execute the contract after the fact +} FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( htlc_fulfilled ) +{ +try { + ACTORS((alice)(bob)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + transfer( committee_account, bob_id, graphene::chain::asset(init_balance) ); + + advance_past_hardfork(this); + + uint16_t preimage_size = 256; + std::vector pre_image(preimage_size); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // clear everything out + generate_block(); + trx.clear(); + // Alice puts a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + + create_operation.amount = graphene::chain::asset( 20 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 86400; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back( create_operation ); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + graphene::chain::signed_block blk = generate_block(); + processed_transaction alice_trx = blk.transactions[0]; + alice_htlc_id = alice_trx.operation_results[0].get(); + } + + // make sure Alice's money gets put on hold (100 - 20 - 4(fee) ) + BOOST_CHECK_EQUAL( get_balance( alice_id, graphene::chain::asset_id_type()), 76 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + // extend the timeout so that Bob has more time + { + graphene::chain::htlc_extend_operation extend_operation; + extend_operation.htlc_id = alice_htlc_id; + extend_operation.seconds_to_add = 86400; + extend_operation.update_issuer = alice_id; + extend_operation.fee = db.current_fee_schedule().calculate_fee( extend_operation ); + trx.operations.push_back( extend_operation ); + sign( trx, alice_private_key ); + PUSH_TX( db, trx, ~0 ); + trx.clear(); + generate_blocks( db.head_block_time() + fc::seconds(87000) ); + set_expiration( db, trx ); + } + + // make sure Alice's money is still on hold, and account for extra fee + BOOST_CHECK_EQUAL( get_balance( alice_id, graphene::chain::asset_id_type()), 72 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + // send a redeem operation to claim the funds + { + graphene::chain::htlc_redeem_operation update_operation; + update_operation.redeemer = bob_id; + update_operation.htlc_id = alice_htlc_id; + update_operation.preimage = pre_image; + update_operation.fee = db.current_fee_schedule().calculate_fee( update_operation ); + trx.operations.push_back( update_operation ); + sign(trx, bob_private_key); + PUSH_TX( db, trx, ~0 ); + generate_block(); + trx.clear(); + } + // verify funds end up in Bob's account (100 + 20 - 4(fee) ) + BOOST_CHECK_EQUAL( get_balance(bob_id, graphene::chain::asset_id_type()), 116 * GRAPHENE_BLOCKCHAIN_PRECISION ); + // verify funds remain out of Alice's acount ( 100 - 20 - 4 ) + BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 72 * GRAPHENE_BLOCKCHAIN_PRECISION ); +} FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( other_peoples_money ) +{ +try { + advance_past_hardfork(this); + + ACTORS((alice)(bob)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // cler everything out + generate_block(); + trx.clear(); + // Bob attempts to put a contract on the blockchain using Alice's funds + { + graphene::chain::htlc_create_operation create_operation; + create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 3; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back(create_operation); + sign(trx, bob_private_key); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx ), fc::exception); + trx.clear(); + } + // now try the same but with Alice's signature (should work) + { + graphene::chain::htlc_create_operation create_operation; + create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 3; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX( db, trx ); + trx.clear(); + } +} FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) +{ + try { + { + // try to set committee parameters before hardfork + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation cmuop; + graphene::chain::htlc_options new_params; + new_params.max_preimage_size = 2048; + new_params.max_timeout_secs = 60 * 60 * 24 * 28; + cmuop.new_parameters.extensions.value.updatable_htlc_options = new_params; + cop.proposed_ops.emplace_back(cmuop); + trx.operations.push_back(cop); + // update with signatures + proposal_update_operation uop; + uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + uop.active_approvals_to_add = {get_account("init0").get_id(), get_account("init1").get_id(), + get_account("init2").get_id(), get_account("init3").get_id(), + get_account("init4").get_id(), get_account("init5").get_id(), + get_account("init6").get_id(), get_account("init7").get_id()}; + trx.operations.push_back(uop); + sign( trx, init_account_priv_key ); + BOOST_TEST_MESSAGE("Sending proposal."); + GRAPHENE_CHECK_THROW(db.push_transaction(trx), fc::exception); + BOOST_TEST_MESSAGE("Verifying that proposal did not succeeed."); + BOOST_CHECK(!db.get_global_properties().parameters.extensions.value.updatable_htlc_options.valid()); + trx.clear(); + } + + { + BOOST_TEST_MESSAGE("Attempting to set HTLC fees before hard fork."); + + // get existing fee_schedule + const chain_parameters& existing_params = db.get_global_properties().parameters; + const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees); + // create a new fee_shedule + fee_schedule_type new_fee_schedule; + new_fee_schedule.scale = existing_fee_schedule.scale; + // replace the old with the new + flat_map params_map = get_htlc_fee_parameters(); + for(auto param : existing_fee_schedule.parameters) + { + auto itr = params_map.find(param.which()); + if (itr == params_map.end()) + new_fee_schedule.parameters.insert(param); + else + { + new_fee_schedule.parameters.insert( (*itr).second); + } + } + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation uop; + uop.new_parameters.current_fees = new_fee_schedule; + cop.proposed_ops.emplace_back(uop); + cop.fee = asset( 100000 ); + trx.operations.push_back( cop ); + GRAPHENE_CHECK_THROW(db.push_transaction( trx ), fc::exception); + trx.clear(); + } + + // now things should start working... + BOOST_TEST_MESSAGE("Advancing to HTLC hardfork time."); + advance_past_hardfork(this); + + proposal_id_type good_proposal_id; + BOOST_TEST_MESSAGE( "Creating a proposal to change the max_preimage_size to 2048 and set higher fees" ); + { + // get existing fee_schedule + const chain_parameters& existing_params = db.get_global_properties().parameters; + const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees); + // create a new fee_shedule + fee_schedule_type new_fee_schedule; + new_fee_schedule.scale = existing_fee_schedule.scale; + // replace the old with the new + flat_map params_map = get_htlc_fee_parameters(); + for(auto param : existing_fee_schedule.parameters) + { + auto itr = params_map.find(param.which()); + if (itr == params_map.end()) + new_fee_schedule.parameters.insert(param); + else + { + new_fee_schedule.parameters.insert( (*itr).second); + } + } + proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties().parameters, db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation uop; + graphene::chain::htlc_options new_params; + new_params.max_preimage_size = 2048; + new_params.max_timeout_secs = 60 * 60 * 24 * 28; + uop.new_parameters.extensions.value.updatable_htlc_options = new_params; + uop.new_parameters.current_fees = new_fee_schedule; + cop.proposed_ops.emplace_back(uop); + trx.operations.push_back(cop); + graphene::chain::processed_transaction proc_trx =db.push_transaction(trx); + good_proposal_id = proc_trx.operation_results[0].get(); + } + + BOOST_TEST_MESSAGE( "Updating proposal by signing with the committee_member private key" ); + { + proposal_update_operation uop; + uop.proposal = good_proposal_id; + uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + uop.active_approvals_to_add = {get_account("init0").get_id(), get_account("init1").get_id(), + get_account("init2").get_id(), get_account("init3").get_id(), + get_account("init4").get_id(), get_account("init5").get_id(), + get_account("init6").get_id(), get_account("init7").get_id()}; + trx.operations.push_back(uop); + sign( trx, init_account_priv_key ); + db.push_transaction(trx); + BOOST_CHECK(good_proposal_id(db).is_authorized_to_execute(db)); + } + BOOST_TEST_MESSAGE( "Verifying that the parameters didn't change immediately" ); + + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + + BOOST_TEST_MESSAGE( "Generating blocks until proposal expires" ); + generate_blocks(good_proposal_id(db).expiration_time + 5); + BOOST_TEST_MESSAGE( "Verify that the parameters still have not changed" ); + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + + BOOST_TEST_MESSAGE( "Verify that the change has been implemented" ); + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u); + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.current_fees->get().fee, 2 * GRAPHENE_BLOCKCHAIN_PRECISION); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( htlc_before_hardfork ) +{ try { + ACTORS((alice)(bob)); + + int64_t init_balance(100000); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // clear everything out + generate_block(); + trx.clear(); + + // Alice tries to put a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + + create_operation.amount = graphene::chain::asset( 10000 ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 60; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx, ~0), fc::exception); + trx.clear(); + } + + // Propose htlc_create + { + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + + graphene::chain::htlc_create_operation create_operation; + create_operation.amount = graphene::chain::asset( 10000 ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 60; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + + pco.proposed_ops.emplace_back( create_operation ); + trx.operations.push_back( pco ); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception ); + trx.clear(); + } + + // Propose htlc_redeem + { + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + + graphene::chain::htlc_redeem_operation rop; + rop.redeemer = bob_id; + rop.htlc_id = alice_htlc_id; + string preimage_str = "Arglebargle"; + rop.preimage.insert( rop.preimage.begin(), preimage_str.begin(), preimage_str.end() ); + + pco.proposed_ops.emplace_back( rop ); + trx.operations.push_back( pco ); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception ); + trx.clear(); + } + + // Propose htlc_extend + { + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + + graphene::chain::htlc_extend_operation xop; + xop.htlc_id = alice_htlc_id; + xop.seconds_to_add = 100; + xop.update_issuer = alice_id; + + pco.proposed_ops.emplace_back( xop ); + trx.operations.push_back( pco ); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception ); + trx.clear(); + } +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fee_calculations ) +{ + // create + { + htlc_create_operation::fee_parameters_type create_fee; + create_fee.fee = 2; + create_fee.fee_per_day = 2; + htlc_create_operation create; + // no days + create.claim_period_seconds = 0; + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 2 ); + // exactly 1 day + create.claim_period_seconds = 60 * 60 * 24; + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 4 ); + // tad over a day + create.claim_period_seconds++; + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 6 ); + } + // redeem + { + htlc_redeem_operation::fee_parameters_type redeem_fee; + redeem_fee.fee_per_kb = 2; + redeem_fee.fee = 2; + htlc_redeem_operation redeem; + // no preimage + redeem.preimage = std::vector(); + BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 2 ) ; + // exactly 1KB + std::string test(1024, 'a'); + redeem.preimage = std::vector( test.begin(), test.end() ); + BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 4 ) ; + // just 1 byte over 1KB + std::string larger(1025, 'a'); + redeem.preimage = std::vector( larger.begin(), larger.end() ); + BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 6 ) ; + } + // extend + { + htlc_extend_operation::fee_parameters_type extend_fee; + extend_fee.fee = 2; + extend_fee.fee_per_day = 2; + htlc_extend_operation extend; + // no days + extend.seconds_to_add = 0; + BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 2 ); + // exactly 1 day + extend.seconds_to_add = 60 * 60 * 24; + BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 4 ); + // 1 day and 1 second + extend.seconds_to_add++; + BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 6 ); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 5e4168562d..398eb42b11 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -303,7 +303,7 @@ BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) pcop.proposed_ops.emplace_back(ascop); trx.operations.push_back(pcop); - BOOST_CHECK_EXCEPTION(db.push_transaction(trx), fc::assert_exception, + BOOST_CHECK_EXCEPTION(PUSH_TX(db, trx), fc::assert_exception, [](fc::assert_exception const &e) -> bool { std::cout << e.to_string() << std::endl; if (e.to_string().find("Virtual operation") != std::string::npos) @@ -333,7 +333,7 @@ BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) trx.operations.push_back(pcop); - BOOST_CHECK_EXCEPTION(db.push_transaction(trx), fc::assert_exception, + BOOST_CHECK_EXCEPTION(PUSH_TX(db, trx), fc::assert_exception, [](fc::assert_exception const &e) -> bool { std::cout << e.to_string() << std::endl; if (e.to_string().find("Virtual operation") != std::string::npos) @@ -656,7 +656,7 @@ BOOST_AUTO_TEST_CASE( call_order_update_target_cr_hardfork_time_test ) tx.operations.push_back( prop ); db.current_fee_schedule().set_fee( tx.operations.back() ); set_expiration( db, tx ); - db.push_transaction( tx, ~0 ); + PUSH_TX( db, tx, ~0 ); }; BOOST_TEST_MESSAGE( "bob tries to propose a proposal with target_cr set, " @@ -1940,7 +1940,7 @@ BOOST_AUTO_TEST_CASE( reserve_asset_test ) transaction tx; tx.operations.push_back( op ); set_expiration( db, tx ); - db.push_transaction( tx, database::skip_authority_check | database::skip_tapos_check | database::skip_transaction_signatures ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } ; auto _issue_uia = [&]( const account_object& recipient, asset amount ) @@ -1952,7 +1952,7 @@ BOOST_AUTO_TEST_CASE( reserve_asset_test ) transaction tx; tx.operations.push_back( op ); set_expiration( db, tx ); - db.push_transaction( tx, database::skip_authority_check | database::skip_tapos_check | database::skip_transaction_signatures ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } ; int64_t init_balance = 10000; @@ -2043,7 +2043,7 @@ BOOST_AUTO_TEST_CASE( cover_with_collateral_test ) transaction tx; tx.operations.push_back( op ); set_expiration( db, tx ); - db.push_transaction( tx, database::skip_authority_check | database::skip_tapos_check | database::skip_transaction_signatures ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } ; // margin call requirement: 1.75x diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index ed43464ba6..15df1b2ece 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -138,7 +139,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_create_before_hardfork_23 ) trx.operations.back() = op; } sign( trx, nathan_private_key ); - db.push_transaction( trx ); + PUSH_TX( db, trx ); trx.clear(); } FC_LOG_AND_RETHROW() } @@ -183,7 +184,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_create_after_hardfork_23 ) trx.operations.back() = op; } sign( trx, nathan_private_key ); - db.push_transaction( trx ); + PUSH_TX( db, trx ); trx.clear(); } FC_LOG_AND_RETHROW() } @@ -564,7 +565,6 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_whitelist_asset_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; @@ -1027,7 +1027,7 @@ BOOST_AUTO_TEST_CASE( feed_limit_test ) op.issuer = bit_usd.issuer; trx.operations = {op}; sign( trx, nathan_private_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_TEST_MESSAGE("Checking current_feed is null"); BOOST_CHECK(bitasset.current_feed.settlement_price.is_null()); @@ -1037,7 +1037,7 @@ BOOST_AUTO_TEST_CASE( feed_limit_test ) trx.clear(); trx.operations = {op}; sign( trx, nathan_private_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_TEST_MESSAGE("Checking current_feed is not null"); BOOST_CHECK(!bitasset.current_feed.settlement_price.is_null()); @@ -1051,7 +1051,6 @@ BOOST_AUTO_TEST_CASE( witness_create ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; generate_block(skip); @@ -1237,7 +1236,6 @@ BOOST_AUTO_TEST_CASE( global_settle_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; @@ -1615,7 +1613,6 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) | database::skip_transaction_dupe_check | database::skip_block_size_check | database::skip_tapos_check - | database::skip_authority_check | database::skip_merkle_check ; @@ -1910,13 +1907,13 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) trx.operations = {op}; _sign( trx, n_key ); // Fail because I'm claiming from an address which hasn't signed - GRAPHENE_CHECK_THROW(db.push_transaction(trx), tx_missing_other_auth); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), tx_missing_other_auth); trx.clear(); op.balance_to_claim = balance_id_type(); op.balance_owner_key = n_key.get_public_key(); trx.operations = {op}; _sign( trx, n_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); // Not using fixture's get_balance() here because it uses fixture's db, not my override BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 1); @@ -1944,7 +1941,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) _sign( trx, n_key ); _sign( trx, v1_key ); // Attempting to claim 1 from a balance with 0 available - GRAPHENE_CHECK_THROW(db.push_transaction(trx), balance_claim_invalid_claim_amount); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_invalid_claim_amount); op.balance_to_claim = vesting_balance_2.id; op.total_claimed.amount = 151; @@ -1954,7 +1951,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) _sign( trx, n_key ); _sign( trx, v2_key ); // Attempting to claim 151 from a balance with 150 available - GRAPHENE_CHECK_THROW(db.push_transaction(trx), balance_claim_invalid_claim_amount); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_invalid_claim_amount); op.balance_to_claim = vesting_balance_2.id; op.total_claimed.amount = 100; @@ -1963,7 +1960,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) trx.clear_signatures(); _sign( trx, n_key ); _sign( trx, v2_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 101); BOOST_CHECK_EQUAL(vesting_balance_2.balance.amount.value, 300); @@ -1973,7 +1970,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) _sign( trx, n_key ); _sign( trx, v2_key ); // Attempting to claim twice within a day - GRAPHENE_CHECK_THROW(db.push_transaction(trx), balance_claim_claimed_too_often); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_claimed_too_often); db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, skip_flags); slot = db.get_slot_at_time(vesting_balance_1.vesting_policy->begin_timestamp + 60); @@ -1987,7 +1984,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) trx.clear_signatures(); _sign( trx, n_key ); _sign( trx, v1_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_CHECK(db.find_object(op.balance_to_claim) == nullptr); BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 601); @@ -1999,7 +1996,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) _sign( trx, n_key ); _sign( trx, v2_key ); // Attempting to claim twice within a day - GRAPHENE_CHECK_THROW(db.push_transaction(trx), balance_claim_claimed_too_often); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_claimed_too_often); db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, skip_flags); slot = db.get_slot_at_time(db.head_block_time() + fc::days(1)); @@ -2011,7 +2008,7 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) trx.clear_signatures(); _sign( trx, n_key ); _sign( trx, v2_key ); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_CHECK(db.find_object(op.balance_to_claim) == nullptr); BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 901); } FC_LOG_AND_RETHROW() } @@ -2031,7 +2028,7 @@ BOOST_AUTO_TEST_CASE(transfer_with_memo) { op.memo->set_message(alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); trx.operations = {op}; trx.sign(alice_private_key, db.get_chain_id()); - db.push_transaction(trx); + PUSH_TX(db, trx); BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 500); BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 500); @@ -2057,7 +2054,7 @@ BOOST_AUTO_TEST_CASE(zero_second_vbo) transaction tx; tx.operations.push_back( op ); set_expiration( db, tx ); - db.push_transaction( tx, database::skip_authority_check | database::skip_tapos_check | database::skip_transaction_signatures ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } enable_fees(); upgrade_to_lifetime_member(alice_id); @@ -2148,6 +2145,14 @@ BOOST_AUTO_TEST_CASE(zero_second_vbo) BOOST_CHECK( vbid(db).get_allowed_withdraw(db.head_block_time()) == asset(0) ); generate_block(); BOOST_CHECK( vbid(db).get_allowed_withdraw(db.head_block_time()) == asset(10000) ); + + /* + db.get_index_type< simple_index >().inspect_all_objects( + [&](const object& o) + { + ilog( "budget: ${brec}", ("brec", static_cast(o)) ); + }); + */ } } FC_LOG_AND_RETHROW() } @@ -2424,18 +2429,18 @@ BOOST_AUTO_TEST_CASE( buyback ) sign( tx, philbin_private_key ); // Alice and Philbin signed, but asset issuer is invalid - GRAPHENE_CHECK_THROW( db.push_transaction(tx), account_create_buyback_incorrect_issuer ); + GRAPHENE_CHECK_THROW( PUSH_TX(db, tx), account_create_buyback_incorrect_issuer ); tx.clear_signatures(); tx.operations.back().get< account_create_operation >().extensions.value.buyback_options->asset_to_buy_issuer = izzy_id; sign( tx, philbin_private_key ); // Izzy didn't sign - GRAPHENE_CHECK_THROW( db.push_transaction(tx), tx_missing_active_auth ); + GRAPHENE_CHECK_THROW( PUSH_TX(db, tx), tx_missing_active_auth ); sign( tx, izzy_private_key ); // OK - processed_transaction ptx = db.push_transaction( tx ); + processed_transaction ptx = PUSH_TX( db, tx ); rex_id = ptx.operation_results.back().get< object_id_type >(); // Try to create another account rex2 which is bbo on same asset @@ -2443,7 +2448,7 @@ BOOST_AUTO_TEST_CASE( buyback ) tx.operations.back().get< account_create_operation >().name = "rex2"; sign( tx, izzy_private_key ); sign( tx, philbin_private_key ); - GRAPHENE_CHECK_THROW( db.push_transaction(tx), account_create_buyback_already_exists ); + GRAPHENE_CHECK_THROW( PUSH_TX(db, tx), account_create_buyback_already_exists ); } // issue some BUYME to Alice diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index f0d7ce9aab..5b9b5c3f20 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -491,4 +491,28 @@ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) } } +/** Creates a black swan, bids on more than outstanding debt + */ +BOOST_AUTO_TEST_CASE( overflow ) +{ try { + init_standard_swan( 700 ); + + wait_for_hf_core_216(); + + bid_collateral( borrower(), back().amount(2200), swan().amount(GRAPHENE_MAX_SHARE_SUPPLY - 1) ); + bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) ); + set_feed(1, 2); + wait_for_maintenance(); + + auto& call_idx = db.get_index_type().indices().get(); + auto itr = call_idx.find( boost::make_tuple(_borrower, _swan) ); + BOOST_REQUIRE( itr != call_idx.end() ); + BOOST_CHECK_EQUAL( 1, itr->debt.value ); + itr = call_idx.find( boost::make_tuple(_borrower2, _swan) ); + BOOST_REQUIRE( itr != call_idx.end() ); + BOOST_CHECK_EQUAL( 1399, itr->debt.value ); + + BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index 95b4fdf4ec..0efc4c5ef9 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -379,7 +379,7 @@ BOOST_AUTO_TEST_CASE( transfer_restricted_test ) transaction tx; tx.operations.push_back( op ); set_expiration( db, tx ); - PUSH_TX( db, tx, database::skip_authority_check | database::skip_tapos_check | database::skip_transaction_signatures ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } ; const asset_object& uia = create_user_issued_asset( "TXRX", sam, transfer_restricted ); @@ -398,7 +398,7 @@ BOOST_AUTO_TEST_CASE( transfer_restricted_test ) transaction tx; tx.operations.push_back( op ); set_expiration( db, tx ); - PUSH_TX( db, tx, database::skip_authority_check | database::skip_tapos_check | database::skip_transaction_signatures ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } ; BOOST_TEST_MESSAGE( "Enable transfer_restricted, send fails" ); diff --git a/tests/tests/voting_tests.cpp b/tests/tests/voting_tests.cpp index 34f39e48c1..141c19e0f3 100644 --- a/tests/tests/voting_tests.cpp +++ b/tests/tests/voting_tests.cpp @@ -414,5 +414,130 @@ BOOST_AUTO_TEST_CASE(invalid_voting_account) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(last_voting_date) +{ + try + { + ACTORS((alice)); + + transfer(committee_account, alice_id, asset(100)); + + // we are going to vote for this witness + auto witness1 = witness_id_type(1)(db); + + auto stats_obj = db.get_account_stats_by_owner(alice_id); + BOOST_CHECK_EQUAL(stats_obj.last_vote_time.sec_since_epoch(), 0); + + // alice votes + graphene::chain::account_update_operation op; + op.account = alice_id; + op.new_options = alice.options; + op.new_options->votes.insert(witness1.vote_id); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0 ); + + auto now = db.head_block_time().sec_since_epoch(); + + // last_vote_time is updated for alice + stats_obj = db.get_account_stats_by_owner(alice_id); + BOOST_CHECK_EQUAL(stats_obj.last_vote_time.sec_since_epoch(), now); + + } FC_LOG_AND_RETHROW() +} +BOOST_AUTO_TEST_CASE(last_voting_date_proxy) +{ + try + { + ACTORS((alice)(proxy)(bob)); + + transfer(committee_account, alice_id, asset(100)); + transfer(committee_account, bob_id, asset(200)); + transfer(committee_account, proxy_id, asset(300)); + + generate_block(); + + // witness to vote for + auto witness1 = witness_id_type(1)(db); + + // round1: alice changes proxy, this is voting activity + { + graphene::chain::account_update_operation op; + op.account = alice_id; + op.new_options = alice_id(db).options; + op.new_options->voting_account = proxy_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0 ); + } + // alice last_vote_time is updated + auto alice_stats_obj = db.get_account_stats_by_owner(alice_id); + auto round1 = db.head_block_time().sec_since_epoch(); + BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1); + + generate_block(); + + // round 2: alice update account but no proxy or voting changes are done + { + graphene::chain::account_update_operation op; + op.account = alice_id; + op.new_options = alice_id(db).options; + trx.operations.push_back(op); + sign(trx, alice_private_key); + set_expiration( db, trx ); + PUSH_TX( db, trx, ~0 ); + } + // last_vote_time is not updated + auto round2 = db.head_block_time().sec_since_epoch(); + alice_stats_obj = db.get_account_stats_by_owner(alice_id); + BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1); + + generate_block(); + + // round 3: bob votes + { + graphene::chain::account_update_operation op; + op.account = bob_id; + op.new_options = bob_id(db).options; + op.new_options->votes.insert(witness1.vote_id); + trx.operations.push_back(op); + sign(trx, bob_private_key); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + } + + // last_vote_time for bob is updated as he voted + auto round3 = db.head_block_time().sec_since_epoch(); + auto bob_stats_obj = db.get_account_stats_by_owner(bob_id); + BOOST_CHECK_EQUAL(bob_stats_obj.last_vote_time.sec_since_epoch(), round3); + + generate_block(); + + // round 4: proxy votes + { + graphene::chain::account_update_operation op; + op.account = proxy_id; + op.new_options = proxy_id(db).options; + op.new_options->votes.insert(witness1.vote_id); + trx.operations.push_back(op); + sign(trx, proxy_private_key); + PUSH_TX(db, trx, ~0); + } + + // proxy just voted so the last_vote_time is updated + auto round4 = db.head_block_time().sec_since_epoch(); + auto proxy_stats_obj = db.get_account_stats_by_owner(proxy_id); + BOOST_CHECK_EQUAL(proxy_stats_obj.last_vote_time.sec_since_epoch(), round4); + + // alice haves proxy, proxy votes but last_vote_time is not updated for alice + alice_stats_obj = db.get_account_stats_by_owner(alice_id); + BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1); + + // bob haves nothing to do with proxy so last_vote_time is not updated + bob_stats_obj = db.get_account_stats_by_owner(bob_id); + BOOST_CHECK_EQUAL(bob_stats_obj.last_vote_time.sec_since_epoch(), round3); + + } FC_LOG_AND_RETHROW() +} BOOST_AUTO_TEST_SUITE_END() From acab2a0bb766452efdb784659d763ad024c9ed14 Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Wed, 23 Jan 2019 16:59:13 +0300 Subject: [PATCH 15/16] Modified hash function - vbo_mfs_hash() --- .../include/graphene/chain/vesting_balance_object.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 226dbf0c0a..ec3ab0c850 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -199,20 +199,18 @@ namespace graphene { namespace chain { * @ingroup object_index */ struct by_account; + // by_vesting_type index MUST NOT be used for iterating because order is not well-defined. struct by_vesting_type; namespace detail { /** Calculate a hash for account_id_type and asset_id. - Use object_id.hash_value function to calculate sum of hashes from account_id_type and asset_id. - then subducts a result from std::numeric_limits::max(). - object_id.hash_value returns uint64_t but only 48 bits are significant - so std::numeric_limits::max() - (48 bits + 48 bits) always correct from point of natural numbers + Use 48 bit value (see object_id.hpp) for account_id and XOR it with 24 bit for asset_id */ inline uint64_t vbo_mfs_hash(const account_id_type& account_id, const asset_id_type& asset_id) { - return std::numeric_limits::max() - ( hash_value(account_id) + hash_value(asset_id) ); + return (asset_id.instance.value << 40) ^ account_id.instance.value; } /** From cce205c8a58f4058f4f2a5e78e5113538a78119c Mon Sep 17 00:00:00 2001 From: OpenLedgerApp Date: Mon, 28 Jan 2019 13:51:27 +0300 Subject: [PATCH 16/16] Update hardfork date into far future --- libraries/chain/hardfork.d/CORE_1268.hf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf index 6872db2fb2..352463c043 100644 --- a/libraries/chain/hardfork.d/CORE_1268.hf +++ b/libraries/chain/hardfork.d/CORE_1268.hf @@ -1,4 +1,4 @@ // #1268 Distribute Asset Market Fees to Referral Program #ifndef HARDFORK_1268_TIME -#define HARDFORK_1268_TIME (fc::time_point_sec( 1530705600 )) // Wednesday, July 4, 2018 12:00:00 PM +#define HARDFORK_1268_TIME (fc::time_point_sec( 1577880000 )) // Wednesday, January 1, 2020 12:00:00 PM #endif