// Copyright (c) 2014-2018 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "chaingen.h" #include "mixin_attr.h" using namespace epee; using namespace currency; mix_attr_tests::mix_attr_tests() { REGISTER_CALLBACK_METHOD(mix_attr_tests, check_balances_1); REGISTER_CALLBACK_METHOD(mix_attr_tests, remember_last_block); REGISTER_CALLBACK_METHOD(mix_attr_tests, check_last_not_changed); REGISTER_CALLBACK_METHOD(mix_attr_tests, check_last2_and_balance); REGISTER_CALLBACK_METHOD(mix_attr_tests, check_last_changed); } bool mix_attr_tests::generate(std::vector& events) const { uint64_t ts_start = 1338224400; GENERATE_ACCOUNT(miner_account); //TODO events MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); // 0 MAKE_ACCOUNT(events, bob_account); // 1 MAKE_ACCOUNT(events, alice_account); // 2 MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account); // 3 MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 4 MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_account); // 5 REWIND_BLOCKS(events, blk_3r, blk_3, miner_account); // MAKE_TX_LIST_START_MIX_ATTR(events, txs_blk_4, miner_account, miner_account, MK_TEST_COINS(5), blk_3r, CURRENCY_TO_KEY_OUT_RELAXED, std::vector()); // 6 + N MAKE_TX_LIST_MIX_ATTR(events, txs_blk_4, miner_account, miner_account, MK_TEST_COINS(5), blk_3r, CURRENCY_TO_KEY_OUT_RELAXED); // 7 + N MAKE_TX_LIST_MIX_ATTR(events, txs_blk_4, miner_account, miner_account, MK_TEST_COINS(5), blk_3r, CURRENCY_TO_KEY_OUT_RELAXED); // 8 + N MAKE_TX_LIST_MIX_ATTR(events, txs_blk_4, miner_account, bob_account, MK_TEST_COINS(5), blk_3r, CURRENCY_TO_KEY_OUT_FORCED_NO_MIX); // 9 + N MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, miner_account, txs_blk_4); // 10 + N DO_CALLBACK(events, "check_balances_1"); // 11 + N REWIND_BLOCKS(events, blk_4r, blk_4, miner_account); // 11 + 2N // let's try to spend Bob's no-mix output mixing in 1 fake outputs -- should fail DO_CALLBACK(events, "remember_last_block"); // 12 + 2N DO_CALLBACK(events, "mark_invalid_tx"); // 13 + 2N MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_TEST_COINS(5) - TESTS_DEFAULT_FEE, 1, blk_4); // 14 + 2N DO_CALLBACK(events, "mark_invalid_block"); // 15 + 2N MAKE_NEXT_BLOCK_TX1(events, blk_5_f, blk_4r, miner_account, tx_0); // 16 + 2N DO_CALLBACK(events, "check_last_not_changed"); // 17 + 2N // then try to spend Bob's no-mix output with direct spend -- should go well MAKE_TX_MIX_ATTR(events, tx_1, bob_account, alice_account, MK_TEST_COINS(5) - TESTS_DEFAULT_FEE, 0, blk_4, CURRENCY_TO_KEY_OUT_RELAXED, false); MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_account, tx_1); DO_CALLBACK(events, "check_last2_and_balance"); //check mixin MAKE_ACCOUNT(events, bob_account2); MAKE_ACCOUNT(events, alice_account2); MAKE_TX_LIST_START_MIX_ATTR(events, txs_blk_6, miner_account, miner_account, MK_TEST_COINS(5), blk_5, CURRENCY_TO_KEY_OUT_RELAXED, std::vector()); //MAKE_TX_LIST_MIX_ATTR(events, txs_blk_6, miner_account, miner_account, MK_COINS(5), blk_5, 4); MAKE_TX_LIST_MIX_ATTR(events, txs_blk_6, miner_account, bob_account2, MK_TEST_COINS(5), blk_5, 4); // mix_attr == 4 MAKE_TX_LIST_MIX_ATTR(events, txs_blk_6, miner_account, bob_account2, MK_TEST_COINS(5), blk_5, 4); // mix_attr == 4 MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5, miner_account, txs_blk_6); // // 7 outputs with amount == MK_TEST_COINS(5) having these mix_attrs: 0, 0, 0, 1 (miner -> miner, txs_blk_4), 0 (miner -> miner, txs_blk_6), 4, 4 (miner -> bob2, txs_blk_6) // DO_CALLBACK(events, "remember_last_block"); bool r = false; try { // should fail to construct as there are not enough outputs to mix (regardless of mix_attr) MAKE_TX_MIX_ATTR(events, tx_3, bob_account2, alice_account2, MK_TEST_COINS(5) - TESTS_DEFAULT_FEE, 7, blk_6, CURRENCY_TO_KEY_OUT_RELAXED, false); } catch (std::runtime_error&) { r = true; } CHECK_AND_ASSERT_MES(r, false, "tx_3 passed ok, but expected to fail"); DO_CALLBACK(events, "mark_invalid_tx"); // should fail because Bob2 outputs may only be used in set of 4 and more outputs (there's only 2 + 1 = 3) MAKE_TX_MIX_ATTR(events, tx_4, bob_account2, alice_account2, MK_TEST_COINS(5) - TESTS_DEFAULT_FEE, 2, blk_6, CURRENCY_TO_KEY_OUT_RELAXED, false); DO_CALLBACK(events, "mark_invalid_tx"); // the same MAKE_TX_MIX_ATTR(events, tx_5, bob_account2, alice_account2, MK_TEST_COINS(5) - TESTS_DEFAULT_FEE, 1, blk_6, CURRENCY_TO_KEY_OUT_RELAXED, false); DO_CALLBACK(events, "mark_invalid_tx"); // the same MAKE_TX_MIX_ATTR(events, tx_6, bob_account2, alice_account2, MK_TEST_COINS(5) - TESTS_DEFAULT_FEE, 0, blk_6, CURRENCY_TO_KEY_OUT_RELAXED, false); MAKE_TX_MIX_ATTR(events, tx_7, bob_account2, alice_account2, MK_TEST_COINS(5) - TESTS_DEFAULT_FEE, 3, blk_6, CURRENCY_TO_KEY_OUT_RELAXED, false); // should pass MAKE_NEXT_BLOCK_TX1(events, blk_7, blk_6, miner_account, tx_7); DO_CALLBACK(events, "check_last_changed"); return true; } bool mix_attr_tests::check_balances_1(currency::core& c, size_t ev_index, const std::vector& events) { currency::account_base m_bob_account = boost::get(events[1]); currency::account_base m_alice_account = boost::get(events[2]); std::list blocks; bool r = c.get_blocks(0, 100 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks); CHECK_TEST_CONDITION(r); std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); CHECK_EQ(MK_TEST_COINS(5), get_balance(m_bob_account, chain, mtx)); CHECK_EQ(0, get_balance(m_alice_account, chain, mtx)); return true; } bool mix_attr_tests::remember_last_block(currency::core& c, size_t ev_index, const std::vector& events) { top_id_befor_split = c.get_tail_id(); return true; } bool mix_attr_tests::check_last_not_changed(currency::core& c, size_t ev_index, const std::vector& events) { CHECK_EQ(top_id_befor_split, c.get_tail_id()); return true; } bool mix_attr_tests::check_last2_and_balance(currency::core& c, size_t ev_index, const std::vector& events) { CHECK_NOT_EQ(top_id_befor_split, c.get_tail_id()); currency::account_base m_bob_account = boost::get(events[1]); currency::account_base m_alice_account = boost::get(events[2]); std::list blocks; bool r = c.get_blocks(0, 100 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks); CHECK_TEST_CONDITION(r); std::vector chain; map_hash2tx_t mtx; r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); CHECK_TEST_CONDITION(r); CHECK_EQ(0, get_balance(m_bob_account, chain, mtx)); CHECK_EQ(MK_TEST_COINS(5)-TESTS_DEFAULT_FEE, get_balance(m_alice_account, chain, mtx)); return true; } bool mix_attr_tests::check_last_changed(currency::core& c, size_t ev_index, const std::vector& events) { CHECK_NOT_EQ(top_id_befor_split, c.get_tail_id()); return true; } //-------------------------------------------------------------------------- struct check_outs_count_params { check_outs_count_params() : amount(0), count(0) {} check_outs_count_params(uint64_t amount, size_t count) : amount(amount), count(count) {} uint64_t amount; size_t count; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_POD_AS_HEX_STRING(amount) KV_SERIALIZE_POD_AS_HEX_STRING(count) END_KV_SERIALIZE_MAP() }; mix_in_spent_outs::mix_in_spent_outs() { REGISTER_CALLBACK_METHOD(mix_in_spent_outs, c1); REGISTER_CALLBACK_METHOD(mix_in_spent_outs, check_outs_count); } bool mix_in_spent_outs::generate(std::vector& events) const { // test idea: use obviously spent outputs as fake ones for mixing in. Such outputs MUST NOT be used for mixing in. m_accounts.resize(TOTAL_ACCS_COUNT); auto& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); auto& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); auto& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); GENERATE_ACCOUNT(preminer_acc); MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 6); m_test_amount = MK_TEST_COINS(6); for(auto& o : blk_0.miner_tx.vout) { CHECK_AND_ASSERT_MES(boost::get(o).amount != m_test_amount, false, "Premine surprisingly has test amount output, change m_test_amount"); } MAKE_TX_LIST_START(events, txs, miner_acc, alice_acc, m_test_amount, blk_0r); MAKE_TX_LIST(events, txs, miner_acc, alice_acc, m_test_amount, blk_0r); MAKE_TX_LIST(events, txs, miner_acc, alice_acc, m_test_amount, blk_0r); MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, txs); DO_CALLBACK_PARAMS_STR(events, "check_outs_count", epee::serialization::store_t_to_json(check_outs_count_params(m_test_amount, 3))); MAKE_TX_MIX(events, tx_0, miner_acc, bob_acc, m_test_amount, 0, blk_1); // mix_attr == 0 MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_0); DO_CALLBACK_PARAMS_STR(events, "check_outs_count", epee::serialization::store_t_to_json(check_outs_count_params(m_test_amount, 4))); // spend 2 of 3 Alice's outputs with direct transfer (not mixins) MAKE_TX(events, tx_1, alice_acc, miner_acc, m_test_amount * 2 - TESTS_DEFAULT_FEE, blk_2); // mix_attr == 0 MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_1); // despite the fact all Alice's outputs are spent now, total number of outputs with such amount should not changed DO_CALLBACK_PARAMS_STR(events, "check_outs_count", epee::serialization::store_t_to_json(check_outs_count_params(m_test_amount, 4))); REWIND_BLOCKS_N(events, blk_3r, blk_3, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); DO_CALLBACK(events, "c1"); return true; } bool mix_in_spent_outs::check_outs_count(currency::core& c, size_t ev_index, const std::vector& events) { // makes sure there are params.count outputs with params.amount in the blockchain check_outs_count_params params; bool r = epee::serialization::load_t_from_json(params, boost::get(events[ev_index]).callback_params); CHECK_AND_ASSERT_MES(r, false, "Can't load params struct"); auto& bcs = c.get_blockchain_storage(); uint64_t such_amount_outs_count = bcs.get_outputs_container().get_item_size(params.amount); CHECK_AND_ASSERT_MES(such_amount_outs_count == params.count, false, "invalid global outputs with amount == " << print_money(params.amount) << " : " << such_amount_outs_count << ", expected: " << params.count); return true; } bool mix_in_spent_outs::c1(currency::core& c, size_t ev_index, const std::vector& events) { bool r = false; std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); bob_wlt->refresh(); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are txs in the pool"); std::vector destinations({ tx_destination_entry(m_test_amount - TESTS_DEFAULT_FEE, m_accounts[MINER_ACC_IDX].get_public_address()) }); // check that mixing in 3 or 2 fake outputs fails because of using already spend outputs for(size_t fake_outputs_to_mixin = 2; fake_outputs_to_mixin >= 2; fake_outputs_to_mixin--) { r = false; try { bob_wlt->transfer(destinations, fake_outputs_to_mixin, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment); } catch (tools::error::not_enough_outs_to_mix& e) { r = true; LOG_PRINT_GREEN("Caught not_enough_outs_to_mix as expected, what() = " << e.what(), LOG_LEVEL_0); } CHECK_AND_ASSERT_MES(r, false, "Expected exception was not caught for fake_outputs_to_mixin == " << fake_outputs_to_mixin); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are txs in the pool"); } transaction tx = AUTO_VAL_INIT(tx); bob_wlt->transfer(destinations, 1, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, tx); // mixing in 1 fake output should be okay as Alice did not spend it CHECK_AND_ASSERT_MES(tx.vin.size() == 1, false, "tx vin.size() is not 1"); CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(txin_to_key) && boost::get(tx.vin[0]).key_offsets.size() == 2, false, "Incorrect vin[0] type of key_offsets"); // make sure 1 fake output was used CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of txs in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are txs in the pool"); return true; }