// 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& 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) // 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) // // 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 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& events) { m_recipient_account_1 = boost::get(events[1]); m_recipient_account_2 = boost::get(events[2]); m_recipient_account_3 = boost::get(events[3]); m_recipient_account_4 = boost::get(events[4]); std::list 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(events[20 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW])); // blk_4 CHECK_EQ(2, c.get_alternative_blocks_count()); 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(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(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 tx_pool; r = c.get_pool_transactions(tx_pool); CHECK_TEST_CONDITION(r); CHECK_EQ(1, tx_pool.size()); std::vector 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& events) { std::list 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(events[24 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW])); // blk_7 std::list 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 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(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 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 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& 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, TX_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, TX_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 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 TX_DEFAULT_FEE std::shared_ptr 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", TX_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& 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 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& 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({comment_att}); // create txs with attachment (and put it in the pool) MAKE_TX_ATTACH(events, tx_1, miner_acc, miner_acc, TX_DEFAULT_FEE, blk_0r, attachment); MAKE_TX_ATTACH(events, tx_2, miner_acc, miner_acc, TX_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({ 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({ 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& events) { params_tx_pool p = AUTO_VAL_INIT(p); bool r = epee::serialization::load_t_from_json(p, boost::get(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& 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& 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, TX_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& 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 sources; r = fill_tx_sources(sources, events, blk_1r, miner_acc.get_keys(), new_amount + TX_DEFAULT_FEE, 0); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); std::vector 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); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 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 + TX_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 r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_2, 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({ 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& 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, TX_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& events) { crypto::hash tx_0_hash = null_hash; epee::string_tools::hex_to_pod(boost::get(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& events) { crypto::hash tx_0_hash = null_hash; epee::string_tools::hex_to_pod(boost::get(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; }