diff options
author | Antonio Juarez <antonio.maria.juarez@live.com> | 2014-03-03 22:07:58 +0000 |
---|---|---|
committer | Antonio Juarez <antonio.maria.juarez@live.com> | 2014-03-03 22:07:58 +0000 |
commit | 296ae46ed8f8f6e5f986f978febad302e3df231a (patch) | |
tree | 1629164454a239308f33c9e12afb22e7f3cd8eeb /tests/core_tests | |
parent | changed name (diff) | |
download | monero-296ae46ed8f8f6e5f986f978febad302e3df231a.tar.xz |
moved all stuff to github
Diffstat (limited to 'tests/core_tests')
24 files changed, 5318 insertions, 0 deletions
diff --git a/tests/core_tests/block_reward.cpp b/tests/core_tests/block_reward.cpp new file mode 100644 index 000000000..14d181d75 --- /dev/null +++ b/tests/core_tests/block_reward.cpp @@ -0,0 +1,250 @@ +// 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 "chaingen_tests_list.h" + +#include "block_reward.h" + +using namespace epee; +using namespace cryptonote; + +namespace +{ + bool construct_miner_tx_by_size(transaction& miner_tx, uint64_t height, uint64_t already_generated_coins, + const account_public_address& miner_address, std::vector<size_t>& block_sizes, size_t target_tx_size, + size_t target_block_size, uint64_t fee = 0) + { + if (!construct_miner_tx(height, already_generated_coins, miner_address, miner_tx, fee, block_sizes, target_block_size)) + return false; + + size_t current_size = get_object_blobsize(miner_tx); + size_t try_count = 0; + while (target_tx_size != current_size) + { + ++try_count; + if (10 < try_count) + return false; + + if (target_tx_size < current_size) + { + size_t diff = current_size - target_tx_size; + if (diff <= miner_tx.extra.size()) + miner_tx.extra.resize(miner_tx.extra.size() - diff); + else + return false; + } + else + { + size_t diff = target_tx_size - current_size; + miner_tx.extra.resize(miner_tx.extra.size() + diff); + } + + current_size = get_object_blobsize(miner_tx); + } + + return true; + } + + bool construct_max_size_block(test_generator& generator, block& blk, const block& blk_prev, const account_base& miner_account, + size_t median_block_count = CRYPTONOTE_REWARD_BLOCKS_WINDOW) + { + std::vector<size_t> block_sizes; + generator.get_last_n_block_sizes(block_sizes, get_block_hash(blk_prev), median_block_count); + + size_t median = misc_utils::median(block_sizes); + median = std::max(median, static_cast<size_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE)); + + transaction miner_tx; + bool r = construct_miner_tx_by_size(miner_tx, get_block_height(blk_prev) + 1, generator.get_already_generated_coins(blk_prev), + miner_account.get_keys().m_account_address, block_sizes, 2 * median, 2 * median); + if (!r) + return false; + + return generator.construct_block_manually(blk, blk_prev, miner_account, test_generator::bf_miner_tx, 0, 0, 0, + crypto::hash(), 0, miner_tx); + } + + bool rewind_blocks(std::vector<test_event_entry>& events, test_generator& generator, block& blk, const block& blk_prev, + const account_base& miner_account, size_t block_count) + { + blk = blk_prev; + for (size_t i = 0; i < block_count; ++i) + { + block blk_i; + if (!construct_max_size_block(generator, blk_i, blk, miner_account)) + return false; + + events.push_back(blk_i); + blk = blk_i; + } + + return true; + } + + uint64_t get_tx_out_amount(const transaction& tx) + { + uint64_t amount = 0; + BOOST_FOREACH(auto& o, tx.vout) + amount += o.amount; + return amount; + } +} + +gen_block_reward::gen_block_reward() + : m_invalid_block_index(0) +{ + REGISTER_CALLBACK_METHOD(gen_block_reward, mark_invalid_block); + REGISTER_CALLBACK_METHOD(gen_block_reward, mark_checked_block); + REGISTER_CALLBACK_METHOD(gen_block_reward, check_block_rewards); +} + +bool gen_block_reward::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + DO_CALLBACK(events, "mark_checked_block"); + MAKE_ACCOUNT(events, bob_account); + + // Test: miner transactions without outputs (block reward == 0) + block blk_0r; + if (!rewind_blocks(events, generator, blk_0r, blk_0, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW)) + return false; + + // Test: block reward is calculated using median of the latest CRYPTONOTE_REWARD_BLOCKS_WINDOW blocks + DO_CALLBACK(events, "mark_invalid_block"); + block blk_1_bad_1; + if (!construct_max_size_block(generator, blk_1_bad_1, blk_0r, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW + 1)) + return false; + events.push_back(blk_1_bad_1); + + DO_CALLBACK(events, "mark_invalid_block"); + block blk_1_bad_2; + if (!construct_max_size_block(generator, blk_1_bad_2, blk_0r, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1)) + return false; + events.push_back(blk_1_bad_2); + + block blk_1; + if (!construct_max_size_block(generator, blk_1, blk_0r, miner_account)) + return false; + events.push_back(blk_1); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); + DO_CALLBACK(events, "mark_checked_block"); + MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_account); + DO_CALLBACK(events, "mark_checked_block"); + MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_account); + DO_CALLBACK(events, "mark_checked_block"); + MAKE_NEXT_BLOCK(events, blk_5, blk_4, miner_account); + DO_CALLBACK(events, "mark_checked_block"); + + block blk_5r; + if (!rewind_blocks(events, generator, blk_5r, blk_5, miner_account, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)) + return false; + + // Test: fee increases block reward + transaction tx_0(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 3 * TESTS_DEFAULT_FEE)); + MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5r, miner_account, tx_0); + DO_CALLBACK(events, "mark_checked_block"); + + // Test: fee from all block transactions increase block reward + std::list<transaction> txs_0; + txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 5 * TESTS_DEFAULT_FEE)); + txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 7 * TESTS_DEFAULT_FEE)); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_7, blk_6, miner_account, txs_0); + DO_CALLBACK(events, "mark_checked_block"); + + // Test: block reward == transactions fee + { + transaction tx_1 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 11 * TESTS_DEFAULT_FEE); + transaction tx_2 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 13 * TESTS_DEFAULT_FEE); + size_t txs_1_size = get_object_blobsize(tx_1) + get_object_blobsize(tx_2); + uint64_t txs_fee = get_tx_fee(tx_1) + get_tx_fee(tx_2); + + std::vector<size_t> block_sizes; + generator.get_last_n_block_sizes(block_sizes, get_block_hash(blk_7), CRYPTONOTE_REWARD_BLOCKS_WINDOW); + size_t median = misc_utils::median(block_sizes); + + transaction miner_tx; + bool r = construct_miner_tx_by_size(miner_tx, get_block_height(blk_7) + 1, generator.get_already_generated_coins(blk_7), + miner_account.get_keys().m_account_address, block_sizes, 2 * median - txs_1_size, 2 * median, txs_fee); + if (!r) + return false; + + std::vector<crypto::hash> txs_1_hashes; + txs_1_hashes.push_back(get_transaction_hash(tx_1)); + txs_1_hashes.push_back(get_transaction_hash(tx_2)); + + block blk_8; + generator.construct_block_manually(blk_8, blk_7, miner_account, test_generator::bf_miner_tx | test_generator::bf_tx_hashes, + 0, 0, 0, crypto::hash(), 0, miner_tx, txs_1_hashes, txs_1_size); + + events.push_back(blk_8); + DO_CALLBACK(events, "mark_checked_block"); + } + + DO_CALLBACK(events, "check_block_rewards"); + + return true; +} + +bool gen_block_reward::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) +{ + if (m_invalid_block_index == event_idx) + { + m_invalid_block_index = 0; + return bvc.m_verifivation_failed; + } + else + { + return !bvc.m_verifivation_failed; + } +} + +bool gen_block_reward::mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) +{ + m_invalid_block_index = ev_index + 1; + return true; +} + +bool gen_block_reward::mark_checked_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) +{ + m_checked_blocks_indices.push_back(ev_index - 1); + return true; +} + +bool gen_block_reward::check_block_rewards(cryptonote::core& /*c*/, size_t /*ev_index*/, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_block_reward_without_txs::check_block_rewards"); + + std::array<uint64_t, 7> blk_rewards; + blk_rewards[0] = MONEY_SUPPLY >> 18; + uint64_t cumulative_reward = blk_rewards[0]; + for (size_t i = 1; i < blk_rewards.size(); ++i) + { + blk_rewards[i] = (MONEY_SUPPLY - cumulative_reward) >> 18; + cumulative_reward += blk_rewards[i]; + } + + for (size_t i = 0; i < 5; ++i) + { + block blk_i = boost::get<block>(events[m_checked_blocks_indices[i]]); + CHECK_EQ(blk_rewards[i], get_tx_out_amount(blk_i.miner_tx)); + } + + block blk_n1 = boost::get<block>(events[m_checked_blocks_indices[5]]); + CHECK_EQ(blk_rewards[5] + 3 * TESTS_DEFAULT_FEE, get_tx_out_amount(blk_n1.miner_tx)); + + block blk_n2 = boost::get<block>(events[m_checked_blocks_indices[6]]); + CHECK_EQ(blk_rewards[6] + (5 + 7) * TESTS_DEFAULT_FEE, get_tx_out_amount(blk_n2.miner_tx)); + + block blk_n3 = boost::get<block>(events[m_checked_blocks_indices[7]]); + CHECK_EQ((11 + 13) * TESTS_DEFAULT_FEE, get_tx_out_amount(blk_n3.miner_tx)); + + return true; +} diff --git a/tests/core_tests/block_reward.h b/tests/core_tests/block_reward.h new file mode 100644 index 000000000..506d7466c --- /dev/null +++ b/tests/core_tests/block_reward.h @@ -0,0 +1,23 @@ +// 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. + +#pragma once +#include "chaingen.h" + +struct gen_block_reward : public test_chain_unit_base +{ + gen_block_reward(); + + bool generate(std::vector<test_event_entry>& events) const; + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& blk); + + bool mark_invalid_block(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool mark_checked_block(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_block_rewards(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + size_t m_invalid_block_index; + std::vector<size_t> m_checked_blocks_indices; +}; diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp new file mode 100644 index 000000000..36e769c65 --- /dev/null +++ b/tests/core_tests/block_validation.cpp @@ -0,0 +1,613 @@ +// 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 "chaingen_tests_list.h" + +using namespace epee; +using namespace cryptonote; + +namespace +{ + bool lift_up_difficulty(std::vector<test_event_entry>& events, std::vector<uint64_t>& timestamps, + std::vector<difficulty_type>& cummulative_difficulties, test_generator& generator, + size_t new_block_count, const block blk_last, const account_base& miner_account) + { + difficulty_type commulative_diffic = cummulative_difficulties.empty() ? 0 : cummulative_difficulties.back(); + block blk_prev = blk_last; + for (size_t i = 0; i < new_block_count; ++i) + { + block blk_next; + difficulty_type diffic = next_difficulty(timestamps, cummulative_difficulties); + if (!generator.construct_block_manually(blk_next, blk_prev, miner_account, + test_generator::bf_timestamp | test_generator::bf_diffic, 0, 0, blk_prev.timestamp, crypto::hash(), diffic)) + return false; + + commulative_diffic += diffic; + if (timestamps.size() == DIFFICULTY_WINDOW) + { + timestamps.erase(timestamps.begin()); + cummulative_difficulties.erase(cummulative_difficulties.begin()); + } + timestamps.push_back(blk_next.timestamp); + cummulative_difficulties.push_back(commulative_diffic); + + events.push_back(blk_next); + blk_prev = blk_next; + } + + return true; + } +} + +#define BLOCK_VALIDATION_INIT_GENERATE() \ + GENERATE_ACCOUNT(miner_account); \ + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, 1338224400); + +//---------------------------------------------------------------------------------------------------------------------- +// Tests + +bool gen_block_big_major_version::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_major_ver, CURRENT_BLOCK_MAJOR_VERSION + 1); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_big_minor_version::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_minor_ver, 0, CURRENT_BLOCK_MINOR_VERSION + 1); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_accepted"); + + return true; +} + +bool gen_block_ts_not_checked::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - 2); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, 0, 0, blk_0.timestamp - 60 * 60); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_accepted"); + + return true; +} + +bool gen_block_ts_in_past::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - 1); + + uint64_t ts_below_median = boost::get<block>(events[BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW / 2 - 1]).timestamp; + block blk_1; + generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, 0, 0, ts_below_median); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_ts_in_future::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_timestamp, 0, 0, time(NULL) + 60*60 + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_invalid_prev_id::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + block blk_1; + crypto::hash prev_id = get_block_hash(blk_0); + reinterpret_cast<char &>(prev_id) ^= 1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_prev_id, 0, 0, 0, prev_id); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_invalid_prev_id::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) +{ + if (1 == event_idx) + return bvc.m_marked_as_orphaned && !bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; + else + return !bvc.m_marked_as_orphaned && bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; +} + +bool gen_block_invalid_nonce::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + std::vector<uint64_t> timestamps; + std::vector<difficulty_type> commulative_difficulties; + if (!lift_up_difficulty(events, timestamps, commulative_difficulties, generator, 2, blk_0, miner_account)) + return false; + + // Create invalid nonce + difficulty_type diffic = next_difficulty(timestamps, commulative_difficulties); + assert(1 < diffic); + const block& blk_last = boost::get<block>(events.back()); + uint64_t timestamp = blk_last.timestamp; + block blk_3; + do + { + ++timestamp; + blk_3.miner_tx.set_null(); + if (!generator.construct_block_manually(blk_3, blk_last, miner_account, + test_generator::bf_diffic | test_generator::bf_timestamp, 0, 0, timestamp, crypto::hash(), diffic)) + return false; + } + while (0 == blk_3.nonce); + --blk_3.nonce; + events.push_back(blk_3); + + return true; +} + +bool gen_block_no_miner_tx::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + transaction miner_tx; + miner_tx.set_null(); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_unlock_time_is_low::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + --miner_tx.unlock_time; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_unlock_time_is_high::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + ++miner_tx.unlock_time; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_unlock_time_is_timestamp_in_past::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + miner_tx.unlock_time = blk_0.timestamp - 10 * 60; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_unlock_time_is_timestamp_in_future::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + miner_tx.unlock_time = blk_0.timestamp + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW * DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_height_is_low::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + boost::get<txin_gen>(miner_tx.vin[0]).height--; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_height_is_high::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + boost::get<txin_gen>(miner_tx.vin[0]).height++; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_has_2_tx_gen_in::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + + txin_gen in; + in.height = get_block_height(blk_0) + 1; + miner_tx.vin.push_back(in); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_has_2_in::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + GENERATE_ACCOUNT(alice); + + tx_source_entry se; + se.amount = blk_0.miner_tx.vout[0].amount; + se.outputs.push_back(std::make_pair(0, boost::get<txout_to_key>(blk_0.miner_tx.vout[0].target).key)); + se.real_output = 0; + se.real_out_tx_key = get_tx_pub_key_from_extra(blk_0.miner_tx); + se.real_output_in_tx_index = 0; + std::vector<tx_source_entry> sources; + sources.push_back(se); + + tx_destination_entry de; + de.addr = miner_account.get_keys().m_account_address; + de.amount = se.amount; + std::vector<tx_destination_entry> destinations; + destinations.push_back(de); + + transaction tmp_tx; + if (!construct_tx(miner_account.get_keys(), sources, destinations, tmp_tx, 0)) + return false; + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + miner_tx.vin.push_back(tmp_tx.vin[0]); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_with_txin_to_key::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + // This block has only one output + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_none); + events.push_back(blk_1); + + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + + tx_source_entry se; + se.amount = blk_1.miner_tx.vout[0].amount; + se.outputs.push_back(std::make_pair(0, boost::get<txout_to_key>(blk_1.miner_tx.vout[0].target).key)); + se.real_output = 0; + se.real_out_tx_key = get_tx_pub_key_from_extra(blk_1.miner_tx); + se.real_output_in_tx_index = 0; + std::vector<tx_source_entry> sources; + sources.push_back(se); + + tx_destination_entry de; + de.addr = miner_account.get_keys().m_account_address; + de.amount = se.amount; + std::vector<tx_destination_entry> destinations; + destinations.push_back(de); + + transaction tmp_tx; + if (!construct_tx(miner_account.get_keys(), sources, destinations, tmp_tx, 0)) + return false; + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_1); + miner_tx.vin[0] = tmp_tx.vin[0]; + + block blk_2; + generator.construct_block_manually(blk_2, blk_1r, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_2); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_out_is_small::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + miner_tx.vout[0].amount /= 2; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_out_is_big::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + miner_tx.vout[0].amount *= 2; + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_has_no_out::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + miner_tx.vout.clear(); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_miner_tx_has_out_to_alice::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + GENERATE_ACCOUNT(alice); + + keypair txkey; + MAKE_MINER_TX_AND_KEY_MANUALLY(miner_tx, blk_0, &txkey); + + crypto::key_derivation derivation; + crypto::public_key out_eph_public_key; + crypto::generate_key_derivation(alice.get_keys().m_account_address.m_view_public_key, txkey.sec, derivation); + crypto::derive_public_key(derivation, 1, alice.get_keys().m_account_address.m_spend_public_key, out_eph_public_key); + + tx_out out_to_alice; + out_to_alice.amount = miner_tx.vout[0].amount / 2; + miner_tx.vout[0].amount -= out_to_alice.amount; + out_to_alice.target = txout_to_key(out_eph_public_key); + miner_tx.vout.push_back(out_to_alice); + + block blk_1; + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_accepted"); + + return true; +} + +bool gen_block_has_invalid_tx::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + std::vector<crypto::hash> tx_hashes; + tx_hashes.push_back(crypto::hash()); + + block blk_1; + generator.construct_block_manually_tx(blk_1, blk_0, miner_account, tx_hashes, 0); + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +bool gen_block_is_too_big::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + // Creating a huge miner_tx, it will have a lot of outs + MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); + static const size_t tx_out_count = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE / 2; + uint64_t amount = get_outs_money_amount(miner_tx); + uint64_t portion = amount / tx_out_count; + uint64_t remainder = amount % tx_out_count; + txout_target_v target = miner_tx.vout[0].target; + miner_tx.vout.clear(); + for (size_t i = 0; i < tx_out_count; ++i) + { + tx_out o; + o.amount = portion; + o.target = target; + miner_tx.vout.push_back(o); + } + if (0 < remainder) + { + tx_out o; + o.amount = remainder; + o.target = target; + miner_tx.vout.push_back(o); + } + + // Block reward will be incorrect, as it must be reduced if cumulative block size is very big, + // but in this test it doesn't matter + block blk_1; + if (!generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx)) + return false; + + events.push_back(blk_1); + + DO_CALLBACK(events, "check_block_purged"); + + return true; +} + +gen_block_invalid_binary_format::gen_block_invalid_binary_format() + : m_corrupt_blocks_begin_idx(0) +{ + REGISTER_CALLBACK("check_all_blocks_purged", gen_block_invalid_binary_format::check_all_blocks_purged); + REGISTER_CALLBACK("corrupt_blocks_boundary", gen_block_invalid_binary_format::corrupt_blocks_boundary); +} + +bool gen_block_invalid_binary_format::generate(std::vector<test_event_entry>& events) const +{ + BLOCK_VALIDATION_INIT_GENERATE(); + + std::vector<uint64_t> timestamps; + std::vector<difficulty_type> cummulative_difficulties; + difficulty_type cummulative_diff = 1; + + // Unlock blk_0 outputs + block blk_last = blk_0; + assert(CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW < DIFFICULTY_WINDOW); + for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + { + MAKE_NEXT_BLOCK(events, blk_curr, blk_last, miner_account); + timestamps.push_back(blk_curr.timestamp); + cummulative_difficulties.push_back(++cummulative_diff); + blk_last = blk_curr; + } + + // Lifting up takes a while + difficulty_type diffic; + do + { + blk_last = boost::get<block>(events.back()); + diffic = next_difficulty(timestamps, cummulative_difficulties); + if (!lift_up_difficulty(events, timestamps, cummulative_difficulties, generator, 1, blk_last, miner_account)) + return false; + std::cout << "Block #" << events.size() << ", difficulty: " << diffic << std::endl; + } + while (diffic < 1500); + + blk_last = boost::get<block>(events.back()); + MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(120), boost::get<block>(events[1])); + DO_CALLBACK(events, "corrupt_blocks_boundary"); + + block blk_test; + std::vector<crypto::hash> tx_hashes; + tx_hashes.push_back(get_transaction_hash(tx_0)); + size_t txs_size = get_object_blobsize(tx_0); + diffic = next_difficulty(timestamps, cummulative_difficulties); + if (!generator.construct_block_manually(blk_test, blk_last, miner_account, + test_generator::bf_diffic | test_generator::bf_timestamp | test_generator::bf_tx_hashes, 0, 0, blk_last.timestamp, + crypto::hash(), diffic, transaction(), tx_hashes, txs_size)) + return false; + + blobdata blob = t_serializable_object_to_blob(blk_test); + for (size_t i = 0; i < blob.size(); ++i) + { + for (size_t bit_idx = 0; bit_idx < sizeof(blobdata::value_type) * 8; ++bit_idx) + { + serialized_block sr_block(blob); + blobdata::value_type& ch = sr_block.data[i]; + ch ^= 1 << bit_idx; + + events.push_back(sr_block); + } + } + + DO_CALLBACK(events, "check_all_blocks_purged"); + + return true; +} + +bool gen_block_invalid_binary_format::check_block_verification_context(const cryptonote::block_verification_context& bvc, + size_t event_idx, const cryptonote::block& blk) +{ + if (0 == m_corrupt_blocks_begin_idx || event_idx < m_corrupt_blocks_begin_idx) + { + return bvc.m_added_to_main_chain; + } + else + { + return !bvc.m_added_to_main_chain && (bvc.m_already_exists || bvc.m_marked_as_orphaned || bvc.m_verifivation_failed); + } +} + +bool gen_block_invalid_binary_format::corrupt_blocks_boundary(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + m_corrupt_blocks_begin_idx = ev_index + 1; + return true; +} + +bool gen_block_invalid_binary_format::check_all_blocks_purged(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_block_invalid_binary_format::check_all_blocks_purged"); + + CHECK_EQ(1, c.get_pool_transactions_count()); + CHECK_EQ(m_corrupt_blocks_begin_idx - 2, c.get_current_blockchain_height()); + + return true; +} diff --git a/tests/core_tests/block_validation.h b/tests/core_tests/block_validation.h new file mode 100644 index 000000000..fe5b859a7 --- /dev/null +++ b/tests/core_tests/block_validation.h @@ -0,0 +1,182 @@ +// 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. + +#pragma once +#include "chaingen.h" + +template<size_t invalid_block_idx = 0> +class gen_block_verification_base : public test_chain_unit_base +{ +public: + gen_block_verification_base() + { + REGISTER_CALLBACK("check_block_purged", gen_block_verification_base<invalid_block_idx>::check_block_purged); + } + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) + { + if (invalid_block_idx == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; + } + + bool check_block_purged(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) + { + DEFINE_TESTS_ERROR_CONTEXT("gen_block_verification_base::check_block_purged"); + + CHECK_TEST_CONDITION(invalid_block_idx < ev_index); + CHECK_EQ(0, c.get_pool_transactions_count()); + CHECK_EQ(invalid_block_idx, c.get_current_blockchain_height()); + + return true; + } +}; + +template<size_t expected_blockchain_height> +struct gen_block_accepted_base : public test_chain_unit_base +{ + gen_block_accepted_base() + { + REGISTER_CALLBACK("check_block_accepted", gen_block_accepted_base::check_block_accepted); + } + + bool check_block_accepted(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/) + { + DEFINE_TESTS_ERROR_CONTEXT("gen_block_accepted_base::check_block_accepted"); + + CHECK_EQ(0, c.get_pool_transactions_count()); + CHECK_EQ(expected_blockchain_height, c.get_current_blockchain_height()); + + return true; + } +}; + +struct gen_block_big_major_version : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_big_minor_version : public gen_block_accepted_base<2> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_ts_not_checked : public gen_block_accepted_base<BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_ts_in_past : public gen_block_verification_base<BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_ts_in_future : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_invalid_prev_id : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/); +}; + +struct gen_block_invalid_nonce : public gen_block_verification_base<3> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_no_miner_tx : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_unlock_time_is_low : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_unlock_time_is_high : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_unlock_time_is_timestamp_in_past : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_unlock_time_is_timestamp_in_future : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_height_is_low : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_height_is_high : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_has_2_tx_gen_in : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_has_2_in : public gen_block_verification_base<CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW + 1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_with_txin_to_key : public gen_block_verification_base<CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW + 2> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_out_is_small : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_out_is_big : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_has_no_out : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_miner_tx_has_out_to_alice : public gen_block_accepted_base<2> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_has_invalid_tx : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_is_too_big : public gen_block_verification_base<1> +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_block_invalid_binary_format : public test_chain_unit_base +{ + gen_block_invalid_binary_format(); + bool generate(std::vector<test_event_entry>& events) const; + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/); + bool check_all_blocks_purged(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool corrupt_blocks_boundary(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + size_t m_corrupt_blocks_begin_idx; +}; diff --git a/tests/core_tests/chain_split_1.cpp b/tests/core_tests/chain_split_1.cpp new file mode 100644 index 000000000..bd598852e --- /dev/null +++ b/tests/core_tests/chain_split_1.cpp @@ -0,0 +1,293 @@ +// 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 "chaingen_tests_list.h" + +using namespace std; + +using namespace epee; +using namespace cryptonote; + + +gen_simple_chain_split_1::gen_simple_chain_split_1() +{ + REGISTER_CALLBACK("check_split_not_switched", gen_simple_chain_split_1::check_split_not_switched); + REGISTER_CALLBACK("check_split_not_switched2", gen_simple_chain_split_1::check_split_not_switched2); + REGISTER_CALLBACK("check_split_switched", gen_simple_chain_split_1::check_split_switched); + REGISTER_CALLBACK("check_split_not_switched_back", gen_simple_chain_split_1::check_split_not_switched_back); + REGISTER_CALLBACK("check_split_switched_back_1", gen_simple_chain_split_1::check_split_switched_back_1); + REGISTER_CALLBACK("check_split_switched_back_2", gen_simple_chain_split_1::check_split_switched_back_2); + REGISTER_CALLBACK("check_mempool_1", gen_simple_chain_split_1::check_mempool_1); + REGISTER_CALLBACK("check_mempool_2", gen_simple_chain_split_1::check_mempool_2); + //REGISTER_CALLBACK("check_orphaned_chain_1", gen_simple_chain_split_1::check_orphaned_chain_1); + //REGISTER_CALLBACK("check_orphaned_switched_to_alternative", gen_simple_chain_split_1::check_orphaned_switched_to_alternative); + //REGISTER_CALLBACK("check_orphaned_chain_2", gen_simple_chain_split_1::check_orphaned_chain_2); + //REGISTER_CALLBACK("check_orphaned_switched_to_main", gen_simple_chain_split_1::check_orphaned_switched_to_main); + //REGISTER_CALLBACK("check_orphaned_chain_38", gen_simple_chain_split_1::check_orphaned_chain_38); + //REGISTER_CALLBACK("check_orphaned_chain_39", gen_simple_chain_split_1::check_orphaned_chain_39); + //REGISTER_CALLBACK("check_orphaned_chain_40", gen_simple_chain_split_1::check_orphaned_chain_40); + //REGISTER_CALLBACK("check_orphaned_chain_41", gen_simple_chain_split_1::check_orphaned_chain_41); +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::generate(std::vector<test_event_entry> &events) const +{ + uint64_t ts_start = 1338224400; + /* + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <-- main blockchain height + (0 )-(1 )-(2 )-(3 )-(4 )-(5 ) -(6 ) -(7 ) -(8 )|-(17) -(18) -(19) -(20) -(21)|-(22)|-(23)|-(24)| + \ -(9 ) -(10)|-(11)|-(12)|-(13) -(14) -(15) -(16) + -(25) -(26)| + -(27)| #check switching to alternative + ---------------------------------------------------------------------------------- + -(28) -(29) -(30) -(31)| + -(32)| #check switching orphans to main + ---------------------------------------------------------------------------------- + -(33) -(34) -(35) -(36) -(37) -(38)| + -(39)| #<--this part becomes alternative chain connected to main + -(40)| #still marked as orphaned + -(41)| + #check orphaned with block in the middle of the orphaned chain + */ + + GENERATE_ACCOUNT(first_miner_account); + // events index + MAKE_GENESIS_BLOCK(events, blk_0, first_miner_account, ts_start); // 0 + MAKE_NEXT_BLOCK(events, blk_1, blk_0, first_miner_account); // 1 + MAKE_NEXT_BLOCK(events, blk_2, blk_1, first_miner_account); // 2 + MAKE_NEXT_BLOCK(events, blk_3, blk_2, first_miner_account); // 3 + MAKE_NEXT_BLOCK(events, blk_4, blk_3, first_miner_account); // 4 + MAKE_NEXT_BLOCK(events, blk_5, blk_4, first_miner_account); // 5 + MAKE_NEXT_BLOCK(events, blk_6, blk_5, first_miner_account); // 6 + MAKE_NEXT_BLOCK(events, blk_7, blk_6, first_miner_account); // 7 + MAKE_NEXT_BLOCK(events, blk_8, blk_7, first_miner_account); // 8 + //split + MAKE_NEXT_BLOCK(events, blk_9, blk_5, first_miner_account); // 9 + MAKE_NEXT_BLOCK(events, blk_10, blk_9, first_miner_account); // 10 + DO_CALLBACK(events, "check_split_not_switched"); // 11 + MAKE_NEXT_BLOCK(events, blk_11, blk_10, first_miner_account); // 12 + DO_CALLBACK(events, "check_split_not_switched2"); // 13 + MAKE_NEXT_BLOCK(events, blk_12, blk_11, first_miner_account); // 14 + DO_CALLBACK(events, "check_split_switched"); // 15 + MAKE_NEXT_BLOCK(events, blk_13, blk_12, first_miner_account); // 16 + MAKE_NEXT_BLOCK(events, blk_14, blk_13, first_miner_account); // 17 + MAKE_NEXT_BLOCK(events, blk_15, blk_14, first_miner_account); // 18 + MAKE_NEXT_BLOCK(events, blk_16, blk_15, first_miner_account); // 19 + //split again and check back switching + MAKE_NEXT_BLOCK(events, blk_17, blk_8, first_miner_account); // 20 + MAKE_NEXT_BLOCK(events, blk_18, blk_17, first_miner_account); // 21 + MAKE_NEXT_BLOCK(events, blk_19, blk_18, first_miner_account); // 22 + MAKE_NEXT_BLOCK(events, blk_20, blk_19, first_miner_account); // 23 + MAKE_NEXT_BLOCK(events, blk_21, blk_20, first_miner_account); // 24 + DO_CALLBACK(events, "check_split_not_switched_back"); // 25 + MAKE_NEXT_BLOCK(events, blk_22, blk_21, first_miner_account); // 26 + DO_CALLBACK(events, "check_split_switched_back_1"); // 27 + MAKE_NEXT_BLOCK(events, blk_23, blk_22, first_miner_account); // 28 + DO_CALLBACK(events, "check_split_switched_back_2"); // 29 + + REWIND_BLOCKS(events, blk_23r, blk_23, first_miner_account); // 30...N1 + GENERATE_ACCOUNT(alice); + MAKE_TX(events, tx_0, first_miner_account, alice, MK_COINS(10), blk_23); // N1+1 + MAKE_TX(events, tx_1, first_miner_account, alice, MK_COINS(20), blk_23); // N1+2 + MAKE_TX(events, tx_2, first_miner_account, alice, MK_COINS(30), blk_23); // N1+3 + DO_CALLBACK(events, "check_mempool_1"); // N1+4 + MAKE_NEXT_BLOCK_TX1(events, blk_24, blk_23r, first_miner_account, tx_0); // N1+5 + DO_CALLBACK(events, "check_mempool_2"); // N1+6 + /* + //check orphaned blocks + MAKE_NEXT_BLOCK_NO_ADD(events, blk_orph_27, blk_16, get_test_target(), first_miner_account); + MAKE_NEXT_BLOCK(events, blk_25, blk_orph_27, get_test_target(), first_miner_account); // 36 + MAKE_NEXT_BLOCK(events, blk_26, blk_25, get_test_target(), first_miner_account); // 37 + DO_CALLBACK(events, "check_orphaned_chain_1"); // 38 + ADD_BLOCK(events, blk_orph_27); // 39 + DO_CALLBACK(events, "check_orphaned_switched_to_alternative"); // 40 + + //check orphaned check to main chain + MAKE_NEXT_BLOCK_NO_ADD(events, blk_orph_32, blk_16, get_test_target(), first_miner_account); + MAKE_NEXT_BLOCK(events, blk_28, blk_orph_32, get_test_target(), first_miner_account); // 41 + MAKE_NEXT_BLOCK(events, blk_29, blk_28, get_test_target(), first_miner_account); // 42 + MAKE_NEXT_BLOCK(events, blk_30, blk_29, get_test_target(), first_miner_account); // 43 + MAKE_NEXT_BLOCK(events, blk_31, blk_30, get_test_target(), first_miner_account); // 44 + DO_CALLBACK(events, "check_orphaned_chain_2"); // 45 + ADD_BLOCK(events, blk_orph_32); // 46 + DO_CALLBACK(events, "check_orphaned_switched_to_main"); // 47 + + //check orphaned check to main chain + MAKE_NEXT_BLOCK_NO_ADD(events, blk_orph_39, blk_16, get_test_target(), first_miner_account); + MAKE_NEXT_BLOCK(events, blk_33, blk_orph_39, get_test_target(), first_miner_account); // 48 + MAKE_NEXT_BLOCK(events, blk_34, blk_33, get_test_target(), first_miner_account); // 49 + MAKE_NEXT_BLOCK_NO_ADD(events, blk_orph_41, blk_34, get_test_target(), first_miner_account); + MAKE_NEXT_BLOCK(events, blk_35, blk_orph_41, get_test_target(), first_miner_account); // 50 + MAKE_NEXT_BLOCK(events, blk_36, blk_35, get_test_target(), first_miner_account); // 51 + MAKE_NEXT_BLOCK_NO_ADD(events, blk_orph_40, blk_36, get_test_target(), first_miner_account); + MAKE_NEXT_BLOCK(events, blk_37, blk_orph_40, get_test_target(), first_miner_account); // 52 + MAKE_NEXT_BLOCK(events, blk_38, blk_37, get_test_target(), first_miner_account); // 53 + DO_CALLBACK(events, "check_orphaned_chain_38"); // 54 + ADD_BLOCK(events, blk_orph_39); // 55 + DO_CALLBACK(events, "check_orphaned_chain_39"); // 56 + ADD_BLOCK(events, blk_orph_40); // 57 + DO_CALLBACK(events, "check_orphaned_chain_40"); // 58 + ADD_BLOCK(events, blk_orph_41); // 59 + DO_CALLBACK(events, "check_orphaned_chain_41"); // 60 + */ + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_mempool_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_mempool_2"); + CHECK_TEST_CONDITION(c.get_pool_transactions_count() == 2); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_mempool_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_mempool_1"); + CHECK_TEST_CONDITION(c.get_pool_transactions_count() == 3); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_split_not_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_split_not_switched"); + //check height + CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 9); + CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 9); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get<cryptonote::block>(events[8]))); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 2); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_split_not_switched2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_split_not_switched2"); + //check height + CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 9); + CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 9); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get<cryptonote::block>(events[8]))); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 3); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_split_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_split_switched"); + + //check height + CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 10); + CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 10); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get<cryptonote::block>(events[14]))); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 3); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_split_not_switched_back(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_split_not_switched_back"); + //check height + CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 14); + CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 14); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get<cryptonote::block>(events[19]))); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 8); + + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_split_switched_back_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_split_switched_back_1"); + + //check height + CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 15); + CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 15); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get<cryptonote::block>(events[26]))); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 8); + + return true; +}//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_split_switched_back_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_split_switched_back_2"); + + //check height + CHECK_TEST_CONDITION(c.get_current_blockchain_height() == 16); + CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 16); + CHECK_TEST_CONDITION(c.get_tail_id() == get_block_hash(boost::get<cryptonote::block>(events[28]))); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 8); + return true; +} +//----------------------------------------------------------------------------------------------------- +/* +bool gen_simple_chain_split_1::check_orphaned_chain_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_chain_1"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 2); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_orphaned_switched_to_alternative(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_switched_to_alternative"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 0); + CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 17); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 11); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_orphaned_chain_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_chain_2"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 4); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_orphaned_switched_to_main(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_switched_to_main"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 0); + CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 19); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 14); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_orphaned_chain_38(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_chain_38"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 6); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 14); + CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 19); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_orphaned_chain_39(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_chain_39"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 4); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 17); + CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 19); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_orphaned_chain_40(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_chain_40"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 5); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 17); + CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 19); + return true; +} +//----------------------------------------------------------------------------------------------------- +bool gen_simple_chain_split_1::check_orphaned_chain_41(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_simple_chain_split_1::check_orphaned_chain_41"); + CHECK_TEST_CONDITION(c.get_orphaned_by_prev_blocks_count() == 0); + CHECK_TEST_CONDITION(c.get_alternative_blocks_count() == 19); + CHECK_TEST_CONDITION(c.get_current_blockchain_height()== 23); + + return true; +}*/ +//----------------------------------------------------------------------------------------------------- + + + diff --git a/tests/core_tests/chain_split_1.h b/tests/core_tests/chain_split_1.h new file mode 100644 index 000000000..df7efea82 --- /dev/null +++ b/tests/core_tests/chain_split_1.h @@ -0,0 +1,33 @@ +// 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. + +#pragma once +#include "chaingen.h" + +/************************************************************************/ +/* */ +/************************************************************************/ +class gen_simple_chain_split_1 : public test_chain_unit_base +{ +public: + gen_simple_chain_split_1(); + bool generate(std::vector<test_event_entry> &events) const; + bool check_split_not_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_split_not_switched2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_split_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_split_not_switched_back(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_split_switched_back_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_split_switched_back_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_mempool_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_mempool_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + /*bool check_orphaned_chain_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_orphaned_switched_to_alternative(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_orphaned_chain_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_orphaned_switched_to_main(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_orphaned_chain_38(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_orphaned_chain_39(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_orphaned_chain_40(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool check_orphaned_chain_41(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); */ +private: +}; diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp new file mode 100644 index 000000000..8e9a2c1db --- /dev/null +++ b/tests/core_tests/chain_switch_1.cpp @@ -0,0 +1,184 @@ +// 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 "chaingen_tests_list.h" + +using namespace epee; +using namespace cryptonote; + + +gen_chain_switch_1::gen_chain_switch_1() +{ + 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 = 1338224400; + /* + (0 )-(1 )-(2 ) -(3 )-(4 ) <- main chain, until 7 isn't connected + \ |-(5 )-(6 )-(7 )| <- alt chain, until 7 isn't connected + + 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); + + // events + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); // 0 + 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_COINS(5), blk_0); // 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_COINS(7), blk_2); // 8 + 2N + MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(11), blk_2); // 9 + 2N + MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_COINS(13), blk_2); // 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_COINS(1), blk_2); // 11 + 2N + txs_blk_5.push_back(txs_blk_3.back()); + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(2), blk_2); // 12 + 2N + txs_blk_6.push_back(txs_blk_3.back()); + + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(1), blk_2); // 13 + 2N + txs_blk_5.push_back(txs_blk_3.back()); + MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_2, MK_COINS(2), blk_2); // 14 + 2N + txs_blk_5.push_back(txs_blk_4.back()); + + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_3, MK_COINS(1), blk_2); // 15 + 2N + txs_blk_6.push_back(txs_blk_3.back()); + MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(2), blk_2); // 16 + 2N + txs_blk_5.push_back(txs_blk_4.back()); + + MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_4, MK_COINS(1), blk_2); // 17 + 2N + txs_blk_5.push_back(txs_blk_4.back()); + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_4, MK_COINS(2), blk_2); // 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(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_chain_switch_1::check_split_not_switched"); + + 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 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size()); + CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[20 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_4 + + CHECK_EQ(2, c.get_alternative_blocks_count()); + + std::vector<cryptonote::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_COINS(8), get_balance(m_recipient_account_1, chain, mtx)); + CHECK_EQ(MK_COINS(10), get_balance(m_recipient_account_2, chain, mtx)); + CHECK_EQ(MK_COINS(14), get_balance(m_recipient_account_3, chain, mtx)); + CHECK_EQ(MK_COINS(3), get_balance(m_recipient_account_4, chain, mtx)); + + std::list<transaction> tx_pool; + r = c.get_pool_transactions(tx_pool); + CHECK_TEST_CONDITION(r); + CHECK_EQ(1, tx_pool.size()); + + std::vector<size_t> tx_outs; + uint64_t transfered; + lookup_acc_outs(m_recipient_account_4.get_keys(), tx_pool.front(), get_tx_pub_key_from_extra(tx_pool.front()), tx_outs, transfered); + CHECK_EQ(MK_COINS(13), transfered); + + m_chain_1.swap(blocks); + m_tx_pool.swap(tx_pool); + + return true; +} + +//----------------------------------------------------------------------------------------------------- +bool gen_chain_switch_1::check_split_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_chain_switch_1::check_split_switched"); + + std::list<block> blocks; + bool r = c.get_blocks(0, 10000, blocks); + CHECK_TEST_CONDITION(r); + CHECK_EQ(6 + 2 * CRYPTONOTE_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 * CRYPTONOTE_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<cryptonote::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_COINS(8), get_balance(m_recipient_account_1, chain, mtx)); + CHECK_EQ(MK_COINS(3), get_balance(m_recipient_account_2, chain, mtx)); + CHECK_EQ(MK_COINS(14), get_balance(m_recipient_account_3, chain, mtx)); + CHECK_EQ(MK_COINS(16), get_balance(m_recipient_account_4, chain, mtx)); + + 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<size_t> tx_outs; + uint64_t transfered; + lookup_acc_outs(m_recipient_account_2.get_keys(), tx_pool.front(), tx_outs, transfered); + CHECK_EQ(MK_COINS(7), transfered); + + return true; +} diff --git a/tests/core_tests/chain_switch_1.h b/tests/core_tests/chain_switch_1.h new file mode 100644 index 000000000..05e84e6cf --- /dev/null +++ b/tests/core_tests/chain_switch_1.h @@ -0,0 +1,30 @@ +// 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. + +#pragma once +#include "chaingen.h" + +/************************************************************************/ +/* */ +/************************************************************************/ +class gen_chain_switch_1 : public test_chain_unit_base +{ +public: + gen_chain_switch_1(); + + bool generate(std::vector<test_event_entry>& events) const; + + bool check_split_not_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_split_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + std::list<cryptonote::block> m_chain_1; + + cryptonote::account_base m_recipient_account_1; + cryptonote::account_base m_recipient_account_2; + cryptonote::account_base m_recipient_account_3; + cryptonote::account_base m_recipient_account_4; + + std::list<cryptonote::transaction> m_tx_pool; +}; diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp new file mode 100644 index 000000000..c876e3a75 --- /dev/null +++ b/tests/core_tests/chaingen.cpp @@ -0,0 +1,637 @@ +// 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 <vector> +#include <iostream> +#include <sstream> + +#include "include_base_utils.h" + +#include "console_handler.h" + +#include "p2p/net_node.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/miner.h" + +#include "chaingen.h" + +using namespace std; + +using namespace epee; +using namespace cryptonote; + + +void test_generator::get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const +{ + crypto::hash curr = head; + while (null_hash != curr && blockchain.size() < n) + { + auto it = m_blocks_info.find(curr); + if (m_blocks_info.end() == it) + { + throw std::runtime_error("block hash wasn't found"); + } + + blockchain.push_back(it->second); + curr = it->second.prev_id; + } + + std::reverse(blockchain.begin(), blockchain.end()); +} + +void test_generator::get_last_n_block_sizes(std::vector<size_t>& block_sizes, const crypto::hash& head, size_t n) const +{ + std::vector<block_info> blockchain; + get_block_chain(blockchain, head, n); + BOOST_FOREACH(auto& bi, blockchain) + { + block_sizes.push_back(bi.block_size); + } +} + +uint64_t test_generator::get_already_generated_coins(const crypto::hash& blk_id) const +{ + auto it = m_blocks_info.find(blk_id); + if (it == m_blocks_info.end()) + throw std::runtime_error("block hash wasn't found"); + + return it->second.already_generated_coins; +} + +uint64_t test_generator::get_already_generated_coins(const cryptonote::block& blk) const +{ + crypto::hash blk_hash; + get_block_hash(blk, blk_hash); + return get_already_generated_coins(blk_hash); +} + +void test_generator::add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins) +{ + const size_t block_size = tsx_size + get_object_blobsize(blk.miner_tx); + bool block_too_big; + uint64_t block_reward = get_block_reward(block_sizes, block_size, block_too_big, already_generated_coins); + m_blocks_info[get_block_hash(blk)] = block_info(blk.prev_id, already_generated_coins + block_reward, block_size); +} + +bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id, + const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins, + std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list) +{ + blk.major_version = CURRENT_BLOCK_MAJOR_VERSION; + blk.minor_version = CURRENT_BLOCK_MINOR_VERSION; + blk.timestamp = timestamp; + blk.prev_id = prev_id; + + blk.tx_hashes.reserve(tx_list.size()); + BOOST_FOREACH(const transaction &tx, tx_list) + { + crypto::hash tx_hash; + get_transaction_hash(tx, tx_hash); + blk.tx_hashes.push_back(tx_hash); + } + + uint64_t total_fee = 0; + size_t txs_size = 0; + BOOST_FOREACH(auto& tx, tx_list) + { + uint64_t fee = 0; + bool r = get_tx_fee(tx, fee); + CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); + total_fee += fee; + txs_size += get_object_blobsize(tx); + } + + blk.miner_tx = AUTO_VAL_INIT(blk.miner_tx); + size_t target_block_size = txs_size + get_object_blobsize(blk.miner_tx); + while (true) + { + if (!construct_miner_tx(height, already_generated_coins, miner_acc.get_keys().m_account_address, blk.miner_tx, total_fee, block_sizes, target_block_size, 10)) + return false; + + size_t actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); + if (target_block_size < actual_block_size) + { + target_block_size = actual_block_size; + } + else if (actual_block_size < target_block_size) + { + size_t delta = target_block_size - actual_block_size; + blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); + actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); + if (actual_block_size == target_block_size) + { + break; + } + else + { + CHECK_AND_ASSERT_MES(target_block_size < actual_block_size, false, "Unexpected block size"); + delta = actual_block_size - target_block_size; + blk.miner_tx.extra.resize(blk.miner_tx.extra.size() - delta); + actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); + if (actual_block_size == target_block_size) + { + break; + } + else + { + CHECK_AND_ASSERT_MES(actual_block_size < target_block_size, false, "Unexpected block size"); + blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); + target_block_size = txs_size + get_object_blobsize(blk.miner_tx); + } + } + } + else + { + break; + } + } + + //blk.tree_root_hash = get_tx_tree_hash(blk); + + // Nonce search... + blk.nonce = 0; + while (!miner::find_nonce_for_given_block(blk, get_test_difficulty(), height)) + blk.timestamp++; + + add_block(blk, txs_size, block_sizes, already_generated_coins); + + return true; +} + +bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::account_base& miner_acc, uint64_t timestamp) +{ + std::vector<size_t> block_sizes; + std::list<cryptonote::transaction> tx_list; + return construct_block(blk, 0, null_hash, miner_acc, timestamp, 0, block_sizes, tx_list); +} + +bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::block& blk_prev, + const cryptonote::account_base& miner_acc, + const std::list<cryptonote::transaction>& tx_list/* = std::list<cryptonote::transaction>()*/) +{ + uint64_t height = boost::get<txin_gen>(blk_prev.miner_tx.vin.front()).height + 1; + crypto::hash prev_id = get_block_hash(blk_prev); + // Keep difficulty unchanged + uint64_t timestamp = blk_prev.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN; + uint64_t already_generated_coins = get_already_generated_coins(prev_id); + std::vector<size_t> block_sizes; + get_last_n_block_sizes(block_sizes, prev_id, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + + return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, block_sizes, tx_list); +} + +bool test_generator::construct_block_manually(block& blk, const block& prev_block, const account_base& miner_acc, + int actual_params/* = bf_none*/, uint8_t major_ver/* = 0*/, + uint8_t minor_ver/* = 0*/, uint64_t timestamp/* = 0*/, + const crypto::hash& prev_id/* = crypto::hash()*/, const difficulty_type& diffic/* = 1*/, + const transaction& miner_tx/* = transaction()*/, + const std::vector<crypto::hash>& tx_hashes/* = std::vector<crypto::hash>()*/, + size_t txs_sizes/* = 0*/) +{ + blk.major_version = actual_params & bf_major_ver ? major_ver : CURRENT_BLOCK_MAJOR_VERSION; + blk.minor_version = actual_params & bf_minor_ver ? minor_ver : CURRENT_BLOCK_MINOR_VERSION; + blk.timestamp = actual_params & bf_timestamp ? timestamp : prev_block.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN; // Keep difficulty unchanged + blk.prev_id = actual_params & bf_prev_id ? prev_id : get_block_hash(prev_block); + blk.tx_hashes = actual_params & bf_tx_hashes ? tx_hashes : std::vector<crypto::hash>(); + + size_t height = get_block_height(prev_block) + 1; + uint64_t already_generated_coins = get_already_generated_coins(prev_block); + std::vector<size_t> block_sizes; + get_last_n_block_sizes(block_sizes, get_block_hash(prev_block), CRYPTONOTE_REWARD_BLOCKS_WINDOW); + if (actual_params & bf_miner_tx) + { + blk.miner_tx = miner_tx; + } + else + { + size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx); + // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + if (!construct_miner_tx(height, already_generated_coins, miner_acc.get_keys().m_account_address, blk.miner_tx, 0, block_sizes, current_block_size, 1)) + return false; + } + + //blk.tree_root_hash = get_tx_tree_hash(blk); + + difficulty_type a_diffic = actual_params & bf_diffic ? diffic : get_test_difficulty(); + fill_nonce(blk, a_diffic, height); + + add_block(blk, txs_sizes, block_sizes, already_generated_coins); + + return true; +} + +bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, + const cryptonote::account_base& miner_acc, + const std::vector<crypto::hash>& tx_hashes, size_t txs_size) +{ + return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, transaction(), tx_hashes, txs_size); +} + + +struct output_index { + const cryptonote::txout_target_v out; + uint64_t amount; + size_t blk_height; // block height + size_t tx_no; // index of transaction in block + size_t out_no; // index of out in transaction + size_t idx; + bool spent; + const cryptonote::block *p_blk; + const cryptonote::transaction *p_tx; + + output_index(const cryptonote::txout_target_v &_out, uint64_t _a, size_t _h, size_t tno, size_t ono, const cryptonote::block *_pb, const cryptonote::transaction *_pt) + : out(_out), amount(_a), blk_height(_h), tx_no(tno), out_no(ono), idx(0), spent(false), p_blk(_pb), p_tx(_pt) { } + + output_index(const output_index &other) + : out(other.out), amount(other.amount), blk_height(other.blk_height), tx_no(other.tx_no), out_no(other.out_no), idx(other.idx), spent(other.spent), p_blk(other.p_blk), p_tx(other.p_tx) { } + + const std::string toString() const { + std::stringstream ss; + + ss << "output_index{blk_height=" << blk_height + << " tx_no=" << tx_no + << " out_no=" << out_no + << " amount=" << amount + << " idx=" << idx + << " spent=" << spent + << "}"; + + return ss.str(); + } + + output_index& operator=(const output_index& other) + { + new(this) output_index(other); + return *this; + } +}; + +typedef std::map<uint64_t, std::vector<size_t> > map_output_t; +typedef std::map<uint64_t, std::vector<output_index> > map_output_idx_t; +typedef pair<uint64_t, size_t> outloc_t; + +namespace +{ + uint64_t get_inputs_amount(const vector<tx_source_entry> &s) + { + uint64_t r = 0; + BOOST_FOREACH(const tx_source_entry &e, s) + { + r += e.amount; + } + + return r; + } +} + +bool init_output_indices(map_output_idx_t& outs, std::map<uint64_t, std::vector<size_t> >& outs_mine, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { + + BOOST_FOREACH (const block& blk, blockchain) { + vector<const transaction*> vtx; + vtx.push_back(&blk.miner_tx); + + BOOST_FOREACH(const crypto::hash &h, blk.tx_hashes) { + const map_hash2tx_t::const_iterator cit = mtx.find(h); + if (mtx.end() == cit) + throw std::runtime_error("block contains an unknown tx hash"); + + vtx.push_back(cit->second); + } + + //vtx.insert(vtx.end(), blk.); + // TODO: add all other txes + for (size_t i = 0; i < vtx.size(); i++) { + const transaction &tx = *vtx[i]; + + for (size_t j = 0; j < tx.vout.size(); ++j) { + const tx_out &out = tx.vout[j]; + + output_index oi(out.target, out.amount, boost::get<txin_gen>(*blk.miner_tx.vin.begin()).height, i, j, &blk, vtx[i]); + + if (2 == out.target.which()) { // out_to_key + outs[out.amount].push_back(oi); + size_t tx_global_idx = outs[out.amount].size() - 1; + outs[out.amount][tx_global_idx].idx = tx_global_idx; + // Is out to me? + if (is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), j)) { + outs_mine[out.amount].push_back(tx_global_idx); + } + } + } + } + } + + return true; +} + +bool init_spent_output_indices(map_output_idx_t& outs, map_output_t& outs_mine, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { + + BOOST_FOREACH (const map_output_t::value_type &o, outs_mine) { + for (size_t i = 0; i < o.second.size(); ++i) { + output_index &oi = outs[o.first][o.second[i]]; + + // construct key image for this output + crypto::key_image img; + keypair in_ephemeral; + generate_key_image_helper(from.get_keys(), get_tx_pub_key_from_extra(*oi.p_tx), oi.out_no, in_ephemeral, img); + + // lookup for this key image in the events vector + BOOST_FOREACH(auto& tx_pair, mtx) { + const transaction& tx = *tx_pair.second; + BOOST_FOREACH(const txin_v &in, tx.vin) { + if (typeid(txin_to_key) == in.type()) { + const txin_to_key &itk = boost::get<txin_to_key>(in); + if (itk.k_image == img) { + oi.spent = true; + } + } + } + } + } + } + + return true; +} + +bool fill_output_entries(std::vector<output_index>& out_indices, size_t sender_out, size_t nmix, size_t& real_entry_idx, std::vector<tx_source_entry::output_entry>& output_entries) +{ + if (out_indices.size() <= nmix) + return false; + + bool sender_out_found = false; + size_t rest = nmix; + for (size_t i = 0; i < out_indices.size() && (0 < rest || !sender_out_found); ++i) + { + const output_index& oi = out_indices[i]; + if (oi.spent) + continue; + + bool append = false; + if (i == sender_out) + { + append = true; + sender_out_found = true; + real_entry_idx = output_entries.size(); + } + else if (0 < rest) + { + --rest; + append = true; + } + + if (append) + { + const txout_to_key& otk = boost::get<txout_to_key>(oi.out); + output_entries.push_back(tx_source_entry::output_entry(oi.idx, otk.key)); + } + } + + return 0 == rest && sender_out_found; +} + +bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<test_event_entry>& events, + const block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix) +{ + map_output_idx_t outs; + map_output_t outs_mine; + + std::vector<cryptonote::block> blockchain; + map_hash2tx_t mtx; + if (!find_block_chain(events, blockchain, mtx, get_block_hash(blk_head))) + return false; + + if (!init_output_indices(outs, outs_mine, blockchain, mtx, from)) + return false; + + if (!init_spent_output_indices(outs, outs_mine, blockchain, mtx, from)) + return false; + + // Iterate in reverse is more efficiency + uint64_t sources_amount = 0; + bool sources_found = false; + BOOST_REVERSE_FOREACH(const map_output_t::value_type o, outs_mine) + { + for (size_t i = 0; i < o.second.size() && !sources_found; ++i) + { + size_t sender_out = o.second[i]; + const output_index& oi = outs[o.first][sender_out]; + if (oi.spent) + continue; + + cryptonote::tx_source_entry ts; + ts.amount = oi.amount; + ts.real_output_in_tx_index = oi.out_no; + ts.real_out_tx_key = get_tx_pub_key_from_extra(*oi.p_tx); // incoming tx public key + if (!fill_output_entries(outs[o.first], sender_out, nmix, ts.real_output, ts.outputs)) + continue; + + sources.push_back(ts); + + sources_amount += ts.amount; + sources_found = amount <= sources_amount; + } + + if (sources_found) + break; + } + + return sources_found; +} + +bool fill_tx_destination(tx_destination_entry &de, const cryptonote::account_base &to, uint64_t amount) { + de.addr = to.get_keys().m_account_address; + de.amount = amount; + return true; +} + +void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const block& blk_head, + const cryptonote::account_base& from, const cryptonote::account_base& to, + uint64_t amount, uint64_t fee, size_t nmix, std::vector<tx_source_entry>& sources, + std::vector<tx_destination_entry>& destinations) +{ + sources.clear(); + destinations.clear(); + + if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix)) + throw std::runtime_error("couldn't fill transaction sources"); + + tx_destination_entry de; + if (!fill_tx_destination(de, to, amount)) + throw std::runtime_error("couldn't fill transaction destination"); + destinations.push_back(de); + + tx_destination_entry de_change; + uint64_t cache_back = get_inputs_amount(sources) - (amount + fee); + if (0 < cache_back) + { + if (!fill_tx_destination(de_change, from, cache_back)) + throw std::runtime_error("couldn't fill transaction cache back destination"); + destinations.push_back(de_change); + } +} + +void fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) +{ + blk.nonce = 0; + while (!miner::find_nonce_for_given_block(blk, diffic, height)) + blk.timestamp++; +} + +bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins, + const account_public_address& miner_address, transaction& tx, uint64_t fee, + keypair* p_txkey/* = 0*/) +{ + keypair txkey; + txkey = keypair::generate(); + add_tx_pub_key_to_extra(tx, txkey.pub); + + if (0 != p_txkey) + *p_txkey = txkey; + + txin_gen in; + in.height = height; + tx.vin.push_back(in); + + // This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + std::vector<size_t> block_sizes; + bool block_too_big = false; + uint64_t block_reward = get_block_reward(block_sizes, 0, block_too_big, already_generated_coins) + fee; + if (block_too_big) + { + LOG_PRINT_L0("Block is too big"); + return false; + } + + crypto::key_derivation derivation; + crypto::public_key out_eph_public_key; + crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); + crypto::derive_public_key(derivation, 0, miner_address.m_spend_public_key, out_eph_public_key); + + tx_out out; + out.amount = block_reward; + out.target = txout_to_key(out_eph_public_key); + tx.vout.push_back(out); + + tx.version = CURRENT_TRANSACTION_VERSION; + tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + + return true; +} + +bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const block& blk_head, + const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, + uint64_t fee, size_t nmix) +{ + vector<tx_source_entry> sources; + vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, nmix, sources, destinations); + + return construct_tx(from.get_keys(), sources, destinations, tx, 0); +} + +transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head, + const account_base& acc_from, const account_base& acc_to, uint64_t amount, uint64_t fee) +{ + transaction tx; + construct_tx_to_key(events, tx, blk_head, acc_from, acc_to, amount, fee, 0); + events.push_back(tx); + return tx; +} + +uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx) { + uint64_t res = 0; + std::map<uint64_t, std::vector<output_index> > outs; + std::map<uint64_t, std::vector<size_t> > outs_mine; + + map_hash2tx_t confirmed_txs; + get_confirmed_txs(blockchain, mtx, confirmed_txs); + + if (!init_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) + return false; + + if (!init_spent_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) + return false; + + BOOST_FOREACH (const map_output_t::value_type &o, outs_mine) { + for (size_t i = 0; i < o.second.size(); ++i) { + if (outs[o.first][o.second[i]].spent) + continue; + + res += outs[o.first][o.second[i]].amount; + } + } + + return res; +} + +void get_confirmed_txs(const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) +{ + std::unordered_set<crypto::hash> confirmed_hashes; + BOOST_FOREACH(const block& blk, blockchain) + { + BOOST_FOREACH(const crypto::hash& tx_hash, blk.tx_hashes) + { + confirmed_hashes.insert(tx_hash); + } + } + + BOOST_FOREACH(const auto& tx_pair, mtx) + { + if (0 != confirmed_hashes.count(tx_pair.first)) + { + confirmed_txs.insert(tx_pair); + } + } +} + +bool find_block_chain(const std::vector<test_event_entry>& events, std::vector<cryptonote::block>& blockchain, map_hash2tx_t& mtx, const crypto::hash& head) { + std::unordered_map<crypto::hash, const block*> block_index; + BOOST_FOREACH(const test_event_entry& ev, events) + { + if (typeid(block) == ev.type()) + { + const block* blk = &boost::get<block>(ev); + block_index[get_block_hash(*blk)] = blk; + } + else if (typeid(transaction) == ev.type()) + { + const transaction& tx = boost::get<transaction>(ev); + mtx[get_transaction_hash(tx)] = &tx; + } + } + + bool b_success = false; + crypto::hash id = head; + for (auto it = block_index.find(id); block_index.end() != it; it = block_index.find(id)) + { + blockchain.push_back(*it->second); + id = it->second->prev_id; + if (null_hash == id) + { + b_success = true; + break; + } + } + reverse(blockchain.begin(), blockchain.end()); + + return b_success; +} + + +void test_chain_unit_base::register_callback(const std::string& cb_name, verify_callback cb) +{ + m_callbacks[cb_name] = cb; +} +bool test_chain_unit_base::verify(const std::string& cb_name, cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + auto cb_it = m_callbacks.find(cb_name); + if(cb_it == m_callbacks.end()) + { + LOG_ERROR("Failed to find callback " << cb_name); + return false; + } + return cb_it->second(c, ev_index, events); +} diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h new file mode 100644 index 000000000..3d47edeef --- /dev/null +++ b/tests/core_tests/chaingen.h @@ -0,0 +1,646 @@ +// 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. + +#pragma once + +#include <vector> +#include <iostream> +#include <stdint.h> + +#include <boost/archive/binary_oarchive.hpp> +#include <boost/archive/binary_iarchive.hpp> +#include <boost/program_options.hpp> +#include <boost/serialization/vector.hpp> +#include <boost/serialization/variant.hpp> + +#include "include_base_utils.h" +#include "common/boost_serialization_helper.h" +#include "common/command_line.h" + +#include "cryptonote_core/account_boost_serialization.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_core/cryptonote_boost_serialization.h" +#include "misc_language.h" + + +namespace concolor +{ + inline std::basic_ostream<char, std::char_traits<char> >& bright_white(std::basic_ostream<char, std::char_traits<char> >& ostr) + { + epee::log_space::set_console_color(epee::log_space::console_color_white, true); + return ostr; + } + + inline std::basic_ostream<char, std::char_traits<char> >& red(std::basic_ostream<char, std::char_traits<char> >& ostr) + { + epee::log_space::set_console_color(epee::log_space::console_color_red, true); + return ostr; + } + + inline std::basic_ostream<char, std::char_traits<char> >& green(std::basic_ostream<char, std::char_traits<char> >& ostr) + { + epee::log_space::set_console_color(epee::log_space::console_color_green, true); + return ostr; + } + + inline std::basic_ostream<char, std::char_traits<char> >& magenta(std::basic_ostream<char, std::char_traits<char> >& ostr) + { + epee::log_space::set_console_color(epee::log_space::console_color_magenta, true); + return ostr; + } + + inline std::basic_ostream<char, std::char_traits<char> >& yellow(std::basic_ostream<char, std::char_traits<char> >& ostr) + { + epee::log_space::set_console_color(epee::log_space::console_color_yellow, true); + return ostr; + } + + inline std::basic_ostream<char, std::char_traits<char> >& normal(std::basic_ostream<char, std::char_traits<char> >& ostr) + { + epee::log_space::reset_console_color(); + return ostr; + } +} + + +struct callback_entry +{ + std::string callback_name; + BEGIN_SERIALIZE_OBJECT() + FIELD(callback_name) + END_SERIALIZE() + +private: + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive & ar, const unsigned int /*version*/) + { + ar & callback_name; + } +}; + +template<typename T> +struct serialized_object +{ + serialized_object() { } + + serialized_object(const cryptonote::blobdata& a_data) + : data(a_data) + { + } + + cryptonote::blobdata data; + BEGIN_SERIALIZE_OBJECT() + FIELD(data) + END_SERIALIZE() + +private: + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive & ar, const unsigned int /*version*/) + { + ar & data; + } +}; + +typedef serialized_object<cryptonote::block> serialized_block; +typedef serialized_object<cryptonote::transaction> serialized_transaction; + +struct event_visitor_settings +{ + int valid_mask; + bool txs_keeped_by_block; + + enum settings + { + set_txs_keeped_by_block = 1 << 0 + }; + + event_visitor_settings(int a_valid_mask = 0, bool a_txs_keeped_by_block = false) + : valid_mask(a_valid_mask) + , txs_keeped_by_block(a_txs_keeped_by_block) + { + } + +private: + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive & ar, const unsigned int /*version*/) + { + ar & valid_mask; + ar & txs_keeped_by_block; + } +}; + +VARIANT_TAG(binary_archive, callback_entry, 0xcb); +VARIANT_TAG(binary_archive, cryptonote::account_base, 0xcc); +VARIANT_TAG(binary_archive, serialized_block, 0xcd); +VARIANT_TAG(binary_archive, serialized_transaction, 0xce); +VARIANT_TAG(binary_archive, event_visitor_settings, 0xcf); + +typedef boost::variant<cryptonote::block, cryptonote::transaction, cryptonote::account_base, callback_entry, serialized_block, serialized_transaction, event_visitor_settings> test_event_entry; +typedef std::unordered_map<crypto::hash, const cryptonote::transaction*> map_hash2tx_t; + +class test_chain_unit_base +{ +public: + typedef boost::function<bool (cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events)> verify_callback; + typedef std::map<std::string, verify_callback> callbacks_map; + + void register_callback(const std::string& cb_name, verify_callback cb); + bool verify(const std::string& cb_name, cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); +private: + callbacks_map m_callbacks; +}; + + +class test_generator +{ +public: + struct block_info + { + block_info() + : prev_id() + , already_generated_coins(0) + , block_size(0) + { + } + + block_info(crypto::hash a_prev_id, uint64_t an_already_generated_coins, size_t a_block_size) + : prev_id(a_prev_id) + , already_generated_coins(an_already_generated_coins) + , block_size(a_block_size) + { + } + + crypto::hash prev_id; + uint64_t already_generated_coins; + size_t block_size; + }; + + enum block_fields + { + bf_none = 0, + bf_major_ver = 1 << 0, + bf_minor_ver = 1 << 1, + bf_timestamp = 1 << 2, + bf_prev_id = 1 << 3, + bf_miner_tx = 1 << 4, + bf_tx_hashes = 1 << 5, + bf_diffic = 1 << 6 + }; + + void get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const; + void get_last_n_block_sizes(std::vector<size_t>& block_sizes, const crypto::hash& head, size_t n) const; + uint64_t get_already_generated_coins(const crypto::hash& blk_id) const; + uint64_t get_already_generated_coins(const cryptonote::block& blk) const; + + void add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins); + bool construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id, + const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins, + std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list); + bool construct_block(cryptonote::block& blk, const cryptonote::account_base& miner_acc, uint64_t timestamp); + bool construct_block(cryptonote::block& blk, const cryptonote::block& blk_prev, const cryptonote::account_base& miner_acc, + const std::list<cryptonote::transaction>& tx_list = std::list<cryptonote::transaction>()); + + bool construct_block_manually(cryptonote::block& blk, const cryptonote::block& prev_block, + const cryptonote::account_base& miner_acc, int actual_params = bf_none, uint8_t major_ver = 0, + uint8_t minor_ver = 0, uint64_t timestamp = 0, const crypto::hash& prev_id = crypto::hash(), + const cryptonote::difficulty_type& diffic = 1, const cryptonote::transaction& miner_tx = cryptonote::transaction(), + const std::vector<crypto::hash>& tx_hashes = std::vector<crypto::hash>(), size_t txs_sizes = 0); + bool construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, + const cryptonote::account_base& miner_acc, const std::vector<crypto::hash>& tx_hashes, size_t txs_size); + +private: + std::unordered_map<crypto::hash, block_info> m_blocks_info; +}; + +inline cryptonote::difficulty_type get_test_difficulty() {return 1;} +void fill_nonce(cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); + +bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins, + const cryptonote::account_public_address& miner_address, cryptonote::transaction& tx, + uint64_t fee, cryptonote::keypair* p_txkey = 0); +bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, + const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, + uint64_t amount, uint64_t fee, size_t nmix); +cryptonote::transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const cryptonote::block& blk_head, + const cryptonote::account_base& acc_from, const cryptonote::account_base& acc_to, + uint64_t amount, uint64_t fee); + +void get_confirmed_txs(const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs); +bool find_block_chain(const std::vector<test_event_entry>& events, std::vector<cryptonote::block>& blockchain, map_hash2tx_t& mtx, const crypto::hash& head); +void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const cryptonote::block& blk_head, + const cryptonote::account_base& from, const cryptonote::account_base& to, + uint64_t amount, uint64_t fee, size_t nmix, + std::vector<cryptonote::tx_source_entry>& sources, + std::vector<cryptonote::tx_destination_entry>& destinations); +uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx); + +//-------------------------------------------------------------------------- +template<class t_test_class> +auto do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::transaction& tx, t_test_class& validator, int) + -> decltype(validator.check_tx_verification_context(tvc, tx_added, event_index, tx)) +{ + return validator.check_tx_verification_context(tvc, tx_added, event_index, tx); +} +//-------------------------------------------------------------------------- +template<class t_test_class> +bool do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t /*event_index*/, const cryptonote::transaction& /*tx*/, t_test_class&, long) +{ + // Default block verification context check + if (tvc.m_verifivation_failed) + throw std::runtime_error("Transaction verification failed"); + return true; +} +//-------------------------------------------------------------------------- +template<class t_test_class> +bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::transaction& tx, t_test_class& validator) +{ + // SFINAE in action + return do_check_tx_verification_context(tvc, tx_added, event_index, tx, validator, 0); +} +//-------------------------------------------------------------------------- +template<class t_test_class> +auto do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::block& blk, t_test_class& validator, int) + -> decltype(validator.check_block_verification_context(bvc, event_index, blk)) +{ + return validator.check_block_verification_context(bvc, event_index, blk); +} +//-------------------------------------------------------------------------- +template<class t_test_class> +bool do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t /*event_index*/, const cryptonote::block& /*blk*/, t_test_class&, long) +{ + // Default block verification context check + if (bvc.m_verifivation_failed) + throw std::runtime_error("Block verification failed"); + return true; +} +//-------------------------------------------------------------------------- +template<class t_test_class> +bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::block& blk, t_test_class& validator) +{ + // SFINAE in action + return do_check_block_verification_context(bvc, event_index, blk, validator, 0); +} + +/************************************************************************/ +/* */ +/************************************************************************/ +template<class t_test_class> +struct push_core_event_visitor: public boost::static_visitor<bool> +{ +private: + cryptonote::core& m_c; + const std::vector<test_event_entry>& m_events; + t_test_class& m_validator; + size_t m_ev_index; + + bool m_txs_keeped_by_block; + +public: + push_core_event_visitor(cryptonote::core& c, const std::vector<test_event_entry>& events, t_test_class& validator) + : m_c(c) + , m_events(events) + , m_validator(validator) + , m_ev_index(0) + , m_txs_keeped_by_block(false) + { + } + + void event_index(size_t ev_index) + { + m_ev_index = ev_index; + } + + bool operator()(const event_visitor_settings& settings) + { + log_event("event_visitor_settings"); + + if (settings.valid_mask & event_visitor_settings::set_txs_keeped_by_block) + { + m_txs_keeped_by_block = settings.txs_keeped_by_block; + } + + return true; + } + + bool operator()(const cryptonote::transaction& tx) const + { + log_event("cryptonote::transaction"); + + cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + size_t pool_size = m_c.get_pool_transactions_count(); + m_c.handle_incoming_tx(t_serializable_object_to_blob(tx), tvc, m_txs_keeped_by_block); + bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count(); + bool r = check_tx_verification_context(tvc, tx_added, m_ev_index, tx, m_validator); + CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed"); + return true; + } + + bool operator()(const cryptonote::block& b) const + { + log_event("cryptonote::block"); + + cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); + m_c.handle_incoming_block(t_serializable_object_to_blob(b), bvc); + bool r = check_block_verification_context(bvc, m_ev_index, b, m_validator); + CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed"); + return r; + } + + bool operator()(const callback_entry& cb) const + { + log_event(std::string("callback_entry ") + cb.callback_name); + return m_validator.verify(cb.callback_name, m_c, m_ev_index, m_events); + } + + bool operator()(const cryptonote::account_base& ab) const + { + log_event("cryptonote::account_base"); + return true; + } + + bool operator()(const serialized_block& sr_block) const + { + log_event("serialized_block"); + + cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); + m_c.handle_incoming_block(sr_block.data, bvc); + + cryptonote::block blk; + std::stringstream ss; + ss << sr_block.data; + binary_archive<false> ba(ss); + ::serialization::serialize(ba, blk); + if (!ss.good()) + { + blk = cryptonote::block(); + } + bool r = check_block_verification_context(bvc, m_ev_index, blk, m_validator); + CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed"); + return true; + } + + bool operator()(const serialized_transaction& sr_tx) const + { + log_event("serialized_transaction"); + + cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + size_t pool_size = m_c.get_pool_transactions_count(); + m_c.handle_incoming_tx(sr_tx.data, tvc, m_txs_keeped_by_block); + bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count(); + + cryptonote::transaction tx; + std::stringstream ss; + ss << sr_tx.data; + binary_archive<false> ba(ss); + ::serialization::serialize(ba, tx); + if (!ss.good()) + { + tx = cryptonote::transaction(); + } + + bool r = check_tx_verification_context(tvc, tx_added, m_ev_index, tx, m_validator); + CHECK_AND_NO_ASSERT_MES(r, false, "transaction verification context check failed"); + return true; + } + +private: + void log_event(const std::string& event_type) const + { + std::cout << concolor::yellow << "=== EVENT # " << m_ev_index << ": " << event_type << concolor::normal << std::endl; + } +}; +//-------------------------------------------------------------------------- +template<class t_test_class> +inline bool replay_events_through_core(cryptonote::core& cr, const std::vector<test_event_entry>& events, t_test_class& validator) +{ + TRY_ENTRY(); + + //init core here + + CHECK_AND_ASSERT_MES(typeid(cryptonote::block) == events[0].type(), false, "First event must be genesis block creation"); + cr.set_genesis_block(boost::get<cryptonote::block>(events[0])); + + bool r = true; + push_core_event_visitor<t_test_class> visitor(cr, events, validator); + for(size_t i = 1; i < events.size() && r; ++i) + { + visitor.event_index(i); + r = boost::apply_visitor(visitor, events[i]); + } + + return r; + + CATCH_ENTRY_L0("replay_events_through_core", false); +} +//-------------------------------------------------------------------------- +template<class t_test_class> +inline bool do_replay_events(std::vector<test_event_entry>& events) +{ + boost::program_options::options_description desc("Allowed options"); + cryptonote::core::init_options(desc); + command_line::add_arg(desc, command_line::arg_data_dir); + boost::program_options::variables_map vm; + bool r = command_line::handle_error_helper(desc, [&]() + { + boost::program_options::store(boost::program_options::basic_parsed_options<char>(&desc), vm); + boost::program_options::notify(vm); + return true; + }); + if (!r) + return false; + + cryptonote::cryptonote_protocol_stub pr; //TODO: stub only for this kind of test, make real validation of relayed objects + cryptonote::core c(&pr); + if (!c.init(vm)) + { + std::cout << concolor::magenta << "Failed to init core" << concolor::normal << std::endl; + return false; + } + t_test_class validator; + return replay_events_through_core<t_test_class>(c, events, validator); +} +//-------------------------------------------------------------------------- +template<class t_test_class> +inline bool do_replay_file(const std::string& filename) +{ + std::vector<test_event_entry> events; + if (!tools::unserialize_obj_from_file(events, filename)) + { + std::cout << concolor::magenta << "Failed to deserialize data from file: " << filename << concolor::normal << std::endl; + return false; + } + return do_replay_events<t_test_class>(events); +} +//-------------------------------------------------------------------------- +#define GENERATE_ACCOUNT(account) \ + cryptonote::account_base account; \ + account.generate(); + +#define MAKE_ACCOUNT(VEC_EVENTS, account) \ + cryptonote::account_base account; \ + account.generate(); \ + VEC_EVENTS.push_back(account); + +#define DO_CALLBACK(VEC_EVENTS, CB_NAME) \ +{ \ + callback_entry CALLBACK_ENTRY; \ + CALLBACK_ENTRY.callback_name = CB_NAME; \ + VEC_EVENTS.push_back(CALLBACK_ENTRY); \ +} + +#define REGISTER_CALLBACK(CB_NAME, CLBACK) \ + register_callback(CB_NAME, boost::bind(&CLBACK, this, _1, _2, _3)); + +#define REGISTER_CALLBACK_METHOD(CLASS, METHOD) \ + register_callback(#METHOD, boost::bind(&CLASS::METHOD, this, _1, _2, _3)); + +#define MAKE_GENESIS_BLOCK(VEC_EVENTS, BLK_NAME, MINER_ACC, TS) \ + test_generator generator; \ + cryptonote::block BLK_NAME; \ + generator.construct_block(BLK_NAME, MINER_ACC, TS); \ + VEC_EVENTS.push_back(BLK_NAME); + +#define MAKE_NEXT_BLOCK(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) \ + cryptonote::block BLK_NAME; \ + generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC); \ + VEC_EVENTS.push_back(BLK_NAME); + +#define MAKE_NEXT_BLOCK_TX1(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, TX1) \ + cryptonote::block BLK_NAME; \ + { \ + std::list<cryptonote::transaction> tx_list; \ + tx_list.push_back(TX1); \ + generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC, tx_list); \ + } \ + VEC_EVENTS.push_back(BLK_NAME); + +#define MAKE_NEXT_BLOCK_TX_LIST(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, TXLIST) \ + cryptonote::block BLK_NAME; \ + generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC, TXLIST); \ + VEC_EVENTS.push_back(BLK_NAME); + +#define REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, COUNT) \ + cryptonote::block BLK_NAME; \ + { \ + cryptonote::block blk_last = PREV_BLOCK; \ + for (size_t i = 0; i < COUNT; ++i) \ + { \ + MAKE_NEXT_BLOCK(VEC_EVENTS, blk, blk_last, MINER_ACC); \ + blk_last = blk; \ + } \ + BLK_NAME = blk_last; \ + } + +#define REWIND_BLOCKS(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) + +#define MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ + cryptonote::transaction TX_NAME; \ + construct_tx_to_key(VEC_EVENTS, TX_NAME, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX); \ + VEC_EVENTS.push_back(TX_NAME); + +#define MAKE_TX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, HEAD) MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, 0, HEAD) + +#define MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ + { \ + cryptonote::transaction t; \ + construct_tx_to_key(VEC_EVENTS, t, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX); \ + SET_NAME.push_back(t); \ + VEC_EVENTS.push_back(t); \ + } + +#define MAKE_TX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD) MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, 0, HEAD) + +#define MAKE_TX_LIST_START(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD) \ + std::list<cryptonote::transaction> SET_NAME; \ + MAKE_TX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD); + +#define MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, KEY) \ + transaction TX; \ + if (!construct_miner_tx_manually(get_block_height(BLK) + 1, generator.get_already_generated_coins(BLK), \ + miner_account.get_keys().m_account_address, TX, 0, KEY)) \ + return false; + +#define MAKE_MINER_TX_MANUALLY(TX, BLK) MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, 0) + +#define SET_EVENT_VISITOR_SETT(VEC_EVENTS, SETT, VAL) VEC_EVENTS.push_back(event_visitor_settings(SETT, VAL)); + +#define GENERATE(filename, genclass) \ + { \ + std::vector<test_event_entry> events; \ + genclass g; \ + g.generate(events); \ + if (!tools::serialize_obj_to_file(events, filename)) \ + { \ + std::cout << concolor::magenta << "Failed to serialize data to file: " << filename << concolor::normal << std::endl; \ + throw std::runtime_error("Failed to serialize data to file"); \ + } \ + } + + +#define PLAY(filename, genclass) \ + if(!do_replay_file<genclass>(filename)) \ + { \ + std::cout << concolor::magenta << "Failed to pass test : " << #genclass << concolor::normal << std::endl; \ + return 1; \ + } + +#define GENERATE_AND_PLAY(genclass) \ + { \ + std::vector<test_event_entry> events; \ + ++tests_count; \ + bool generated = false; \ + try \ + { \ + genclass g; \ + generated = g.generate(events);; \ + } \ + catch (const std::exception& ex) \ + { \ + LOG_PRINT(#genclass << " generation failed: what=" << ex.what(), 0); \ + } \ + catch (...) \ + { \ + LOG_PRINT(#genclass << " generation failed: generic exception", 0); \ + } \ + if (generated && do_replay_events< genclass >(events)) \ + { \ + std::cout << concolor::green << "#TEST# Succeeded " << #genclass << concolor::normal << '\n'; \ + } \ + else \ + { \ + std::cout << concolor::magenta << "#TEST# Failed " << #genclass << concolor::normal << '\n'; \ + failed_tests.push_back(#genclass); \ + } \ + std::cout << std::endl; \ + } + +#define CALL_TEST(test_name, function) \ + { \ + if(!function()) \ + { \ + std::cout << concolor::magenta << "#TEST# Failed " << test_name << concolor::normal << std::endl; \ + return 1; \ + } \ + else \ + { \ + std::cout << concolor::green << "#TEST# Succeeded " << test_name << concolor::normal << std::endl; \ + } \ + } + +#define QUOTEME(x) #x +#define DEFINE_TESTS_ERROR_CONTEXT(text) const char* perr_context = text; +#define CHECK_TEST_CONDITION(cond) CHECK_AND_ASSERT_MES(cond, false, "[" << perr_context << "] failed: \"" << QUOTEME(cond) << "\"") +#define CHECK_EQ(v1, v2) CHECK_AND_ASSERT_MES(v1 == v2, false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " == " << QUOTEME(v2) << "\", " << v1 << " != " << v2) +#define CHECK_NOT_EQ(v1, v2) CHECK_AND_ASSERT_MES(!(v1 == v2), false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " != " << QUOTEME(v2) << "\", " << v1 << " == " << v2) +#define MK_COINS(amount) (UINT64_C(amount) * COIN) +#define TESTS_DEFAULT_FEE ((uint64_t)1000000) // pow(10, 6) diff --git a/tests/core_tests/chaingen001.cpp b/tests/core_tests/chaingen001.cpp new file mode 100644 index 000000000..f79420e69 --- /dev/null +++ b/tests/core_tests/chaingen001.cpp @@ -0,0 +1,135 @@ +// 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 <vector> +#include <iostream> + +#include "include_base_utils.h" + +#include "console_handler.h" + +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/cryptonote_format_utils.h" + +#include "chaingen.h" +#include "chaingen_tests_list.h" + +using namespace std; + +using namespace epee; +using namespace cryptonote; + +//////// +// class one_block; + +one_block::one_block() +{ + REGISTER_CALLBACK("verify_1", one_block::verify_1); +} + +bool one_block::generate(std::vector<test_event_entry> &events) +{ + uint64_t ts_start = 1338224400; + + MAKE_GENESIS_BLOCK(events, blk_0, alice, ts_start); + MAKE_ACCOUNT(events, alice); + DO_CALLBACK(events, "verify_1"); + + return true; +} + +bool one_block::verify_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + DEFINE_TESTS_ERROR_CONTEXT("one_block::verify_1"); + + alice = boost::get<cryptonote::account_base>(events[1]); + + // check balances + //std::vector<const cryptonote::block*> chain; + //map_hash2tx_t mtx; + //CHECK_TEST_CONDITION(find_block_chain(events, chain, mtx, get_block_hash(boost::get<cryptonote::block>(events[1])))); + //CHECK_TEST_CONDITION(get_block_reward(0) == get_balance(alice, events, chain, mtx)); + + // check height + std::list<cryptonote::block> blocks; + std::list<crypto::public_key> outs; + bool r = c.get_blocks(0, 100, blocks); + //c.get_outs(100, outs); + CHECK_TEST_CONDITION(r); + CHECK_TEST_CONDITION(blocks.size() == 1); + //CHECK_TEST_CONDITION(outs.size() == blocks.size()); + CHECK_TEST_CONDITION(c.get_blockchain_total_transactions() == 1); + CHECK_TEST_CONDITION(blocks.back() == boost::get<cryptonote::block>(events[0])); + + return true; +} + + +//////// +// class gen_simple_chain_001; + +gen_simple_chain_001::gen_simple_chain_001() +{ + REGISTER_CALLBACK("verify_callback_1", gen_simple_chain_001::verify_callback_1); + REGISTER_CALLBACK("verify_callback_2", gen_simple_chain_001::verify_callback_2); +} + +bool gen_simple_chain_001::generate(std::vector<test_event_entry> &events) +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner); + GENERATE_ACCOUNT(alice); + + MAKE_GENESIS_BLOCK(events, blk_0, miner, ts_start); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner); + MAKE_NEXT_BLOCK(events, blk_1_side, blk_0, miner); + MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner); + //MAKE_TX(events, tx_0, first_miner_account, alice, 151, blk_2); + + std::vector<cryptonote::block> chain; + map_hash2tx_t mtx; + /*bool r = */find_block_chain(events, chain, mtx, get_block_hash(boost::get<cryptonote::block>(events[3]))); + std::cout << "BALANCE = " << get_balance(miner, chain, mtx) << std::endl; + + REWIND_BLOCKS(events, blk_2r, blk_2, miner); + MAKE_TX_LIST_START(events, txlist_0, miner, alice, MK_COINS(1), blk_2); + MAKE_TX_LIST(events, txlist_0, miner, alice, MK_COINS(2), blk_2); + MAKE_TX_LIST(events, txlist_0, miner, alice, MK_COINS(4), blk_2); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner, txlist_0); + REWIND_BLOCKS(events, blk_3r, blk_3, miner); + MAKE_TX(events, tx_1, miner, alice, MK_COINS(50), blk_3); + MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3r, miner, tx_1); + REWIND_BLOCKS(events, blk_4r, blk_4, miner); + MAKE_TX(events, tx_2, miner, alice, MK_COINS(50), blk_4); + MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner, tx_2); + REWIND_BLOCKS(events, blk_5r, blk_5, miner); + MAKE_TX(events, tx_3, miner, alice, MK_COINS(50), blk_5); + MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5r, miner, tx_3); + + DO_CALLBACK(events, "verify_callback_1"); + //e.t.c. + //MAKE_BLOCK_TX1(events, blk_3, 3, get_block_hash(blk_0), get_test_target(), first_miner_account, ts_start + 10, tx_0); + //MAKE_BLOCK_TX1(events, blk_3, 3, get_block_hash(blk_0), get_test_target(), first_miner_account, ts_start + 10, tx_0); + //DO_CALLBACK(events, "verify_callback_2"); + +/* std::vector<const cryptonote::block*> chain; + map_hash2tx_t mtx; + if (!find_block_chain(events, chain, mtx, get_block_hash(blk_6))) + throw; + cout << "miner = " << get_balance(first_miner_account, events, chain, mtx) << endl; + cout << "alice = " << get_balance(alice, events, chain, mtx) << endl;*/ + + return true; +} + +bool gen_simple_chain_001::verify_callback_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + return true; +} + +bool gen_simple_chain_001::verify_callback_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + return true; +} diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp new file mode 100644 index 000000000..c575f9272 --- /dev/null +++ b/tests/core_tests/chaingen_main.cpp @@ -0,0 +1,171 @@ +// 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 "chaingen_tests_list.h" +#include "common/command_line.h" +#include "transaction_tests.h" + +namespace po = boost::program_options; + +namespace +{ + const command_line::arg_descriptor<std::string> arg_test_data_path = {"test_data_path", "", ""}; + const command_line::arg_descriptor<bool> arg_generate_test_data = {"generate_test_data", ""}; + const command_line::arg_descriptor<bool> arg_play_test_data = {"play_test_data", ""}; + const command_line::arg_descriptor<bool> arg_generate_and_play_test_data = {"generate_and_play_test_data", ""}; + const command_line::arg_descriptor<bool> arg_test_transactions = {"test_transactions", ""}; +} + +int main(int argc, char* argv[]) +{ + TRY_ENTRY(); + string_tools::set_module_name_and_folder(argv[0]); + + //set up logging options + log_space::get_set_log_detalisation_level(true, LOG_LEVEL_3); + log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_2); + + log_space::log_singletone::add_logger(LOGGER_FILE, + log_space::log_singletone::get_default_log_file().c_str(), + log_space::log_singletone::get_default_log_folder().c_str()); + + po::options_description desc_options("Allowed options"); + command_line::add_arg(desc_options, command_line::arg_help); + command_line::add_arg(desc_options, arg_test_data_path); + command_line::add_arg(desc_options, arg_generate_test_data); + command_line::add_arg(desc_options, arg_play_test_data); + command_line::add_arg(desc_options, arg_generate_and_play_test_data); + command_line::add_arg(desc_options, arg_test_transactions); + + po::variables_map vm; + bool r = command_line::handle_error_helper(desc_options, [&]() + { + po::store(po::parse_command_line(argc, argv, desc_options), vm); + po::notify(vm); + return true; + }); + if (!r) + return 1; + + if (command_line::get_arg(vm, command_line::arg_help)) + { + std::cout << desc_options << std::endl; + return 0; + } + + size_t tests_count = 0; + std::vector<std::string> failed_tests; + std::string tests_folder = command_line::get_arg(vm, arg_test_data_path); + if (command_line::get_arg(vm, arg_generate_test_data)) + { + GENERATE("chain001.dat", gen_simple_chain_001); + } + else if (command_line::get_arg(vm, arg_play_test_data)) + { + PLAY("chain001.dat", gen_simple_chain_001); + } + else if (command_line::get_arg(vm, arg_generate_and_play_test_data)) + { + GENERATE_AND_PLAY(gen_simple_chain_001); + GENERATE_AND_PLAY(gen_simple_chain_split_1); + GENERATE_AND_PLAY(one_block); + GENERATE_AND_PLAY(gen_chain_switch_1); + GENERATE_AND_PLAY(gen_ring_signature_1); + GENERATE_AND_PLAY(gen_ring_signature_2); + //GENERATE_AND_PLAY(gen_ring_signature_big); // Takes up to XXX hours (if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 10) + + // Block verification tests + GENERATE_AND_PLAY(gen_block_big_major_version); + GENERATE_AND_PLAY(gen_block_big_minor_version); + GENERATE_AND_PLAY(gen_block_ts_not_checked); + GENERATE_AND_PLAY(gen_block_ts_in_past); + GENERATE_AND_PLAY(gen_block_ts_in_future); + GENERATE_AND_PLAY(gen_block_invalid_prev_id); + GENERATE_AND_PLAY(gen_block_invalid_nonce); + GENERATE_AND_PLAY(gen_block_no_miner_tx); + GENERATE_AND_PLAY(gen_block_unlock_time_is_low); + GENERATE_AND_PLAY(gen_block_unlock_time_is_high); + GENERATE_AND_PLAY(gen_block_unlock_time_is_timestamp_in_past); + GENERATE_AND_PLAY(gen_block_unlock_time_is_timestamp_in_future); + GENERATE_AND_PLAY(gen_block_height_is_low); + GENERATE_AND_PLAY(gen_block_height_is_high); + GENERATE_AND_PLAY(gen_block_miner_tx_has_2_tx_gen_in); + GENERATE_AND_PLAY(gen_block_miner_tx_has_2_in); + GENERATE_AND_PLAY(gen_block_miner_tx_with_txin_to_key); + GENERATE_AND_PLAY(gen_block_miner_tx_out_is_small); + GENERATE_AND_PLAY(gen_block_miner_tx_out_is_big); + GENERATE_AND_PLAY(gen_block_miner_tx_has_no_out); + GENERATE_AND_PLAY(gen_block_miner_tx_has_out_to_alice); + GENERATE_AND_PLAY(gen_block_has_invalid_tx); + GENERATE_AND_PLAY(gen_block_is_too_big); + GENERATE_AND_PLAY(gen_block_invalid_binary_format); // Takes up to 3 hours, if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 500, up to 30 minutes, if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 10 + + // Transaction verification tests + GENERATE_AND_PLAY(gen_tx_big_version); + GENERATE_AND_PLAY(gen_tx_unlock_time); + GENERATE_AND_PLAY(gen_tx_input_is_not_txin_to_key); + GENERATE_AND_PLAY(gen_tx_no_inputs_no_outputs); + GENERATE_AND_PLAY(gen_tx_no_inputs_has_outputs); + GENERATE_AND_PLAY(gen_tx_has_inputs_no_outputs); + GENERATE_AND_PLAY(gen_tx_invalid_input_amount); + GENERATE_AND_PLAY(gen_tx_input_wo_key_offsets); + GENERATE_AND_PLAY(gen_tx_sender_key_offest_not_exist); + GENERATE_AND_PLAY(gen_tx_key_offest_points_to_foreign_key); + GENERATE_AND_PLAY(gen_tx_mixed_key_offest_not_exist); + GENERATE_AND_PLAY(gen_tx_key_image_not_derive_from_tx_key); + GENERATE_AND_PLAY(gen_tx_key_image_is_invalid); + GENERATE_AND_PLAY(gen_tx_check_input_unlock_time); + GENERATE_AND_PLAY(gen_tx_txout_to_key_has_invalid_key); + GENERATE_AND_PLAY(gen_tx_output_with_zero_amount); + GENERATE_AND_PLAY(gen_tx_output_is_not_txout_to_key); + GENERATE_AND_PLAY(gen_tx_signatures_are_invalid); + + // Double spend + GENERATE_AND_PLAY(gen_double_spend_in_tx<false>); + GENERATE_AND_PLAY(gen_double_spend_in_tx<true>); + GENERATE_AND_PLAY(gen_double_spend_in_the_same_block<false>); + GENERATE_AND_PLAY(gen_double_spend_in_the_same_block<true>); + GENERATE_AND_PLAY(gen_double_spend_in_different_blocks<false>); + GENERATE_AND_PLAY(gen_double_spend_in_different_blocks<true>); + GENERATE_AND_PLAY(gen_double_spend_in_different_chains); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_the_same_block<false>); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_the_same_block<true>); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks<false>); + GENERATE_AND_PLAY(gen_double_spend_in_alt_chain_in_different_blocks<true>); + + GENERATE_AND_PLAY(gen_uint_overflow_1); + GENERATE_AND_PLAY(gen_uint_overflow_2); + + GENERATE_AND_PLAY(gen_block_reward); + + std::cout << (failed_tests.empty() ? concolor::green : concolor::magenta); + std::cout << "\nREPORT:\n"; + std::cout << " Test run: " << tests_count << '\n'; + std::cout << " Failures: " << failed_tests.size() << '\n'; + if (!failed_tests.empty()) + { + std::cout << "FAILED TESTS:\n"; + BOOST_FOREACH(auto test_name, failed_tests) + { + std::cout << " " << test_name << '\n'; + } + } + std::cout << concolor::normal << std::endl; + } + else if (command_line::get_arg(vm, arg_test_transactions)) + { + CALL_TEST("TRANSACTIONS TESTS", test_transactions); + } + else + { + std::cout << concolor::magenta << "Wrong arguments" << concolor::normal << std::endl; + std::cout << desc_options << std::endl; + return 2; + } + + return failed_tests.empty() ? 0 : 1; + + CATCH_ENTRY_L0("main", 1); +} diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h new file mode 100644 index 000000000..d56f78121 --- /dev/null +++ b/tests/core_tests/chaingen_tests_list.h @@ -0,0 +1,35 @@ +// 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. + +#pragma once + +#include "chaingen.h" +#include "block_reward.h" +#include "block_validation.h" +#include "chain_split_1.h" +#include "chain_switch_1.h" +#include "double_spend.h" +#include "integer_overflow.h" +#include "ring_signature_1.h" +#include "tx_validation.h" +/************************************************************************/ +/* */ +/************************************************************************/ +class gen_simple_chain_001: public test_chain_unit_base +{ +public: + gen_simple_chain_001(); + bool generate(std::vector<test_event_entry> &events); + bool verify_callback_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); + bool verify_callback_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); +}; + +class one_block: public test_chain_unit_base +{ + cryptonote::account_base alice; +public: + one_block(); + bool generate(std::vector<test_event_entry> &events); + bool verify_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events); +}; diff --git a/tests/core_tests/double_spend.cpp b/tests/core_tests/double_spend.cpp new file mode 100644 index 000000000..9a007ce26 --- /dev/null +++ b/tests/core_tests/double_spend.cpp @@ -0,0 +1,70 @@ +// 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 "chaingen_tests_list.h" + +using namespace epee; +using namespace cryptonote; + + +//====================================================================================================================== + +gen_double_spend_in_different_chains::gen_double_spend_in_different_chains() +{ + REGISTER_CALLBACK_METHOD(gen_double_spend_in_different_chains, check_double_spend); +} + +bool gen_double_spend_in_different_chains::generate(std::vector<test_event_entry>& events) const +{ + INIT_DOUBLE_SPEND_TEST(); + + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, true); + MAKE_TX(events, tx_1, bob_account, alice_account, send_amount / 2 - TESTS_DEFAULT_FEE, blk_1); + events.pop_back(); + MAKE_TX(events, tx_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + events.pop_back(); + + // Main chain + events.push_back(tx_1); + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_account, tx_1); + + // Alternative chain + events.push_back(tx_2); + MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_1r, miner_account, tx_2); + // Switch to alternative chain + MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_account); + CHECK_AND_NO_ASSERT_MES(expected_blockchain_height == get_block_height(blk_4) + 1, false, "expected_blockchain_height has invalid value"); + + DO_CALLBACK(events, "check_double_spend"); + + return true; +} + +bool gen_double_spend_in_different_chains::check_double_spend(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_double_spend_in_different_chains::check_double_spend"); + + std::list<block> block_list; + bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list); + CHECK_TEST_CONDITION(r); + + std::vector<block> blocks(block_list.begin(), block_list.end()); + CHECK_EQ(expected_blockchain_height, blocks.size()); + + CHECK_EQ(1, c.get_pool_transactions_count()); + CHECK_EQ(1, c.get_alternative_blocks_count()); + + cryptonote::account_base bob_account = boost::get<cryptonote::account_base>(events[1]); + cryptonote::account_base alice_account = boost::get<cryptonote::account_base>(events[2]); + + std::vector<cryptonote::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(bob_account, blocks, mtx)); + CHECK_EQ(send_amount - TESTS_DEFAULT_FEE, get_balance(alice_account, blocks, mtx)); + + return true; +} diff --git a/tests/core_tests/double_spend.h b/tests/core_tests/double_spend.h new file mode 100644 index 000000000..e43cc2ed3 --- /dev/null +++ b/tests/core_tests/double_spend.h @@ -0,0 +1,125 @@ +// 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. + +#pragma once +#include "chaingen.h" + +const size_t invalid_index_value = std::numeric_limits<size_t>::max(); + + +template<class concrete_test> +class gen_double_spend_base : public test_chain_unit_base +{ +public: + static const uint64_t send_amount = MK_COINS(17); + + gen_double_spend_base(); + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& tx); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& block); + + bool mark_last_valid_block(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool mark_invalid_tx(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool mark_invalid_block(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_double_spend(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + cryptonote::block m_last_valid_block; + size_t m_invalid_tx_index; + size_t m_invalid_block_index; +}; + + +template<bool txs_keeped_by_block> +struct gen_double_spend_in_tx : public gen_double_spend_base< gen_double_spend_in_tx<txs_keeped_by_block> > +{ + static const uint64_t send_amount = MK_COINS(17); + static const bool has_invalid_tx = true; + static const size_t expected_pool_txs_count = 0; + static const uint64_t expected_bob_balance = send_amount; + static const uint64_t expected_alice_balance = 0; + + bool generate(std::vector<test_event_entry>& events) const; +}; + + +template<bool txs_keeped_by_block> +struct gen_double_spend_in_the_same_block : public gen_double_spend_base< gen_double_spend_in_the_same_block<txs_keeped_by_block> > +{ + static const uint64_t send_amount = MK_COINS(17); + static const bool has_invalid_tx = !txs_keeped_by_block; + static const size_t expected_pool_txs_count = has_invalid_tx ? 1 : 2; + static const uint64_t expected_bob_balance = send_amount; + static const uint64_t expected_alice_balance = 0; + + bool generate(std::vector<test_event_entry>& events) const; +}; + + +template<bool txs_keeped_by_block> +struct gen_double_spend_in_different_blocks : public gen_double_spend_base< gen_double_spend_in_different_blocks<txs_keeped_by_block> > +{ + static const uint64_t send_amount = MK_COINS(17); + static const bool has_invalid_tx = !txs_keeped_by_block; + static const size_t expected_pool_txs_count = has_invalid_tx ? 0 : 1; + static const uint64_t expected_bob_balance = 0; + static const uint64_t expected_alice_balance = send_amount - TESTS_DEFAULT_FEE; + + bool generate(std::vector<test_event_entry>& events) const; +}; + + +template<bool txs_keeped_by_block> +struct gen_double_spend_in_alt_chain_in_the_same_block : public gen_double_spend_base< gen_double_spend_in_alt_chain_in_the_same_block<txs_keeped_by_block> > +{ + static const uint64_t send_amount = MK_COINS(17); + static const bool has_invalid_tx = !txs_keeped_by_block; + static const size_t expected_pool_txs_count = has_invalid_tx ? 1 : 2; + static const uint64_t expected_bob_balance = send_amount; + static const uint64_t expected_alice_balance = 0; + + bool generate(std::vector<test_event_entry>& events) const; +}; + + +template<bool txs_keeped_by_block> +struct gen_double_spend_in_alt_chain_in_different_blocks : public gen_double_spend_base< gen_double_spend_in_alt_chain_in_different_blocks<txs_keeped_by_block> > +{ + static const uint64_t send_amount = MK_COINS(17); + static const bool has_invalid_tx = !txs_keeped_by_block; + static const size_t expected_pool_txs_count = has_invalid_tx ? 1 : 2; + static const uint64_t expected_bob_balance = send_amount; + static const uint64_t expected_alice_balance = 0; + + bool generate(std::vector<test_event_entry>& events) const; +}; + + +class gen_double_spend_in_different_chains : public test_chain_unit_base +{ +public: + static const uint64_t send_amount = MK_COINS(17); + static const size_t expected_blockchain_height = 4 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + + gen_double_spend_in_different_chains(); + + bool generate(std::vector<test_event_entry>& events) const; + + bool check_double_spend(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); +}; + + +#define INIT_DOUBLE_SPEND_TEST() \ + uint64_t ts_start = 1338224400; \ + GENERATE_ACCOUNT(miner_account); \ + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); \ + MAKE_ACCOUNT(events, bob_account); \ + MAKE_ACCOUNT(events, alice_account); \ + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); \ + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); \ + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_0); \ + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + + +#include "double_spend.inl" diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl new file mode 100644 index 000000000..38873a8b4 --- /dev/null +++ b/tests/core_tests/double_spend.inl @@ -0,0 +1,254 @@ +#pragma once + +//====================================================================================================================== + +template<class concrete_test> +gen_double_spend_base<concrete_test>::gen_double_spend_base() + : m_invalid_tx_index(invalid_index_value) + , m_invalid_block_index(invalid_index_value) +{ + REGISTER_CALLBACK_METHOD(gen_double_spend_base<concrete_test>, mark_last_valid_block); + REGISTER_CALLBACK_METHOD(gen_double_spend_base<concrete_test>, mark_invalid_tx); + REGISTER_CALLBACK_METHOD(gen_double_spend_base<concrete_test>, mark_invalid_block); + REGISTER_CALLBACK_METHOD(gen_double_spend_base<concrete_test>, check_double_spend); +} + +template<class concrete_test> +bool gen_double_spend_base<concrete_test>::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) +{ + if (m_invalid_tx_index == event_idx) + return tvc.m_verifivation_failed; + else + return !tvc.m_verifivation_failed && tx_added; +} + +template<class concrete_test> +bool gen_double_spend_base<concrete_test>::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) +{ + if (m_invalid_block_index == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; +} + +template<class concrete_test> +bool gen_double_spend_base<concrete_test>::mark_last_valid_block(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/) +{ + std::list<cryptonote::block> block_list; + bool r = c.get_blocks(c.get_current_blockchain_height() - 1, 1, block_list); + CHECK_AND_ASSERT_MES(r, false, "core::get_blocks failed"); + m_last_valid_block = block_list.back(); + return true; +} + +template<class concrete_test> +bool gen_double_spend_base<concrete_test>::mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) +{ + m_invalid_tx_index = ev_index + 1; + return true; +} + +template<class concrete_test> +bool gen_double_spend_base<concrete_test>::mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) +{ + m_invalid_block_index = ev_index + 1; + return true; +} + +template<class concrete_test> +bool gen_double_spend_base<concrete_test>::check_double_spend(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_double_spend_base::check_double_spend"); + + if (concrete_test::has_invalid_tx) + { + CHECK_NOT_EQ(invalid_index_value, m_invalid_tx_index); + } + CHECK_NOT_EQ(invalid_index_value, m_invalid_block_index); + + std::list<cryptonote::block> block_list; + bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list); + CHECK_TEST_CONDITION(r); + CHECK_TEST_CONDITION(m_last_valid_block == block_list.back()); + + CHECK_EQ(concrete_test::expected_pool_txs_count, c.get_pool_transactions_count()); + + cryptonote::account_base bob_account = boost::get<cryptonote::account_base>(events[1]); + cryptonote::account_base alice_account = boost::get<cryptonote::account_base>(events[2]); + + std::vector<cryptonote::block> chain; + map_hash2tx_t mtx; + std::vector<cryptonote::block> blocks(block_list.begin(), block_list.end()); + r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); + CHECK_TEST_CONDITION(r); + CHECK_EQ(concrete_test::expected_bob_balance, get_balance(bob_account, blocks, mtx)); + CHECK_EQ(concrete_test::expected_alice_balance, get_balance(alice_account, blocks, mtx)); + + return true; +} + +//====================================================================================================================== + +template<bool txs_keeped_by_block> +bool gen_double_spend_in_tx<txs_keeped_by_block>::generate(std::vector<test_event_entry>& events) const +{ + INIT_DOUBLE_SPEND_TEST(); + DO_CALLBACK(events, "mark_last_valid_block"); + + std::vector<cryptonote::tx_source_entry> sources; + cryptonote::tx_source_entry se; + se.amount = tx_0.vout[0].amount; + se.outputs.push_back(std::make_pair(0, boost::get<cryptonote::txout_to_key>(tx_0.vout[0].target).key)); + se.real_output = 0; + se.real_out_tx_key = get_tx_pub_key_from_extra(tx_0); + se.real_output_in_tx_index = 0; + sources.push_back(se); + // Double spend! + sources.push_back(se); + + cryptonote::tx_destination_entry de; + de.addr = alice_account.get_keys().m_account_address; + de.amount = 2 * se.amount - TESTS_DEFAULT_FEE; + std::vector<cryptonote::tx_destination_entry> destinations; + destinations.push_back(de); + + cryptonote::transaction tx_1; + if (!construct_tx(bob_account.get_keys(), sources, destinations, tx_1, 0)) + return false; + + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(tx_1); + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_account, tx_1); + DO_CALLBACK(events, "check_double_spend"); + + return true; +} + +template<bool txs_keeped_by_block> +bool gen_double_spend_in_the_same_block<txs_keeped_by_block>::generate(std::vector<test_event_entry>& events) const +{ + INIT_DOUBLE_SPEND_TEST(); + + DO_CALLBACK(events, "mark_last_valid_block"); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + + MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + cryptonote::transaction tx_1 = txs_1.front(); + auto tx_1_idx = events.size() - 1; + // Remove tx_1, it is being inserted back a little later + events.pop_back(); + + if (has_invalid_tx) + { + DO_CALLBACK(events, "mark_invalid_tx"); + } + MAKE_TX_LIST(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + events.insert(events.begin() + tx_1_idx, tx_1); + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_1); + DO_CALLBACK(events, "check_double_spend"); + + return true; +} + +template<bool txs_keeped_by_block> +bool gen_double_spend_in_different_blocks<txs_keeped_by_block>::generate(std::vector<test_event_entry>& events) const +{ + INIT_DOUBLE_SPEND_TEST(); + + DO_CALLBACK(events, "mark_last_valid_block"); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + + // Create two identical transactions, but don't push it to events list + MAKE_TX(events, tx_blk_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + events.pop_back(); + MAKE_TX(events, tx_blk_3, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + events.pop_back(); + + events.push_back(tx_blk_2); + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_account, tx_blk_2); + DO_CALLBACK(events, "mark_last_valid_block"); + + if (has_invalid_tx) + { + DO_CALLBACK(events, "mark_invalid_tx"); + } + events.push_back(tx_blk_3); + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_account, tx_blk_3); + + DO_CALLBACK(events, "check_double_spend"); + + return true; +} + +template<bool txs_keeped_by_block> +bool gen_double_spend_in_alt_chain_in_the_same_block<txs_keeped_by_block>::generate(std::vector<test_event_entry>& events) const +{ + INIT_DOUBLE_SPEND_TEST(); + + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + + // Main chain + MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_account); + DO_CALLBACK(events, "mark_last_valid_block"); + + // Alt chain + MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + cryptonote::transaction tx_1 = txs_1.front(); + auto tx_1_idx = events.size() - 1; + // Remove tx_1, it is being inserted back a little later + events.pop_back(); + + if (has_invalid_tx) + { + DO_CALLBACK(events, "mark_invalid_tx"); + } + MAKE_TX_LIST(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + events.insert(events.begin() + tx_1_idx, tx_1); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_1r, miner_account, txs_1); + + // Try to switch to alternative chain + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_account); + + DO_CALLBACK(events, "check_double_spend"); + + return true; +} + +template<bool txs_keeped_by_block> +bool gen_double_spend_in_alt_chain_in_different_blocks<txs_keeped_by_block>::generate(std::vector<test_event_entry>& events) const +{ + INIT_DOUBLE_SPEND_TEST(); + + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + + // Main chain + MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_account); + DO_CALLBACK(events, "mark_last_valid_block"); + + // Alternative chain + MAKE_TX(events, tx_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + events.pop_back(); + MAKE_TX(events, tx_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); + events.pop_back(); + + events.push_back(tx_1); + MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_1r, miner_account, tx_1); + + // Try to switch to alternative chain + if (has_invalid_tx) + { + DO_CALLBACK(events, "mark_invalid_tx"); + } + events.push_back(tx_2); + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_account, tx_2); + + DO_CALLBACK(events, "check_double_spend"); + + return true; +} diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp new file mode 100644 index 000000000..82bfb2150 --- /dev/null +++ b/tests/core_tests/integer_overflow.cpp @@ -0,0 +1,184 @@ +// 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 "chaingen_tests_list.h" + +#include "integer_overflow.h" + +using namespace epee; +using namespace cryptonote; + +namespace +{ + void split_miner_tx_outs(transaction& miner_tx, uint64_t amount_1) + { + uint64_t total_amount = get_outs_money_amount(miner_tx); + uint64_t amount_2 = total_amount - amount_1; + txout_target_v target = miner_tx.vout[0].target; + + miner_tx.vout.clear(); + + tx_out out1; + out1.amount = amount_1; + out1.target = target; + miner_tx.vout.push_back(out1); + + tx_out out2; + out2.amount = amount_2; + out2.target = target; + miner_tx.vout.push_back(out2); + } + + void append_tx_source_entry(std::vector<cryptonote::tx_source_entry>& sources, const transaction& tx, size_t out_idx) + { + cryptonote::tx_source_entry se; + se.amount = tx.vout[out_idx].amount; + se.outputs.push_back(std::make_pair(0, boost::get<cryptonote::txout_to_key>(tx.vout[out_idx].target).key)); + se.real_output = 0; + se.real_out_tx_key = get_tx_pub_key_from_extra(tx); + se.real_output_in_tx_index = out_idx; + + sources.push_back(se); + } +} + +//====================================================================================================================== + +gen_uint_overflow_base::gen_uint_overflow_base() + : m_last_valid_block_event_idx(static_cast<size_t>(-1)) +{ + REGISTER_CALLBACK_METHOD(gen_uint_overflow_1, mark_last_valid_block); +} + +bool gen_uint_overflow_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) +{ + return m_last_valid_block_event_idx < event_idx ? !tx_added && tvc.m_verifivation_failed : tx_added && !tvc.m_verifivation_failed; +} + +bool gen_uint_overflow_base::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) +{ + return m_last_valid_block_event_idx < event_idx ? bvc.m_verifivation_failed | bvc.m_marked_as_orphaned : !bvc.m_verifivation_failed; +} + +bool gen_uint_overflow_base::mark_last_valid_block(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + m_last_valid_block_event_idx = ev_index - 1; + return true; +} + +//====================================================================================================================== + +bool gen_uint_overflow_1::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + DO_CALLBACK(events, "mark_last_valid_block"); + MAKE_ACCOUNT(events, bob_account); + MAKE_ACCOUNT(events, alice_account); + + // Problem 1. Miner tx output overflow + MAKE_MINER_TX_MANUALLY(miner_tx_0, blk_0); + split_miner_tx_outs(miner_tx_0, MONEY_SUPPLY); + block blk_1; + if (!generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx_0)) + return false; + events.push_back(blk_1); + + // Problem 1. Miner tx outputs overflow + MAKE_MINER_TX_MANUALLY(miner_tx_1, blk_1); + split_miner_tx_outs(miner_tx_1, MONEY_SUPPLY); + block blk_2; + if (!generator.construct_block_manually(blk_2, blk_1, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx_1)) + return false; + events.push_back(blk_2); + + REWIND_BLOCKS(events, blk_2r, blk_2, miner_account); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2); + MAKE_TX_LIST(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_0); + REWIND_BLOCKS(events, blk_3r, blk_3, miner_account); + + // Problem 2. total_fee overflow, block_reward overflow + std::list<cryptonote::transaction> txs_1; + // Create txs with huge fee + txs_1.push_back(construct_tx_with_fee(events, blk_3, bob_account, alice_account, MK_COINS(1), MONEY_SUPPLY - MK_COINS(1))); + txs_1.push_back(construct_tx_with_fee(events, blk_3, bob_account, alice_account, MK_COINS(1), MONEY_SUPPLY - MK_COINS(1))); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, miner_account, txs_1); + + return true; +} + +//====================================================================================================================== + +bool gen_uint_overflow_2::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + MAKE_ACCOUNT(events, bob_account); + MAKE_ACCOUNT(events, alice_account); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + DO_CALLBACK(events, "mark_last_valid_block"); + + // Problem 1. Regular tx outputs overflow + std::vector<cryptonote::tx_source_entry> sources; + for (size_t i = 0; i < blk_0.miner_tx.vout.size(); ++i) + { + if (TESTS_DEFAULT_FEE < blk_0.miner_tx.vout[i].amount) + { + append_tx_source_entry(sources, blk_0.miner_tx, i); + break; + } + } + if (sources.empty()) + { + return false; + } + + std::vector<cryptonote::tx_destination_entry> destinations; + const account_public_address& bob_addr = bob_account.get_keys().m_account_address; + destinations.push_back(tx_destination_entry(MONEY_SUPPLY, bob_addr)); + destinations.push_back(tx_destination_entry(MONEY_SUPPLY - 1, bob_addr)); + // sources.front().amount = destinations[0].amount + destinations[2].amount + destinations[3].amount + TESTS_DEFAULT_FEE + destinations.push_back(tx_destination_entry(sources.front().amount - MONEY_SUPPLY - MONEY_SUPPLY + 1 - TESTS_DEFAULT_FEE, bob_addr)); + + cryptonote::transaction tx_1; + if (!construct_tx(miner_account.get_keys(), sources, destinations, tx_1, 0)) + return false; + events.push_back(tx_1); + + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_1); + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + + // Problem 2. Regular tx inputs overflow + sources.clear(); + for (size_t i = 0; i < tx_1.vout.size(); ++i) + { + auto& tx_1_out = tx_1.vout[i]; + if (tx_1_out.amount < MONEY_SUPPLY - 1) + continue; + + append_tx_source_entry(sources, tx_1, i); + } + + destinations.clear(); + cryptonote::tx_destination_entry de; + de.addr = alice_account.get_keys().m_account_address; + de.amount = MONEY_SUPPLY - TESTS_DEFAULT_FEE; + destinations.push_back(de); + destinations.push_back(de); + + cryptonote::transaction tx_2; + if (!construct_tx(bob_account.get_keys(), sources, destinations, tx_2, 0)) + return false; + events.push_back(tx_2); + + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_account, tx_2); + + return true; +} diff --git a/tests/core_tests/integer_overflow.h b/tests/core_tests/integer_overflow.h new file mode 100644 index 000000000..0e00ada32 --- /dev/null +++ b/tests/core_tests/integer_overflow.h @@ -0,0 +1,29 @@ +// 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. + +#pragma once +#include "chaingen.h" + +struct gen_uint_overflow_base : public test_chain_unit_base +{ + gen_uint_overflow_base(); + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& tx); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& block); + + bool mark_last_valid_block(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + size_t m_last_valid_block_event_idx; +}; + +struct gen_uint_overflow_1 : public gen_uint_overflow_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_uint_overflow_2 : public gen_uint_overflow_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; diff --git a/tests/core_tests/ring_signature_1.cpp b/tests/core_tests/ring_signature_1.cpp new file mode 100644 index 000000000..bfdaf38f0 --- /dev/null +++ b/tests/core_tests/ring_signature_1.cpp @@ -0,0 +1,318 @@ +// 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 "chaingen_tests_list.h" + +using namespace epee; +using namespace cryptonote; + + +//////// +// class gen_ring_signature_1; + +gen_ring_signature_1::gen_ring_signature_1() +{ + REGISTER_CALLBACK("check_balances_1", gen_ring_signature_1::check_balances_1); + REGISTER_CALLBACK("check_balances_2", gen_ring_signature_1::check_balances_2); +} + +namespace +{ + // To be sure that miner tx outputs don't match any bob_account and some_accounts inputs + const uint64_t rnd_11 = 475921; + const uint64_t rnd_20 = 360934; + const uint64_t rnd_29 = 799665; +} + +bool gen_ring_signature_1::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + + // events + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); // 0 + MAKE_ACCOUNT(events, some_account_1); // 1 + MAKE_ACCOUNT(events, some_account_2); // 2 + MAKE_ACCOUNT(events, bob_account); // 3 + MAKE_ACCOUNT(events, alice_account); // 4 + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account); // 5 + MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 6 + MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_account); // 7 + MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_account); // 8 + REWIND_BLOCKS(events, blk_5, blk_4, miner_account); // <N blocks> + REWIND_BLOCKS(events, blk_5r, blk_5, miner_account); // <N blocks> + MAKE_TX_LIST_START(events, txs_blk_6, miner_account, bob_account, MK_COINS(1), blk_5); // 9 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(11) + rnd_11, blk_5); // 10 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(11) + rnd_11, blk_5); // 11 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(20) + rnd_20, blk_5); // 12 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5); // 13 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5); // 14 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5); // 15 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 16 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 17 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 18 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 19 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(20) + rnd_20, blk_5); // 20 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_2, MK_COINS(20) + rnd_20, blk_5); // 21 + 2N + MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5r, miner_account, txs_blk_6); // 22 + 2N + DO_CALLBACK(events, "check_balances_1"); // 23 + 2N + REWIND_BLOCKS(events, blk_6r, blk_6, miner_account); // <N blocks> + // 129 = 11 + 11 + 20 + 29 + 29 + 29 + MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - TESTS_DEFAULT_FEE, 2, blk_6); // 24 + 3N + MAKE_NEXT_BLOCK_TX1(events, blk_7, blk_6r, miner_account, tx_0); // 25 + 3N + DO_CALLBACK(events, "check_balances_2"); // 26 + 3N + + return true; +} + +bool gen_ring_signature_1::check_balances_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_1::check_balances_1"); + + m_bob_account = boost::get<account_base>(events[3]); + m_alice_account = boost::get<account_base>(events[4]); + + std::list<block> blocks; + bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + CHECK_TEST_CONDITION(r); + + std::vector<cryptonote::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_COINS(130) + 2 * rnd_11 + rnd_20 + 3 * rnd_29, get_balance(m_bob_account, chain, mtx)); + CHECK_EQ(0, get_balance(m_alice_account, chain, mtx)); + + return true; +} + +bool gen_ring_signature_1::check_balances_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_1::check_balances_2"); + + std::list<block> blocks; + bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + CHECK_TEST_CONDITION(r); + + std::vector<cryptonote::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_COINS(1), get_balance(m_bob_account, chain, mtx)); + CHECK_EQ(MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - TESTS_DEFAULT_FEE, get_balance(m_alice_account, chain, mtx)); + + return true; +} + + +//////// +// class gen_ring_signature_2; + +gen_ring_signature_2::gen_ring_signature_2() +{ + REGISTER_CALLBACK("check_balances_1", gen_ring_signature_2::check_balances_1); + REGISTER_CALLBACK("check_balances_2", gen_ring_signature_2::check_balances_2); +} + +/** + * Bob has 4 inputs by 61 coins. He sends 4 * 61 coins to Alice, using ring signature with nmix = 3. Each Bob's input + * is used as mix for 3 others. + */ +bool gen_ring_signature_2::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + + // 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(events, txs_blk_4, miner_account, bob_account, MK_COINS(61), blk_3); // 6 + N + MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(61), blk_3); // 7 + N + MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(61), blk_3); // 8 + N + MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(61), blk_3); // 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); // <N blocks> + MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(244) - TESTS_DEFAULT_FEE, 3, blk_4); // 12 + 2N + MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_account, tx_0); // 13 + 2N + DO_CALLBACK(events, "check_balances_2"); // 14 + 2N + + return true; +} + +bool gen_ring_signature_2::check_balances_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_2::check_balances_1"); + + m_bob_account = boost::get<account_base>(events[1]); + m_alice_account = boost::get<account_base>(events[2]); + + std::list<block> blocks; + bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + CHECK_TEST_CONDITION(r); + + std::vector<cryptonote::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_COINS(244), get_balance(m_bob_account, chain, mtx)); + CHECK_EQ(0, get_balance(m_alice_account, chain, mtx)); + + return true; +} + +bool gen_ring_signature_2::check_balances_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_2::check_balances_2"); + + std::list<block> blocks; + bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + CHECK_TEST_CONDITION(r); + + std::vector<cryptonote::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_COINS(244) - TESTS_DEFAULT_FEE, get_balance(m_alice_account, chain, mtx)); + + return true; +} + + +//////// +// class gen_ring_signature_big; + +gen_ring_signature_big::gen_ring_signature_big() + : m_test_size(100) + , m_tx_amount(MK_COINS(29)) +{ + REGISTER_CALLBACK("check_balances_1", gen_ring_signature_big::check_balances_1); + REGISTER_CALLBACK("check_balances_2", gen_ring_signature_big::check_balances_2); +} + +/** + * Check ring signature with m_test_size-1 sources. + * - Create 100 accounts. + * - Create 100 blocks, each block contains transaction from the miner to account[i]. + * - Create transaction with ring signature from account[99] to Alice with nmix = 99. + * - Check balances. + */ +bool gen_ring_signature_big::generate(std::vector<test_event_entry>& events) const +{ + std::vector<account_base> accounts(m_test_size); + std::vector<block> blocks; + blocks.reserve(m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + + uint64_t ts_start = 1338224400; + GENERATE_ACCOUNT(miner_account); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + + for (size_t i = 0; i < m_test_size; ++i) + { + MAKE_ACCOUNT(events, an_account); + accounts[i] = an_account; + } + MAKE_ACCOUNT(events, alice_account); + + size_t blk_0r_idx = events.size(); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + blocks.push_back(blk_0); + for (size_t i = blk_0r_idx; i < events.size(); ++i) + { + blocks.push_back(boost::get<block>(events[i])); + } + + for (size_t i = 0; i < m_test_size; ++i) + { + block blk_with_unlocked_out = blocks[blocks.size() - 1 - CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW]; + MAKE_TX_LIST_START(events, txs_blk_i, miner_account, accounts[i], m_tx_amount, blk_with_unlocked_out); + for (size_t j = 0; j <= i; ++j) + { + MAKE_TX_LIST(events, txs_blk_i, miner_account, accounts[i], TESTS_DEFAULT_FEE, blk_with_unlocked_out); + } + MAKE_NEXT_BLOCK_TX_LIST(events, blk_i, blocks.back(), miner_account, txs_blk_i); + blocks.push_back(blk_i); + + std::vector<cryptonote::block> chain; + map_hash2tx_t mtx; + bool r = find_block_chain(events, chain, mtx, get_block_hash(blk_i)); + CHECK_AND_NO_ASSERT_MES(r, false, "failed to call find_block_chain"); + std::cout << i << ": " << get_balance(accounts[i], chain, mtx) << std::endl; + } + + DO_CALLBACK(events, "check_balances_1"); + MAKE_TX_MIX(events, tx_0, accounts[0], alice_account, m_tx_amount, m_test_size - 1, blocks.back()); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blocks.back(), miner_account, tx_0); + DO_CALLBACK(events, "check_balances_2"); + + return true; +} + +bool gen_ring_signature_big::check_balances_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_big::check_balances_1"); + + m_bob_account = boost::get<account_base>(events[1]); + m_alice_account = boost::get<account_base>(events[1 + m_test_size]); + + std::list<block> blocks; + bool r = c.get_blocks(0, 2 * m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + CHECK_TEST_CONDITION(r); + + std::vector<cryptonote::block> chain; + map_hash2tx_t mtx; + r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back())); + CHECK_TEST_CONDITION(r); + CHECK_EQ(m_tx_amount + TESTS_DEFAULT_FEE, get_balance(m_bob_account, chain, mtx)); + CHECK_EQ(0, get_balance(m_alice_account, chain, mtx)); + + for (size_t i = 2; i < 1 + m_test_size; ++i) + { + const account_base& an_account = boost::get<account_base>(events[i]); + uint64_t balance = m_tx_amount + TESTS_DEFAULT_FEE * i; + CHECK_EQ(balance, get_balance(an_account, chain, mtx)); + } + + return true; +} + +bool gen_ring_signature_big::check_balances_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events) +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_big::check_balances_2"); + + std::list<block> blocks; + bool r = c.get_blocks(0, 2 * m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + CHECK_TEST_CONDITION(r); + + std::vector<cryptonote::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(m_tx_amount, get_balance(m_alice_account, chain, mtx)); + + for (size_t i = 2; i < 1 + m_test_size; ++i) + { + const account_base& an_account = boost::get<account_base>(events[i]); + uint64_t balance = m_tx_amount + TESTS_DEFAULT_FEE * i; + CHECK_EQ(balance, get_balance(an_account, chain, mtx)); + } + + std::vector<size_t> tx_outs; + uint64_t transfered; + lookup_acc_outs(m_alice_account.get_keys(), boost::get<transaction>(events[events.size() - 3]), get_tx_pub_key_from_extra(boost::get<transaction>(events[events.size() - 3])), tx_outs, transfered); + CHECK_EQ(m_tx_amount, transfered); + + return true; +} diff --git a/tests/core_tests/ring_signature_1.h b/tests/core_tests/ring_signature_1.h new file mode 100644 index 000000000..1d74f6802 --- /dev/null +++ b/tests/core_tests/ring_signature_1.h @@ -0,0 +1,65 @@ +// 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. + +#pragma once +#include "chaingen.h" + +/************************************************************************/ +/* */ +/************************************************************************/ +class gen_ring_signature_1 : public test_chain_unit_base +{ +public: + gen_ring_signature_1(); + + bool generate(std::vector<test_event_entry>& events) const; + + bool check_balances_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_balances_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + cryptonote::account_base m_bob_account; + cryptonote::account_base m_alice_account; +}; + + +/************************************************************************/ +/* */ +/************************************************************************/ +class gen_ring_signature_2 : public test_chain_unit_base +{ +public: + gen_ring_signature_2(); + + bool generate(std::vector<test_event_entry>& events) const; + + bool check_balances_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_balances_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + cryptonote::account_base m_bob_account; + cryptonote::account_base m_alice_account; +}; + + +/************************************************************************/ +/* */ +/************************************************************************/ +class gen_ring_signature_big : public test_chain_unit_base +{ +public: + gen_ring_signature_big(); + + bool generate(std::vector<test_event_entry>& events) const; + + bool check_balances_1(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_balances_2(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + +private: + size_t m_test_size; + uint64_t m_tx_amount; + + cryptonote::account_base m_bob_account; + cryptonote::account_base m_alice_account; +}; diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp new file mode 100644 index 000000000..4da9446cd --- /dev/null +++ b/tests/core_tests/transaction_tests.cpp @@ -0,0 +1,146 @@ +// 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 "include_base_utils.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/account.h" +#include "cryptonote_core/cryptonote_format_utils.h" + +using namespace cryptonote; + + + +bool test_transaction_generation_and_ring_signature() +{ + + account_base miner_acc1; + miner_acc1.generate(); + account_base miner_acc2; + miner_acc2.generate(); + account_base miner_acc3; + miner_acc3.generate(); + account_base miner_acc4; + miner_acc4.generate(); + account_base miner_acc5; + miner_acc5.generate(); + account_base miner_acc6; + miner_acc6.generate(); + + std::string add_str = miner_acc3.get_public_address_str(); + + + account_base rv_acc; + rv_acc.generate(); + account_base rv_acc2; + rv_acc2.generate(); + std::vector<size_t> b; + transaction tx_mine_1; + construct_miner_tx(0, 0, miner_acc1.get_keys().m_account_address, tx_mine_1, 0, b, 10); + transaction tx_mine_2; + construct_miner_tx(0, 0, miner_acc2.get_keys().m_account_address, tx_mine_2, 0, b, 0); + transaction tx_mine_3; + construct_miner_tx(0, 0, miner_acc3.get_keys().m_account_address, tx_mine_3, 0, b, 0); + transaction tx_mine_4; + construct_miner_tx(0, 0, miner_acc4.get_keys().m_account_address, tx_mine_4, 0, b, 0); + transaction tx_mine_5; + construct_miner_tx(0, 0, miner_acc5.get_keys().m_account_address, tx_mine_5, 0, b, 0); + transaction tx_mine_6; + construct_miner_tx(0, 0, miner_acc6.get_keys().m_account_address, tx_mine_6, 0, b, 0); + + //fill inputs entry + typedef tx_source_entry::output_entry tx_output_entry; + std::vector<tx_source_entry> sources; + sources.resize(sources.size()+1); + tx_source_entry& src = sources.back(); + src.amount = 70368744177663; + { + tx_output_entry oe; + oe.first = 0; + oe.second = boost::get<txout_to_key>(tx_mine_1.vout[0].target).key; + src.outputs.push_back(oe); + + oe.first = 1; + oe.second = boost::get<txout_to_key>(tx_mine_2.vout[0].target).key; + src.outputs.push_back(oe); + + oe.first = 2; + oe.second = boost::get<txout_to_key>(tx_mine_3.vout[0].target).key; + src.outputs.push_back(oe); + + oe.first = 3; + oe.second = boost::get<txout_to_key>(tx_mine_4.vout[0].target).key; + src.outputs.push_back(oe); + + oe.first = 4; + oe.second = boost::get<txout_to_key>(tx_mine_5.vout[0].target).key; + src.outputs.push_back(oe); + + oe.first = 5; + oe.second = boost::get<txout_to_key>(tx_mine_6.vout[0].target).key; + src.outputs.push_back(oe); + + crypto::public_key tx_pub_key = null_pkey; + cryptonote::parse_and_validate_tx_extra(tx_mine_2, tx_pub_key); + src.real_out_tx_key = tx_pub_key; + src.real_output = 1; + src.real_output_in_tx_index = 0; + } + //fill outputs entry + tx_destination_entry td; + td.addr = rv_acc.get_keys().m_account_address; + td.amount = 69368744177663; + std::vector<tx_destination_entry> destinations; + destinations.push_back(td); + + transaction tx_rc1; + bool r = construct_tx(miner_acc2.get_keys(), sources, destinations, tx_rc1, 0); + CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + + crypto::hash pref_hash = get_transaction_prefix_hash(tx_rc1); + std::vector<const crypto::public_key *> output_keys; + output_keys.push_back(&boost::get<txout_to_key>(tx_mine_1.vout[0].target).key); + output_keys.push_back(&boost::get<txout_to_key>(tx_mine_2.vout[0].target).key); + output_keys.push_back(&boost::get<txout_to_key>(tx_mine_3.vout[0].target).key); + output_keys.push_back(&boost::get<txout_to_key>(tx_mine_4.vout[0].target).key); + output_keys.push_back(&boost::get<txout_to_key>(tx_mine_5.vout[0].target).key); + output_keys.push_back(&boost::get<txout_to_key>(tx_mine_6.vout[0].target).key); + r = crypto::check_ring_signature(pref_hash, boost::get<txin_to_key>(tx_rc1.vin[0]).k_image, output_keys, &tx_rc1.signatures[0][0]); + CHECK_AND_ASSERT_MES(r, false, "failed to check ring signature"); + + std::vector<size_t> outs; + uint64_t money = 0; + + r = lookup_acc_outs(rv_acc.get_keys(), tx_rc1, get_tx_pub_key_from_extra(tx_rc1), outs, money); + CHECK_AND_ASSERT_MES(r, false, "failed to lookup_acc_outs"); + CHECK_AND_ASSERT_MES(td.amount == money, false, "wrong money amount in new transaction"); + money = 0; + r = lookup_acc_outs(rv_acc2.get_keys(), tx_rc1, get_tx_pub_key_from_extra(tx_rc1), outs, money); + CHECK_AND_ASSERT_MES(r, false, "failed to lookup_acc_outs"); + CHECK_AND_ASSERT_MES(0 == money, false, "wrong money amount in new transaction"); + return true; +} + +bool test_block_creation() +{ + uint64_t vszs[] = {80,476,476,475,475,474,475,474,474,475,472,476,476,475,475,474,475,474,474,475,472,476,476,475,475,474,475,474,474,475,9391,476,476,475,475,474,475,8819,8301,475,472,4302,5316,14347,16620,19583,19403,19728,19442,19852,19015,19000,19016,19795,19749,18087,19787,19704,19750,19267,19006,19050,19445,19407,19522,19546,19788,19369,19486,19329,19370,18853,19600,19110,19320,19746,19474,19474,19743,19494,19755,19715,19769,19620,19368,19839,19532,23424,28287,30707}; + std::vector<uint64_t> szs(&vszs[0], &vszs[90]); + account_public_address adr; + bool r = get_account_address_from_str(adr, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2"); + CHECK_AND_ASSERT_MES(r, false, "failed to import"); + block b; + r = construct_miner_tx(90, 3553616528562147, adr, b.miner_tx, 10000000, szs, 33094, 11); + return r; +} + +bool test_transactions() +{ + if(!test_transaction_generation_and_ring_signature()) + return false; + if(!test_block_creation()) + return false; + + + return true; +} diff --git a/tests/core_tests/transaction_tests.h b/tests/core_tests/transaction_tests.h new file mode 100644 index 000000000..460192802 --- /dev/null +++ b/tests/core_tests/transaction_tests.h @@ -0,0 +1,10 @@ +// 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. + +#pragma once + + + +bool test_transactions(); +bool test_block_creation(); diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp new file mode 100644 index 000000000..93cb68c95 --- /dev/null +++ b/tests/core_tests/tx_validation.cpp @@ -0,0 +1,746 @@ +// 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 "chaingen_tests_list.h" + +using namespace epee; +using namespace crypto; +using namespace cryptonote; + +namespace +{ + struct tx_builder + { + void step1_init(size_t version = CURRENT_TRANSACTION_VERSION, uint64_t unlock_time = 0) + { + m_tx.vin.clear(); + m_tx.vout.clear(); + m_tx.signatures.clear(); + + m_tx.version = version; + m_tx.unlock_time = unlock_time; + + m_tx_key = keypair::generate(); + add_tx_pub_key_to_extra(m_tx, m_tx_key.pub); + } + + void step2_fill_inputs(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources) + { + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + { + m_in_contexts.push_back(keypair()); + keypair& in_ephemeral = m_in_contexts.back(); + crypto::key_image img; + generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img); + + // put key image into tx input + txin_to_key input_to_key; + input_to_key.amount = src_entr.amount; + input_to_key.k_image = img; + + // fill outputs array and use relative offsets + BOOST_FOREACH(const tx_source_entry::output_entry& out_entry, src_entr.outputs) + input_to_key.key_offsets.push_back(out_entry.first); + + input_to_key.key_offsets = absolute_output_offsets_to_relative(input_to_key.key_offsets); + m_tx.vin.push_back(input_to_key); + } + } + + void step3_fill_outputs(const std::vector<tx_destination_entry>& destinations) + { + size_t output_index = 0; + BOOST_FOREACH(const tx_destination_entry& dst_entr, destinations) + { + crypto::key_derivation derivation; + crypto::public_key out_eph_public_key; + crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, m_tx_key.sec, derivation); + crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); + + tx_out out; + out.amount = dst_entr.amount; + txout_to_key tk; + tk.key = out_eph_public_key; + out.target = tk; + m_tx.vout.push_back(out); + output_index++; + } + } + + void step4_calc_hash() + { + get_transaction_prefix_hash(m_tx, m_tx_prefix_hash); + } + + void step5_sign(const std::vector<tx_source_entry>& sources) + { + m_tx.signatures.clear(); + + size_t i = 0; + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + { + std::vector<const crypto::public_key*> keys_ptrs; + BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) + { + keys_ptrs.push_back(&o.second); + } + + m_tx.signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = m_tx.signatures.back(); + sigs.resize(src_entr.outputs.size()); + generate_ring_signature(m_tx_prefix_hash, boost::get<txin_to_key>(m_tx.vin[i]).k_image, keys_ptrs, m_in_contexts[i].sec, src_entr.real_output, sigs.data()); + i++; + } + } + + transaction m_tx; + keypair m_tx_key; + std::vector<keypair> m_in_contexts; + crypto::hash m_tx_prefix_hash; + }; + + transaction make_simple_tx_with_unlock_time(const std::vector<test_event_entry>& events, + const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, + uint64_t amount, uint64_t unlock_time) + { + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_head, from, to, amount, TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(CURRENT_TRANSACTION_VERSION, unlock_time); + builder.step2_fill_inputs(from.get_keys(), sources); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources); + return builder.m_tx; + }; + + crypto::public_key generate_invalid_pub_key() + { + for (int i = 0; i <= 0xFF; ++i) + { + crypto::public_key key; + memset(&key, i, sizeof(crypto::public_key)); + if (!crypto::check_key(key)) + { + return key; + } + } + + throw std::runtime_error("invalid public key wasn't found"); + return crypto::public_key(); + } +} + +//---------------------------------------------------------------------------------------------------------------------- +// Tests + +bool gen_tx_big_version::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(CURRENT_TRANSACTION_VERSION + 1, 0); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_unlock_time::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS_N(events, blk_1, blk_0, miner_account, 10); + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + + auto make_tx_with_unlock_time = [&](uint64_t unlock_time) -> transaction + { + return make_simple_tx_with_unlock_time(events, blk_1, miner_account, miner_account, MK_COINS(1), unlock_time); + }; + + std::list<transaction> txs_0; + + txs_0.push_back(make_tx_with_unlock_time(0)); + events.push_back(txs_0.back()); + + txs_0.push_back(make_tx_with_unlock_time(get_block_height(blk_1r) - 1)); + events.push_back(txs_0.back()); + + txs_0.push_back(make_tx_with_unlock_time(get_block_height(blk_1r))); + events.push_back(txs_0.back()); + + txs_0.push_back(make_tx_with_unlock_time(get_block_height(blk_1r) + 1)); + events.push_back(txs_0.back()); + + txs_0.push_back(make_tx_with_unlock_time(get_block_height(blk_1r) + 2)); + events.push_back(txs_0.back()); + + txs_0.push_back(make_tx_with_unlock_time(ts_start - 1)); + events.push_back(txs_0.back()); + + txs_0.push_back(make_tx_with_unlock_time(time(0) + 60 * 60)); + events.push_back(txs_0.back()); + + MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); + + return true; +} + +bool gen_tx_input_is_not_txin_to_key::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + MAKE_NEXT_BLOCK(events, blk_tmp, blk_0r, miner_account); + events.pop_back(); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(blk_tmp.miner_tx); + + auto make_tx_with_input = [&](const txin_v& tx_input) -> transaction + { + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.m_tx.vin.push_back(tx_input); + builder.step3_fill_outputs(destinations); + return builder.m_tx; + }; + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_input(txin_to_script())); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_input(txin_to_scripthash())); + + return true; +} + +bool gen_tx_no_inputs_no_outputs::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + + tx_builder builder; + builder.step1_init(); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_no_inputs_has_outputs::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step3_fill_outputs(destinations); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_has_inputs_no_outputs::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + destinations.clear(); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources); + + events.push_back(builder.m_tx); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, builder.m_tx); + + return true; +} + +bool gen_tx_invalid_input_amount::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + sources.front().amount++; + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_input_wo_key_offsets::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + builder.step3_fill_outputs(destinations); + txin_to_key& in_to_key = boost::get<txin_to_key>(builder.m_tx.vin.front()); + uint64_t key_offset = in_to_key.key_offsets.front(); + in_to_key.key_offsets.pop_back(); + CHECK_AND_ASSERT_MES(in_to_key.key_offsets.empty(), false, "txin contained more than one key_offset"); + builder.step4_calc_hash(); + in_to_key.key_offsets.push_back(key_offset); + builder.step5_sign(sources); + in_to_key.key_offsets.pop_back(); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_key_offest_points_to_foreign_key::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account); + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + MAKE_ACCOUNT(events, alice_account); + MAKE_ACCOUNT(events, bob_account); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(60) + 1, blk_1); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(60) + 1, blk_1); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); + + std::vector<tx_source_entry> sources_bob; + std::vector<tx_destination_entry> destinations_bob; + fill_tx_sources_and_destinations(events, blk_2, bob_account, miner_account, MK_COINS(60) + 1 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, 0, sources_bob, destinations_bob); + + std::vector<tx_source_entry> sources_alice; + std::vector<tx_destination_entry> destinations_alice; + fill_tx_sources_and_destinations(events, blk_2, alice_account, miner_account, MK_COINS(60) + 1 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, 0, sources_alice, destinations_alice); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(bob_account.get_keys(), sources_bob); + txin_to_key& in_to_key = boost::get<txin_to_key>(builder.m_tx.vin.front()); + in_to_key.key_offsets.front() = sources_alice.front().outputs.front().first; + builder.step3_fill_outputs(destinations_bob); + builder.step4_calc_hash(); + builder.step5_sign(sources_bob); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_sender_key_offest_not_exist::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + txin_to_key& in_to_key = boost::get<txin_to_key>(builder.m_tx.vin.front()); + in_to_key.key_offsets.front() = std::numeric_limits<uint64_t>::max(); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_mixed_key_offest_not_exist::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account); + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + MAKE_ACCOUNT(events, alice_account); + MAKE_ACCOUNT(events, bob_account); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_2, bob_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 1, sources, destinations); + + sources.front().outputs[(sources.front().real_output + 1) % 2].first = std::numeric_limits<uint64_t>::max(); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(bob_account.get_keys(), sources); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_key_image_not_derive_from_tx_key::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + + txin_to_key& in_to_key = boost::get<txin_to_key>(builder.m_tx.vin.front()); + keypair kp = keypair::generate(); + key_image another_ki; + crypto::generate_key_image(kp.pub, kp.sec, another_ki); + in_to_key.k_image = another_ki; + + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + + // Tx with invalid key image can't be subscribed, so create empty signature + builder.m_tx.signatures.resize(1); + builder.m_tx.signatures[0].resize(1); + builder.m_tx.signatures[0][0] = boost::value_initialized<crypto::signature>(); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_key_image_is_invalid::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + + txin_to_key& in_to_key = boost::get<txin_to_key>(builder.m_tx.vin.front()); + crypto::public_key pub = generate_invalid_pub_key(); + memcpy(&in_to_key.k_image, &pub, sizeof(crypto::ec_point)); + + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + + // Tx with invalid key image can't be subscribed, so create empty signature + builder.m_tx.signatures.resize(1); + builder.m_tx.signatures[0].resize(1); + builder.m_tx.signatures[0][0] = boost::value_initialized<crypto::signature>(); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_check_input_unlock_time::generate(std::vector<test_event_entry>& events) const +{ + static const size_t tests_count = 6; + + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS_N(events, blk_1, blk_0, miner_account, tests_count - 1); + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + + std::array<account_base, tests_count> accounts; + for (size_t i = 0; i < tests_count; ++i) + { + MAKE_ACCOUNT(events, acc); + accounts[i] = acc; + } + + std::list<transaction> txs_0; + auto make_tx_to_acc = [&](size_t acc_idx, uint64_t unlock_time) + { + txs_0.push_back(make_simple_tx_with_unlock_time(events, blk_1, miner_account, accounts[acc_idx], + MK_COINS(1) + TESTS_DEFAULT_FEE, unlock_time)); + events.push_back(txs_0.back()); + }; + + uint64_t blk_3_height = get_block_height(blk_1r) + 2; + make_tx_to_acc(0, 0); + make_tx_to_acc(1, blk_3_height - 1); + make_tx_to_acc(2, blk_3_height); + make_tx_to_acc(3, blk_3_height + 1); + make_tx_to_acc(4, time(0) - 1); + make_tx_to_acc(5, time(0) + 60 * 60); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); + + std::list<transaction> txs_1; + auto make_tx_from_acc = [&](size_t acc_idx, bool invalid) + { + transaction tx = make_simple_tx_with_unlock_time(events, blk_2, accounts[acc_idx], miner_account, MK_COINS(1), 0); + if (invalid) + { + DO_CALLBACK(events, "mark_invalid_tx"); + } + else + { + txs_1.push_back(tx); + } + events.push_back(tx); + }; + + make_tx_from_acc(0, false); + make_tx_from_acc(1, false); + make_tx_from_acc(2, false); + make_tx_from_acc(3, true); + make_tx_from_acc(4, false); + make_tx_from_acc(5, true); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2, miner_account, txs_1); + + return true; +} + +bool gen_tx_txout_to_key_has_invalid_key::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + builder.step3_fill_outputs(destinations); + + txout_to_key& out_to_key = boost::get<txout_to_key>(builder.m_tx.vout.front().target); + out_to_key.key = generate_invalid_pub_key(); + + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_output_with_zero_amount::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + builder.step3_fill_outputs(destinations); + + builder.m_tx.vout.front().amount = 0; + + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_output_is_not_txout_to_key::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); + + std::vector<tx_source_entry> sources; + std::vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + + builder.m_tx.vout.push_back(tx_out()); + builder.m_tx.vout.back().amount = 1; + builder.m_tx.vout.back().target = txout_to_script(); + + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + builder.step1_init(); + builder.step2_fill_inputs(miner_account.get_keys(), sources); + + builder.m_tx.vout.push_back(tx_out()); + builder.m_tx.vout.back().amount = 1; + builder.m_tx.vout.back().target = txout_to_scripthash(); + + builder.step4_calc_hash(); + builder.step5_sign(sources); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(builder.m_tx); + + return true; +} + +bool gen_tx_signatures_are_invalid::generate(std::vector<test_event_entry>& events) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account); + REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); + MAKE_ACCOUNT(events, alice_account); + MAKE_ACCOUNT(events, bob_account); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); + + MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(60), blk_2); + events.pop_back(); + + MAKE_TX_MIX(events, tx_1, bob_account, miner_account, MK_COINS(1), 1, blk_2); + events.pop_back(); + + // Tx with nmix = 0 without signatures + DO_CALLBACK(events, "mark_invalid_tx"); + blobdata sr_tx = t_serializable_object_to_blob(static_cast<transaction_prefix>(tx_0)); + events.push_back(serialized_transaction(sr_tx)); + + // Tx with nmix = 0 have a few inputs, and not enough signatures + DO_CALLBACK(events, "mark_invalid_tx"); + sr_tx = t_serializable_object_to_blob(tx_0); + sr_tx.resize(sr_tx.size() - sizeof(crypto::signature)); + events.push_back(serialized_transaction(sr_tx)); + + // Tx with nmix = 0 have a few inputs, and too many signatures + DO_CALLBACK(events, "mark_invalid_tx"); + sr_tx = t_serializable_object_to_blob(tx_0); + sr_tx.insert(sr_tx.end(), sr_tx.end() - sizeof(crypto::signature), sr_tx.end()); + events.push_back(serialized_transaction(sr_tx)); + + // Tx with nmix = 1 without signatures + DO_CALLBACK(events, "mark_invalid_tx"); + sr_tx = t_serializable_object_to_blob(static_cast<transaction_prefix>(tx_1)); + events.push_back(serialized_transaction(sr_tx)); + + // Tx with nmix = 1 have not enough signatures + DO_CALLBACK(events, "mark_invalid_tx"); + sr_tx = t_serializable_object_to_blob(tx_1); + sr_tx.resize(sr_tx.size() - sizeof(crypto::signature)); + events.push_back(serialized_transaction(sr_tx)); + + // Tx with nmix = 1 have too many signatures + DO_CALLBACK(events, "mark_invalid_tx"); + sr_tx = t_serializable_object_to_blob(tx_1); + sr_tx.insert(sr_tx.end(), sr_tx.end() - sizeof(crypto::signature), sr_tx.end()); + events.push_back(serialized_transaction(sr_tx)); + + return true; +} diff --git a/tests/core_tests/tx_validation.h b/tests/core_tests/tx_validation.h new file mode 100644 index 000000000..e1939c049 --- /dev/null +++ b/tests/core_tests/tx_validation.h @@ -0,0 +1,139 @@ +// 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. + +#pragma once +#include "chaingen.h" + +struct get_tx_validation_base : public test_chain_unit_base +{ + get_tx_validation_base() + : m_invalid_tx_index(0) + , m_invalid_block_index(0) + { + REGISTER_CALLBACK_METHOD(get_tx_validation_base, mark_invalid_tx); + REGISTER_CALLBACK_METHOD(get_tx_validation_base, mark_invalid_block); + } + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) + { + if (m_invalid_tx_index == event_idx) + return tvc.m_verifivation_failed; + else + return !tvc.m_verifivation_failed && tx_added; + } + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) + { + if (m_invalid_block_index == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; + } + + bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_block_index = ev_index + 1; + return true; + } + + bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_tx_index = ev_index + 1; + return true; + } + +private: + size_t m_invalid_tx_index; + size_t m_invalid_block_index; +}; + +struct gen_tx_big_version : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_unlock_time : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_input_is_not_txin_to_key : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_no_inputs_no_outputs : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_no_inputs_has_outputs : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_has_inputs_no_outputs : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_invalid_input_amount : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_input_wo_key_offsets : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_key_offest_points_to_foreign_key : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_sender_key_offest_not_exist : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_mixed_key_offest_not_exist : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_key_image_not_derive_from_tx_key : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_key_image_is_invalid : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_check_input_unlock_time : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_txout_to_key_has_invalid_key : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_output_with_zero_amount : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_output_is_not_txout_to_key : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct gen_tx_signatures_are_invalid : public get_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; |