diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a9b8554c..19bbc9e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,36 +1,28 @@ stages: - - pull - build - test -before_script: - - cd /var/www/Projects/595.peerplays/blockchain - -pulljob: - stage: pull - script: - - git pull origin master - only: - - master - tags: - - pp-dev - -buildjob: +build: stage: build script: + - git submodule update --init --recursive - cmake . - - make - only: - - master - tags: - - pp-dev - -testjob: + - make -j$(nproc) + artifacts: + untracked: true + paths: + - libraries/ + - programs/ + - tests/ + tags: + - builder + +test: stage: test + dependencies: + - build script: + - ./tests/betting_test - ./tests/chain_test - - ./tests/tournament_test - only: - - master - tags: - - pp-dev \ No newline at end of file + tags: + - builder \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 595e1cc0..d7b01087 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,7 @@ else( WIN32 ) # Apple AND Linux endif( APPLE ) if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp -Wno-class-memaccess -Wno-parentheses -Wno-terminate -Wno-invalid-offsetof" ) elseif( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) if( CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.0.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0.0 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-partial-specialization" ) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 0cb6ae0d..318ad821 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -550,26 +550,32 @@ namespace graphene { namespace app { unsigned limit, operation_history_id_type start ) const { - FC_ASSERT( _app.chain_database() ); - const auto& db = *_app.chain_database(); - FC_ASSERT( limit <= 100 ); - vector result; - const auto& stats = account(db).statistics(db); - if( stats.most_recent_op == account_transaction_history_id_type() ) return result; - const account_transaction_history_object* node = &stats.most_recent_op(db); - if( start == operation_history_id_type() ) - start = node->operation_id; + FC_ASSERT( _app.chain_database() ); + const auto& db = *_app.chain_database(); + FC_ASSERT( limit <= 100 ); + vector result; + try { + const account_transaction_history_object& node = account(db).statistics(db).most_recent_op(db); + if(start == operation_history_id_type() || start.instance.value > node.operation_id.instance.value) + start = node.operation_id; + } catch(...) { return result; } - while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit) - { - if( node->operation_id.instance.value <= start.instance.value ) - result.push_back( node->operation_id(db) ); - if( node->next == account_transaction_history_id_type() ) - node = nullptr; - else node = &node->next(db); - } + const auto& hist_idx = db.get_index_type(); + const auto& by_op_idx = hist_idx.indices().get(); + auto index_start = by_op_idx.begin(); + auto itr = by_op_idx.lower_bound(boost::make_tuple(account, start)); - return result; + while(itr != index_start && itr->account == account && itr->operation_id.instance.value > stop.instance.value && result.size() < limit) + { + if(itr->operation_id.instance.value <= start.instance.value) + result.push_back(itr->operation_id(db)); + --itr; + } + if(stop.instance.value == 0 && result.size() < limit && itr->account == account) { + result.push_back(itr->operation_id(db)); + } + + return result; } vector history_api::get_account_history_operations( account_id_type account, @@ -594,11 +600,16 @@ namespace graphene { namespace app { if(node->operation_id(db).op.which() == operation_id) result.push_back( node->operation_id(db) ); - } + } if( node->next == account_transaction_history_id_type() ) node = nullptr; else node = &node->next(db); } + if( stop.instance.value == 0 && result.size() < limit ) { + auto head = db.find(account_transaction_history_id_type()); + if (head != nullptr && 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 1a0b8b65..75cb95fd 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -226,7 +226,7 @@ namespace detail { void new_connection( const fc::http::websocket_connection_ptr& c ) { - auto wsc = std::make_shared(*c); + auto wsc = std::make_shared(c); auto login = std::make_shared( std::ref(*_self) ); login->enable_api("database_api"); diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 8dd52e08..9aebc8f7 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -554,7 +554,7 @@ vector> database_api_impl::get_key_references( vectorsecond.size() ); + result.reserve( result.size() + itr->second.size() ); for( auto item : itr->second ) { wdump((a)(item)(item(_db).name)); @@ -565,7 +565,7 @@ vector> database_api_impl::get_key_references( vectorsecond.size() ); + result.reserve( result.size() + itr->second.size() ); for( auto item : itr->second ) result.push_back(item); } final_result.emplace_back( std::move(result) ); diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 90d97692..c19eedb6 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -119,9 +119,9 @@ set account_member_index::get_account_members(const account_obj result.insert(auth.first); return result; } -set account_member_index::get_key_members(const account_object& a)const +set account_member_index::get_key_members(const account_object& a)const { - set result; + set result; for( auto auth : a.owner.key_auths ) result.insert(auth.first); for( auto auth : a.active.key_auths ) @@ -213,7 +213,7 @@ void account_member_index::object_modified(const object& after) { - set after_key_members = get_key_members(a); + set after_key_members = get_key_members(a); vector removed; removed.reserve(before_key_members.size()); std::set_difference(before_key_members.begin(), before_key_members.end(), diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index ad4ad1a0..e7a11f3f 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -611,7 +611,8 @@ void database::_apply_block( const signed_block& next_block ) if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) update_witness_schedule(next_block); - update_global_dynamic_data(next_block); + const uint32_t missed = update_witness_missed_blocks( next_block ); + update_global_dynamic_data( next_block, missed ); update_signing_witness(signing_witness, next_block); update_last_irreversible_block(); @@ -670,9 +671,14 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx auto& trx_idx = get_mutable_index_type(); const chain_id_type& chain_id = get_chain_id(); - auto trx_id = trx.id(); - FC_ASSERT( (skip & skip_transaction_dupe_check) || - trx_idx.indices().get().find(trx_id) == trx_idx.indices().get().end() ); + transaction_id_type trx_id; + + if( !(skip & skip_transaction_dupe_check) ) + { + trx_id = trx.id(); + FC_ASSERT( trx_idx.indices().get().find(trx_id) == trx_idx.indices().get().end() ); + } + transaction_evaluation_state eval_state(this); const chain_parameters& chain_parameters = get_global_properties().parameters; eval_state._trx = &trx; @@ -706,7 +712,7 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx //Insert transaction into unique transactions database. if( !(skip & skip_transaction_dupe_check) ) { - create([&](transaction_object& transaction) { + create([&trx_id,&trx](transaction_object& transaction) { transaction.trx_id = trx_id; transaction.trx = trx; }); diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 61f23db3..029a55d4 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -175,7 +175,9 @@ void database::reindex( fc::path data_dir ) void database::wipe(const fc::path& data_dir, bool include_blocks) { ilog("Wiping database", ("include_blocks", include_blocks)); - close(); + if (_opened) { + close(); + } object_database::wipe(data_dir); if( include_blocks ) fc::remove_all( data_dir / "database" ); @@ -221,12 +223,16 @@ void database::open( ("last_block->id", last_block)("head_block_id",head_block_num()) ); reindex( data_dir ); } + _opened = true; } FC_CAPTURE_LOG_AND_RETHROW( (data_dir) ) } void database::close(bool rewind) { + if (!_opened) + return; + // TODO: Save pending tx's on close() clear_pending(); @@ -263,6 +269,8 @@ void database::close(bool rewind) _block_id_to_block.close(); _fork_db.reset(); + + _opened = false; } void database::force_slow_replays() diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index ad98837e..7df02a39 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -43,43 +43,13 @@ namespace graphene { namespace chain { -void database::update_global_dynamic_data( const signed_block& b ) +void database::update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks ) { const dynamic_global_property_object& _dgp = dynamic_global_property_id_type(0)(*this); const global_property_object& gpo = get_global_properties(); - uint32_t missed_blocks = get_slot_at_time( b.timestamp ); - -//#define DIRTY_TRICK // problem with missed_blocks can occur when "maintenance_interval" set to few minutes -#ifdef DIRTY_TRICK - if (missed_blocks != 0) { -#else - assert( missed_blocks != 0 ); -#endif -// bad if-condition, this code needs to execute for both shuffled and rng algorithms -// if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) -// { - missed_blocks--; - for( uint32_t i = 0; i < missed_blocks; ++i ) { - const auto& witness_missed = get_scheduled_witness( i+1 )(*this); - if( witness_missed.id != b.witness ) { - /* - const auto& witness_account = witness_missed.witness_account(*this); - if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) ) - wlog( "Witness ${name} missed block ${n} around ${t}", ("name",witness_account.name)("n",b.block_num())("t",b.timestamp) ); - */ - - modify( witness_missed, [&]( witness_object& w ) { - w.total_missed++; - }); - } - } -// } -#ifdef DIRTY_TRICK - } -#endif // dynamic global properties updating - modify( _dgp, [&]( dynamic_global_property_object& dgp ){ + modify( _dgp, [&b,this,missed_blocks]( dynamic_global_property_object& dgp ){ secret_hash_type::encoder enc; fc::raw::pack( enc, dgp.random ); fc::raw::pack( enc, b.previous_secret ); @@ -87,9 +57,10 @@ void database::update_global_dynamic_data( const signed_block& b ) _random_number_generator = fc::hash_ctr_rng(dgp.random.data()); - if( BOOST_UNLIKELY( b.block_num() == 1 ) ) + const uint32_t block_num = b.block_num(); + if( BOOST_UNLIKELY( block_num == 1 ) ) dgp.recently_missed_count = 0; - else if( _checkpoints.size() && _checkpoints.rbegin()->first >= b.block_num() ) + else if( _checkpoints.size() && _checkpoints.rbegin()->first >= block_num ) dgp.recently_missed_count = 0; else if( missed_blocks ) dgp.recently_missed_count += GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT*missed_blocks; @@ -98,7 +69,7 @@ void database::update_global_dynamic_data( const signed_block& b ) else if( dgp.recently_missed_count > 0 ) dgp.recently_missed_count--; - dgp.head_block_number = b.block_num(); + dgp.head_block_number = block_num; dgp.head_block_id = b.id(); dgp.time = b.timestamp; dgp.current_witness = b.witness; diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index 3a2378a9..e12c81dc 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -226,6 +226,22 @@ void database::update_witness_schedule(const signed_block& next_block) idump( ( double(total_time/1000000.0)/calls) ); } +uint32_t database::update_witness_missed_blocks( const signed_block& b ) +{ + uint32_t missed_blocks = get_slot_at_time( b.timestamp ); + FC_ASSERT( missed_blocks != 0, "Trying to push double-produced block onto current block?!" ); + missed_blocks--; + const auto& witnesses = witness_schedule_id_type()(*this).current_shuffled_witnesses; + if( missed_blocks < witnesses.size() ) + for( uint32_t i = 0; i < missed_blocks; ++i ) { + const auto& witness_missed = get_scheduled_witness( i+1 )(*this); + modify( witness_missed, []( witness_object& w ) { + w.total_missed++; + }); + } + return missed_blocks; +} + uint32_t database::witness_participation_rate()const { const global_property_object& gpo = get_global_properties(); diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 914ade33..36412d88 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -278,6 +278,25 @@ namespace graphene { namespace chain { */ class account_member_index : public secondary_index { + /* std::less::operator() is less efficient so using key_compare here. + * Let assume that it has two keys key1 and key2 and we want to insert key3. + * the insert function needs to first call std::less::operator() with key1 and key3. + * Assume std::less::operator()(key1, key3) returns false. + * It has to call std::less::operator() again with the keys switched, + * std::less::operator()(key3, key1), to decide whether key1 is equal to key3 or + * key3 is greater than key1. There are two calls to std::less::operator() to make + * a decision if the first call returns false. + * std::map::insert and std::set used key_compare, + * there would be sufficient information to make the right decision using just one call. + */ + class key_compare { + public: + inline bool operator()( const public_key_type& a, const public_key_type& b )const + { + return a.key_data < b.key_data; + } + }; + public: virtual void object_inserted( const object& obj ) override; virtual void object_removed( const object& obj ) override; @@ -287,18 +306,18 @@ namespace graphene { namespace chain { /** given an account or key, map it to the set of accounts that reference it in an active or owner authority */ map< account_id_type, set > account_to_account_memberships; - map< public_key_type, set > account_to_key_memberships; + map< public_key_type, set, key_compare > account_to_key_memberships; /** some accounts use address authorities in the genesis block */ map< address, set > account_to_address_memberships; protected: set get_account_members( const account_object& a )const; - set get_key_members( const account_object& a )const; + set get_key_members( const account_object& a )const; set
get_address_members( const account_object& a )const; set before_account_members; - set before_key_members; + set before_key_members; set
before_address_members; }; diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index afd5215a..f1df4681 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -230,8 +230,16 @@ namespace graphene { namespace chain { share_type settlement_fund; ///@} + /// The time when @ref current_feed would expire time_point_sec feed_expiration_time()const - { return current_feed_publication_time + options.feed_lifetime_sec; } + { + uint32_t current_feed_seconds = current_feed_publication_time.sec_since_epoch(); + if( std::numeric_limits::max() - current_feed_seconds <= options.feed_lifetime_sec ) + return time_point_sec::maximum(); + else + return current_feed_publication_time + options.feed_lifetime_sec; + } + bool feed_is_expired_before_hardfork_615(time_point_sec current_time)const { return feed_expiration_time() >= current_time; } bool feed_is_expired(time_point_sec current_time)const diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 77699ee4..483bfc8a 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -490,8 +490,11 @@ namespace graphene { namespace chain { const witness_object& _validate_block_header( const signed_block& next_block )const; void create_block_summary(const signed_block& next_block); + //////////////////// db_witness_schedule.cpp //////////////////// + uint32_t update_witness_missed_blocks( const signed_block& b ); + //////////////////// db_update.cpp //////////////////// - void update_global_dynamic_data( const signed_block& b ); + void update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks ); void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block); void update_last_irreversible_block(); void clear_expired_transactions(); @@ -560,6 +563,15 @@ namespace graphene { namespace chain { node_property_object _node_property_object; fc::hash_ctr_rng _random_number_generator; bool _slow_replays = false; + + /** + * Whether database is successfully opened or not. + * + * The database is considered open when there's no exception + * or assertion fail during database::open() method, and + * database::close() has not been called, or failed during execution. + */ + bool _opened = false; }; namespace detail diff --git a/libraries/chain/include/graphene/chain/proposal_object.hpp b/libraries/chain/include/graphene/chain/proposal_object.hpp index d41ea7ea..0affcbab 100644 --- a/libraries/chain/include/graphene/chain/proposal_object.hpp +++ b/libraries/chain/include/graphene/chain/proposal_object.hpp @@ -51,8 +51,9 @@ class proposal_object : public abstract_object flat_set available_owner_approvals; flat_set available_key_approvals; account_id_type proposer; + std::string fail_reason; - bool is_authorized_to_execute(database& db)const; + bool is_authorized_to_execute(database& db) const; }; /** diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 8dd346ed..789442fd 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -33,9 +33,6 @@ #include #include -#define offset_d(i,f) (long(&(i)->f) - long(i)) -#define offset_s(t,f) offset_d((t*)1000, f) - namespace graphene { namespace chain { using namespace graphene::db; @@ -191,7 +188,7 @@ namespace graphene { namespace chain { member_offset, member_offset //member - //member_offset + //member_offset >, composite_key_compare< std::less< asset_id_type >, diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index a6640ea4..3a44ca5c 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -244,20 +244,6 @@ void_result proposal_update_evaluator::do_evaluate(const proposal_update_operati "", ("id", id)("available", _proposal->available_owner_approvals) ); } - /* All authority checks happen outside of evaluators - if( (d.get_node_properties().skip_flags & database::skip_authority_check) == 0 ) - { - for( const auto& id : o.key_approvals_to_add ) - { - FC_ASSERT( trx_state->signed_by(id) ); - } - for( const auto& id : o.key_approvals_to_remove ) - { - FC_ASSERT( trx_state->signed_by(id) ); - } - } - */ - return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } @@ -293,6 +279,9 @@ void_result proposal_update_evaluator::do_apply(const proposal_update_operation& try { _processed_transaction = d.push_proposal(*_proposal); } catch(fc::exception& e) { + d.modify(*_proposal, [&e](proposal_object& p) { + p.fail_reason = e.to_string(fc::log_level(fc::log_level::all)); + }); wlog("Proposed transaction ${id} failed to apply once approved with exception:\n----\n${reason}\n----\nWill try again when it expires.", ("id", o.proposal)("reason", e.to_detail_string())); _proposal_failed = true; diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 565964a5..343edce2 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -43,14 +43,11 @@ bool proposal_object::is_authorized_to_execute(database& db) const } catch ( const fc::exception& e ) { - //idump((available_active_approvals)); - //wlog((e.to_detail_string())); return false; } return true; } - void required_approval_index::object_inserted( const object& obj ) { assert( dynamic_cast(&obj) ); diff --git a/libraries/db/include/graphene/db/index.hpp b/libraries/db/include/graphene/db/index.hpp index aebdb8b9..15c0f94c 100644 --- a/libraries/db/include/graphene/db/index.hpp +++ b/libraries/db/include/graphene/db/index.hpp @@ -234,14 +234,12 @@ namespace graphene { namespace db { fc::raw::unpack(ds, _next_id); fc::raw::unpack(ds, open_ver); FC_ASSERT( open_ver == get_object_version(), "Incompatible Version, the serialization of objects in this index has changed" ); - try { - vector tmp; - while( true ) - { - fc::raw::unpack( ds, tmp ); - load( tmp ); - } - } catch ( const fc::exception& ){} + vector tmp; + while( ds.remaining() > 0 ) + { + fc::raw::unpack( ds, tmp ); + load( tmp ); + } } virtual void save( const path& db ) override diff --git a/libraries/fc b/libraries/fc index 94b046dc..443f858d 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 94b046dce6bb86fd22abd1831fc9056103f4aa5d +Subproject commit 443f858d9b4733bb6d894da9315ce00ac3246065 diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 67cd362b..81acb01e 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -81,11 +81,23 @@ void account_history_plugin_impl::update_account_histories( const signed_block& { graphene::chain::database& db = database(); vector >& hist = db.get_applied_operations(); + bool is_first = true; + auto skip_oho_id = [&is_first,&db,this]() { + if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo + { + db.remove( db.create( []( operation_history_object& obj) {} ) ); + is_first = false; + } + else + _oho_index->use_next_id(); + }; + for( optional< operation_history_object >& o_op : hist ) { optional oho; auto create_oho = [&]() { + is_first = false; operation_history_object result = db.create( [&]( operation_history_object& h ) { if( o_op.valid() ) @@ -99,7 +111,7 @@ void account_history_plugin_impl::update_account_histories( const signed_block& { // Note: the 2nd and 3rd checks above are for better performance, when the db is not clean, // they will break consistency of account_stats.total_ops and removed_ops and most_recent_op - _oho_index->use_next_id(); + skip_oho_id(); continue; } else if( !_partial_operations ) @@ -179,7 +191,7 @@ void account_history_plugin_impl::update_account_histories( const signed_block& } } if (_partial_operations && ! oho.valid()) - _oho_index->use_next_id(); + skip_oho_id(); } } diff --git a/libraries/plugins/delayed_node/delayed_node_plugin.cpp b/libraries/plugins/delayed_node/delayed_node_plugin.cpp index fb70cb68..5b8aadad 100644 --- a/libraries/plugins/delayed_node/delayed_node_plugin.cpp +++ b/libraries/plugins/delayed_node/delayed_node_plugin.cpp @@ -65,7 +65,7 @@ void delayed_node_plugin::plugin_set_program_options(bpo::options_description& c void delayed_node_plugin::connect() { - my->client_connection = std::make_shared(*my->client.connect(my->remote_endpoint)); + my->client_connection = std::make_shared(my->client.connect(my->remote_endpoint)); my->database_api = my->client_connection->get_remote_api(0); my->client_connection_closed = my->client_connection->closed.connect([this] { connection_failed(); diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 59564852..0df82e7d 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -718,8 +718,6 @@ public: } account_object get_account(account_id_type id) const { - if( _wallet.my_accounts.get().count(id) ) - return *_wallet.my_accounts.get().find(id); auto rec = _remote_db->get_accounts({id}).front(); FC_ASSERT(rec); return *rec; @@ -733,19 +731,6 @@ public: // It's an ID return get_account(*id); } else { - // It's a name - if( _wallet.my_accounts.get().count(account_name_or_id) ) - { - auto local_account = *_wallet.my_accounts.get().find(account_name_or_id); - auto blockchain_account = _remote_db->lookup_account_names({account_name_or_id}).front(); - FC_ASSERT( blockchain_account ); - if (local_account.id != blockchain_account->id) - elog("my account id ${id} different from blockchain id ${id2}", ("id", local_account.id)("id2", blockchain_account->id)); - if (local_account.name != blockchain_account->name) - elog("my account name ${id} different from blockchain name ${id2}", ("id", local_account.name)("id2", blockchain_account->name)); - - return *_wallet.my_accounts.get().find(account_name_or_id); - } auto rec = _remote_db->lookup_account_names({account_name_or_id}).front(); FC_ASSERT( rec && rec->name == account_name_or_id ); return *rec; @@ -2206,77 +2191,15 @@ public: 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 ); + tx.set_reference_block(dyn_props.head_block_id); // first, some bookkeeping, expire old items from _recently_generated_transactions // since transactions include the head block id, we just need the index for keeping transactions unique @@ -2290,23 +2213,11 @@ public: uint32_t expiration_time_offset = 0; for (;;) { - tx.set_expiration( dyn_props.time + fc::seconds(30 + expiration_time_offset) ); + 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); @@ -2328,11 +2239,11 @@ public: { try { - _remote_net_broadcast->broadcast_transaction( tx ); + _remote_net_broadcast->broadcast_transaction(tx); } catch (const fc::exception& e) { - elog("Caught exception while broadcasting tx ${id}: ${e}", ("id", tx.id().str())("e", e.to_detail_string()) ); + elog("Caught exception while broadcasting tx ${id}: ${e}", ("id", tx.id().str())("e", e.to_detail_string())); throw; } } diff --git a/programs/cli_wallet/main.cpp b/programs/cli_wallet/main.cpp index 0155897c..53404633 100644 --- a/programs/cli_wallet/main.cpp +++ b/programs/cli_wallet/main.cpp @@ -175,7 +175,7 @@ int main( int argc, char** argv ) fc::http::websocket_client client; idump((wdata.ws_server)); auto con = client.connect( wdata.ws_server ); - auto apic = std::make_shared(*con); + auto apic = std::make_shared(con); auto remote_api = apic->get_remote_api< login_api >(1); edump((wdata.ws_user)(wdata.ws_password) ); @@ -215,7 +215,7 @@ int main( int argc, char** argv ) _websocket_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){ std::cout << "here... \n"; wlog("." ); - auto wsc = std::make_shared(*c); + auto wsc = std::make_shared(c); wsc->register_api(wapi); c->set_session_data( wsc ); }); @@ -232,7 +232,7 @@ int main( int argc, char** argv ) if( options.count("rpc-tls-endpoint") ) { _websocket_tls_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){ - auto wsc = std::make_shared(*c); + auto wsc = std::make_shared(c); wsc->register_api(wapi); c->set_session_data( wsc ); }); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 57a451aa..44af778b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,4 +41,14 @@ file(GLOB RANDOM_SOURCES "random/*.cpp") add_executable( random_test ${RANDOM_SOURCES} ${COMMON_SOURCES} ) target_link_libraries( random_test graphene_chain graphene_app graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +file(GLOB CLI_SOURCES "cli/*.cpp") +add_executable( cli_test ${CLI_SOURCES} ) +if(WIN32) + list(APPEND PLATFORM_SPECIFIC_LIBS ws2_32) +endif() +target_link_libraries( cli_test graphene_chain graphene_app graphene_witness graphene_wallet graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +if(MSVC) + set_source_files_properties( cli/main.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) +endif(MSVC) + add_subdirectory( generate_empty_blocks ) diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp new file mode 100644 index 00000000..82adb1c5 --- /dev/null +++ b/tests/cli/main.cpp @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2019 PBSA, 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 + +#ifdef _WIN32 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 + #endif + #include + #include +#else +#include +#include +#include +#endif +#include + +#include + +#define BOOST_TEST_MODULE Test Application +#include + +/***** + * Global Initialization for Windows + * ( sets up Winsock stuf ) + */ +#ifdef _WIN32 +int sockInit(void) +{ + WSADATA wsa_data; + return WSAStartup(MAKEWORD(1,1), &wsa_data); +} +int sockQuit(void) +{ + return WSACleanup(); +} +#endif + +/********************* + * Helper Methods + *********************/ + +#include "../common/genesis_file_util.hpp" + +#define INVOKE(test) ((struct test*)this)->test_method(); + +////// +/// @brief attempt to find an available port on localhost +/// @returns an available port number, or -1 on error +///// +int get_available_port() +{ + struct sockaddr_in sin; + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) + return -1; + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (::bind(socket_fd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1) + return -1; + socklen_t len = sizeof(sin); + if (getsockname(socket_fd, (struct sockaddr *)&sin, &len) == -1) + return -1; +#ifdef _WIN32 + closesocket(socket_fd); +#else + close(socket_fd); +#endif + return ntohs(sin.sin_port); +} + +/////////// +/// @brief Start the application +/// @param app_dir the temporary directory to use +/// @param server_port_number to be filled with the rpc endpoint port number +/// @returns the application object +////////// +std::shared_ptr start_application(fc::temp_directory& app_dir, int& server_port_number) { + std::shared_ptr app1(new graphene::app::application{}); + + app1->register_plugin< graphene::bookie::bookie_plugin>(); + app1->startup_plugins(); + boost::program_options::variables_map cfg; +#ifdef _WIN32 + sockInit(); +#endif + server_port_number = get_available_port(); + cfg.emplace( + "rpc-endpoint", + boost::program_options::variable_value(string("127.0.0.1:" + std::to_string(server_port_number)), false) + ); + cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); + cfg.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); + + app1->initialize(app_dir.path(), cfg); + + app1->initialize_plugins(cfg); + app1->startup_plugins(); + + app1->startup(); + fc::usleep(fc::milliseconds(500)); + return app1; +} + +/////////// +/// Send a block to the db +/// @param app the application +/// @param returned_block the signed block +/// @returns true on success +/////////// +bool generate_block(std::shared_ptr app, graphene::chain::signed_block& returned_block) +{ + try { + fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + auto db = app->chain_database(); + returned_block = db->generate_block( db->get_slot_time(1), + db->get_scheduled_witness(1), + committee_key, + database::skip_nothing ); + return true; + } catch (exception &e) { + return false; + } +} + +bool generate_block(std::shared_ptr app) +{ + graphene::chain::signed_block returned_block; + return generate_block(app, returned_block); +} + +/////////// +/// @brief Skip intermediate blocks, and generate a maintenance block +/// @param app the application +/// @returns true on success +/////////// +bool generate_maintenance_block(std::shared_ptr app) { + try { + fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + uint32_t skip = ~0; + auto db = app->chain_database(); + auto maint_time = db->get_dynamic_global_properties().next_maintenance_time; + auto slots_to_miss = db->get_slot_at_time(maint_time); + db->generate_block(db->get_slot_time(slots_to_miss), + db->get_scheduled_witness(slots_to_miss), + committee_key, + skip); + return true; + } catch (exception& e) + { + return false; + } +} + +/////////// +/// @brief a class to make connecting to the application server easier +/////////// +class client_connection +{ +public: + ///////// + // constructor + ///////// + client_connection( + std::shared_ptr app, + const fc::temp_directory& data_dir, + const int server_port_number + ) + { + wallet_data.chain_id = app->chain_database()->get_chain_id(); + wallet_data.ws_server = "ws://127.0.0.1:" + std::to_string(server_port_number); + wallet_data.ws_user = ""; + wallet_data.ws_password = ""; + websocket_connection = websocket_client.connect( wallet_data.ws_server ); + + api_connection = std::make_shared(websocket_connection); + + remote_login_api = api_connection->get_remote_api< graphene::app::login_api >(1); + BOOST_CHECK(remote_login_api->login( wallet_data.ws_user, wallet_data.ws_password ) ); + + wallet_api_ptr = std::make_shared(wallet_data, remote_login_api); + wallet_filename = data_dir.path().generic_string() + "/wallet.json"; + wallet_api_ptr->set_wallet_filename(wallet_filename); + + wallet_api = fc::api(wallet_api_ptr); + + wallet_cli = std::make_shared(); + for( auto& name_formatter : wallet_api_ptr->get_result_formatters() ) + wallet_cli->format_result( name_formatter.first, name_formatter.second ); + + boost::signals2::scoped_connection closed_connection(websocket_connection->closed.connect([=]{ + cerr << "Server has disconnected us.\n"; + wallet_cli->stop(); + })); + (void)(closed_connection); + } + ~client_connection() + { + // wait for everything to finish up + fc::usleep(fc::milliseconds(500)); + } +public: + fc::http::websocket_client websocket_client; + graphene::wallet::wallet_data wallet_data; + fc::http::websocket_connection_ptr websocket_connection; + std::shared_ptr api_connection; + fc::api remote_login_api; + std::shared_ptr wallet_api_ptr; + fc::api wallet_api; + std::shared_ptr wallet_cli; + std::string wallet_filename; +}; + + +/////////////////////////////// +// Cli Wallet Fixture +/////////////////////////////// + +struct cli_fixture +{ + class dummy + { + public: + ~dummy() + { + // wait for everything to finish up + fc::usleep(fc::milliseconds(500)); + } + }; + dummy dmy; + int server_port_number; + fc::temp_directory app_dir; + std::shared_ptr app1; + client_connection con; + std::vector nathan_keys; + + cli_fixture() : + server_port_number(0), + app_dir( graphene::utilities::temp_directory_path() ), + app1( start_application(app_dir, server_port_number) ), + con( app1, app_dir, server_port_number ), + nathan_keys( {"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"} ) + { + BOOST_TEST_MESSAGE("Setup cli_wallet::boost_fixture_test_case"); + + using namespace graphene::chain; + using namespace graphene::app; + + try + { + BOOST_TEST_MESSAGE("Setting wallet password"); + con.wallet_api_ptr->set_password("supersecret"); + con.wallet_api_ptr->unlock("supersecret"); + + // import Nathan account + BOOST_TEST_MESSAGE("Importing nathan key"); + BOOST_CHECK_EQUAL(nathan_keys[0], "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); + BOOST_CHECK(con.wallet_api_ptr->import_key("nathan", nathan_keys[0])); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } + } + + ~cli_fixture() + { + BOOST_TEST_MESSAGE("Cleanup cli_wallet::boost_fixture_test_case"); + + // wait for everything to finish up + fc::usleep(fc::seconds(1)); + + app1->shutdown(); +#ifdef _WIN32 + sockQuit(); +#endif + } +}; + +/////////////////////////////// +// Tests +/////////////////////////////// + +//////////////// +// Start a server and connect using the same calls as the CLI +//////////////// +BOOST_FIXTURE_TEST_CASE( cli_connect, cli_fixture ) +{ + BOOST_TEST_MESSAGE("Testing wallet connection."); +} + +BOOST_FIXTURE_TEST_CASE( upgrade_nathan_account, cli_fixture ) +{ + try + { + BOOST_TEST_MESSAGE("Upgrade Nathan's account"); + + account_object nathan_acct_before_upgrade, nathan_acct_after_upgrade; + std::vector import_txs; + signed_transaction upgrade_tx; + + BOOST_TEST_MESSAGE("Importing nathan's balance"); + import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + nathan_acct_before_upgrade = con.wallet_api_ptr->get_account("nathan"); + + BOOST_CHECK(generate_block(app1)); + + // upgrade nathan + BOOST_TEST_MESSAGE("Upgrading Nathan to LTM"); + upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); + + nathan_acct_after_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // verify that the upgrade was successful + BOOST_CHECK_PREDICATE( + std::not_equal_to(), + (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch()) + (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) + ); + BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member()); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_FIXTURE_TEST_CASE( create_new_account, cli_fixture ) +{ + try + { + INVOKE(upgrade_nathan_account); + + // create a new account + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key( + bki.brain_priv_key, "jmjatlanta", "nathan", "nathan", true + ); + // save the private key for this new account in the wallet file + BOOST_CHECK(con.wallet_api_ptr->import_key("jmjatlanta", bki.wif_priv_key)); + con.wallet_api_ptr->save_wallet_file(con.wallet_filename); + + // attempt to give jmjatlanta some CORE + BOOST_TEST_MESSAGE("Transferring CORE from Nathan to jmjatlanta"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer( + "nathan", "jmjatlanta", "10000", "1.3.0", "Here are some CORE token for your new account", true + ); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +/////////////////////// +// Start a server and connect using the same calls as the CLI +// Vote for two witnesses, and make sure they both stay there +// after a maintenance block +/////////////////////// +BOOST_FIXTURE_TEST_CASE( cli_vote_for_2_witnesses, cli_fixture ) +{ + try + { + BOOST_TEST_MESSAGE("Cli Vote Test for 2 Witnesses"); + + INVOKE(upgrade_nathan_account); // just to fund nathan + + // get the details for init1 + witness_object init1_obj = con.wallet_api_ptr->get_witness("init1"); + int init1_start_votes = init1_obj.total_votes; + // Vote for a witness + signed_transaction vote_witness1_tx = con.wallet_api_ptr->vote_for_witness("nathan", "init1", true, true); + + // generate a block to get things started + BOOST_CHECK(generate_block(app1)); + // wait for a maintenance interval + BOOST_CHECK(generate_maintenance_block(app1)); + + // Verify that the vote is there + init1_obj = con.wallet_api_ptr->get_witness("init1"); + witness_object init2_obj = con.wallet_api_ptr->get_witness("init2"); + int init1_middle_votes = init1_obj.total_votes; + BOOST_CHECK(init1_middle_votes > init1_start_votes); + + // Vote for a 2nd witness + int init2_start_votes = init2_obj.total_votes; + signed_transaction vote_witness2_tx = con.wallet_api_ptr->vote_for_witness("nathan", "init2", true, true); + + // send another block to trigger maintenance interval + BOOST_CHECK(generate_maintenance_block(app1)); + + // Verify that both the first vote and the 2nd are there + init2_obj = con.wallet_api_ptr->get_witness("init2"); + init1_obj = con.wallet_api_ptr->get_witness("init1"); + + int init2_middle_votes = init2_obj.total_votes; + BOOST_CHECK(init2_middle_votes > init2_start_votes); + int init1_last_votes = init1_obj.total_votes; + BOOST_CHECK(init1_last_votes > init1_start_votes); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 1741c787..a2691c09 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -109,6 +109,24 @@ database_fixture::database_fixture() genesis_state.initial_parameters.current_fees->zero_all_fees(); open_database(); + // add account tracking for ahplugin for special test case with track-account enabled + if( !options.count("track-account") && boost::unit_test::framework::current_test_case().p_name.value == "track_account") { + std::vector track_account; + std::string track = "\"1.2.18\""; + track_account.push_back(track); + options.insert(std::make_pair("track-account", boost::program_options::variable_value(track_account, false))); + options.insert(std::make_pair("partial-operations", boost::program_options::variable_value(true, false))); + } + // account tracking 2 accounts + if( !options.count("track-account") && boost::unit_test::framework::current_test_case().p_name.value == "track_account2") { + std::vector track_account; + std::string track = "\"1.2.0\""; + track_account.push_back(track); + track = "\"1.2.17\""; + track_account.push_back(track); + options.insert(std::make_pair("track-account", boost::program_options::variable_value(track_account, false))); + } + // app.initialize(); ahplugin->plugin_set_app(&app); ahplugin->plugin_initialize(options); diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index f5efbb9d..02d61441 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE( simple_single_signature ) sign(trx, nathan_key); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(nathan, core), old_balance - 500); + BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast(old_balance - 500)); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; @@ -97,25 +97,25 @@ BOOST_AUTO_TEST_CASE( any_two_of_three ) GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception); sign(trx, nathan_key2); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(nathan, core), old_balance - 500); + BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast(old_balance - 500)); trx.signatures.clear(); sign(trx, nathan_key2); sign(trx, nathan_key3); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(nathan, core), old_balance - 1000); + BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast(old_balance - 1000)); trx.signatures.clear(); sign(trx, nathan_key1); sign(trx, nathan_key3); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(nathan, core), old_balance - 1500); + BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast(old_balance - 1500)); trx.signatures.clear(); //sign(trx, fc::ecc::private_key::generate()); sign(trx,nathan_key3); GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception); - BOOST_CHECK_EQUAL(get_balance(nathan, core), old_balance - 1500); + BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast(old_balance - 1500)); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; @@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE( recursive_accounts ) BOOST_TEST_MESSAGE( "Attempting to transfer with parent1 and parent2 signature, should succeed" ); sign(trx,parent1_key); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(child, core), old_balance - 500); + BOOST_CHECK_EQUAL(get_balance(child, core), static_cast(old_balance - 500)); trx.operations.clear(); trx.signatures.clear(); @@ -180,7 +180,7 @@ BOOST_AUTO_TEST_CASE( recursive_accounts ) sign(trx,parent1_key); sign(trx,parent2_key); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_REQUIRE_EQUAL(child.active.num_auths(), 3); + BOOST_REQUIRE_EQUAL(child.active.num_auths(), 3u); trx.operations.clear(); trx.signatures.clear(); } @@ -203,13 +203,13 @@ BOOST_AUTO_TEST_CASE( recursive_accounts ) BOOST_TEST_MESSAGE( "Attempting transfer both parents, should succeed" ); sign(trx, parent1_key); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(child, core), old_balance - 1000); + BOOST_CHECK_EQUAL(get_balance(child, core), static_cast(old_balance - 1000)); trx.signatures.clear(); BOOST_TEST_MESSAGE( "Attempting transfer with just child key, should succeed" ); sign(trx, child_key); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(child, core), old_balance - 1500); + BOOST_CHECK_EQUAL(get_balance(child, core), static_cast(old_balance - 1500)); trx.operations.clear(); trx.signatures.clear(); @@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE( recursive_accounts ) BOOST_TEST_MESSAGE( "Attempt to transfer using parent2_key and grandparent_key" ); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(child, core), old_balance - 2000); + BOOST_CHECK_EQUAL(get_balance(child, core), static_cast(old_balance - 2000)); trx.clear(); BOOST_TEST_MESSAGE( "Update grandparent account authority to be committee account" ); @@ -268,7 +268,7 @@ BOOST_AUTO_TEST_CASE( recursive_accounts ) trx.signatures.clear(); sign(trx, child_key); PUSH_TX( db, trx, database::skip_transaction_dupe_check ); - BOOST_CHECK_EQUAL(get_balance(child, core), old_balance - 2500); + BOOST_CHECK_EQUAL(get_balance(child, core), static_cast(old_balance - 2500)); trx.operations.clear(); trx.signatures.clear(); @@ -329,17 +329,17 @@ BOOST_AUTO_TEST_CASE( proposed_single_account ) vector other; flat_set active_set, owner_set; operation_get_required_authorities(op,active_set,owner_set,other); - BOOST_CHECK_EQUAL(active_set.size(), 1); - BOOST_CHECK_EQUAL(owner_set.size(), 0); - BOOST_CHECK_EQUAL(other.size(), 0); + BOOST_CHECK_EQUAL(active_set.size(), 1lu); + BOOST_CHECK_EQUAL(owner_set.size(), 0lu); + BOOST_CHECK_EQUAL(other.size(), 0lu); BOOST_CHECK(*active_set.begin() == moneyman.get_id()); active_set.clear(); other.clear(); operation_get_required_authorities(op.proposed_ops.front().op,active_set,owner_set,other); - BOOST_CHECK_EQUAL(active_set.size(), 1); - BOOST_CHECK_EQUAL(owner_set.size(), 0); - BOOST_CHECK_EQUAL(other.size(), 0); + BOOST_CHECK_EQUAL(active_set.size(), 1lu); + BOOST_CHECK_EQUAL(owner_set.size(), 0lu); + BOOST_CHECK_EQUAL(other.size(), 0lu); BOOST_CHECK(*active_set.begin() == nathan.id); } @@ -349,10 +349,10 @@ BOOST_AUTO_TEST_CASE( proposed_single_account ) sign( trx, init_account_priv_key ); const proposal_object& proposal = db.get(PUSH_TX( db, trx ).operation_results.front().get()); - BOOST_CHECK_EQUAL(proposal.required_active_approvals.size(), 1); - BOOST_CHECK_EQUAL(proposal.available_active_approvals.size(), 0); - BOOST_CHECK_EQUAL(proposal.required_owner_approvals.size(), 0); - BOOST_CHECK_EQUAL(proposal.available_owner_approvals.size(), 0); + BOOST_CHECK_EQUAL(proposal.required_active_approvals.size(), 1lu); + BOOST_CHECK_EQUAL(proposal.available_active_approvals.size(), 0lu); + BOOST_CHECK_EQUAL(proposal.required_owner_approvals.size(), 0lu); + BOOST_CHECK_EQUAL(proposal.available_owner_approvals.size(), 0lu); BOOST_CHECK(*proposal.required_active_approvals.begin() == nathan.id); proposal_update_operation pup; @@ -389,6 +389,49 @@ BOOST_AUTO_TEST_CASE( proposed_single_account ) } } +BOOST_AUTO_TEST_CASE( proposal_failure ) +{ + try + { + ACTORS( (bob) (alice) ); + + fund( bob, asset(1000000) ); + fund( alice, asset(1000000) ); + + // create proposal that will eventually fail due to lack of funds + transfer_operation top; + top.to = alice_id; + top.from = bob_id; + top.amount = asset(2000000); + proposal_create_operation pop; + pop.proposed_ops.push_back( { top } ); + pop.expiration_time = db.head_block_time() + fc::days(1); + pop.fee_paying_account = bob_id; + trx.operations.push_back( pop ); + trx.signatures.clear(); + sign( trx, bob_private_key ); + processed_transaction processed = PUSH_TX( db, trx ); + proposal_object prop = db.get(processed.operation_results.front().get()); + trx.clear(); + generate_block(); + // add signature + proposal_update_operation up_op; + up_op.proposal = prop.id; + up_op.fee_paying_account = bob_id; + up_op.active_approvals_to_add.emplace( bob_id ); + trx.operations.push_back( up_op ); + sign( trx, bob_private_key ); + PUSH_TX( db, trx ); + trx.clear(); + + // check fail reason + const proposal_object& result = db.get(prop.id); + BOOST_CHECK(!result.fail_reason.empty()); + BOOST_CHECK_EQUAL( result.fail_reason.substr(0, 16), "Assert Exception"); + } + FC_LOG_AND_RETHROW() +} + /// Verify that committee authority cannot be invoked in a normal transaction BOOST_AUTO_TEST_CASE( committee_authority ) { try { @@ -696,7 +739,7 @@ BOOST_FIXTURE_TEST_CASE( proposal_delete, database_fixture ) PUSH_TX( db, trx ); trx.clear(); BOOST_CHECK(!prop.is_authorized_to_execute(db)); - BOOST_CHECK_EQUAL(prop.available_active_approvals.size(), 1); + BOOST_CHECK_EQUAL(prop.available_active_approvals.size(), 1lu); std::swap(uop.active_approvals_to_add, uop.active_approvals_to_remove); trx.operations.push_back(uop); @@ -704,7 +747,7 @@ BOOST_FIXTURE_TEST_CASE( proposal_delete, database_fixture ) PUSH_TX( db, trx ); trx.clear(); BOOST_CHECK(!prop.is_authorized_to_execute(db)); - BOOST_CHECK_EQUAL(prop.available_active_approvals.size(), 0); + BOOST_CHECK_EQUAL(prop.available_active_approvals.size(), 0lu); } { @@ -758,8 +801,8 @@ BOOST_FIXTURE_TEST_CASE( proposal_owner_authority_delete, database_fixture ) } const proposal_object& prop = *db.get_index_type().indices().begin(); - BOOST_CHECK_EQUAL(prop.required_active_approvals.size(), 1); - BOOST_CHECK_EQUAL(prop.required_owner_approvals.size(), 1); + BOOST_CHECK_EQUAL(prop.required_active_approvals.size(), 1lu); + BOOST_CHECK_EQUAL(prop.required_owner_approvals.size(), 1lu); BOOST_CHECK(!prop.is_authorized_to_execute(db)); { @@ -772,7 +815,7 @@ BOOST_FIXTURE_TEST_CASE( proposal_owner_authority_delete, database_fixture ) PUSH_TX( db, trx ); trx.clear(); BOOST_CHECK(!prop.is_authorized_to_execute(db)); - BOOST_CHECK_EQUAL(prop.available_owner_approvals.size(), 1); + BOOST_CHECK_EQUAL(prop.available_owner_approvals.size(), 1lu); std::swap(uop.owner_approvals_to_add, uop.owner_approvals_to_remove); trx.operations.push_back(uop); @@ -780,7 +823,7 @@ BOOST_FIXTURE_TEST_CASE( proposal_owner_authority_delete, database_fixture ) PUSH_TX( db, trx ); trx.clear(); BOOST_CHECK(!prop.is_authorized_to_execute(db)); - BOOST_CHECK_EQUAL(prop.available_owner_approvals.size(), 0); + BOOST_CHECK_EQUAL(prop.available_owner_approvals.size(), 0lu); } { @@ -835,8 +878,8 @@ BOOST_FIXTURE_TEST_CASE( proposal_owner_authority_complete, database_fixture ) } const proposal_object& prop = *db.get_index_type().indices().begin(); - BOOST_CHECK_EQUAL(prop.required_active_approvals.size(), 1); - BOOST_CHECK_EQUAL(prop.required_owner_approvals.size(), 1); + BOOST_CHECK_EQUAL(prop.required_active_approvals.size(), 1lu); + BOOST_CHECK_EQUAL(prop.required_owner_approvals.size(), 1lu); BOOST_CHECK(!prop.is_authorized_to_execute(db)); { @@ -852,7 +895,7 @@ BOOST_FIXTURE_TEST_CASE( proposal_owner_authority_complete, database_fixture ) PUSH_TX( db, trx ); trx.clear(); BOOST_CHECK(!prop.is_authorized_to_execute(db)); - BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 1); + BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 1lu); std::swap(uop.key_approvals_to_add, uop.key_approvals_to_remove); trx.operations.push_back(uop); @@ -862,7 +905,7 @@ BOOST_FIXTURE_TEST_CASE( proposal_owner_authority_complete, database_fixture ) PUSH_TX( db, trx ); trx.clear(); BOOST_CHECK(!prop.is_authorized_to_execute(db)); - BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 0); + BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 0lu); std::swap(uop.key_approvals_to_add, uop.key_approvals_to_remove); trx.operations.push_back(uop); @@ -872,7 +915,7 @@ BOOST_FIXTURE_TEST_CASE( proposal_owner_authority_complete, database_fixture ) PUSH_TX( db, trx ); trx.clear(); BOOST_CHECK(!prop.is_authorized_to_execute(db)); - BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 1); + BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 1lu); uop.key_approvals_to_add.clear(); uop.owner_approvals_to_add.insert(nathan.get_id()); diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 834c174b..da608541 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -540,4 +540,19 @@ BOOST_AUTO_TEST_CASE( merkle_root ) BOOST_CHECK( block.calculate_merkle_root() == c(dO) ); } +/** + * Reproduces https://github.com/bitshares/bitshares-core/issues/888 and tests fix for it. + */ +BOOST_AUTO_TEST_CASE( bitasset_feed_expiration_test ) +{ + time_point_sec now = fc::time_point::now(); + + asset_bitasset_data_object o; + + o.current_feed_publication_time = now - fc::hours(1); + o.options.feed_lifetime_sec = std::numeric_limits::max() - 1; + + BOOST_CHECK( !o.feed_is_expired( now ) ); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index daa0734b..99be3c6a 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -1292,18 +1293,50 @@ BOOST_AUTO_TEST_CASE( genesis_reserve_ids ) } } +BOOST_FIXTURE_TEST_CASE( miss_some_blocks, database_fixture ) +{ try { + std::vector witnesses = witness_schedule_id_type()(db).current_shuffled_witnesses; + BOOST_CHECK_EQUAL( 10, witnesses.size() ); + // database_fixture constructor calls generate_block once, signed by witnesses[0] + generate_block(); // witnesses[1] + generate_block(); // witnesses[2] + for( const auto& id : witnesses ) + BOOST_CHECK_EQUAL( 0, id(db).total_missed ); + // generate_blocks generates another block *now* (witnesses[3]) + // and one at now+10 blocks (witnesses[12%10]) + generate_blocks( db.head_block_time() + db.get_global_properties().parameters.block_interval * 10, true ); + // i. e. 8 blocks are missed in between by witness[4..11%10] + for( uint32_t i = 0; i < witnesses.size(); i++ ) + BOOST_CHECK_EQUAL( (i+7) % 10 < 2 ? 0 : 1, witnesses[i](db).total_missed ); +} FC_LOG_AND_RETHROW() } + BOOST_FIXTURE_TEST_CASE( miss_many_blocks, database_fixture ) { try { + auto get_misses = []( database& db ) { + std::map< witness_id_type, uint32_t > misses; + for( const auto& witness_id : witness_schedule_id_type()(db).current_shuffled_witnesses ) + misses[witness_id] = witness_id(db).total_missed; + return misses; + }; generate_block(); generate_block(); generate_block(); + auto missed_before = get_misses( db ); // miss 10 maintenance intervals generate_blocks( db.get_dynamic_global_properties().next_maintenance_time + db.get_global_properties().parameters.maintenance_interval * 10, true ); generate_block(); generate_block(); generate_block(); + auto missed_after = get_misses( db ); + BOOST_CHECK_EQUAL( missed_before.size(), missed_after.size() ); + for( const auto& miss : missed_before ) + { + const auto& after = missed_after.find( miss.first ); + BOOST_REQUIRE( after != missed_after.end() ); + BOOST_CHECK_EQUAL( miss.second, after->second ); + } } catch (fc::exception& e) { diff --git a/tests/tests/database_tests.cpp b/tests/tests/database_tests.cpp index 0e2f1295..8f870fca 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 { @@ -78,3 +80,34 @@ BOOST_AUTO_TEST_CASE( merge_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 00000000..0c7d202a --- /dev/null +++ b/tests/tests/history_api_tests.cpp @@ -0,0 +1,594 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * Copyright (c) 2019 PBSA, 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 "../common/database_fixture.hpp" + +#include +#include + +using namespace graphene::app; +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(account_history_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()); + auto dan_acc = create_account("dan"); + auto bob_acc = 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(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u); + 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(), 1u); + 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(), 2u); + 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(bob_acc.get_id(), operation_history_id_type(), 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(zero_id_object) { + try { + graphene::app::history_api hist_api(app); + + // no history at all in the chain + vector histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 4, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + create_bitasset("USD", account_id_type()); // create op 0 + generate_block(); + fc::usleep(fc::milliseconds(2000)); + + // what if the account only has one history entry and it is 0? + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(get_account_history_additional) { + try { + graphene::app::history_api hist_api(app); + + // A = account_id_type() with records { 5, 3, 1, 0 }, and + // B = dan with records { 6, 4, 2, 1 } + // account_id_type() and dan share operation id 1(account create) - share can be also in id 0 + + // no history at all in the chain + vector histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 4, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + create_bitasset("USD", account_id_type()); // create op 0 + generate_block(); + // what if the account only has one history entry and it is 0? + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u); + + const account_object& dan = create_account("dan"); // create op 1 + + create_bitasset("CNY", dan.id); // create op 2 + create_bitasset("BTC", account_id_type()); // create op 3 + create_bitasset("XMR", dan.id); // create op 4 + create_bitasset("EUR", account_id_type()); // create op 5 + create_bitasset("OIL", dan.id); // create op 6 + + generate_block(); + + // f(A, 0, 4, 9) = { 5, 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type(9)); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); + + // f(A, 0, 4, 6) = { 5, 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type(6)); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); + + // f(A, 0, 4, 5) = { 5, 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type(5)); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); + + // f(A, 0, 4, 4) = { 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type(4)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u); + + // f(A, 0, 4, 3) = { 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type(3)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u); + + // f(A, 0, 4, 2) = { 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u); + + // f(A, 0, 4, 1) = { 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type(1)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u); + + // f(A, 0, 4, 0) = { 5, 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 4, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); + + // f(A, 1, 5, 9) = { 5, 3 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(9)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + + // f(A, 1, 5, 6) = { 5, 3 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(6)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + + // f(A, 1, 5, 5) = { 5, 3 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(5)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + + // f(A, 1, 5, 4) = { 3 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(4)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + + // f(A, 1, 5, 3) = { 3 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(3)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + + // f(A, 1, 5, 2) = { } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // f(A, 1, 5, 1) = { } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(1)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // f(A, 1, 5, 0) = { 5, 3 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 5, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + + // f(A, 0, 3, 9) = { 5, 3, 1 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type(9)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + + // f(A, 0, 3, 6) = { 5, 3, 1 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type(6)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + + // f(A, 0, 3, 5) = { 5, 3, 1 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type(5)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + + // f(A, 0, 3, 4) = { 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type(4)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u); + + // f(A, 0, 3, 3) = { 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type(3)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u); + + // f(A, 0, 3, 2) = { 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u); + + // f(A, 0, 3, 1) = { 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type(1)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u); + + // f(A, 0, 3, 0) = { 5, 3, 1 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(), 3, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + + // f(B, 0, 4, 9) = { 6, 4, 2, 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type(9)); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u); + + // f(B, 0, 4, 6) = { 6, 4, 2, 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type(6)); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u); + + // f(B, 0, 4, 5) = { 4, 2, 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type(5)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + + // f(B, 0, 4, 4) = { 4, 2, 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type(4)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + + // f(B, 0, 4, 3) = { 2, 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type(3)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u); + + // f(B, 0, 4, 2) = { 2, 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u); + + // f(B, 0, 4, 1) = { 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type(1)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u); + + // f(B, 0, 4, 0) = { 6, 4, 2, 1 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(), 4, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u); + + // f(B, 2, 4, 9) = { 6, 4 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(9)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + + // f(B, 2, 4, 6) = { 6, 4 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(6)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + + // f(B, 2, 4, 5) = { 4 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(5)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); + + // f(B, 2, 4, 4) = { 4 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(4)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); + + // f(B, 2, 4, 3) = { } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(3)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // f(B, 2, 4, 2) = { } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // f(B, 2, 4, 1) = { } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(1)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // f(B, 2, 4, 0) = { 6, 4 } + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(2), 4, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + + // 0 limits + histories = hist_api.get_account_history(dan.get_id(), operation_history_id_type(0), 0, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(3), 0, operation_history_id_type(9)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // create a new account C = alice { 7 } + auto alice = create_account("alice"); + + generate_block(); + + // f(C, 0, 4, 10) = { 7 } + histories = hist_api.get_account_history(alice.get_id(), operation_history_id_type(0), 4, operation_history_id_type(10)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 7u); + + // f(C, 8, 4, 10) = { } + histories = hist_api.get_account_history(alice.get_id(), operation_history_id_type(8), 4, operation_history_id_type(10)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // f(A, 0, 10, 0) = { 7, 5, 3, 1, 0 } + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 5u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 7u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 5u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[4].id.instance(), 0u); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(track_account) { + try { + graphene::app::history_api hist_api(app); + + // account_id_type() is not tracked + + // account_id_type() creates alice(not tracked account) + const account_object& alice = create_account("alice"); + auto alice_id = alice.id; + + //account_id_type() creates some ops + create_bitasset("CNY", account_id_type()); + create_bitasset("USD", account_id_type()); + + // account_id_type() creates dan(account tracked) + const account_object& dan = create_account("dan"); + auto dan_id = dan.id; + + // dan makes 1 op + create_bitasset("EUR", dan_id); + + generate_block( ~database::skip_fork_db ); + + // anything against account_id_type() should be {} + vector histories = + hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 1, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // anything against alice should be {} + histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + // dan should have history + histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); + + // create more ops, starting with an untracked account + create_bitasset( "BTC", account_id_type() ); + create_bitasset( "GBP", dan_id ); + + generate_block( ~database::skip_fork_db ); + + histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); + + db.pop_block(); + + // Try again, should result in same object IDs + create_bitasset( "BTC", account_id_type() ); + create_bitasset( "GBP", dan_id ); + + generate_block(); + + histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); + } catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(track_account2) { + try { + graphene::app::history_api hist_api(app); + + // account_id_type() is tracked + + // account_id_type() creates alice(tracked account) + const account_object& alice = create_account("alice"); + auto alice_id = alice.id; + + //account_id_type() creates some ops + create_bitasset("CNY", account_id_type()); + create_bitasset("USD", account_id_type()); + + // alice makes 1 op + create_bitasset("EUR", alice_id); + + // account_id_type() creates dan(account not tracked) + const account_object& dan = create_account("dan"); + auto dan_id = dan.id; + + generate_block(); + + // all account_id_type() should have 4 ops {4,2,1,0} + vector histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 4u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); + BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); + + // all alice account should have 2 ops {3, 0} + histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u); + + // alice first op should be {0} + histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 1, operation_history_id_type(1)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u); + + // alice second op should be {3} + histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); + + // anything against dan should be {} + histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 10, operation_history_id_type(0)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 1, operation_history_id_type(2)); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + } catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; + } +} + +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(), 1u); + BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u); + 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(), 2u); + 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(), 0u); + + // 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(), 1u); + 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(), 1u); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + } catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file