diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b03d58a8..c1eced4e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,10 @@ file(GLOB PERFORMANCE_TESTS "performance/*.cpp") add_executable( performance_test ${PERFORMANCE_TESTS} ${COMMON_SOURCES} ) target_link_libraries( performance_test graphene_chain graphene_app graphene_account_history graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +file(GLOB TOURNAMENT_TESTS "tournament/*.cpp") +add_executable( tournament_test ${TOURNAMENT_TESTS} ${COMMON_SOURCES} ) +target_link_libraries( tournament_test graphene_chain graphene_app graphene_account_history graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) + file(GLOB BENCH_MARKS "benchmarks/*.cpp") add_executable( chain_bench ${BENCH_MARKS} ${COMMON_SOURCES} ) target_link_libraries( chain_bench graphene_chain graphene_app graphene_account_history graphene_time graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 64400522..cfa11ea1 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -72,6 +72,7 @@ database_fixture::database_fixture() if( arg == "--show-test-names" ) std::cout << "running test " << boost::unit_test::framework::current_test_case().p_name << std::endl; } + auto ahplugin = app.register_plugin(); auto mhplugin = app.register_plugin(); init_account_pub_key = init_account_priv_key.get_public_key(); @@ -81,7 +82,7 @@ database_fixture::database_fixture() genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP ); genesis_state.initial_active_witnesses = 10; - for( unsigned i = 0; i < genesis_state.initial_active_witnesses; ++i ) + for( int i = 0; i < genesis_state.initial_active_witnesses; ++i ) { auto name = "init"+fc::to_string(i); genesis_state.initial_accounts.emplace_back(name, diff --git a/tests/tournament/tournament_tests.cpp b/tests/tournament/tournament_tests.cpp new file mode 100644 index 00000000..1b86d939 --- /dev/null +++ b/tests/tournament/tournament_tests.cpp @@ -0,0 +1,756 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include + + +#include +#include +#include +#include "../common/database_fixture.hpp" +#include + +using namespace graphene::chain; + +// defined if "bye" matches fix available +#define BYE_MATCHES_FIXED + +BOOST_AUTO_TEST_SUITE(tournament_tests) + +// class performing operations necessary for creating tournaments, +// having players join the tournaments and playing tournaments to completion. +class tournaments_helper +{ +public: + + tournaments_helper(database_fixture& df) : df(df) + { + assets.insert(asset_id_type()); + players.insert(TOURNAMENT_RAKE_FEE_ACCOUNT_ID); + } + + const std::set& list_tournaments() + { + return tournaments; + } + + std::map> list_players_balances() + { + std::map> result; + for (account_id_type player_id: players) + { + for( asset_id_type asset_id: assets) + { + asset a = df.db.get_balance(player_id, asset_id); + result[player_id][a.asset_id] = a.amount; + } + } + return result; + } + + std::map> get_players_fees() + { + return players_fees; + } + + void reset_players_fees() + { + for (account_id_type player_id: players) + { + for( asset_id_type asset_id: assets) + { + players_fees[player_id][asset_id] = 0; + } + } + } + + const tournament_id_type create_tournament (const account_id_type& creator, + const fc::ecc::private_key& sig_priv_key, + asset buy_in, + uint32_t number_of_players = 2, + uint32_t time_per_commit_move = 3, + uint32_t time_per_reveal_move = 1, + uint32_t number_of_wins = 3, + uint32_t start_delay = 3, + uint32_t round_delay = 3, + bool insurance_enabled = false + ) + { + if (current_tournament_idx.valid()) + current_tournament_idx = *current_tournament_idx + 1; + else + current_tournament_idx = 0; + + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction trx; + tournament_options options; + tournament_create_operation op; + rock_paper_scissors_game_options& game_options = options.game_options.get(); + + game_options.number_of_gestures = 3; + game_options.time_per_commit_move = time_per_commit_move; + game_options.time_per_reveal_move = time_per_reveal_move; + game_options.insurance_enabled = insurance_enabled; + + options.registration_deadline = db.head_block_time() + fc::hours(1 + *current_tournament_idx); + options.buy_in = buy_in; + options.number_of_players = number_of_players; + options.start_delay = start_delay; + options.round_delay = round_delay; + options.number_of_wins = number_of_wins; + + op.creator = creator; + op.options = options; + trx.operations = {op}; + for( auto& op : trx.operations ) + db.current_fee_schedule().set_fee(op); + trx.validate(); + trx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(trx, sig_priv_key); + PUSH_TX(db, trx); + + tournament_id_type tournament_id = tournament_id_type(*current_tournament_idx); + tournaments.insert(tournament_id); + return tournament_id; + } + + void join_tournament(const tournament_id_type & tournament_id, + const account_id_type& player_id, + const account_id_type& payer_id, + const fc::ecc::private_key& sig_priv_key, + asset buy_in + ) + { + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + tournament_join_operation op; + + op.payer_account_id = payer_id; + op.buy_in = buy_in; + op.player_account_id = player_id; + op.tournament_id = tournament_id; + tx.operations = {op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + players.insert(player_id); + players_keys[player_id] = sig_priv_key; + } + + // stolen from cli_wallet + void rps_throw(const game_id_type& game_id, + const account_id_type& player_id, + rock_paper_scissors_gesture gesture, + const fc::ecc::private_key& sig_priv_key + ) + { + + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + + // check whether the gesture is appropriate for the game we're playing + game_object game_obj = game_id(db); + match_object match_obj = game_obj.match_id(db); + tournament_object tournament_obj = match_obj.tournament_id(db); + rock_paper_scissors_game_options game_options = tournament_obj.options.game_options.get(); + assert((int)gesture < game_options.number_of_gestures); + + account_object player_account_obj = player_id(db); + + // construct the complete throw, the commit, and reveal + rock_paper_scissors_throw full_throw; + rand_bytes((char*)&full_throw.nonce1, sizeof(full_throw.nonce1)); + rand_bytes((char*)&full_throw.nonce2, sizeof(full_throw.nonce2)); + full_throw.gesture = gesture; + + rock_paper_scissors_throw_commit commit_throw; + commit_throw.nonce1 = full_throw.nonce1; + std::vector full_throw_packed(fc::raw::pack(full_throw)); + commit_throw.throw_hash = fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size()); + + rock_paper_scissors_throw_reveal reveal_throw; + reveal_throw.nonce2 = full_throw.nonce2; + reveal_throw.gesture = full_throw.gesture; + + // store off the reveal for applying after both players commit + committed_game_moves[commit_throw] = reveal_throw; + + signed_transaction tx; + game_move_operation move_operation; + move_operation.game_id = game_id; + move_operation.player_account_id = player_account_obj.id; + move_operation.move = commit_throw; + tx.operations = {move_operation}; + for( operation& op : tx.operations ) + { + asset f = db.current_fee_schedule().set_fee(op); + players_fees[player_id][f.asset_id] -= f.amount; + } + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + } + + // spaghetti programming + // walking through all tournaments, matches and games and throwing random moves + void play_games() + { + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + rock_paper_scissors_game_options game_options = tournament.options.game_options.get(); + for(const auto& match_id: tournament_details.matches) + { + const match_object& match = match_id(db); + for(const auto& game_id: match.games ) + { + const game_object& game = game_id(db); + if (game.get_state() == game_state::expecting_commit_moves) + { + for(const auto& player_id: game.players) + { + if (players_keys.find(player_id) != players_keys.end()) + { + rps_throw(game_id, player_id, (rock_paper_scissors_gesture) (std::rand() % game_options.number_of_gestures), players_keys[player_id]); + } + } + } + else if (game.get_state() == game_state::expecting_reveal_moves) + { + const rock_paper_scissors_game_details& rps_details = game.game_details.get(); + + for (unsigned i = 0; i < 2; ++i) + { + if (rps_details.commit_moves.at(i) && + !rps_details.reveal_moves.at(i)) + { + const account_id_type& player_id = game.players[i]; + if (players_keys.find(player_id) != players_keys.end()) + { + { + auto iter = committed_game_moves.find(*rps_details.commit_moves.at(i)); + if (iter != committed_game_moves.end()) + { + const rock_paper_scissors_throw_reveal& reveal = iter->second; + + game_move_operation move_operation; + move_operation.game_id = game.id; + move_operation.player_account_id = player_id; + move_operation.move = reveal; + + signed_transaction tx; + tx.operations = {move_operation}; + for( auto& op : tx.operations ) + { + asset f = db.current_fee_schedule().set_fee(op); + players_fees[player_id][f.asset_id] -= f.amount; + } + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, players_keys[player_id]); + PUSH_TX(db, tx); + } + } + } + } + } + } + } + } + } + } + +private: + database_fixture& df; + // index of last created tournament + fc::optional current_tournament_idx; + // assets : core and maybe others + std::set assets; + // tournaments to be played + std::set tournaments; + // all players registered in tournaments + std::set players; + // players' private keys + std::map players_keys; + // total charges for moves made by every player + std::map> players_fees; + // store of commits and reveals + std::map committed_game_moves; + + // taken from rand.cpp + void rand_bytes(char* buf, int count) + { + fc::init_openssl(); + + int result = RAND_bytes((unsigned char*)buf, count); + if (result != 1) + FC_THROW("Error calling OpenSSL's RAND_bytes(): ${code}", ("code", (uint32_t)ERR_get_error())); + } +}; + +// Test of basic functionality creating two tournamenst, joinig players, +// playing tournaments to completion, distributing prize. +// Can be used to test of "bye" matches handling if "bye" matches fix available. +// Number of players 2+1 4+1 8+1 ... seem to be most critical for handling of "bye" matches. +// Moves are generated automatically. +BOOST_FIXTURE_TEST_CASE( simple, database_fixture ) +{ + try + { +#ifdef BYE_MATCHES_FIXED + #define TEST1_NR_OF_PLAYERS_NUMBER 3 + #define TEST2_NR_OF_PLAYERS_NUMBER 5 +#else + #define TEST1_NR_OF_PLAYERS_NUMBER 2 + #define TEST2_NR_OF_PLAYERS_NUMBER 4 +#endif + BOOST_TEST_MESSAGE("Hello simple tournament test"); + ACTORS((nathan)(alice)(bob)(carol)(dave)(ed)(frank)(george)(harry)(ike)); + + tournaments_helper tournament_helper(*this); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + + BOOST_TEST_MESSAGE( "Giving folks some money" ); + transfer(committee_account, nathan_id, asset(1000000000)); + transfer(committee_account, alice_id, asset(2000000)); + transfer(committee_account, bob_id, asset(3000000)); + transfer(committee_account, carol_id, asset(4000000)); + transfer(committee_account, dave_id, asset(5000000)); +#if TEST2_NR_OF_PLAYERS_NUMBER > 4 + transfer(committee_account, ed_id, asset(6000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 5 + transfer(committee_account, frank_id, asset(7000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 6 + transfer(committee_account, george_id, asset(8000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 7 + transfer(committee_account, harry_id, asset(9000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 8 + transfer(committee_account, ike_id, asset(1000000)); +#endif + + BOOST_TEST_MESSAGE( "Preparing nathan" ); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + uint16_t tournaments_to_complete = 0; + asset buy_in = asset(12000); + tournament_id_type tournament_id; + + BOOST_TEST_MESSAGE( "Preparing a tournament" ); + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, TEST1_NR_OF_PLAYERS_NUMBER); + BOOST_REQUIRE(tournament_id == tournament_id_type()); + + tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); +#if TEST1_NR_OF_PLAYERS_NUMBER > 2 + tournament_helper.join_tournament(tournament_id, carol_id, carol_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("carol"))), buy_in); +#endif + ++tournaments_to_complete; + + BOOST_TEST_MESSAGE( "Preparing another one" ); + buy_in = asset(13000); + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, TEST2_NR_OF_PLAYERS_NUMBER); + BOOST_REQUIRE(tournament_id == tournament_id_type(1)); + tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); + tournament_helper.join_tournament(tournament_id, carol_id, carol_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("carol"))), buy_in); + tournament_helper.join_tournament(tournament_id, dave_id, dave_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("dave"))), buy_in); +#if TEST2_NR_OF_PLAYERS_NUMBER > 4 + tournament_helper.join_tournament(tournament_id, ed_id, ed_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("ed"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 5 + tournament_helper.join_tournament(tournament_id, frank_id, frank_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("frank"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 6 + tournament_helper.join_tournament(tournament_id, george_id, george_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("george"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 7 + tournament_helper.join_tournament(tournament_id, harry_id, harry_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("harry"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 8 + tournament_helper.join_tournament(tournament_id, ike_id, ike_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("ike"))), buy_in); +#endif + ++tournaments_to_complete; + + auto abc = [&] (string s) + { +#if 0 + wlog(s); + auto a = db.get_balance(alice_id, asset_id_type()); wdump(("# alice's balance") (a)); + auto b = db.get_balance(bob_id, asset_id_type()); wdump(("# bob's balance") (b)); + auto c = db.get_balance(carol_id, asset_id_type()); wdump(("# carol's balance") (c)); + auto d = db.get_balance(dave_id, asset_id_type()); wdump(("# dave's balance") (d)); +#if TEST2_NR_OF_PLAYERS_NUMBER > 4 + auto e = db.get_balance(ed_id, asset_id_type()); wdump(("# ed's balance") (e)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 5 + auto f = db.get_balance(frank_id, asset_id_type()); wdump(("# frank's balance") (f)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 6 + auto g = db.get_balance(george_id, asset_id_type()); wdump(("# george's balance") (g)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 7 + auto h = db.get_balance(harry_id, asset_id_type()); wdump(("# harry's balance") (f)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 8 + auto i = db.get_balance(ike_id, asset_id_type()); wdump(("# ike's balance") (i)); +#endif + + auto n = db.get_balance(nathan_id, asset_id_type()); wdump(("# nathan's balance") (n)); + auto r = db.get_balance(TOURNAMENT_RAKE_FEE_ACCOUNT_ID, asset_id_type()); wdump(("# rake's balance") (r)); +#endif + }; + +#if 0 + // trying to randomize automatic moves ? + auto n = std::rand() % 100; + for(int i = 0; i < n ; ++i) + db.get_random_bits(3); +#endif + abc("@ tournament awaiting start"); + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + generate_block(); + abc("@ after first generated block"); + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_balances = tournament_helper.list_players_balances(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; + + while(tournaments_to_complete > 0) + { + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + + const account_object winner = winner_id(db); + BOOST_TEST_MESSAGE( "The winner is " + winner.name ); + + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + players_balances[TOURNAMENT_RAKE_FEE_ACCOUNT_ID][tournament.options.buy_in.asset_id] += rake_amount; + players_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + generate_block(); + sleep(1); + } + + abc("@ tournament concluded"); + // checking if prizes were distributed correctly + BOOST_CHECK(tournaments.size() == 0); + std::map> last_players_balances = tournament_helper.list_players_balances(); + for (auto a: last_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_balances[a.first][asset_id_type()]); + } + BOOST_TEST_MESSAGE("Bye simple tournament test\n"); + + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} + +// Test of concurrently played tournaments having +// 2, 4, 8 ... 64 players randomly registered from global pool +// generates random moves, +// checks prizes distribution and fees calculation, +// no "bye" matches. +BOOST_FIXTURE_TEST_CASE( basic, database_fixture ) +{ + try + { + #define MIN_PLAYERS_NUMBER 2 + #define MAX_PLAYERS_NUMBER 64 + + BOOST_TEST_MESSAGE("Hello basic tournament test"); + + ACTORS((nathan)); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + transfer(committee_account, nathan_id, asset(1000000000)); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + // creating random order of numbers of players joining tournaments + std::vector players; + for(unsigned i = MIN_PLAYERS_NUMBER; i <= MAX_PLAYERS_NUMBER; i*=2) + { + players.emplace_back(i); + } + for (unsigned i = players.size() - 1; i >= 1; --i) + { + if (std::rand() % 2 == 0) continue; + unsigned j = std::rand() % i; + std::swap(players[i], players[j]); + } + + // creating a pool of actors + std::vector> actors; + for(unsigned i = 0; i < 3 * MAX_PLAYERS_NUMBER; ++i) + { + std::string name = "account" + std::to_string(i); + auto priv_key = generate_private_key(name); + const auto& account = create_account(name, priv_key.get_public_key()); + actors.emplace_back(name, account.id, priv_key); + transfer(committee_account, account.id, asset((uint64_t)1000000000 * players.size() + 100000000 * (i+1))); + } +#if 0 + enable_fees(); + wdump((db.get_global_properties())); +#endif + // creating tournaments, registering players + tournaments_helper tournament_helper(*this); + for (unsigned i = 0; i < players.size(); ++i) + { + auto number_of_players = players[i]; + BOOST_TEST_MESSAGE( "Preparing tournament #" + std::to_string(i) + " with " + std::to_string(number_of_players) + " players"); + + asset buy_in = asset(1000 * number_of_players + 100 * i); + tournament_id_type tournament_id; + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, number_of_players, 30, 30); + + for (unsigned j = 0; j < actors.size() && number_of_players > 0; ++j) + { + if (number_of_players < actors.size() - j && std::rand() % 2 == 0) continue; + auto a = actors[j]; + --number_of_players; + tournament_helper.join_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a), buy_in); + } + } + + uint16_t tournaments_to_complete = players.size(); + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_initial_balances = tournament_helper.list_players_balances(); + tournament_helper.reset_players_fees(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; +#if 0 + wlog( "Prepared tournaments:"); + for(const tournament_id_type& tid: tournament_helper.list_tournaments()) + { + wlog(" # ${i}, players count ${c}", ("i", tid.instance) ("c", tid(db).registered_players)); + } + +#endif + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + tournament_helper.reset_players_fees(); + while(tournaments_to_complete > 0) + { + generate_block(); + enable_fees(); + tournament_helper.play_games(); + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + BOOST_TEST_MESSAGE( "The winner of " + std::string(object_id_type(tournament_id)) + " is " + winner_id(db).name + " " + std::string(object_id_type(winner_id))); + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + players_initial_balances[TOURNAMENT_RAKE_FEE_ACCOUNT_ID][tournament.options.buy_in.asset_id] += rake_amount; + players_initial_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + sleep(1); + } + BOOST_CHECK(tournaments.size() == 0); + + // checking if prizes were distributed correctly and fees calculated properly + std::map> current_players_balances = tournament_helper.list_players_balances(); + std::map> players_paid_fees = tournament_helper.get_players_fees(); + for (auto a: current_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_initial_balances[a.first][asset_id_type()] + players_paid_fees[a.first][asset_id_type()]); + } + + BOOST_TEST_MESSAGE("Bye basic tournament test\n"); + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } + +} + +#ifdef BYE_MATCHES_FIXED +// Test of several concurrently played tournaments having +// randomized number of players registered from global pool, +// generates random moves, +// checks prizes distribution. +// "bye" matches fix required +BOOST_FIXTURE_TEST_CASE( massive, database_fixture ) +{ + try + { + #define MIN_TOURNAMENTS_NUMBER 1 + #define MAX_TOURNAMENTS_NUMBER 10 + + #define MIN_PLAYERS_NUMBER 2 + #define MAX_PLAYERS_NUMBER 64 + + BOOST_TEST_MESSAGE("Hello massive tournament test"); + + ACTORS((nathan)); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + transfer(committee_account, nathan_id, asset(1000000000)); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + // creating a pool of actors + std::vector> actors; + for(unsigned i = 0; i < 2 * MAX_PLAYERS_NUMBER; ++i) + { + std::string name = "account" + std::to_string(i); + auto priv_key = generate_private_key(name); + const auto& account = create_account(name, priv_key.get_public_key()); + actors.emplace_back(name, account.id, priv_key); + transfer(committee_account, account.id, asset((uint64_t)1000000 * MAX_TOURNAMENTS_NUMBER + 100000 * (i+1))); + } + + // creating tournaments, registering players + tournaments_helper tournament_helper(*this); + unsigned number_of_tournaments = std::rand() % (MAX_TOURNAMENTS_NUMBER - 1) + MIN_TOURNAMENTS_NUMBER; + for(unsigned i = 0; i < number_of_tournaments; ++i) + { + unsigned number_of_players = std::rand() % (MAX_PLAYERS_NUMBER - 1) + MIN_PLAYERS_NUMBER; + BOOST_TEST_MESSAGE( "Preparing tournament with " + std::to_string(number_of_players) + " players"); + + asset buy_in = asset(1000 * number_of_players + 100 * i); + tournament_id_type tournament_id; + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, number_of_players, 30, 30); + + for (unsigned j = 0; j < actors.size() && number_of_players > 0; ++j) + { + if (number_of_players < actors.size() - j && std::rand() % 2 == 0) continue; + auto a = actors[j]; + --number_of_players; + tournament_helper.join_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a), buy_in); + } + } + + uint16_t tournaments_to_complete = number_of_tournaments; + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_balances = tournament_helper.list_players_balances(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; + + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + while(tournaments_to_complete > 0) + { + generate_block(); + tournament_helper.play_games(); + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + BOOST_TEST_MESSAGE( "The winner of " + std::string(object_id_type(tournament_id)) + " is " + winner_id(db).name + " " + std::string(object_id_type(winner_id))); + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + players_balances[TOURNAMENT_RAKE_FEE_ACCOUNT_ID][tournament.options.buy_in.asset_id] += rake_amount; + players_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + sleep(1); + } + BOOST_CHECK(tournaments.size() == 0); +#if 0 + wlog( "Performed tournaments:"); + for(const tournament_id_type& tid: tournament_helper.list_tournaments()) + { + const tournament_object tournament = tid(db); + wlog(" # ${i}, players count ${c}", ("i", tid.instance) ("c", tournament.registered_players)); + } +#endif + // checking if prizes were distributed correctly + std::map> last_players_balances = tournament_helper.list_players_balances(); + for (auto a: last_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_balances[a.first][asset_id_type()]); + } + + BOOST_TEST_MESSAGE("Bye massive tournament test\n"); + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} +#endif + +BOOST_AUTO_TEST_SUITE_END() + +//#define BOOST_TEST_MODULE "C++ Unit Tests for Graphene Blockchain Database" +#include +#include +#include + +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + std::srand(time(NULL)); + std::cout << "Random number generator seeded to " << time(NULL) << std::endl; + return nullptr; +}