diff --git a/README.md b/README.md index 97bf058a2f..a760ff6de6 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ API 0 is accessible using regular JSON-RPC: Accessing restricted API's -------------------------- -You can restrict API's to particular users by specifying an `apiaccess` file in `config.ini`. Here is an example `apiaccess` file which allows +You can restrict API's to particular users by specifying an `api-access` file in `config.ini` or by using the `--api-access /full/path/to/api-access.json` startup node command. Here is an example `api-access` file which allows user `bytemaster` with password `supersecret` to access four different API's, while allowing any other user to access the three public API's necessary to use the wallet: diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index d2a0ee7d10..9f29966474 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -460,7 +460,8 @@ namespace graphene { namespace app { node = nullptr; else node = &node->next(db); } - + if( stop.instance.value == 0 && account_transaction_history_id_type()(db).account == account && result.size() < limit ) + result.push_back( account_transaction_history_id_type()(db).operation_id(db) ); return result; } @@ -491,6 +492,11 @@ namespace graphene { namespace app { node = nullptr; else node = &node->next(db); } + if( stop.instance.value == 0 && result.size() < limit ) { + const account_transaction_history_object head = account_transaction_history_id_type()(db); + if( head.account == account && head.operation_id(db).op.which() == operation_id ) + result.push_back(head.operation_id(db)); + } return result; } diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 02b623d27a..1940ab2cfe 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -456,9 +456,22 @@ namespace detail { _force_validate = true; } - if( _options->count("api-access") ) - _apiaccess = fc::json::from_file( _options->at("api-access").as() ) - .as(); + if( _options->count("api-access") ) { + + if(fc::exists(_options->at("api-access").as())) + { + _apiaccess = fc::json::from_file( _options->at("api-access").as() ).as(); + ilog("Using api access file from ${path}", + ("path", _options->at("api-access").as().string())); + } + else + { + elog("Failed to load file from ${path}", + ("path", _options->at("api-access").as().string())); + std::exit(EXIT_FAILURE); + } + } + else { // TODO: Remove this generous default access policy @@ -930,7 +943,8 @@ namespace detail { std::shared_ptr _websocket_server; std::shared_ptr _websocket_tls_server; - std::map> _plugins; + std::map> _active_plugins; + std::map> _available_plugins; bool _is_finished_syncing = false; }; @@ -969,6 +983,7 @@ void application::set_program_options(boost::program_options::options_descriptio ("genesis-json", bpo::value(), "File to read Genesis State from") ("dbg-init-key", bpo::value(), "Block signing key to use for init witnesses, overrides genesis file") ("api-access", bpo::value(), "JSON file specifying API permissions") + ("plugins", bpo::value(), "Space-separated list of plugins to activate") ; command_line_options.add(configuration_file_options); command_line_options.add_options() @@ -1014,6 +1029,22 @@ void application::initialize(const fc::path& data_dir, const boost::program_opti std::exit(EXIT_SUCCESS); } + + std::vector wanted; + if( options.count("plugins") ) + { + boost::split(wanted, options.at("plugins").as(), [](char c){return c == ' ';}); + } + else + { + wanted.push_back("witness"); + wanted.push_back("account_history"); + wanted.push_back("market_history"); + } + for (auto& it : wanted) + { + if (!it.empty()) enable_plugin(it); + } } void application::startup() @@ -1031,7 +1062,7 @@ void application::startup() std::shared_ptr application::get_plugin(const string& name) const { - return my->_plugins[name]; + return my->_active_plugins[name]; } net::node_ptr application::p2p_node() @@ -1064,14 +1095,21 @@ bool application::is_finished_syncing() const return my->_is_finished_syncing; } -void graphene::app::application::add_plugin(const string& name, std::shared_ptr p) +void graphene::app::application::enable_plugin(const string& name) { - my->_plugins[name] = p; + FC_ASSERT(my->_available_plugins[name], "Unknown plugin '" + name + "'"); + my->_active_plugins[name] = my->_available_plugins[name]; + my->_active_plugins[name]->plugin_set_app(this); +} + +void graphene::app::application::add_available_plugin(std::shared_ptr p) +{ + my->_available_plugins[p->plugin_name()] = p; } void application::shutdown_plugins() { - for( auto& entry : my->_plugins ) + for( auto& entry : my->_active_plugins ) entry.second->plugin_shutdown(); return; } @@ -1080,19 +1118,22 @@ void application::shutdown() if( my->_p2p_network ) my->_p2p_network->close(); if( my->_chain_db ) + { my->_chain_db->close(); + my->_chain_db = nullptr; + } } void application::initialize_plugins( const boost::program_options::variables_map& options ) { - for( auto& entry : my->_plugins ) + for( auto& entry : my->_active_plugins ) entry.second->plugin_initialize( options ); return; } void application::startup_plugins() { - for( auto& entry : my->_plugins ) + for( auto& entry : my->_active_plugins ) entry.second->plugin_startup(); return; } diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 65499e861c..4fb301bdb9 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * Copyright (c) 2017 Cryptonomex, Inc., and contributors. * * The MIT License * @@ -54,6 +54,7 @@ class database_api_impl : public std::enable_shared_from_this database_api_impl( graphene::chain::database& db ); ~database_api_impl(); + // Objects fc::variants get_objects(const vector& ids)const; @@ -78,7 +79,7 @@ class database_api_impl : public std::enable_shared_from_this // Keys vector> get_key_references( vector key )const; - bool is_public_key_registered(string public_key) const; + bool is_public_key_registered(string public_key) const; // Accounts vector> get_accounts(const vector& account_ids)const; @@ -123,6 +124,12 @@ class database_api_impl : public std::enable_shared_from_this vector> get_committee_members(const vector& committee_member_ids)const; fc::optional get_committee_member_by_account(account_id_type account)const; map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; + uint64_t get_committee_count()const; + + // Workers + vector get_all_workers()const; + vector> get_workers_by_account(account_id_type account)const; + uint64_t get_worker_count()const; // Votes vector lookup_vote_ids( const vector& votes )const; @@ -143,6 +150,7 @@ class database_api_impl : public std::enable_shared_from_this // Blinded balances vector get_blinded_balances( const flat_set& commitments )const; + //private: template void subscribe_to_item( const T& i )const @@ -618,9 +626,10 @@ std::map database_api_impl::get_full_accounts( const if( subscribe ) { - FC_ASSERT( std::distance(_subscribed_accounts.begin(), _subscribed_accounts.end()) <= 100 ); - _subscribed_accounts.insert( account->get_id() ); - subscribe_to_item( account->id ); + if(_subscribed_accounts.size() < 100) { + _subscribed_accounts.insert( account->get_id() ); + subscribe_to_item( account->id ); + } } // fc::mutable_variant_object full_account; @@ -1031,9 +1040,16 @@ vector database_api_impl::get_call_orders(asset_id_type a, ui const auto& call_index = _db.get_index_type().indices().get(); const asset_object& mia = _db.get(a); price index_price = price::min(mia.bitasset_data(_db).options.short_backing_asset, mia.get_id()); - - return vector(call_index.lower_bound(index_price.min()), - call_index.lower_bound(index_price.max())); + + vector< call_order_object> result; + auto itr_min = call_index.lower_bound(index_price.min()); + auto itr_max = call_index.lower_bound(index_price.max()); + while( itr_min != itr_max && result.size() < limit ) + { + result.emplace_back(*itr_min); + ++itr_min; + } + return result; } vector database_api::get_settle_orders(asset_id_type a, uint32_t limit)const @@ -1045,8 +1061,16 @@ vector database_api_impl::get_settle_orders(asset_id_ty { const auto& settle_index = _db.get_index_type().indices().get(); const asset_object& mia = _db.get(a); - return vector(settle_index.lower_bound(mia.get_id()), - settle_index.upper_bound(mia.get_id())); + + vector result; + auto itr_min = settle_index.lower_bound(mia.get_id()); + auto itr_max = settle_index.upper_bound(mia.get_id()); + while( itr_min != itr_max && result.size() < limit ) + { + result.emplace_back(*itr_min); + ++itr_min; + } + return result; } vector database_api::get_margin_positions( const account_id_type& id )const @@ -1293,6 +1317,12 @@ vector database_api_impl::get_trade_history( const string& base, trade.date = itr->time; trade.price = trade.value / trade.amount; + trade.side1_account_id = itr->op.account_id; + + auto next_itr = std::next(itr); + + trade.side2_account_id = next_itr->op.account_id; + result.push_back( trade ); ++count; } @@ -1316,22 +1346,6 @@ vector> database_api::get_witnesses(const vectorget_witnesses( witness_ids ); } -vector database_api::get_workers_by_account(account_id_type account)const -{ - const auto& idx = my->_db.get_index_type().indices().get(); - auto itr = idx.find(account); - vector result; - - if( itr != idx.end() && itr->worker_account == account ) - { - result.emplace_back( *itr ); - ++itr; - } - - return result; -} - - vector> database_api_impl::get_witnesses(const vector& witness_ids)const { vector> result; result.reserve(witness_ids.size()); @@ -1461,6 +1475,69 @@ map database_api_impl::lookup_committee_member return committee_members_by_account_name; } +uint64_t database_api::get_committee_count()const +{ + return my->get_committee_count(); +} + +uint64_t database_api_impl::get_committee_count()const +{ + return _db.get_index_type().indices().size(); +} + + +////////////////////////////////////////////////////////////////////// +// // +// Workers // +// // +////////////////////////////////////////////////////////////////////// + +vector database_api::get_all_workers()const +{ + return my->get_all_workers(); +} + +vector database_api_impl::get_all_workers()const +{ + vector result; + const auto& workers_idx = _db.get_index_type().indices().get(); + for( const auto& w : workers_idx ) + { + result.push_back( w ); + } + return result; +} + +vector> database_api::get_workers_by_account(account_id_type account)const +{ + return my->get_workers_by_account( account ); +} + +vector> database_api_impl::get_workers_by_account(account_id_type account)const +{ + vector> result; + const auto& workers_idx = _db.get_index_type().indices().get(); + + for( const auto& w : workers_idx ) + { + if( w.worker_account == account ) + result.push_back( w ); + } + return result; +} + +uint64_t database_api::get_worker_count()const +{ + return my->get_worker_count(); +} + +uint64_t database_api_impl::get_worker_count()const +{ + return _db.get_index_type().indices().size(); +} + + + ////////////////////////////////////////////////////////////////////// // // // Votes // diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index 26ae78efd6..758069a0a0 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -63,7 +63,7 @@ namespace graphene { namespace app { if( !plugin_cfg_options.options().empty() ) _cfg_options.add(plugin_cfg_options); - add_plugin( plug->plugin_name(), plug ); + add_available_plugin( plug ); return plug; } std::shared_ptr get_plugin( const string& name )const; @@ -89,7 +89,8 @@ namespace graphene { namespace app { boost::signals2::signal syncing_finished; private: - void add_plugin( const string& name, std::shared_ptr p ); + void enable_plugin( const string& name ); + void add_available_plugin( std::shared_ptr p ); std::shared_ptr my; boost::program_options::options_description _cli_options; diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 1fe64a0956..2e986f9395 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -105,6 +105,8 @@ struct market_trade double price; double amount; double value; + account_id_type side1_account_id; + account_id_type side2_account_id; }; /** @@ -489,13 +491,35 @@ class database_api */ map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; + /** + * @brief Get the total number of committee registered with the blockchain + */ + uint64_t get_committee_count()const; - /// WORKERS + + /////////////////////// + // Worker proposals // + /////////////////////// + + /** + * @brief Get all workers + * @return All the workers + * + */ + vector get_all_workers()const; /** - * Return the worker objects associated with this account. + * @brief Get the workers owned by a given account + * @param account The ID of the account whose worker should be retrieved + * @return The worker object, or null if the account does not have a worker */ - vector get_workers_by_account(account_id_type account)const; + vector> get_workers_by_account(account_id_type account)const; + + /** + * @brief Get the total number of workers registered with the blockchain + */ + uint64_t get_worker_count()const; + /////////// @@ -582,7 +606,7 @@ FC_REFLECT( graphene::app::order, (price)(quote)(base) ); FC_REFLECT( graphene::app::order_book, (base)(quote)(bids)(asks) ); FC_REFLECT( graphene::app::market_ticker, (base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume) ); FC_REFLECT( graphene::app::market_volume, (base)(quote)(base_volume)(quote_volume) ); -FC_REFLECT( graphene::app::market_trade, (date)(price)(amount)(value) ); +FC_REFLECT( graphene::app::market_trade, (date)(price)(amount)(value)(side1_account_id)(side2_account_id) ); FC_API(graphene::app::database_api, // Objects @@ -655,9 +679,13 @@ FC_API(graphene::app::database_api, (get_committee_members) (get_committee_member_by_account) (lookup_committee_member_accounts) + (get_committee_count) // workers + (get_all_workers) (get_workers_by_account) + (get_worker_count) + // Votes (lookup_vote_ids) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index ffe52715d6..3a0dc2e770 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * Copyright (c) 2017 Cryptonomex, Inc., and contributors. * * The MIT License * @@ -208,7 +208,7 @@ void database::initialize_indexes() add_index< primary_index> >(); add_index< primary_index> >(); add_index< primary_index> >(); - add_index< primary_index> >(); + add_index< primary_index> >(); add_index< primary_index > >(); add_index< primary_index > >(); add_index< primary_index > >(); @@ -241,9 +241,6 @@ void database::init_genesis(const genesis_state_type& genesis_state) transaction_evaluation_state genesis_eval_state(this); - flat_index& bsi = get_mutable_index_type< flat_index >(); - bsi.resize(0xffff+1); - // Create blockchain accounts fc::ecc::private_key null_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key"))); create([](account_balance_object& b) { @@ -404,7 +401,8 @@ void database::init_genesis(const genesis_state_type& genesis_state) p.chain_id = chain_id; p.immutable_parameters = genesis_state.immutable_parameters; } ); - create([&](block_summary_object&) {}); + for (uint32_t i = 0; i <= 0x10000; i++) + create( [&]( block_summary_object&) {}); // Create initial accounts for( const auto& account : genesis_state.initial_accounts ) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index c8a4f017b3..76862343c7 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -150,6 +150,7 @@ void database::close(bool rewind) { uint32_t cutoff = get_dynamic_global_properties().last_irreversible_block_num; + ilog( "Rewinding from ${head} to ${cutoff}", ("head",head_block_num())("cutoff",cutoff) ); while( head_block_num() > cutoff ) { // elog("pop"); diff --git a/libraries/chain/hardfork.d/23.hf b/libraries/chain/hardfork.d/23.hf new file mode 100644 index 0000000000..0b31e73eef --- /dev/null +++ b/libraries/chain/hardfork.d/23.hf @@ -0,0 +1,5 @@ +// Issue #23: Withdrawal claims made before the first withdrawal period are incorrectly allowed +// Fork time set to 2017-02-01 00:00:00 UTC +#ifndef HARDFORK_23_TIME +#define HARDFORK_23_TIME (fc::time_point_sec( 1485907200 )) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 05328a37e8..cec4c988b3 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * Copyright (c) 2017 Cryptonomex, Inc., and contributors. * * The MIT License * @@ -24,7 +24,6 @@ #pragma once #include #include -#include #include /** @@ -228,7 +227,6 @@ namespace graphene { namespace chain { > > > asset_bitasset_data_object_multi_index_type; - //typedef flat_index asset_bitasset_data_index; typedef generic_index asset_bitasset_data_index; struct by_symbol; diff --git a/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp b/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp index 000573bd35..f202ee1b66 100644 --- a/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp +++ b/libraries/chain/include/graphene/chain/withdraw_permission_object.hpp @@ -27,7 +27,6 @@ #include namespace graphene { namespace chain { - /** * @class withdraw_permission_object * @brief Grants another account authority to withdraw a limited amount of funds per interval @@ -52,14 +51,23 @@ namespace graphene { namespace chain { asset withdrawal_limit; /// The duration of a withdrawal period in seconds uint32_t withdrawal_period_sec = 0; - /// The beginning of the next withdrawal period + /*** + * The beginning of the next withdrawal period + * WARNING: Due to caching, this value does not always represent the start of the next or current period (because it is only updated after a withdrawal operation such as claim). For the latest current period, use current_period(). + */ time_point_sec period_start_time; /// The time at which this withdraw permission expires time_point_sec expiration; - /// tracks the total amount + /*** + * Tracks the total amount + * WARNING: Due to caching, this value does not always represent the total amount claimed during the current period; it may represent what was claimed during the last claimed period (because it is only updated after a withdrawal operation such as claim). For the latest current period, use current_period(). + */ share_type claimed_this_period; - /// True if the permission may still be claimed for this period; false if it has already been used + + /*** + * Determine how much is still available to be claimed during the period that contains a time of interest. This object and function is mainly intended to be used with the "current" time as a parameter. The current time can be obtained from the time of the current head of the blockchain. + */ asset available_this_period( fc::time_point_sec current_time )const { if( current_time >= period_start_time + withdrawal_period_sec ) diff --git a/libraries/chain/include/graphene/chain/worker_object.hpp b/libraries/chain/include/graphene/chain/worker_object.hpp index 1219fc1c6d..b1e2b7c107 100644 --- a/libraries/chain/include/graphene/chain/worker_object.hpp +++ b/libraries/chain/include/graphene/chain/worker_object.hpp @@ -153,7 +153,6 @@ typedef multi_index_container< > > worker_object_multi_index_type; -//typedef flat_index worker_index; using worker_index = generic_index; } } // graphene::chain diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index a63e6f8033..ac20c79fc6 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -60,13 +60,6 @@ bool is_valid_name( const string& name ) { try { const size_t len = name.size(); - /** this condition will prevent witnesses from including new names before this time, but - * allow them after this time. This check can be removed from the code after HARDFORK_385_TIME - * has passed. - */ - if( fc::time_point::now() < fc::time_point(HARDFORK_385_TIME) ) - FC_ASSERT( len >= 3 ); - if( len < GRAPHENE_MIN_ACCOUNT_NAME_LENGTH ) { ilog( "."); diff --git a/libraries/chain/withdraw_permission_evaluator.cpp b/libraries/chain/withdraw_permission_evaluator.cpp index d001b441ff..3deb3e896a 100644 --- a/libraries/chain/withdraw_permission_evaluator.cpp +++ b/libraries/chain/withdraw_permission_evaluator.cpp @@ -59,12 +59,16 @@ object_id_type withdraw_permission_create_evaluator::do_apply(const operation_ty void_result withdraw_permission_claim_evaluator::do_evaluate(const withdraw_permission_claim_evaluator::operation_type& op) { try { const database& d = db(); + time_point_sec head_block_time = d.head_block_time(); const withdraw_permission_object& permit = op.withdraw_permission(d); - FC_ASSERT(permit.expiration > d.head_block_time() ); + FC_ASSERT(permit.expiration > head_block_time); FC_ASSERT(permit.authorized_account == op.withdraw_to_account); FC_ASSERT(permit.withdraw_from_account == op.withdraw_from_account); - FC_ASSERT(op.amount_to_withdraw <= permit.available_this_period( d.head_block_time() ) ); + if (head_block_time >= HARDFORK_23_TIME) { + FC_ASSERT(permit.period_start_time <= head_block_time); + } + FC_ASSERT(op.amount_to_withdraw <= permit.available_this_period( head_block_time ) ); FC_ASSERT(d.get_balance(op.withdraw_from_account, op.amount_to_withdraw.asset_id) >= op.amount_to_withdraw); const asset_object& _asset = op.amount_to_withdraw.asset_id(d); diff --git a/libraries/db/include/graphene/db/flat_index.hpp b/libraries/db/include/graphene/db/flat_index.hpp deleted file mode 100644 index f1b7912ef7..0000000000 --- a/libraries/db/include/graphene/db/flat_index.hpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. - * - * The MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#pragma once -#include - -namespace graphene { namespace db { - - /** - * @class flat_index - * @brief A flat index uses a vector to store data - * - * This index is preferred in situations where the data will never be - * removed from main memory and when lots of small objects that - * are accessed in order are required. - */ - template - class flat_index : public index - { - public: - typedef T object_type; - - virtual const object& create( const std::function& constructor ) override - { - auto id = get_next_id(); - auto instance = id.instance(); - if( instance >= _objects.size() ) _objects.resize( instance + 1 ); - _objects[instance].id = id; - constructor( _objects[instance] ); - use_next_id(); - return _objects[instance]; - } - - virtual void modify( const object& obj, const std::function& modify_callback ) override - { - assert( obj.id.instance() < _objects.size() ); - modify_callback( _objects[obj.id.instance()] ); - } - - virtual const object& insert( object&& obj )override - { - auto instance = obj.id.instance(); - assert( nullptr != dynamic_cast(&obj) ); - if( _objects.size() <= instance ) _objects.resize( instance+1 ); - _objects[instance] = std::move( static_cast(obj) ); - return _objects[instance]; - } - - virtual void remove( const object& obj ) override - { - assert( nullptr != dynamic_cast(&obj) ); - const auto instance = obj.id.instance(); - _objects[instance] = T(); - } - - virtual const object* find( object_id_type id )const override - { - assert( id.space() == T::space_id ); - assert( id.type() == T::type_id ); - - const auto instance = id.instance(); - if( instance >= _objects.size() ) return nullptr; - return &_objects[instance]; - } - - virtual void inspect_all_objects(std::function inspector)const override - { - try { - for( const auto& ptr : _objects ) - { - inspector(ptr); - } - } FC_CAPTURE_AND_RETHROW() - } - - virtual fc::uint128 hash()const override { - fc::uint128 result; - for( const auto& ptr : _objects ) - result += ptr.hash(); - - return result; - } - - class const_iterator - { - public: - const_iterator(){} - const_iterator( const typename vector::const_iterator& a ):_itr(a){} - friend bool operator==( const const_iterator& a, const const_iterator& b ) { return a._itr == b._itr; } - friend bool operator!=( const const_iterator& a, const const_iterator& b ) { return a._itr != b._itr; } - const T* operator*()const { return static_cast(&*_itr); } - const_iterator& operator++(int){ ++_itr; return *this; } - const_iterator& operator++() { ++_itr; return *this; } - private: - typename vector::const_iterator _itr; - }; - const_iterator begin()const { return const_iterator(_objects.begin()); } - const_iterator end()const { return const_iterator(_objects.end()); } - - size_t size()const{ return _objects.size(); } - - void resize( uint32_t s ) { - _objects.resize(s); - for( uint32_t i = 0; i < s; ++i ) - _objects[i].id = object_id_type(object_type::space_id,object_type::type_id,i); - } - - private: - vector< T > _objects; - }; - -} } // graphene::db diff --git a/libraries/plugins/delayed_node/delayed_node_plugin.cpp b/libraries/plugins/delayed_node/delayed_node_plugin.cpp index fb70cb6852..a73e5923da 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()->required(), "RPC endpoint of a trusted validating node (required)") + ("trusted-node", boost::program_options::value(), "RPC endpoint of a trusted validating node (required)") ; cfg.add(cli); } @@ -74,6 +74,7 @@ void delayed_node_plugin::connect() void delayed_node_plugin::plugin_initialize(const boost::program_options::variables_map& options) { + FC_ASSERT(options.count("trusted-node") > 0); my->remote_endpoint = "ws://" + options.at("trusted-node").as(); } diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index eb6bc887d7..0601260d02 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1794,74 +1794,12 @@ class wallet_api_impl signed_transaction sign_transaction(signed_transaction tx, bool broadcast = false) { - flat_set req_active_approvals; - flat_set req_owner_approvals; - vector other_auths; - - tx.get_required_authorities( req_active_approvals, req_owner_approvals, other_auths ); - - for( const auto& auth : other_auths ) - for( const auto& a : auth.account_auths ) - req_active_approvals.insert(a.first); - - // std::merge lets us de-duplicate account_id's that occur in both - // sets, and dump them into a vector (as required by remote_db api) - // at the same time - vector v_approving_account_ids; - std::merge(req_active_approvals.begin(), req_active_approvals.end(), - req_owner_approvals.begin() , req_owner_approvals.end(), - std::back_inserter(v_approving_account_ids)); - - /// TODO: fetch the accounts specified via other_auths as well. - - vector< optional > approving_account_objects = - _remote_db->get_accounts( v_approving_account_ids ); - - /// TODO: recursively check one layer deeper in the authority tree for keys - - FC_ASSERT( approving_account_objects.size() == v_approving_account_ids.size() ); - - flat_map approving_account_lut; - size_t i = 0; - for( optional& approving_acct : approving_account_objects ) - { - if( !approving_acct.valid() ) - { - wlog( "operation_get_required_auths said approval of non-existing account ${id} was needed", - ("id", v_approving_account_ids[i]) ); - i++; - continue; - } - approving_account_lut[ approving_acct->id ] = &(*approving_acct); - i++; - } - - flat_set approving_key_set; - for( account_id_type& acct_id : req_active_approvals ) - { - const auto it = approving_account_lut.find( acct_id ); - if( it == approving_account_lut.end() ) - continue; - const account_object* acct = it->second; - vector v_approving_keys = acct->active.get_keys(); - for( const public_key_type& approving_key : v_approving_keys ) - approving_key_set.insert( approving_key ); - } - for( account_id_type& acct_id : req_owner_approvals ) - { - const auto it = approving_account_lut.find( acct_id ); - if( it == approving_account_lut.end() ) - continue; - const account_object* acct = it->second; - vector v_approving_keys = acct->owner.get_keys(); - for( const public_key_type& approving_key : v_approving_keys ) - approving_key_set.insert( approving_key ); - } - for( const authority& a : other_auths ) - { - for( const auto& k : a.key_auths ) - approving_key_set.insert( k.first ); - } + set pks = _remote_db->get_potential_signatures( tx ); + flat_set owned_keys; + owned_keys.reserve( pks.size() ); + std::copy_if( pks.begin(), pks.end(), std::inserter(owned_keys, owned_keys.end()), + [this](const public_key_type& pk){ return _keys.find(pk) != _keys.end(); } ); + set approving_key_set = _remote_db->get_required_signatures( tx, owned_keys ); auto dyn_props = get_dynamic_global_properties(); tx.set_reference_block( dyn_props.head_block_id ); @@ -1881,20 +1819,8 @@ class wallet_api_impl tx.set_expiration( dyn_props.time + fc::seconds(30 + expiration_time_offset) ); tx.signatures.clear(); - for( public_key_type& key : approving_key_set ) - { - auto it = _keys.find(key); - if( it != _keys.end() ) - { - fc::optional privkey = wif_to_key( it->second ); - FC_ASSERT( privkey.valid(), "Malformed private key in _keys" ); - tx.sign( *privkey, _chain_id ); - } - /// TODO: if transaction has enough signatures to be "valid" don't add any more, - /// there are cases where the wallet may have more keys than strictly necessary and - /// the transaction will be rejected if the transaction validates without requiring - /// all signatures provided - } + for( const public_key_type& key : approving_key_set ) + tx.sign( get_private_key(key), _chain_id ); graphene::chain::transaction_id_type this_transaction_id = tx.id(); auto iter = _recently_generated_transactions.find(this_transaction_id); @@ -2837,12 +2763,17 @@ vector wallet_api::get_account_history(string name, int limit) vector wallet_api::get_relative_account_history(string name, uint32_t stop, int limit, uint32_t start)const { - - FC_ASSERT( start > 0 || limit <= 100 ); - vector result; auto account_id = get_account(name).get_id(); + const account_object& account = my->get_account(account_id); + const account_statistics_object& stats = my->get_object(account.statistics); + + if(start == 0) + start = stats.total_ops; + else + start = std::min(start, stats.total_ops); + while( limit > 0 ) { vector current = my->_remote_hist->get_relative_account_history(account_id, stop, std::min(100, limit), start); diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 932e69b777..b17a972c96 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -2,7 +2,6 @@ add_subdirectory( build_helpers ) add_subdirectory( cli_wallet ) add_subdirectory( genesis_util ) add_subdirectory( witness_node ) -add_subdirectory( debug_node ) add_subdirectory( delayed_node ) add_subdirectory( js_operation_serializer ) add_subdirectory( size_checker ) diff --git a/programs/debug_node/CMakeLists.txt b/programs/debug_node/CMakeLists.txt deleted file mode 100644 index 8ec7362ba4..0000000000 --- a/programs/debug_node/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_executable( debug_node main.cpp ) -if( UNIX AND NOT APPLE ) - set(rt_library rt ) -endif() - -find_package( Gperftools QUIET ) -if( GPERFTOOLS_FOUND ) - message( STATUS "Found gperftools; compiling debug_node with TCMalloc") - list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) -endif() - -target_link_libraries( debug_node - PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_debug_witness graphene_chain graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) - -install( TARGETS - debug_node - - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib -) diff --git a/programs/debug_node/README.md b/programs/debug_node/README.md deleted file mode 100644 index 53d56733ba..0000000000 --- a/programs/debug_node/README.md +++ /dev/null @@ -1,104 +0,0 @@ - -Introduction ------------- - -The `debug_node` is a tool to allow developers to run many interesting sorts of "what-if" tests using state from a production blockchain. -Like "what happens if I produce enough blocks for the next hardfork time to arrive?" or "what would happen if this account (which I don't have a private key for) did this transaction?" - -Setup ------ - -Be sure you've built the right build targets: - - $ make get_dev_key debug_node cli_wallet witness_node - -Use the `get_dev_key` utility to generate a keypair: - - $ programs/genesis_util/get_dev_key "" nathan - [{"private_key":"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3","public_key":"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","address":"BTSFAbAx7yuxt725qSZvfwWqkdCwp9ZnUama"}] - -Obtain a copy of the blockchain in `block_db` directory: - $ programs/witness_node/witness_node --data-dir data/mydatadir - # ... wait for chain to sync - ^C - $ cp -Rp data/mydatadir/blockchain/database/block_num_to_block ./block_db - -Set up a new datadir with the following `config.ini` settings: - - # setup API endpoint - rpc-endpoint = 127.0.0.1:8090 - # setting this to empty effectively disables the p2p network - seed-nodes = [] - # set apiaccess.json so we can set up - api-access = "data/debug_datadir/api-access.json" - -Then set up `data/debug_datadir/api-access.json` to allow access to the debug API like this: - - { - "permission_map" : - [ - [ - "bytemaster", - { - "password_hash_b64" : "9e9GF7ooXVb9k4BoSfNIPTelXeGOZ5DrgOYMj94elaY=", - "password_salt_b64" : "INDdM6iCi/8=", - "allowed_apis" : ["database_api", "network_broadcast_api", "history_api", "network_node_api", "debug_api"] - } - ], - [ - "*", - { - "password_hash_b64" : "*", - "password_salt_b64" : "*", - "allowed_apis" : ["database_api", "network_broadcast_api", "history_api"] - } - ] - ] - } - -See [here](https://github.com/cryptonomex/graphene#accessing-restricted-apis) for more detail on the `api-access.json` format. - -Once that is set up, run `debug_node` against your newly prepared datadir: - - programs/debug_node/debug_node --data-dir data/debug_datadir - -Run `cli_wallet` to connect to the `debug_node` port, using the username and password to access the new `debug_api` (and also a different wallet file): - - programs/cli_wallet/cli_wallet -s 127.0.0.1:8090 -w debug.wallet -u bytemaster -p supersecret - -Example usage -------------- - -Load some blocks from the datadir: - - dbg_push_blocks block_db 20000 - -Note, when pushing a very large number of blocks sometimes `cli_wallet` hangs and you must Ctrl+C and restart it (leaving the `debug_node` running). - -Generate (fake) blocks with our own private key: - - dbg_generate_blocks 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 1000 - -Update `angel` account to be controlled by our own private key and generate a (fake) transfer: - - dbg_update_object {"_action":"update", "id":"1.2.1090", "active":{"weight_threshold":1,"key_auths":[["BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",1]]}} - import_key angel 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 - transfer angel init0 999999 BTS "" true - -How it works ------------- - -The commands work by creating diff(s) from the main chain that are applied to the local chain at specified block height(s). It lets you easily check out "what-if" -scenarios in a fantasy debug toy world forked from the real chain, e.g. "if we take all of the blocks until today, then generate a bunch more until a hardfork time -in the future arrives, does the chain stay up? Can I do transactions X, Y, and Z in the wallet after the hardfork?" Anyone connecting to this node sees the same -fantasy world, so you can e.g. make changes with the `cli_wallet` and see them exist in other `cli_wallet` instances (or GUI wallets or API scripts). - -Limitations ------------ - -The main limitations are: - -- No export format for the diffs, so you can't really [1] connect multiple `debug_node` to each other. -- Once faked block(s) or tx(s) have been produced on your chain, you can't really [1] stream blocks or tx's from the main network to your chain. - -[1] It should theoretically be possible, but it's non-trivial and totally untested. diff --git a/programs/debug_node/main.cpp b/programs/debug_node/main.cpp deleted file mode 100644 index 7c3d358a22..0000000000 --- a/programs/debug_node/main.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. - * - * The MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#ifdef WIN32 -# include -#else -# include -#endif - -using namespace graphene; -namespace bpo = boost::program_options; - -void write_default_logging_config_to_stream(std::ostream& out); -fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename); - -int main(int argc, char** argv) { - app::application* node = new app::application(); - fc::oexception unhandled_exception; - try { - bpo::options_description app_options("Graphene Witness Node"); - bpo::options_description cfg_options("Graphene Witness Node"); - app_options.add_options() - ("help,h", "Print this help message and exit.") - ("data-dir,d", bpo::value()->default_value("witness_node_data_dir"), "Directory containing databases, configuration file, etc.") - ; - - bpo::variables_map options; - - auto witness_plug = node->register_plugin(); - auto history_plug = node->register_plugin(); - auto market_history_plug = node->register_plugin(); - - try - { - bpo::options_description cli, cfg; - node->set_program_options(cli, cfg); - app_options.add(cli); - cfg_options.add(cfg); - bpo::store(bpo::parse_command_line(argc, argv, app_options), options); - } - catch (const boost::program_options::error& e) - { - std::cerr << "Error parsing command line: " << e.what() << "\n"; - return 1; - } - - if( options.count("help") ) - { - std::cout << app_options << "\n"; - return 0; - } - - fc::path data_dir; - if( options.count("data-dir") ) - { - data_dir = options["data-dir"].as(); - if( data_dir.is_relative() ) - data_dir = fc::current_path() / data_dir; - } - - fc::path config_ini_path = data_dir / "config.ini"; - if( fc::exists(config_ini_path) ) - { - // get the basic options - bpo::store(bpo::parse_config_file(config_ini_path.preferred_string().c_str(), cfg_options, true), options); - - // try to get logging options from the config file. - try - { - fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); - if (logging_config) - fc::configure_logging(*logging_config); - } - catch (const fc::exception&) - { - wlog("Error parsing logging config from config file ${config}, using default config", ("config", config_ini_path.preferred_string())); - } - } - else - { - ilog("Writing new config file at ${path}", ("path", config_ini_path)); - if( !fc::exists(data_dir) ) - fc::create_directories(data_dir); - - std::ofstream out_cfg(config_ini_path.preferred_string()); - for( const boost::shared_ptr od : cfg_options.options() ) - { - if( !od->description().empty() ) - out_cfg << "# " << od->description() << "\n"; - boost::any store; - if( !od->semantic()->apply_default(store) ) - out_cfg << "# " << od->long_name() << " = \n"; - else { - auto example = od->format_parameter(); - if( example.empty() ) - // This is a boolean switch - out_cfg << od->long_name() << " = " << "false\n"; - else { - // The string is formatted "arg (=)" - example.erase(0, 6); - example.erase(example.length()-1); - out_cfg << od->long_name() << " = " << example << "\n"; - } - } - out_cfg << "\n"; - } - write_default_logging_config_to_stream(out_cfg); - out_cfg.close(); - // read the default logging config we just wrote out to the file and start using it - fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); - if (logging_config) - fc::configure_logging(*logging_config); - } - - bpo::notify(options); - node->initialize(data_dir, options); - node->initialize_plugins( options ); - - node->startup(); - node->startup_plugins(); - - fc::promise::ptr exit_promise = new fc::promise("UNIX Signal Handler"); - - fc::set_signal_handler([&exit_promise](int signal) { - elog( "Caught SIGINT attempting to exit cleanly" ); - exit_promise->set_value(signal); - }, SIGINT); - - fc::set_signal_handler([&exit_promise](int signal) { - elog( "Caught SIGTERM attempting to exit cleanly" ); - exit_promise->set_value(signal); - }, SIGTERM); - - ilog("Started witness node on a chain with ${h} blocks.", ("h", node->chain_database()->head_block_num())); - ilog("Chain ID is ${id}", ("id", node->chain_database()->get_chain_id()) ); - - int signal = exit_promise->wait(); - ilog("Exiting from signal ${n}", ("n", signal)); - node->shutdown_plugins(); - node->shutdown(); - delete node; - return 0; - } catch( const fc::exception& e ) { - // deleting the node can yield, so do this outside the exception handler - unhandled_exception = e; - } - - if (unhandled_exception) - { - elog("Exiting with error:\n${e}", ("e", unhandled_exception->to_detail_string())); - node->shutdown(); - delete node; - return 1; - } -} - -// logging config is too complicated to be parsed by boost::program_options, -// so we do it by hand -// -// Currently, you can only specify the filenames and logging levels, which -// are all most users would want to change. At a later time, options can -// be added to control rotation intervals, compression, and other seldom- -// used features -void write_default_logging_config_to_stream(std::ostream& out) -{ - out << "# declare an appender named \"stderr\" that writes messages to the console\n" - "[log.console_appender.stderr]\n" - "stream=std_error\n\n" - "# declare an appender named \"p2p\" that writes messages to p2p.log\n" - "[log.file_appender.p2p]\n" - "filename=logs/p2p/p2p.log\n" - "# filename can be absolute or relative to this config file\n\n" - "# route any messages logged to the default logger to the \"stderr\" logger we\n" - "# declared above, if they are info level are higher\n" - "[logger.default]\n" - "level=info\n" - "appenders=stderr\n\n" - "# route messages sent to the \"p2p\" logger to the p2p appender declared above\n" - "[logger.p2p]\n" - "level=info\n" - "appenders=p2p\n\n"; -} - -fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename) -{ - try - { - fc::logging_config logging_config; - bool found_logging_config = false; - - boost::property_tree::ptree config_ini_tree; - boost::property_tree::ini_parser::read_ini(config_ini_filename.preferred_string().c_str(), config_ini_tree); - for (const auto& section : config_ini_tree) - { - const std::string& section_name = section.first; - const boost::property_tree::ptree& section_tree = section.second; - - const std::string console_appender_section_prefix = "log.console_appender."; - const std::string file_appender_section_prefix = "log.file_appender."; - const std::string logger_section_prefix = "logger."; - - if (boost::starts_with(section_name, console_appender_section_prefix)) - { - std::string console_appender_name = section_name.substr(console_appender_section_prefix.length()); - std::string stream_name = section_tree.get("stream"); - - // construct a default console appender config here - // stdout/stderr will be taken from ini file, everything else hard-coded here - fc::console_appender::config console_appender_config; - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::debug, - fc::console_appender::color::green)); - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::warn, - fc::console_appender::color::brown)); - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::error, - fc::console_appender::color::cyan)); - console_appender_config.stream = fc::variant(stream_name).as(); - logging_config.appenders.push_back(fc::appender_config(console_appender_name, "console", fc::variant(console_appender_config))); - found_logging_config = true; - } - else if (boost::starts_with(section_name, file_appender_section_prefix)) - { - std::string file_appender_name = section_name.substr(file_appender_section_prefix.length()); - fc::path file_name = section_tree.get("filename"); - if (file_name.is_relative()) - file_name = fc::absolute(config_ini_filename).parent_path() / file_name; - - - // construct a default file appender config here - // filename will be taken from ini file, everything else hard-coded here - fc::file_appender::config file_appender_config; - file_appender_config.filename = file_name; - file_appender_config.flush = true; - file_appender_config.rotate = true; - file_appender_config.rotation_interval = fc::hours(1); - file_appender_config.rotation_limit = fc::days(1); - logging_config.appenders.push_back(fc::appender_config(file_appender_name, "file", fc::variant(file_appender_config))); - found_logging_config = true; - } - else if (boost::starts_with(section_name, logger_section_prefix)) - { - std::string logger_name = section_name.substr(logger_section_prefix.length()); - std::string level_string = section_tree.get("level"); - std::string appenders_string = section_tree.get("appenders"); - fc::logger_config logger_config(logger_name); - logger_config.level = fc::variant(level_string).as(); - boost::split(logger_config.appenders, appenders_string, - boost::is_any_of(" ,"), - boost::token_compress_on); - logging_config.loggers.push_back(logger_config); - found_logging_config = true; - } - } - if (found_logging_config) - return logging_config; - else - return fc::optional(); - } - FC_RETHROW_EXCEPTIONS(warn, "") -} diff --git a/programs/delayed_node/main.cpp b/programs/delayed_node/main.cpp index 74cd8fc3ad..32f6ef3090 100644 --- a/programs/delayed_node/main.cpp +++ b/programs/delayed_node/main.cpp @@ -160,6 +160,9 @@ int main(int argc, char** argv) { elog("Error parsing configuration file: ${e}", ("e", e.what())); return 1; } + if( !options.count("plugins") ) + options.insert( std::make_pair( "plugins", bpo::variable_value(std::string("delayed_node account_history market_history"), true) ) ); + node.initialize(data_dir, options); node.initialize_plugins( options ); diff --git a/programs/witness_node/CMakeLists.txt b/programs/witness_node/CMakeLists.txt index 0509a0af79..7cdb5c32f6 100644 --- a/programs/witness_node/CMakeLists.txt +++ b/programs/witness_node/CMakeLists.txt @@ -11,7 +11,7 @@ endif() # We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246 target_link_libraries( witness_node - PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) + PRIVATE graphene_app graphene_delayed_node graphene_account_history graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) install( TARGETS witness_node diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index ce48835374..b2b1bc89d8 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -24,8 +24,10 @@ #include #include +#include #include #include +#include #include #include @@ -42,6 +44,7 @@ #include #include #include +#include #include #include @@ -72,8 +75,10 @@ int main(int argc, char** argv) { bpo::variables_map options; auto witness_plug = node->register_plugin(); + auto debug_witness_plug = node->register_plugin(); auto history_plug = node->register_plugin(); auto market_history_plug = node->register_plugin(); + auto delayed_plug = node->register_plugin(); try { @@ -106,8 +111,18 @@ int main(int argc, char** argv) { fc::path config_ini_path = data_dir / "config.ini"; if( fc::exists(config_ini_path) ) { + boost::container::flat_set seen; + bpo::options_description unique_options("Graphene Witness Node"); + for( const boost::shared_ptr od : cfg_options.options() ) + { + const std::string name = od->long_name(); + if( seen.find(name) != seen.end() ) continue; + seen.insert(name); + unique_options.add( od ); + } + // get the basic options - bpo::store(bpo::parse_config_file(config_ini_path.preferred_string().c_str(), cfg_options, true), options); + bpo::store(bpo::parse_config_file(config_ini_path.preferred_string().c_str(), unique_options, true), options); // try to get logging options from the config file. try @@ -127,6 +142,7 @@ int main(int argc, char** argv) { if( !fc::exists(data_dir) ) fc::create_directories(data_dir); + boost::container::flat_set seen; std::ofstream out_cfg(config_ini_path.preferred_string()); for( const boost::shared_ptr od : cfg_options.options() ) { @@ -136,6 +152,9 @@ int main(int argc, char** argv) { if( !od->semantic()->apply_default(store) ) out_cfg << "# " << od->long_name() << " = \n"; else { + const std::string name = od->long_name(); + if( seen.find(name) != seen.end() ) continue; + seen.insert(name); auto example = od->format_parameter(); if( example.empty() ) // This is a boolean switch @@ -176,7 +195,7 @@ int main(int argc, char** argv) { exit_promise->set_value(signal); }, SIGTERM); - ilog("Started witness node on a chain with ${h} blocks.", ("h", node->chain_database()->head_block_num())); + ilog("Started BitShares node on a chain with ${h} blocks.", ("h", node->chain_database()->head_block_num())); ilog("Chain ID is ${id}", ("id", node->chain_database()->get_chain_id()) ); int signal = exit_promise->wait(); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index ff98c423ab..5cecd071e7 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -96,6 +96,8 @@ database_fixture::database_fixture() // app.initialize(); ahplugin->plugin_set_app(&app); ahplugin->plugin_initialize(options); + + options.insert(std::make_pair("bucket-size", boost::program_options::variable_value(string("[15]"),false))); mhplugin->plugin_set_app(&app); mhplugin->plugin_initialize(options); @@ -124,9 +126,6 @@ database_fixture::~database_fixture() verify_account_history_plugin_index(); BOOST_CHECK( db.get_node_properties().skip_flags == database::skip_nothing ); } - - if( data_dir ) - db.close(); return; } FC_CAPTURE_AND_RETHROW() } @@ -1058,6 +1057,24 @@ vector< operation_history_object > database_fixture::get_operation_history( acco return result; } +vector< graphene::market_history::order_history_object > database_fixture::get_market_order_history( asset_id_type a, asset_id_type b )const +{ + const auto& history_idx = db.get_index_type().indices().get(); + graphene::market_history::history_key hkey; + if( a > b ) std::swap(a,b); + hkey.base = a; + hkey.quote = b; + hkey.sequence = std::numeric_limits::min(); + auto itr = history_idx.lower_bound( hkey ); + vector result; + while( itr != history_idx.end()) + { + result.push_back( *itr ); + ++itr; + } + return result; +} + namespace test { void set_expiration( const database& db, transaction& tx ) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index ec5e9bd7d7..7fd0ad47ff 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -29,6 +29,7 @@ #include #include +#include #include @@ -281,6 +282,7 @@ struct database_fixture { 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; 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; }; namespace test { diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 2390a7c65f..66755a9114 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -51,22 +51,22 @@ BOOST_FIXTURE_TEST_SUITE( basic_tests, database_fixture ) */ BOOST_AUTO_TEST_CASE( valid_name_test ) { - BOOST_CHECK( !is_valid_name( "a" ) ); + BOOST_CHECK( is_valid_name( "a" ) ); BOOST_CHECK( !is_valid_name( "A" ) ); BOOST_CHECK( !is_valid_name( "0" ) ); BOOST_CHECK( !is_valid_name( "." ) ); BOOST_CHECK( !is_valid_name( "-" ) ); - BOOST_CHECK( !is_valid_name( "aa" ) ); + BOOST_CHECK( is_valid_name( "aa" ) ); BOOST_CHECK( !is_valid_name( "aA" ) ); - BOOST_CHECK( !is_valid_name( "a0" ) ); + BOOST_CHECK( is_valid_name( "a0" ) ); BOOST_CHECK( !is_valid_name( "a." ) ); BOOST_CHECK( !is_valid_name( "a-" ) ); BOOST_CHECK( is_valid_name( "aaa" ) ); BOOST_CHECK( !is_valid_name( "aAa" ) ); BOOST_CHECK( is_valid_name( "a0a" ) ); - BOOST_CHECK( !is_valid_name( "a.a" ) ); + BOOST_CHECK( is_valid_name( "a.a" ) ); BOOST_CHECK( is_valid_name( "a-a" ) ); BOOST_CHECK( is_valid_name( "aa0" ) ); @@ -97,9 +97,9 @@ BOOST_AUTO_TEST_CASE( valid_name_test ) BOOST_CHECK( is_valid_name( "aaa.bbb.ccc" ) ); BOOST_CHECK( is_valid_name( "aaa--bbb--ccc" ) ); - BOOST_CHECK( !is_valid_name( "xn--sandmnnchen-p8a.de" ) ); + BOOST_CHECK( is_valid_name( "xn--sandmnnchen-p8a.de" ) ); BOOST_CHECK( is_valid_name( "xn--sandmnnchen-p8a.dex" ) ); - BOOST_CHECK( !is_valid_name( "xn-sandmnnchen-p8a.de" ) ); + BOOST_CHECK( is_valid_name( "xn-sandmnnchen-p8a.de" ) ); BOOST_CHECK( is_valid_name( "xn-sandmnnchen-p8a.dex" ) ); BOOST_CHECK( is_valid_name( "this-label-has-less-than-64-char.acters-63-to-be-really-precise" ) ); diff --git a/tests/tests/database_tests.cpp b/tests/tests/database_tests.cpp index 5dc35f2767..18ea8e4077 100644 --- a/tests/tests/database_tests.cpp +++ b/tests/tests/database_tests.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * Copyright (c) 2017 Cryptonomex, Inc., and contributors. * * The MIT License * @@ -34,6 +34,8 @@ using namespace graphene::chain; +BOOST_FIXTURE_TEST_SUITE( database_tests, database_fixture ) + BOOST_AUTO_TEST_CASE( undo_test ) { try { @@ -59,3 +61,34 @@ BOOST_AUTO_TEST_CASE( undo_test ) throw; } } + +BOOST_AUTO_TEST_CASE( flat_index_test ) +{ + ACTORS((sam)); + const auto& bitusd = create_bitasset("USDBIT", sam.id); + update_feed_producers(bitusd, {sam.id}); + price_feed current_feed; + current_feed.settlement_price = bitusd.amount(100) / asset(100); + publish_feed(bitusd, sam, current_feed); + FC_ASSERT( bitusd.bitasset_data_id->instance == 0 ); + FC_ASSERT( !(*bitusd.bitasset_data_id)(db).current_feed.settlement_price.is_null() ); + try { + auto ses = db._undo_db.start_undo_session(); + const auto& obj1 = db.create( [&]( asset_bitasset_data_object& obj ){ + obj.settlement_fund = 17; + }); + FC_ASSERT( obj1.settlement_fund == 17 ); + throw std::string("Expected"); + // With flat_index, obj1 will not really be removed from the index + } catch ( const std::string& e ) + { // ignore + } + + // force maintenance + const auto& dynamic_global_props = db.get(dynamic_global_property_id_type()); + generate_blocks(dynamic_global_props.next_maintenance_time, true); + + FC_ASSERT( !(*bitusd.bitasset_data_id)(db).current_feed.settlement_price.is_null() ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/history_api_tests.cpp b/tests/tests/history_api_tests.cpp new file mode 100644 index 0000000000..1638bb0c7f --- /dev/null +++ b/tests/tests/history_api_tests.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; +using namespace graphene::app; +BOOST_FIXTURE_TEST_SUITE( history_api_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE(get_account_history) { + try { + graphene::app::history_api hist_api(app); + + //account_id_type() do 3 ops + create_bitasset("USD", account_id_type()); + create_account("dan"); + create_account("bob"); + + generate_block(); + fc::usleep(fc::milliseconds(2000)); + + int asset_create_op_id = operation::tag::value; + int account_create_op_id = operation::tag::value; + + //account_id_type() did 3 ops and includes id0 + vector histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 3); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 0); + BOOST_CHECK_EQUAL(histories[2].op.which(), asset_create_op_id); + + // 1 account_create op larger than id1 + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1); + BOOST_CHECK(histories[0].id.instance() != 0); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + // Limit 2 returns 2 result + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 2, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 2); + BOOST_CHECK(histories[1].id.instance() != 0); + BOOST_CHECK_EQUAL(histories[1].op.which(), account_create_op_id); + // bob has 1 op + histories = hist_api.get_account_history(get_account("bob").id, operation_history_id_type(), 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + } catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(get_account_history_operations) { + try { + graphene::app::history_api hist_api(app); + + //account_id_type() do 3 ops + create_bitasset("CNY", account_id_type()); + create_account("sam"); + create_account("alice"); + + generate_block(); + fc::usleep(fc::milliseconds(2000)); + + int asset_create_op_id = operation::tag::value; + int account_create_op_id = operation::tag::value; + + //account_id_type() did 1 asset_create op + vector histories = hist_api.get_account_history_operations(account_id_type(), asset_create_op_id, operation_history_id_type(), operation_history_id_type(), 100); + BOOST_CHECK_EQUAL(histories.size(), 1); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 0); + BOOST_CHECK_EQUAL(histories[0].op.which(), asset_create_op_id); + + //account_id_type() did 2 account_create ops + histories = hist_api.get_account_history_operations(account_id_type(), account_create_op_id, operation_history_id_type(), operation_history_id_type(), 100); + BOOST_CHECK_EQUAL(histories.size(), 2); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + // No asset_create op larger than id1 + histories = hist_api.get_account_history_operations(account_id_type(), asset_create_op_id, operation_history_id_type(), operation_history_id_type(1), 100); + BOOST_CHECK_EQUAL(histories.size(), 0); + + // Limit 1 returns 1 result + histories = hist_api.get_account_history_operations(account_id_type(), account_create_op_id, operation_history_id_type(),operation_history_id_type(), 1); + BOOST_CHECK_EQUAL(histories.size(), 1); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + // alice has 1 op + histories = hist_api.get_account_history_operations(get_account("alice").id, account_create_op_id, operation_history_id_type(),operation_history_id_type(), 100); + BOOST_CHECK_EQUAL(histories.size(), 1); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + } catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 7b3867d7e5..59c3d8b18b 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include "../common/database_fixture.hpp" @@ -1196,14 +1197,14 @@ BOOST_AUTO_TEST_CASE( witness_feeds ) /** * Create an order such that when the trade executes at the * requested price the resulting payout to one party is 0 - * - * I am unable to actually create such an order; I'm not sure it's possible. What I have done is create an order which - * broke an assert in the matching algorithm. */ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) { try { INVOKE(issue_uia); + generate_blocks( HARDFORK_555_TIME ); + set_expiration( db, trx ); + const asset_object& test = get_asset( "TEST" ); const asset_object& core = get_asset( GRAPHENE_SYMBOL ); const account_object& core_seller = create_account( "shorter1" ); @@ -1216,10 +1217,26 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0); BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000); - //ilog( "=================================== START===================================\n\n"); - create_sell_order(core_seller, core.amount(1), test.amount(900000)); - //ilog( "=================================== STEP===================================\n\n"); - create_sell_order(core_buyer, test.amount(900001), core.amount(1)); + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_buyer, test.amount(3), core.amount(1)); + + BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1); + BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999997); + BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998); + BOOST_CHECK_EQUAL(get_balance(core_seller, test), 3); + + generate_block(); + fc::usleep(fc::milliseconds(1000)); + + //TODO: This will fail because of something-for-nothing bug(#345) + // Must be fixed with a hardfork + auto result = get_market_order_history(core.id, test.id); + BOOST_CHECK_EQUAL(result.size(), 2); + BOOST_CHECK(result[0].op.pays == core.amount(1)); + BOOST_CHECK(result[0].op.receives == test.amount(2)); + BOOST_CHECK(result[1].op.pays == test.amount(2)); + BOOST_CHECK(result[1].op.receives == core.amount(1)); } catch( const fc::exception& e) { edump((e.to_detail_string())); throw; diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 607f7a044d..0f99536c88 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -25,16 +25,12 @@ #include #include -#include #include -#include -#include #include #include #include #include -#include #include #include #include @@ -50,7 +46,112 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( operation_tests, database_fixture ) -BOOST_AUTO_TEST_CASE( withdraw_permission_create ) +/*** + * A descriptor of a particular withdrawal period + */ +struct withdrawal_period_descriptor { + withdrawal_period_descriptor(const time_point_sec start, const time_point_sec end, const asset available, const asset claimed) + : period_start_time(start), period_end_time(end), available_this_period(available), claimed_this_period(claimed) {} + + // Start of period + time_point_sec period_start_time; + + // End of period + time_point_sec period_end_time; + + // Quantify how much is still available to be withdrawn during this period + asset available_this_period; + + // Quantify how much has already been claimed during this period + asset claimed_this_period; + + string const to_string() const { + string asset_id = fc::to_string(available_this_period.asset_id.space_id) + + "." + fc::to_string(available_this_period.asset_id.type_id) + + "." + fc::to_string(available_this_period.asset_id.instance.value); + string text = fc::to_string(available_this_period.amount.value) + + " " + asset_id + + " is available from " + period_start_time.to_iso_string() + + " to " + period_end_time.to_iso_string(); + return text; + } +}; + + +/*** + * Get a description of the current withdrawal period + * @param current_time Current time + * @return A description of the current period + */ +withdrawal_period_descriptor current_period(const withdraw_permission_object& permit, fc::time_point_sec current_time) { + // @todo [6] Is there a potential race condition where a call to available_this_period might become out of sync with this function's later use of period start time? + asset available = permit.available_this_period(current_time); + asset claimed = asset(permit.withdrawal_limit.amount - available.amount, permit.withdrawal_limit.asset_id); + auto periods = (current_time - permit.period_start_time).to_seconds() / permit.withdrawal_period_sec; + time_point_sec current_period_start = permit.period_start_time + (periods * permit.withdrawal_period_sec); + time_point_sec current_period_end = current_period_start + permit.withdrawal_period_sec; + withdrawal_period_descriptor descriptor = withdrawal_period_descriptor(current_period_start, current_period_end, available, claimed); + + return descriptor; +} + +/** + * This auxiliary test is used for two purposes: + * (a) it checks the creation of withdrawal claims, + * (b) it is used as a precursor for tests that evaluate withdrawal claims. + * + * NOTE: This test verifies proper withdrawal claim behavior + * as it occurred before (for backward compatibility) + * Issue #23 was addressed. + * That issue is concerned with ensuring that the first claim + * can occur before the first withdrawal period. + */ +BOOST_AUTO_TEST_CASE( withdraw_permission_create_before_hardfork_23 ) +{ try { + auto nathan_private_key = generate_private_key("nathan"); + auto dan_private_key = generate_private_key("dan"); + account_id_type nathan_id = create_account("nathan", nathan_private_key.get_public_key()).id; + account_id_type dan_id = create_account("dan", dan_private_key.get_public_key()).id; + + transfer(account_id_type(), nathan_id, asset(1000)); + generate_block(); + set_expiration( db, trx ); + + { + withdraw_permission_create_operation op; + op.authorized_account = dan_id; + op.withdraw_from_account = nathan_id; + op.withdrawal_limit = asset(5); + op.withdrawal_period_sec = fc::hours(1).to_seconds(); + op.periods_until_expiration = 5; + op.period_start_time = db.head_block_time() + db.get_global_properties().parameters.block_interval*5; // 5 blocks after current blockchain time + trx.operations.push_back(op); + REQUIRE_OP_VALIDATION_FAILURE(op, withdrawal_limit, asset()); + REQUIRE_OP_VALIDATION_FAILURE(op, periods_until_expiration, 0); + REQUIRE_OP_VALIDATION_FAILURE(op, withdraw_from_account, dan_id); + REQUIRE_OP_VALIDATION_FAILURE(op, withdrawal_period_sec, 0); + REQUIRE_THROW_WITH_VALUE(op, withdrawal_limit, asset(10, asset_id_type(10))); + REQUIRE_THROW_WITH_VALUE(op, authorized_account, account_id_type(1000)); + REQUIRE_THROW_WITH_VALUE(op, period_start_time, fc::time_point_sec(10000)); + REQUIRE_THROW_WITH_VALUE(op, withdrawal_period_sec, 1); + trx.operations.back() = op; + } + sign( trx, nathan_private_key ); + db.push_transaction( trx ); + trx.clear(); +} FC_LOG_AND_RETHROW() } + +/** + * This auxiliary test is used for two purposes: + * (a) it checks the creation of withdrawal claims, + * (b) it is used as a precursor for tests that evaluate withdrawal claims. + * + * NOTE: This test verifies proper withdrawal claim behavior + * as it should occur after Issue #23 is addressed. + * That issue is concerned with ensuring that the first claim + * can occur before the first withdrawal period. + */ +BOOST_AUTO_TEST_CASE( withdraw_permission_create_after_hardfork_23 ) { try { auto nathan_private_key = generate_private_key("nathan"); auto dan_private_key = generate_private_key("dan"); @@ -68,7 +169,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_create ) op.withdrawal_limit = asset(5); op.withdrawal_period_sec = fc::hours(1).to_seconds(); op.periods_until_expiration = 5; - op.period_start_time = db.head_block_time() + db.get_global_properties().parameters.block_interval*5; + op.period_start_time = HARDFORK_23_TIME + db.get_global_properties().parameters.block_interval*5; // 5 blocks after fork time trx.operations.push_back(op); REQUIRE_OP_VALIDATION_FAILURE(op, withdrawal_limit, asset()); REQUIRE_OP_VALIDATION_FAILURE(op, periods_until_expiration, 0); @@ -85,9 +186,183 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_create ) trx.clear(); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( withdraw_permission_test ) +/** + * Test the claims of withdrawals both before and during + * authorized withdrawal periods. + * NOTE: The simulated elapse of blockchain time through the use of + * generate_blocks() must be carefully used in order to simulate + * this test. + * NOTE: This test verifies proper withdrawal claim behavior + * as it occurred before (for backward compatibility) + * Issue #23 was addressed. + * That issue is concerned with ensuring that the first claim + * can occur before the first withdrawal period. + */ +BOOST_AUTO_TEST_CASE( withdraw_permission_test_before_hardfork_23 ) +{ try { + INVOKE(withdraw_permission_create_before_hardfork_23); + + auto nathan_private_key = generate_private_key("nathan"); + auto dan_private_key = generate_private_key("dan"); + account_id_type nathan_id = get_account("nathan").id; + account_id_type dan_id = get_account("dan").id; + withdraw_permission_id_type permit; + set_expiration( db, trx ); + + fc::time_point_sec first_start_time; + { + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(permit_object.authorized_account == dan_id); + BOOST_CHECK(permit_object.withdraw_from_account == nathan_id); + BOOST_CHECK(permit_object.period_start_time > db.head_block_time()); + first_start_time = permit_object.period_start_time; + BOOST_CHECK(permit_object.withdrawal_limit == asset(5)); + BOOST_CHECK(permit_object.withdrawal_period_sec == fc::hours(1).to_seconds()); + BOOST_CHECK(permit_object.expiration == first_start_time + permit_object.withdrawal_period_sec*5 ); + } + + generate_blocks(2); // Still before the first period, but BEFORE the real time during which "early" claims are checked + + { + withdraw_permission_claim_operation op; + op.withdraw_permission = permit; + op.withdraw_from_account = nathan_id; + op.withdraw_to_account = dan_id; + op.amount_to_withdraw = asset(1); + set_expiration( db, trx ); + + trx.operations.push_back(op); + sign( trx, dan_private_key ); // Transaction should be signed to be valid + + // This operation/transaction will be pushed early/before the first + // withdrawal period + // However, this will not cause an exception prior to HARDFORK_23_TIME + // because withdrawaing before that the first period was acceptable + // before the fix + PUSH_TX( db, trx ); // <-- Claim #1 + + + //Get to the actual withdrawal period + bool miss_intermediate_blocks = false; // Required to have generate_blocks() elapse flush to the time of interest + generate_blocks(first_start_time, miss_intermediate_blocks); + set_expiration( db, trx ); + + REQUIRE_THROW_WITH_VALUE(op, withdraw_permission, withdraw_permission_id_type(5)); + REQUIRE_THROW_WITH_VALUE(op, withdraw_from_account, dan_id); + REQUIRE_THROW_WITH_VALUE(op, withdraw_from_account, account_id_type()); + REQUIRE_THROW_WITH_VALUE(op, withdraw_to_account, nathan_id); + REQUIRE_THROW_WITH_VALUE(op, withdraw_to_account, account_id_type()); + REQUIRE_THROW_WITH_VALUE(op, amount_to_withdraw, asset(10)); + REQUIRE_THROW_WITH_VALUE(op, amount_to_withdraw, asset(6)); + set_expiration( db, trx ); + trx.clear(); + trx.operations.push_back(op); + sign( trx, dan_private_key ); + PUSH_TX( db, trx ); // <-- Claim #2 + + // would be legal on its own, but doesn't work because trx already withdrew + REQUIRE_THROW_WITH_VALUE(op, amount_to_withdraw, asset(5)); + + // Make sure we can withdraw again this period, as long as we're not exceeding the periodic limit + trx.clear(); + // withdraw 1 + trx.operations = {op}; + // make it different from previous trx so it's non-duplicate + trx.expiration += fc::seconds(1); + sign( trx, dan_private_key ); + PUSH_TX( db, trx ); // <-- Claim #3 + trx.clear(); + } + + // Account for three (3) claims of one (1) unit + BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 997); + BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 3); + + { + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(permit_object.authorized_account == dan_id); + BOOST_CHECK(permit_object.withdraw_from_account == nathan_id); + BOOST_CHECK(permit_object.period_start_time == first_start_time); + BOOST_CHECK(permit_object.withdrawal_limit == asset(5)); + BOOST_CHECK(permit_object.withdrawal_period_sec == fc::hours(1).to_seconds()); + BOOST_CHECK_EQUAL(permit_object.claimed_this_period.value, 3 ); // <-- Account for three (3) claims of one (1) unit + BOOST_CHECK(permit_object.expiration == first_start_time + 5*permit_object.withdrawal_period_sec); + generate_blocks(first_start_time + permit_object.withdrawal_period_sec); + // lazy update: verify period_start_time isn't updated until new trx occurs + BOOST_CHECK(permit_object.period_start_time == first_start_time); + } + + { + // Leave Nathan with one unit + transfer(nathan_id, dan_id, asset(996)); + + // Attempt a withdrawal claim for units than available + withdraw_permission_claim_operation op; + op.withdraw_permission = permit; + op.withdraw_from_account = nathan_id; + op.withdraw_to_account = dan_id; + op.amount_to_withdraw = asset(5); + trx.operations.push_back(op); + set_expiration( db, trx ); + sign( trx, dan_private_key ); + //Throws because nathan doesn't have the money + GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception); + + // Attempt a withdrawal claim for which nathan does have sufficient units + op.amount_to_withdraw = asset(1); + trx.clear(); + trx.operations = {op}; + set_expiration( db, trx ); + sign( trx, dan_private_key ); + PUSH_TX( db, trx ); + } + + BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 0); + BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 1000); + trx.clear(); + transfer(dan_id, nathan_id, asset(1000)); + + { + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(permit_object.authorized_account == dan_id); + BOOST_CHECK(permit_object.withdraw_from_account == nathan_id); + BOOST_CHECK(permit_object.period_start_time == first_start_time + permit_object.withdrawal_period_sec); + BOOST_CHECK(permit_object.expiration == first_start_time + 5*permit_object.withdrawal_period_sec); + BOOST_CHECK(permit_object.withdrawal_limit == asset(5)); + BOOST_CHECK(permit_object.withdrawal_period_sec == fc::hours(1).to_seconds()); + generate_blocks(permit_object.expiration); + } + // Ensure the permit object has been garbage collected + BOOST_CHECK(db.find_object(permit) == nullptr); + + { + withdraw_permission_claim_operation op; + op.withdraw_permission = permit; + op.withdraw_from_account = nathan_id; + op.withdraw_to_account = dan_id; + op.amount_to_withdraw = asset(5); + trx.operations.push_back(op); + set_expiration( db, trx ); + sign( trx, dan_private_key ); + //Throws because the permission has expired + GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception); + } + } FC_LOG_AND_RETHROW() } + +/** + * Test the claims of withdrawals both before and during + * authorized withdrawal periods. + * NOTE: The simulated elapse of blockchain time through the use of + * generate_blocks() must be carefully used in order to simulate + * this test. + * NOTE: This test verifies proper withdrawal claim behavior + * as it should occur after Issue #23 is addressed. + * That issue is concerned with ensuring that the first claim + * can occur before the first withdrawal period. + */ +BOOST_AUTO_TEST_CASE( withdraw_permission_test_after_hardfork_23 ) { try { - INVOKE(withdraw_permission_create); + INVOKE(withdraw_permission_create_after_hardfork_23); auto nathan_private_key = generate_private_key("nathan"); auto dan_private_key = generate_private_key("dan"); @@ -108,7 +383,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) BOOST_CHECK(permit_object.expiration == first_start_time + permit_object.withdrawal_period_sec*5 ); } - generate_blocks(2); + generate_blocks(HARDFORK_23_TIME); // Still before the first period, but DURING the real time during which "early" claims are checked { withdraw_permission_claim_operation op; @@ -119,10 +394,13 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) set_expiration( db, trx ); trx.operations.push_back(op); + sign( trx, dan_private_key ); // Transaction should be signed to be valid //Throws because we haven't entered the first withdrawal period yet. GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx ), fc::exception); //Get to the actual withdrawal period - generate_blocks(permit(db).period_start_time); + bool miss_intermediate_blocks = false; // Required to have generate_blocks() elapse flush to the time of interest + generate_blocks(first_start_time, miss_intermediate_blocks); + set_expiration( db, trx ); REQUIRE_THROW_WITH_VALUE(op, withdraw_permission, withdraw_permission_id_type(5)); REQUIRE_THROW_WITH_VALUE(op, withdraw_from_account, dan_id); @@ -135,7 +413,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) trx.clear(); trx.operations.push_back(op); sign( trx, dan_private_key ); - PUSH_TX( db, trx ); + PUSH_TX( db, trx ); // <-- Claim #1 // would be legal on its own, but doesn't work because trx already withdrew REQUIRE_THROW_WITH_VALUE(op, amount_to_withdraw, asset(5)); @@ -147,10 +425,11 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) // make it different from previous trx so it's non-duplicate trx.expiration += fc::seconds(1); sign( trx, dan_private_key ); - PUSH_TX( db, trx ); + PUSH_TX( db, trx ); // <-- Claim #2 trx.clear(); } + // Account for two (2) claims of one (1) unit BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 998); BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 2); @@ -161,7 +440,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) BOOST_CHECK(permit_object.period_start_time == first_start_time); BOOST_CHECK(permit_object.withdrawal_limit == asset(5)); BOOST_CHECK(permit_object.withdrawal_period_sec == fc::hours(1).to_seconds()); - BOOST_CHECK_EQUAL(permit_object.claimed_this_period.value, 2 ); + BOOST_CHECK_EQUAL(permit_object.claimed_this_period.value, 2 ); // <-- Account for two (2) claims of one (1) unit BOOST_CHECK(permit_object.expiration == first_start_time + 5*permit_object.withdrawal_period_sec); generate_blocks(first_start_time + permit_object.withdrawal_period_sec); // lazy update: verify period_start_time isn't updated until new trx occurs @@ -169,7 +448,10 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) } { + // Leave Nathan with one unit transfer(nathan_id, dan_id, asset(997)); + + // Attempt a withdrawal claim for units than available withdraw_permission_claim_operation op; op.withdraw_permission = permit; op.withdraw_from_account = nathan_id; @@ -180,6 +462,8 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) sign( trx, dan_private_key ); //Throws because nathan doesn't have the money GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception); + + // Attempt a withdrawal claim for which nathan does have sufficient units op.amount_to_withdraw = asset(1); trx.clear(); trx.operations = {op}; @@ -222,7 +506,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_test ) BOOST_AUTO_TEST_CASE( withdraw_permission_nominal_case ) { try { - INVOKE(withdraw_permission_create); + INVOKE(withdraw_permission_create_before_hardfork_23); auto nathan_private_key = generate_private_key("nathan"); auto dan_private_key = generate_private_key("dan"); @@ -230,6 +514,12 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_nominal_case ) account_id_type dan_id = get_account("dan").id; withdraw_permission_id_type permit; + // Wait until the permission period's start time + const withdraw_permission_object& first_permit_object = permit(db); + generate_blocks( + first_permit_object.period_start_time); + + // Loop through the withdrawal periods and claim a withdrawal while(true) { const withdraw_permission_object& permit_object = permit(db); @@ -247,6 +537,8 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_nominal_case ) // if no further withdrawals are possible BOOST_CHECK(db.find_object(permit) != nullptr); BOOST_CHECK( permit_object.claimed_this_period == 5 ); + BOOST_CHECK_EQUAL( permit_object.available_this_period(db.head_block_time()).amount.value, 0 ); + BOOST_CHECK_EQUAL( current_period(permit_object, db.head_block_time()).available_this_period.amount.value, 0 ); trx.clear(); generate_blocks( permit_object.period_start_time @@ -259,9 +551,228 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_nominal_case ) BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 25); } FC_LOG_AND_RETHROW() } + +/** + * This case checks to see whether the amount claimed within any particular withdrawal period + * is properly reflected within the permission object. + * The maximum withdrawal per period will be limited to 5 units. + * There are a total of 5 withdrawal periods that are permitted. + * The test will evaluate the following withdrawal pattern: + * (1) during Period 1, a withdrawal of 4 units, + * (2) during Period 2, a withdrawal of 1 units, + * (3) during Period 3, a withdrawal of 0 units, + * (4) during Period 4, a withdrawal of 5 units, + * (5) during Period 5, a withdrawal of 3 units. + * + * Total withdrawal will be 13 units. + */ +BOOST_AUTO_TEST_CASE( withdraw_permission_incremental_case ) +{ try { + INVOKE(withdraw_permission_create_after_hardfork_23); + time_point_sec expected_first_period_start_time = HARDFORK_23_TIME + db.get_global_properties().parameters.block_interval*5; // Hard-coded to synchronize with withdraw_permission_create_after_hardfork_23() + uint64_t expected_period_duration_seconds = fc::hours(1).to_seconds(); // Hard-coded to synchronize with withdraw_permission_create_after_hardfork_23() + + auto nathan_private_key = generate_private_key("nathan"); + auto dan_private_key = generate_private_key("dan"); + account_id_type nathan_id = get_account("nathan").id; + account_id_type dan_id = get_account("dan").id; + withdraw_permission_id_type permit; + + // Wait until the permission period's start time + { + const withdraw_permission_object &before_first_permit_object = permit(db); + BOOST_CHECK_EQUAL(before_first_permit_object.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch()); + generate_blocks( + before_first_permit_object.period_start_time); + } + // Before withdrawing, check the period description + const withdraw_permission_object &first_permit_object = permit(db); + const withdrawal_period_descriptor first_period = current_period(first_permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(first_period.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch()); + BOOST_CHECK_EQUAL(first_period.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + expected_period_duration_seconds); + BOOST_CHECK_EQUAL(first_period.available_this_period.amount.value, 5); + + // Period 1: Withdraw 4 units + { + // Before claiming, check the period description + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(db.find_object(permit) != nullptr); + withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 0)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1)); + + // Claim + withdraw_permission_claim_operation op; + op.withdraw_permission = permit; + op.withdraw_from_account = nathan_id; + op.withdraw_to_account = dan_id; + op.amount_to_withdraw = asset(4); + trx.operations.push_back(op); + set_expiration( db, trx ); + sign( trx, dan_private_key ); + PUSH_TX( db, trx ); + + // After claiming, check the period description + BOOST_CHECK(db.find_object(permit) != nullptr); + BOOST_CHECK( permit_object.claimed_this_period == 4 ); + BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 4 ); + period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 1); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 0)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1)); + + // Advance to next period + trx.clear(); + generate_blocks( + permit_object.period_start_time + + permit_object.withdrawal_period_sec ); + } + + // Period 2: Withdraw 1 units + { + // Before claiming, check the period description + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(db.find_object(permit) != nullptr); + withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2)); + + // Claim + withdraw_permission_claim_operation op; + op.withdraw_permission = permit; + op.withdraw_from_account = nathan_id; + op.withdraw_to_account = dan_id; + op.amount_to_withdraw = asset(1); + trx.operations.push_back(op); + set_expiration( db, trx ); + sign( trx, dan_private_key ); + PUSH_TX( db, trx ); + + // After claiming, check the period description + BOOST_CHECK(db.find_object(permit) != nullptr); + BOOST_CHECK( permit_object.claimed_this_period == 1 ); + BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 1 ); + period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 4); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2)); + + // Advance to next period + trx.clear(); + generate_blocks( + permit_object.period_start_time + + permit_object.withdrawal_period_sec ); + } + + // Period 3: Withdraw 0 units + { + // Before claiming, check the period description + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(db.find_object(permit) != nullptr); + withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3)); + + // No claim + + // After doing nothing, check the period description + period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3)); + + // Advance to end of Period 3 + time_point_sec period_end_time = period_descriptor.period_end_time; + generate_blocks(period_end_time); + } + + // Period 4: Withdraw 5 units + { + // Before claiming, check the period description + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(db.find_object(permit) != nullptr); + withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4)); + + // Claim + withdraw_permission_claim_operation op; + op.withdraw_permission = permit; + op.withdraw_from_account = nathan_id; + op.withdraw_to_account = dan_id; + op.amount_to_withdraw = asset(5); + trx.operations.push_back(op); + set_expiration( db, trx ); + sign( trx, dan_private_key ); + PUSH_TX( db, trx ); + + // After claiming, check the period description + BOOST_CHECK(db.find_object(permit) != nullptr); + BOOST_CHECK( permit_object.claimed_this_period == 5 ); + BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 5 ); + period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 0); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4)); + + // Advance to next period + trx.clear(); + generate_blocks( + permit_object.period_start_time + + permit_object.withdrawal_period_sec ); + } + + // Period 5: Withdraw 3 units + { + // Before claiming, check the period description + const withdraw_permission_object& permit_object = permit(db); + BOOST_CHECK(db.find_object(permit) != nullptr); + withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 5)); + + // Claim + withdraw_permission_claim_operation op; + op.withdraw_permission = permit; + op.withdraw_from_account = nathan_id; + op.withdraw_to_account = dan_id; + op.amount_to_withdraw = asset(3); + trx.operations.push_back(op); + set_expiration( db, trx ); + sign( trx, dan_private_key ); + PUSH_TX( db, trx ); + + // After claiming, check the period description + BOOST_CHECK(db.find_object(permit) != nullptr); + BOOST_CHECK( permit_object.claimed_this_period == 3 ); + BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 3 ); + period_descriptor = current_period(permit_object, db.head_block_time()); + BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 2); + BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4)); + BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 5)); + + // Advance to next period + trx.clear(); + generate_blocks( + permit_object.period_start_time + + permit_object.withdrawal_period_sec ); + } + + // Withdrawal periods completed + BOOST_CHECK(db.find_object(permit) == nullptr); + + BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 987); + BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 13); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE( withdraw_permission_update ) { try { - INVOKE(withdraw_permission_create); + INVOKE(withdraw_permission_create_before_hardfork_23); auto nathan_private_key = generate_private_key("nathan"); account_id_type nathan_id = get_account("nathan").id; diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index d6dc83cb17..4004b20998 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -449,6 +449,9 @@ BOOST_AUTO_TEST_CASE( asset_name_test ) GRAPHENE_REQUIRE_THROW( create_user_issued_asset( "ALPHA", alice_id(db), 0 ), fc::exception ); BOOST_CHECK( has_asset("ALPHA") ); BOOST_CHECK( !has_asset("ALPHA.ONE") ); + generate_blocks( HARDFORK_385_TIME ); + generate_block(); + // Bob can't create ALPHA.ONE GRAPHENE_REQUIRE_THROW( create_user_issued_asset( "ALPHA.ONE", bob_id(db), 0 ), fc::exception ); BOOST_CHECK( has_asset("ALPHA") ); BOOST_CHECK( !has_asset("ALPHA.ONE") );