1
0
Fork 0
forked from lthn/blockchain
blockchain/tests/core_tests/mixin_attr.cpp
2019-04-25 23:37:43 +02:00

298 lines
15 KiB
C++

// 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<test_event_entry>& 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); // <N blocks>
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<currency::attachment_v>()); // 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<currency::attachment_v>());
//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<test_event_entry>& events)
{
currency::account_base m_bob_account = boost::get<account_base>(events[1]);
currency::account_base m_alice_account = boost::get<account_base>(events[2]);
std::list<block> blocks;
bool r = c.get_blocks(0, 100 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks);
CHECK_TEST_CONDITION(r);
std::vector<currency::block> 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<test_event_entry>& 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<test_event_entry>& 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<test_event_entry>& events)
{
CHECK_NOT_EQ(top_id_befor_split, c.get_tail_id());
currency::account_base m_bob_account = boost::get<account_base>(events[1]);
currency::account_base m_alice_account = boost::get<account_base>(events[2]);
std::list<block> blocks;
bool r = c.get_blocks(0, 100 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks);
CHECK_TEST_CONDITION(r);
std::vector<currency::block> 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<test_event_entry>& 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<test_event_entry>& 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(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<test_event_entry>& 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<callback_entry>(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<test_event_entry>& events)
{
bool r = false;
std::shared_ptr<tools::wallet2> 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<tx_destination_entry> 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<txin_to_key>(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;
}