From d333dd3812aa212049e0e06f8e904e9b81a14bed Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Wed, 6 Sep 2017 16:52:58 -0400 Subject: [PATCH] Track match/cancel/adjust operations related to a bet in the bookie plugin. Create a paginated version of get_matched_bets_for_bettor() --- libraries/chain/db_block.cpp | 4 ++ .../chain/include/graphene/chain/database.hpp | 7 +++ .../account_history_plugin.cpp | 11 +++-- libraries/plugins/bookie/bookie_api.cpp | 40 +++++++++++++++ libraries/plugins/bookie/bookie_plugin.cpp | 49 +++++++++++++++++++ .../include/graphene/bookie/bookie_api.hpp | 8 +-- .../graphene/bookie/bookie_objects.hpp | 11 +++-- tests/betting/betting_tests.cpp | 17 +++++++ 8 files changed, 136 insertions(+), 11 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 0bcc6f41..1a886e90 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -479,6 +479,10 @@ const vector >& database::get_applied_opera return _applied_ops; } +vector >& database::get_applied_operations() +{ + return _applied_ops; +} //////////////////// private methods //////////////////// void database::apply_block( const signed_block& next_block, uint32_t skip ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 0391c1de..7ccf99b9 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -171,8 +171,15 @@ namespace graphene { namespace chain { */ uint32_t push_applied_operation( const operation& op ); void set_applied_operation_result( uint32_t op_id, const operation_result& r ); + + // most plugins should use the const version of get_applied_operations const vector >& get_applied_operations()const; + // the account_history plugin uses the non-const version. When it decides to track an + // operation and assigns an operation_id to it, it will store that id into the operation + // history object so other plugins that evaluate later can reference it. + vector >& get_applied_operations(); + string to_pretty_string( const asset& a )const; /** diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index f1bba84d..5cd00235 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -75,23 +75,24 @@ class account_history_plugin_impl account_history_plugin_impl::~account_history_plugin_impl() { - return; } void account_history_plugin_impl::update_account_histories( const signed_block& b ) { graphene::chain::database& db = database(); - const vector >& hist = db.get_applied_operations(); - for( const optional< operation_history_object >& o_op : hist ) + vector >& hist = db.get_applied_operations(); + for( optional< operation_history_object >& o_op : hist ) { optional oho; auto create_oho = [&]() { - return optional( db.create( [&]( operation_history_object& h ) + operation_history_object result = db.create( [&]( operation_history_object& h ) { if( o_op.valid() ) h = *o_op; - } ) ); + } ); + o_op->id = result.id; + return optional(result); }; if( !o_op.valid() || ( _max_ops_per_account == 0 && _partial_operations ) ) diff --git a/libraries/plugins/bookie/bookie_api.cpp b/libraries/plugins/bookie/bookie_api.cpp index 0e6fa016..ce01a99b 100644 --- a/libraries/plugins/bookie/bookie_api.cpp +++ b/libraries/plugins/bookie/bookie_api.cpp @@ -32,6 +32,7 @@ class bookie_api_impl std::vector get_events_containing_sub_string(const std::string& sub_string, const std::string& language); fc::variants get_objects(const vector& ids) const; std::vector get_matched_bets_for_bettor(account_id_type bettor_id) const; + std::vector get_all_matched_bets_for_bettor(account_id_type bettor_id, bet_id_type start, unsigned limit) const; graphene::app::application& app; }; @@ -196,6 +197,7 @@ std::vector bookie_api_impl::get_matched_bets_for_bettor(acc match.back_or_lay = iter->ephemeral_bet_object.back_or_lay; match.end_of_delay = iter->ephemeral_bet_object.end_of_delay; match.amount_matched = iter->amount_matched; + match.associated_operations = iter->associated_operations; result.emplace_back(std::move(match)); ++iter; @@ -203,6 +205,37 @@ std::vector bookie_api_impl::get_matched_bets_for_bettor(acc return result; } +std::vector bookie_api_impl::get_all_matched_bets_for_bettor(account_id_type bettor_id, bet_id_type start, unsigned limit) const +{ + FC_ASSERT(limit <= 1000, "You may request at most 1000 matched bets at a time"); + + std::vector result; + std::shared_ptr db = app.chain_database(); + auto& persistent_bets_by_bettor_id = db->get_index_type().indices().get(); + persistent_bet_multi_index_type::index::type::iterator iter; + if (start == bet_id_type()) + iter = persistent_bets_by_bettor_id.lower_bound(std::make_tuple(bettor_id, true)); + else + iter = persistent_bets_by_bettor_id.lower_bound(std::make_tuple(bettor_id, true, start)); + while (iter != persistent_bets_by_bettor_id.end() && + iter->get_bettor_id() == bettor_id && + iter->is_matched() && + result.size() < limit) + { + matched_bet_object match; + match.id = iter->ephemeral_bet_object.id; + match.bettor_id = iter->ephemeral_bet_object.bettor_id; + match.betting_market_id = iter->ephemeral_bet_object.betting_market_id; + match.amount_to_bet = iter->ephemeral_bet_object.amount_to_bet; + match.back_or_lay = iter->ephemeral_bet_object.back_or_lay; + match.end_of_delay = iter->ephemeral_bet_object.end_of_delay; + match.amount_matched = iter->amount_matched; + result.emplace_back(std::move(match)); + + ++iter; + } + return result; +} std::shared_ptr bookie_api_impl::get_plugin() { @@ -251,6 +284,13 @@ std::vector bookie_api::get_matched_bets_for_bettor(account_ return my->get_matched_bets_for_bettor(bettor_id); } +std::vector bookie_api::get_all_matched_bets_for_bettor(account_id_type bettor_id, + bet_id_type start /* = bet_id_type() */, + unsigned limit /* = 1000 */) const +{ + return my->get_all_matched_bets_for_bettor(bettor_id, start, limit); +} + } } // graphene::bookie diff --git a/libraries/plugins/bookie/bookie_plugin.cpp b/libraries/plugins/bookie/bookie_plugin.cpp index 86065674..29ee69af 100644 --- a/libraries/plugins/bookie/bookie_plugin.cpp +++ b/libraries/plugins/bookie/bookie_plugin.cpp @@ -272,6 +272,20 @@ void bookie_plugin_impl::on_objects_changed(const vector& change } } +bool is_operation_history_object_stored(operation_history_id_type id) +{ + if (id == operation_history_id_type()) + { + elog("Warning: the operation history object for an operation the bookie plugin needs to track " + "has id of ${id}, which means the account history plugin isn't storing this operation, or that " + "it is running after the bookie plugin. Make sure the account history plugin is tracking operations for " + "all accounts,, and that it is loaded before the bookie plugin", ("id", id)); + return false; + } + else + return true; +} + void bookie_plugin_impl::on_block_applied( const signed_block& ) { graphene::chain::database& db = database(); @@ -296,6 +310,8 @@ void bookie_plugin_impl::on_block_applied( const signed_block& ) { db.modify(*bet_iter, [&]( persistent_bet_object& obj ) { obj.amount_matched += amount_bet.amount; + if (is_operation_history_object_stored(op.id)) + obj.associated_operations.emplace_back(op.id); }); const bet_object& bet_obj = bet_iter->ephemeral_bet_object; @@ -332,6 +348,39 @@ void bookie_plugin_impl::on_block_applied( const signed_block& ) result.first->second = pair.second; } } + else if ( op.op.which() == operation::tag::value ) + { + const bet_canceled_operation& bet_canceled_op = op.op.get(); + auto& persistent_bets_by_bet_id = db.get_index_type().indices().get(); + auto bet_iter = persistent_bets_by_bet_id.find(bet_canceled_op.bet_id); + assert(bet_iter != persistent_bets_by_bet_id.end()); + if (bet_iter != persistent_bets_by_bet_id.end()) + { + ilog("Adding bet_canceled_operation ${canceled_id} to bet ${bet_id}'s associated operations", + ("canceled_id", op.id)("bet_id", bet_canceled_op.bet_id)); + if (is_operation_history_object_stored(op.id)) + db.modify(*bet_iter, [&]( persistent_bet_object& obj ) { + obj.associated_operations.emplace_back(op.id); + }); + } + } + else if ( op.op.which() == operation::tag::value ) + { + const bet_adjusted_operation& bet_adjusted_op = op.op.get(); + auto& persistent_bets_by_bet_id = db.get_index_type().indices().get(); + auto bet_iter = persistent_bets_by_bet_id.find(bet_adjusted_op.bet_id); + assert(bet_iter != persistent_bets_by_bet_id.end()); + if (bet_iter != persistent_bets_by_bet_id.end()) + { + ilog("Adding bet_adjusted_operation ${adjusted_id} to bet ${bet_id}'s associated operations", + ("adjusted_id", op.id)("bet_id", bet_adjusted_op.bet_id)); + if (is_operation_history_object_stored(op.id)) + db.modify(*bet_iter, [&]( persistent_bet_object& obj ) { + obj.associated_operations.emplace_back(op.id); + }); + } + } + } } diff --git a/libraries/plugins/bookie/include/graphene/bookie/bookie_api.hpp b/libraries/plugins/bookie/include/graphene/bookie/bookie_api.hpp index b22059da..34a554a5 100644 --- a/libraries/plugins/bookie/include/graphene/bookie/bookie_api.hpp +++ b/libraries/plugins/bookie/include/graphene/bookie/bookie_api.hpp @@ -50,6 +50,8 @@ struct matched_bet_object { // plus fields from this plugin share_type amount_matched; + + std::vector associated_operations; }; class bookie_api @@ -67,7 +69,7 @@ class bookie_api std::vector get_events_containing_sub_string(const std::string& sub_string, const std::string& language); fc::variants get_objects(const vector& ids)const; std::vector get_matched_bets_for_bettor(account_id_type bettor_id) const; - + std::vector get_all_matched_bets_for_bettor(account_id_type bettor_id, bet_id_type start = bet_id_type(), unsigned limit = 1000) const; std::shared_ptr my; }; @@ -75,7 +77,7 @@ class bookie_api FC_REFLECT(graphene::bookie::order_bin, (amount_to_bet)(backer_multiplier)) FC_REFLECT(graphene::bookie::binned_order_book, (aggregated_back_bets)(aggregated_lay_bets)) -FC_REFLECT(graphene::bookie::matched_bet_object, (id)(bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(end_of_delay)(amount_matched)) +FC_REFLECT(graphene::bookie::matched_bet_object, (id)(bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(end_of_delay)(amount_matched)(associated_operations)) FC_API(graphene::bookie::bookie_api, (get_binned_order_book) @@ -83,5 +85,5 @@ FC_API(graphene::bookie::bookie_api, (get_events_containing_sub_string) (get_objects) (get_matched_bets_for_bettor) - ) + (get_all_matched_bets_for_bettor)) diff --git a/libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp b/libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp index cff2d399..3604aac5 100644 --- a/libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp +++ b/libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp @@ -159,6 +159,8 @@ class persistent_bet_object : public graphene::db::abstract_object associated_operations; + bet_id_type get_bet_id() const { return ephemeral_bet_object.id; } account_id_type get_bettor_id() const { return ephemeral_bet_object.bettor_id; } bool is_matched() const { return amount_matched != share_type(); } @@ -176,8 +178,11 @@ typedef multi_index_container< persistent_bet_object, const_mem_fun, const_mem_fun, - const_mem_fun > > > > persistent_bet_multi_index_type; - + const_mem_fun >, + composite_key_compare< + std::less, + std::less, + std::greater > > > > persistent_bet_multi_index_type; typedef generic_index persistent_bet_index; @@ -186,5 +191,5 @@ typedef generic_index pe FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_event_object, (graphene::db::object), (ephemeral_event_object) ) FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_betting_market_group_object, (graphene::db::object), (ephemeral_betting_market_group_object)(total_matched_bets_amount) ) FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_betting_market_object, (graphene::db::object), (ephemeral_betting_market_object) ) -FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_bet_object, (graphene::db::object), (ephemeral_bet_object)(amount_matched) ) +FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_bet_object, (graphene::db::object), (ephemeral_bet_object)(amount_matched)(associated_operations) ) diff --git a/tests/betting/betting_tests.cpp b/tests/betting/betting_tests.cpp index 148bd6aa..2838a9c2 100644 --- a/tests/betting/betting_tests.cpp +++ b/tests/betting/betting_tests.cpp @@ -378,15 +378,19 @@ BOOST_AUTO_TEST_CASE(persistent_objects_test) // lay 46 at 1.94 odds (50:47) -- this is too small to be placed on the books and there's // nothing for it to match, so it should be canceled bet_id_type automatically_canceled_bet_id = place_bet(alice_id, capitals_win_market_id, bet_type::lay, asset(46, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + generate_blocks(1); BOOST_CHECK_MESSAGE(!db.find(automatically_canceled_bet_id), "Bet should have been canceled, but the blockchain still knows about it"); fc::variants objects_from_bookie = bookie_api.get_objects({automatically_canceled_bet_id}); + idump((objects_from_bookie)); BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 1); BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as() == automatically_canceled_bet_id, "Bookie Plugin didn't return a deleted bet it"); // lay 47 at 1.94 odds (50:47) -- this bet should go on the order books normally bet_id_type first_bet_on_books = place_bet(alice_id, capitals_win_market_id, bet_type::lay, asset(47, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + generate_blocks(1); BOOST_CHECK_MESSAGE(db.find(first_bet_on_books), "Bet should exist on the blockchain"); objects_from_bookie = bookie_api.get_objects({first_bet_on_books}); + idump((objects_from_bookie)); BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 1); BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as() == first_bet_on_books, "Bookie Plugin didn't return a bet that is currently on the books"); @@ -397,6 +401,7 @@ BOOST_AUTO_TEST_CASE(persistent_objects_test) generate_blocks(1); // the bookie plugin doesn't detect matches until a block is generated objects_from_bookie = bookie_api.get_objects({first_bet_on_books, matching_bet}); + idump((objects_from_bookie)); BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 2); BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as() == first_bet_on_books, "Bookie Plugin didn't return a bet that has been filled"); BOOST_CHECK_MESSAGE(objects_from_bookie[1]["id"].as() == matching_bet, "Bookie Plugin didn't return a bet that has been filled"); @@ -408,9 +413,21 @@ BOOST_AUTO_TEST_CASE(persistent_objects_test) // test get_matched_bets_for_bettor std::vector alice_matched_bets = bookie_api.get_matched_bets_for_bettor(alice_id); + for (const graphene::bookie::matched_bet_object& matched_bet : alice_matched_bets) + { + idump((matched_bet)); + for (operation_history_id_type id : matched_bet.associated_operations) + idump((id(db))); + } BOOST_REQUIRE_EQUAL(alice_matched_bets.size(), 1); BOOST_CHECK(alice_matched_bets[0].amount_matched == 47); std::vector bob_matched_bets = bookie_api.get_matched_bets_for_bettor(bob_id); + for (const graphene::bookie::matched_bet_object& matched_bet : bob_matched_bets) + { + idump((matched_bet)); + for (operation_history_id_type id : matched_bet.associated_operations) + idump((id(db))); + } BOOST_REQUIRE_EQUAL(bob_matched_bets.size(), 1); BOOST_CHECK(bob_matched_bets[0].amount_matched == 50);