blockchain/tests/core_tests/chain_switch_1.cpp
2022-07-06 13:22:05 +02:00

766 lines
35 KiB
C++

// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote 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 "chain_switch_1.h"
#include "random_helper.h"
using namespace epee;
using namespace currency;
#include "currency_core/currency_format_utils.h"
gen_chain_switch_1::gen_chain_switch_1()
: m_miner_initial_amount(0)
{
REGISTER_CALLBACK("check_split_not_switched", gen_chain_switch_1::check_split_not_switched);
REGISTER_CALLBACK("check_split_switched", gen_chain_switch_1::check_split_switched);
}
//-----------------------------------------------------------------------------------------------------
bool gen_chain_switch_1::generate(std::vector<test_event_entry>& events) const
{
uint64_t ts_start = 1450000000;
/*
0 10...11 12...22 23 24 25 <- main blockchain height (assuming unlock window is 10 blocks)
(0 )-(0r)-(1 )-(2 )-(2r) -(3 )-(4 ) <- main chain, until 7 isn't connected
\ |-(5 )-(6 )| <- alt chain, until 7 isn't connected
(0 )-(0r)-(1 )-(2 )-(2r) -(5 )-(6 )-(7 ) <- main chain, after reorganize
\ |-(3 )-(4 )| <- alt chain, after reorganize
MAIN CHAIN (3 )-(4 )
miner -5 -14 -16 = -35
acc_1 +5 +3 = +8
acc_2 +8 +2 = +10
acc_3 +1 +13 = +14
acc_4 +2 +1 = +3
ALT CHAIN (5 )-(6 )-(7 )
miner -5 -20 -16 = -41
acc_1 +5 +1 +2 = +8
acc_2 +3 = +3
acc_3 +2 +12 = +14
acc_4 +14 +2 = +16
transactions ([n] - tx amount, (m) - block):
(1) : miner -[ 5]-> account_1 ( +5 in main chain, +5 in alt chain)
(3) : miner -[ 7]-> account_2 ( +7 in main chain, +0 in alt chain), tx will be in tx pool after switch
(4), (6): miner -[11]-> account_3 (+11 in main chain, +11 in alt chain)
(5) : miner -[13]-> account_4 ( +0 in main chain, +13 in alt chain), tx will be in tx pool before switch
transactions orders ([n] - tx amount, (m) - block):
miner -[1], [2]-> account_1: in main chain (3), (3), in alt chain (5), (6)
miner -[1], [2]-> account_2: in main chain (3), (4), in alt chain (5), (5)
miner -[1], [2]-> account_3: in main chain (3), (4), in alt chain (6), (5)
miner -[1], [2]-> account_4: in main chain (4), (3), in alt chain (5), (6)
*/
GENERATE_ACCOUNT(miner_account);
m_miner_account = miner_account;
// events
MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); // 0
m_miner_initial_amount = get_outs_money_amount(blk_0.miner_tx);
MAKE_ACCOUNT(events, recipient_account_1); // 1
MAKE_ACCOUNT(events, recipient_account_2); // 2
MAKE_ACCOUNT(events, recipient_account_3); // 3
MAKE_ACCOUNT(events, recipient_account_4); // 4
REWIND_BLOCKS(events, blk_0r, blk_0, miner_account) // <N blocks>
MAKE_TX(events, tx_00, miner_account, recipient_account_1, MK_TEST_COINS(5), blk_0r); // 5 + N
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_00); // 6 + N
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 7 + N
REWIND_BLOCKS(events, blk_2r, blk_2, miner_account) // <N blocks>
// Transactions to test account balances after switch
MAKE_TX_LIST_START(events, txs_blk_3, miner_account, recipient_account_2, MK_TEST_COINS(7), blk_2r); // 8 + 2N
MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_TEST_COINS(11), blk_2r); // 9 + 2N
MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_TEST_COINS(13), blk_2r); // 10 + 2N
std::list<transaction> txs_blk_6;
txs_blk_6.push_back(txs_blk_4.front());
// Transactions, that has different order in alt block chains
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_TEST_COINS(1), blk_2r); // 11 + 2N
txs_blk_5.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_TEST_COINS(2), blk_2r); // 12 + 2N
txs_blk_6.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_2, MK_TEST_COINS(1), blk_2r); // 13 + 2N
txs_blk_5.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_2, MK_TEST_COINS(2), blk_2r); // 14 + 2N
txs_blk_5.push_back(txs_blk_4.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_3, MK_TEST_COINS(1), blk_2r); // 15 + 2N
txs_blk_6.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_3, MK_TEST_COINS(2), blk_2r); // 16 + 2N
txs_blk_5.push_back(txs_blk_4.back());
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_4, MK_TEST_COINS(1), blk_2r); // 17 + 2N
txs_blk_5.push_back(txs_blk_4.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_4, MK_TEST_COINS(2), blk_2r); // 18 + 2N
txs_blk_6.push_back(txs_blk_3.back());
MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_blk_3); // 19 + 2N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3, miner_account, txs_blk_4); // 20 + 2N
//split
MAKE_NEXT_BLOCK_TX_LIST(events, blk_5, blk_2r, miner_account, txs_blk_5); // 22 + 2N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5, miner_account, txs_blk_6); // 23 + 2N
DO_CALLBACK(events, "check_split_not_switched"); // 21 + 2N
MAKE_NEXT_BLOCK(events, blk_7, blk_6, miner_account); // 24 + 2N
DO_CALLBACK(events, "check_split_switched"); // 25 + 2N
return true;
}
//-----------------------------------------------------------------------------------------------------
bool gen_chain_switch_1::check_split_not_switched(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
m_recipient_account_1 = boost::get<account_base>(events[1]);
m_recipient_account_2 = boost::get<account_base>(events[2]);
m_recipient_account_3 = boost::get<account_base>(events[3]);
m_recipient_account_4 = boost::get<account_base>(events[4]);
std::list<block> blocks;
bool r = c.get_blocks(0, 10000, blocks);
CHECK_TEST_CONDITION(r);
CHECK_EQ(5 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[20 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW])); // blk_4
CHECK_EQ(2, c.get_alternative_blocks_count());
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(8), get_balance(m_recipient_account_1, chain, mtx));
CHECK_EQ(MK_TEST_COINS(10), get_balance(m_recipient_account_2, chain, mtx));
CHECK_EQ(MK_TEST_COINS(14), get_balance(m_recipient_account_3, chain, mtx));
CHECK_EQ(MK_TEST_COINS(3), get_balance(m_recipient_account_4, chain, mtx));
crypto::hash genesis_block_hash = currency::get_block_hash(boost::get<currency::block>(events[0]));
uint64_t mined_after_genesis = 0;
BOOST_FOREACH(currency::block b, blocks)
{
if (currency::get_block_hash(b) != genesis_block_hash)
mined_after_genesis += get_outs_money_amount(b.miner_tx);
}
const size_t tx_main_chain_count = 11;
uint64_t amount = m_miner_initial_amount + mined_after_genesis - MK_TEST_COINS(35) - TESTS_DEFAULT_FEE * tx_main_chain_count;
uint64_t balance = get_balance(m_miner_account, chain, mtx);
CHECK_TEST_CONDITION(amount > balance ? amount - balance : balance - amount <= DEFAULT_DUST_THRESHOLD);
std::list<transaction> tx_pool;
r = c.get_pool_transactions(tx_pool);
CHECK_TEST_CONDITION(r);
CHECK_EQ(1, tx_pool.size());
std::vector<wallet_out_info> tx_outs;
uint64_t transfered;
crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);
lookup_acc_outs(m_recipient_account_4.get_keys(), tx_pool.front(), get_tx_pub_key_from_extra(tx_pool.front()), tx_outs, transfered, derivation);
CHECK_EQ(MK_TEST_COINS(13), transfered);
m_chain_1.swap(blocks);
m_tx_pool.swap(tx_pool);
return true;
}
//-----------------------------------------------------------------------------------------------------
bool gen_chain_switch_1::check_split_switched(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
std::list<block> blocks;
bool r = c.get_blocks(0, 10000, blocks);
CHECK_TEST_CONDITION(r);
CHECK_EQ(6 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
auto it = blocks.end();
--it; --it; --it;
CHECK_TEST_CONDITION(std::equal(blocks.begin(), it, m_chain_1.begin()));
CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[24 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW])); // blk_7
std::list<block> alt_blocks;
r = c.get_alternative_blocks(alt_blocks);
CHECK_TEST_CONDITION(r);
CHECK_EQ(2, c.get_alternative_blocks_count());
// Some blocks that were in main chain are in alt chain now
BOOST_FOREACH(block b, alt_blocks)
{
CHECK_TEST_CONDITION(m_chain_1.end() != std::find(m_chain_1.begin(), m_chain_1.end(), b));
}
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(8), get_balance(m_recipient_account_1, chain, mtx));
CHECK_EQ(MK_TEST_COINS(3), get_balance(m_recipient_account_2, chain, mtx));
CHECK_EQ(MK_TEST_COINS(14), get_balance(m_recipient_account_3, chain, mtx));
CHECK_EQ(MK_TEST_COINS(16), get_balance(m_recipient_account_4, chain, mtx));
crypto::hash genesis_block_hash = currency::get_block_hash(boost::get<currency::block>(events[0]));
uint64_t mined_after_genesis = 0;
BOOST_FOREACH(currency::block b, blocks)
{
if (currency::get_block_hash(b) != genesis_block_hash)
mined_after_genesis += get_outs_money_amount(b.miner_tx);
}
const size_t tx_alt_chain_count = 11;
uint64_t amount = m_miner_initial_amount + mined_after_genesis - MK_TEST_COINS(41) - TESTS_DEFAULT_FEE * tx_alt_chain_count;
uint64_t balance = get_balance(m_miner_account, chain, mtx);
CHECK_TEST_CONDITION(amount > balance ? amount - balance : balance - amount <= DEFAULT_DUST_THRESHOLD);
std::list<transaction> tx_pool;
r = c.get_pool_transactions(tx_pool);
CHECK_TEST_CONDITION(r);
CHECK_EQ(1, tx_pool.size());
CHECK_TEST_CONDITION(!(tx_pool.front() == m_tx_pool.front()));
std::vector<wallet_out_info> tx_outs;
uint64_t transfered;
crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);
lookup_acc_outs(m_recipient_account_2.get_keys(), tx_pool.front(), tx_outs, transfered, derivation);
CHECK_EQ(MK_TEST_COINS(7), transfered);
return true;
}
//-----------------------------------------------------------------------------------------------------
bad_chain_switching_with_rollback::bad_chain_switching_with_rollback()
{
REGISTER_CALLBACK_METHOD(bad_chain_switching_with_rollback, c1);
}
bool bad_chain_switching_with_rollback::generate(std::vector<test_event_entry>& events) const
{
GENERATE_ACCOUNT(preminer_acc);
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
GENERATE_ACCOUNT(bob_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 + 4);
MAKE_TX(events, tx_1, miner_acc, alice_acc, TESTS_DEFAULT_FEE * 2, blk_0r);
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1);
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc);
// make tx_2 referring to tx_1 output
MAKE_TX(events, tx_2, alice_acc, bob_acc, TESTS_DEFAULT_FEE, blk_2);
MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_2);
// check balance at gentime
// Alice should have nothing
std::shared_ptr<tools::wallet2> alice_wlt;
generator.init_test_wallet(alice_acc, get_block_hash(blk_0), alice_wlt);
bool r = generator.refresh_test_wallet(events, alice_wlt.get(), get_block_hash(blk_3), CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4 + 3);
CHECK_AND_ASSERT_MES(r, false, "refresh_test_wallet failed");
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "Alice", 0, 0, 0, 0, 0), false, "");
// Bob should have TESTS_DEFAULT_FEE
std::shared_ptr<tools::wallet2> bob_wlt;
generator.init_test_wallet(bob_acc, get_block_hash(blk_0), bob_wlt);
r = generator.refresh_test_wallet(events, bob_wlt.get(), get_block_hash(blk_3), CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4 + 3);
CHECK_AND_ASSERT_MES(r, false, "refresh_test_wallet failed");
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt.get(), "Bob", TESTS_DEFAULT_FEE, 0, 0, 0, 0), false, "");
// start altchain from blk_0r, include tx_2 but NOT include tx_1
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_1a, blk_0r, miner_acc, tx_2);
DO_CALLBACK(events, "mark_orphan_block");
MAKE_NEXT_BLOCK(events, blk_2a, blk_1a, miner_acc);
// 0 ... 14 15 16 17 18 19 <- height
// (0 )- (0r)- (1 )- (2 )- (3 )- <- main chain
// | tx_1 tx_2 <- txs (tx_2 uses output of tx_1)
// | | |
// | +--<--<--<--+
// |
// \- !1a!- !2a!- <- alt chain
// tx_2 <- txs (tx_2 uses output of tx_1)
// top block should be the 8last block in the main chain -- blk_3
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_3), get_block_hash(blk_3)));
m_invalid_block_hash_to_check = get_block_hash(blk_1a);
DO_CALLBACK(events, "c1");
return true;
}
bool bad_chain_switching_with_rollback::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
// altblocks now are marked as invalid only if rollback was unsuccessfull, so don't check it here
// invalid block can be checked by have_block() but can't be retreived by get_block_by_hash
//bool r = c.get_blockchain_storage().have_block(m_invalid_block_hash_to_check);
//CHECK_AND_ASSERT_MES(r, false, "have_block failed when called with id = " << m_invalid_block_hash_to_check);
block b = AUTO_VAL_INIT(b);
bool r = c.get_blockchain_storage().get_block_by_hash(m_invalid_block_hash_to_check, b);
CHECK_AND_ASSERT_MES(!r, false, "get_block_extended_info_by_hash suceeded, but expected to fail");
return true;
}
//-----------------------------------------------------------------------------------------------------
struct tx_in_pool_info
{
crypto::hash hash;
size_t blobsize;
};
struct params_tx_pool
{
params_tx_pool() {}
params_tx_pool(crypto::hash hash, size_t blobsize) : txs{ tx_in_pool_info{ hash, blobsize } }
{}
params_tx_pool(crypto::hash hash1, size_t blobsize1, crypto::hash hash2, size_t blobsize2)
{
txs.push_back(tx_in_pool_info{ hash1, blobsize1 });
txs.push_back(tx_in_pool_info{ hash2, blobsize2 });
}
std::vector<tx_in_pool_info> txs;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(txs)
END_KV_SERIALIZE_MAP()
};
chain_switching_and_tx_with_attachment_blobsize::chain_switching_and_tx_with_attachment_blobsize()
{
REGISTER_CALLBACK_METHOD(chain_switching_and_tx_with_attachment_blobsize, check_tx_pool_txs);
}
bool chain_switching_and_tx_with_attachment_blobsize::generate(std::vector<test_event_entry>& events) const
{
GENERATE_ACCOUNT(preminer_acc);
GENERATE_ACCOUNT(miner_acc);
MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time());
REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW+4);
tx_comment comment_att = AUTO_VAL_INIT(comment_att);
comment_att.comment = get_random_text(CURRENCY_MAX_TRANSACTION_BLOB_SIZE * 9 / 10); // this is necessary to push cummulative block size out of full reward zone, so block reward will be non-default
std::vector<attachment_v> attachment({comment_att});
// create txs with attachment (and put it in the pool)
MAKE_TX_ATTACH(events, tx_1, miner_acc, miner_acc, TESTS_DEFAULT_FEE, blk_0r, attachment);
MAKE_TX_ATTACH(events, tx_2, miner_acc, miner_acc, TESTS_DEFAULT_FEE, blk_0r, attachment);
// make sure the pool has correct txs
DO_CALLBACK_PARAMS_STR(events, "check_tx_pool_txs", epee::serialization::store_t_to_json(params_tx_pool(get_transaction_hash(tx_1), get_object_blobsize(tx_1), get_transaction_hash(tx_2), get_object_blobsize(tx_2))));
MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner_acc);
MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1, miner_acc, std::list<transaction>({ tx_1, tx_2 }));
// make sure tx pool is empty now
DO_CALLBACK(events, "check_tx_pool_empty");
// make altchain and grow it till reorganization happens
MAKE_NEXT_BLOCK(events, blk_1a, blk_0r, miner_acc);
MAKE_NEXT_BLOCK(events, blk_2a, blk_1a, miner_acc);
MAKE_NEXT_BLOCK(events, blk_3a, blk_2a, miner_acc);
// make sure chain switching happened indeed
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_3a), get_block_hash(blk_3a)));
// txs must have been added back to tx_pool, make sure they are
DO_CALLBACK_PARAMS_STR(events, "check_tx_pool_txs", epee::serialization::store_t_to_json(params_tx_pool(get_transaction_hash(tx_1), get_object_blobsize(tx_1), get_transaction_hash(tx_2), get_object_blobsize(tx_2))));
// put a block with txs
// (this step triggered invalid block reward calculation a.k.a. "coinbase transaction doesn't use full amount of block reward"
// in past due to invalid tx blob size calculations)
MAKE_NEXT_BLOCK_TX_LIST(events, blk_4a, blk_3a, miner_acc, std::list<transaction>({ tx_1, tx_2 }));
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));
// make sure tx pool is empty now
DO_CALLBACK(events, "check_tx_pool_empty");
return true;
}
bool chain_switching_and_tx_with_attachment_blobsize::check_tx_pool_txs(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
params_tx_pool p = AUTO_VAL_INIT(p);
bool r = epee::serialization::load_t_from_json(p, boost::get<callback_entry>(events[ev_index]).callback_params);
CHECK_AND_ASSERT_MES(r, false, "Can't obtain event params. Forgot to pass them?");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == p.txs.size(), false, "Tx pool has " << c.get_pool_transactions_count() << " transactions, expected: " << p.txs.size());
for (auto &te : p.txs)
{
transaction tx = AUTO_VAL_INIT(tx);
bool r = c.get_transaction(te.hash, tx);
CHECK_AND_ASSERT_MES(r, false, "get_transaction failed for " << te.hash);
crypto::hash hash = get_transaction_hash(tx);
CHECK_AND_ASSERT_MES(te.hash == hash, false, "Tx in the pool has incorrect hash: " << hash << ", while expected is " << te.hash);
size_t blobsize = get_object_blobsize(tx);
CHECK_AND_ASSERT_MES(te.blobsize == blobsize, false, "Tx in the pool has incorrect blobsize == " << blobsize << ", while excepted value is " << te.blobsize);
}
return true;
}
//-----------------------------------------------------------------------------------------------------
bool chain_switching_when_gindex_spent_in_both_chains::generate(std::vector<test_event_entry>& events) const
{
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
MAKE_NEXT_BLOCK(events, blk_1, blk_0, alice_acc);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
uint64_t amount = get_outs_money_amount(blk_1.miner_tx) - TESTS_DEFAULT_FEE;
MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);
MAKE_TX_FEE(events, tx_1, alice_acc, alice_acc, amount, TESTS_DEFAULT_FEE, blk_2);
MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_1);
// 0 1 11 12 13 14 < height
// (0 )- (1 )...(1r)- (2 )- (3 )- < main chain
// ^ \ tx_1 < txs
// | \
// | \-(2a)- (3a)- (4a)- < alt chain
// + tx_1 < txs
MAKE_NEXT_BLOCK(events, blk_2a, blk_1r, miner_acc);
MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2a, miner_acc, tx_1);
MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);
DO_CALLBACK(events, "check_tx_pool_empty");
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));
CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0);
REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4);
CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, amount);
return true;
}
//-----------------------------------------------------------------------------------------------------
bool alt_chain_coins_pow_mined_then_spent::generate(std::vector<test_event_entry>& events) const
{
// test idea: in alt chain mine and then spent some coins, then trigger chain switching
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1);
MAKE_NEXT_BLOCK(events, blk_2a, blk_1, alice_acc);
REWIND_BLOCKS_N(events, blk_2ra, blk_2a, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// 0 1 2 12 13
// (0 )- (1 )- . . . (1r)-
// \
// \- (2a)...(2ra)- (3a)-
// |
// +-------------tx_1 tx_1 spents 2a.miner_tx output
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // simulate alt-block tx behaviour
MAKE_TX(events, tx_1, alice_acc, alice_acc, TESTS_DEFAULT_FEE, blk_2ra);
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));
MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2ra, miner_acc, tx_1);
DO_CALLBACK(events, "check_tx_pool_empty");
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_3a), get_block_hash(blk_3a)));
return true;
};
//-----------------------------------------------------------------------------------------------------
bool alt_blocks_validation_and_same_new_amount_in_two_txs::generate(std::vector<test_event_entry>& events) const
{
// test idea: make an alt block with two txs, containing the same, but never seen before output amount
// Should be processed ok with no problems
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1);
// find out an amount that is never seen
uint64_t stub, new_amount = 0;
bool r = calculate_amounts_many_outs_have_and_no_outs_have(get_outs_money_amount(blk_1.miner_tx), stub, new_amount);
CHECK_AND_ASSERT_MES(r, false, "calculate_amounts_many_outs_have_and_no_outs_have failed");
// main chain
MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);
MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc);
// alt chain
MAKE_NEXT_BLOCK(events, blk_2a, blk_1r, miner_acc);
// make two txs with one output (huge fee, probably - doesn't matter) with amount that is never seen before
std::vector<tx_source_entry> sources;
r = fill_tx_sources(sources, events, blk_1r, miner_acc.get_keys(), new_amount + TESTS_DEFAULT_FEE, 0);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed");
std::vector<tx_destination_entry> destinations;
destinations.push_back(tx_destination_entry(new_amount, miner_acc.get_public_address())); // no cashback, just payment
transaction tx_1 = AUTO_VAL_INIT(tx_1);
uint64_t tx_version = get_tx_version(get_block_height(blk_3), m_hardforks);
r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, tx_version, 0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
events.push_back(tx_1);
// second tx
sources.clear();
r = fill_tx_sources(sources, events, blk_1r, miner_acc.get_keys(), new_amount + TESTS_DEFAULT_FEE, 0, sources);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed");
transaction tx_2 = AUTO_VAL_INIT(tx_2);
// use the same destinations
tx_version = get_tx_version(get_block_height(blk_3), m_hardforks);
r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_2, tx_version, 0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
events.push_back(tx_2);
// make an alt block with these txs
MAKE_NEXT_BLOCK_TX_LIST(events, blk_3a, blk_2a, miner_acc, std::list<transaction>({ tx_1, tx_2 }));
// 0 1 11 12 13
// (0 )- (1 )-...(1r)- (2 )- (3 )-
// \
// \- (2a)- (3a)-
// tx_1
// tx_2
MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc); // this should switch the chains
DO_CALLBACK(events, "check_tx_pool_empty");
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));
return true;
}
//-----------------------------------------------------------------------------------------------------
alt_blocks_with_the_same_txs::alt_blocks_with_the_same_txs()
{
REGISTER_CALLBACK_METHOD(alt_blocks_with_the_same_txs, check_tx_related_to_altblock);
REGISTER_CALLBACK_METHOD(alt_blocks_with_the_same_txs, check_tx_not_related_to_altblock);
}
bool alt_blocks_with_the_same_txs::generate(std::vector<test_event_entry>& events) const
{
// Test idea: check that many two alt blocks having the same tx are correctly handled with respect to is_tx_related_to_altblock()
GENERATE_ACCOUNT(miner_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1);
// 0 1 11 12 13 14
// (0 )- (1 )-...(1r)- (2 )- (3 )-
// \ tx_0
// \
// \- (2a)- (3a)- (4 )-
// tx_0
MAKE_TX(events, tx_0, miner_acc, miner_acc, TESTS_DEFAULT_FEE, blk_1r);
MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_acc, tx_0);
MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc);
crypto::hash tx_0_hash = get_transaction_hash(tx_0);
DO_CALLBACK_PARAMS(events, "check_tx_not_related_to_altblock", tx_0_hash);
MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0);
MAKE_NEXT_BLOCK(events, blk_3a, blk_2a, miner_acc);
DO_CALLBACK_PARAMS(events, "check_tx_related_to_altblock", tx_0_hash);
// this should trigger reorganize
MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));
DO_CALLBACK_PARAMS(events, "check_tx_related_to_altblock", tx_0_hash);
return true;
}
bool alt_blocks_with_the_same_txs::check_tx_related_to_altblock(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
crypto::hash tx_0_hash = null_hash;
epee::string_tools::hex_to_pod(boost::get<callback_entry>(events[ev_index]).callback_params, tx_0_hash);
bool r = c.get_blockchain_storage().is_tx_related_to_altblock(tx_0_hash);
CHECK_AND_ASSERT_MES(r, false, "tx " << tx_0_hash << " is expected to be related to an altblock, but BCS returned false");
return true;
}
bool alt_blocks_with_the_same_txs::check_tx_not_related_to_altblock(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
crypto::hash tx_0_hash = null_hash;
epee::string_tools::hex_to_pod(boost::get<callback_entry>(events[ev_index]).callback_params, tx_0_hash);
bool r = !c.get_blockchain_storage().is_tx_related_to_altblock(tx_0_hash);
CHECK_AND_ASSERT_MES(r, false, "tx " << tx_0_hash << " is expected to NOT be related to an altblock, but BCS returned true");
return true;
}
//-----------------------------------------------------------------------------------------------------
bool chain_switching_when_out_spent_in_alt_chain_mixin::generate(std::vector<test_event_entry>& events) const
{
// Test idea: make sure a tx can spend an output with mixins from another tx when both txs are in an altchain.
bool r = false;
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
GENERATE_ACCOUNT(bob_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// 0 1 11 12 13 14
// (0 )- (1 )-...(1r)- (2 )- (3 )- <-- main chain
// \
// \
// \- (2a)- (3a)- (4a)-
// tx_0 <- tx_1 // tx_1 spends output from tx_0
// send batch of 10 x 5 test coins to Alice for easier tx_1 construction (and to generate free decoys)
transaction tx_0;
r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(), alice_acc.get_public_address(), MK_TEST_COINS(50), 10,
TESTS_DEFAULT_FEE, tx_0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed");
events.push_back(tx_0);
MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);
MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc);
MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0);
// make sure Alice received exactly 50 test coins
CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0);
REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_2a, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2);
CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(50));
// Alice spends her 5 test coins received by tx_0
MAKE_TX_FEE_MIX(events, tx_1, alice_acc, bob_acc, MK_TEST_COINS(4), TESTS_DEFAULT_FEE, 3 /* nmix */, blk_2a);
events.pop_back(); // pop back tx_1 as it won't go into the tx pool normally because of alt chain
// simulate handling a block with that tx: handle tx like going with the block...
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true));
events.push_back(tx_1);
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));
MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2a, miner_acc, tx_1);
MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);
// make sure Alice has correct balance
REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, 2);
CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(45));
// make sure chain successfully switched
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));
return true;
}
//-----------------------------------------------------------------------------------------------------
bool chain_switching_when_out_spent_in_alt_chain_ref_id::generate(std::vector<test_event_entry>& events) const
{
random_state_test_restorer::reset_random(0); // to make the test deterministic
uint64_t ts = 1450000000;
test_core_time::adjust(ts);
// Test idea: make sure tx can spend (using ref_by_id) an output from another tx when both txs are in an altchain.
bool r = false;
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
GENERATE_ACCOUNT(bob_acc);
miner_acc.set_createtime(ts);
alice_acc.set_createtime(ts);
bob_acc.set_createtime(ts);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// 0 1 11 12 13 14
// (0 )- (1 )-...(1r)- (2 )- (3 )- <- main chain
// \
// \
// \- (2a)- (3a)- (4a)-
// tx_0 <- tx_1 // tx_1 spends an output from tx_0 using ref_by_id and mixins
// send batch of 10 x 5 test coins to Alice for easier tx_0 construction
transaction tx_0;
r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(), alice_acc.get_public_address(), MK_TEST_COINS(50), 10,
TESTS_DEFAULT_FEE, tx_0, true);
CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed");
// make sure tx_0 really use ref_by_id
size_t refs_count = 0, gindex_count = 0;
count_ref_by_id_and_gindex_refs_for_tx_inputs(tx_0, refs_count, gindex_count);
CHECK_AND_ASSERT_MES(refs_count == 1 && gindex_count == 0, false, "incorrect input references: " << refs_count << ", " << gindex_count);
events.push_back(tx_0);
MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);
MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc);
MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0);
// make sure Alice received exactly 50 test coins
CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0);
REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_2a, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2);
CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(50));
// Alice spends her 5 test coins received by tx_0
transaction tx_1;
std::vector<tx_destination_entry> destinations;
destinations.push_back(tx_destination_entry(MK_TEST_COINS(4), bob_acc.get_public_address()));
size_t nmix = 3;
r = construct_tx_to_key(m_hardforks, events, tx_1, blk_2a, alice_acc, destinations, TESTS_DEFAULT_FEE, nmix, 0, empty_extra, empty_attachment, true, true, true);
CHECK_AND_ASSERT_MES(r, false, "construct_tx_to_key failed");
// make sure tx_1 really use ref_by_id
refs_count = 0, gindex_count = 0;
count_ref_by_id_and_gindex_refs_for_tx_inputs(tx_1, refs_count, gindex_count);
CHECK_AND_ASSERT_MES(refs_count == nmix + 1 && gindex_count == 0, false, "incorrect input references: " << refs_count << ", " << gindex_count);
// simulate handling a block with that tx: handle tx like going with the block...
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true));
events.push_back(tx_1);
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));
MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2a, miner_acc, tx_1);
MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);
// make sure Alice has correct balance
REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, 2);
CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(45));
// make sure chain successfully switched
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));
return true;
}