blockchain/tests/core_tests/tx_validation.cpp
Dmitry Matsiukhov 94c2e65dad
coretests: tx_coinbase_separate_sig_flag test added; (separate flag for PoW miner tx coinbase (#538))
* add tests for pow separate tx flag

* refactoring test, leave only poW validation
2025-07-19 15:49:10 +03:00

2865 lines
115 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "chaingen.h"
#include "tx_validation.h"
#include "offers_tests_common.h"
#include "tx_builder.h"
#include "chaingen_helpers.h"
#include "../../src/currency_core/tx_semantic_validation.h"
using namespace epee;
using namespace crypto;
using namespace currency;
//----------------------------------------------------------------------------------------------------------------------
// 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_0r, miner_account, miner_account, MK_TEST_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, 25);
REWIND_BLOCKS(events, blk_1r, blk_1, miner_account);
auto make_tx_with_unlock_time = [&](uint64_t unlock_time) -> transaction
{
return tx_builder::make_simple_tx_with_unlock_time(events, blk_1r, miner_account, miner_account, MK_TEST_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_0r, miner_account, miner_account, MK_TEST_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");
//fix script here
//events.push_back(make_tx_with_input(txin_to_script()));
DO_CALLBACK(events, "mark_invalid_tx");
//TODO: fix script here
//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);
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_0r, miner_account, miner_account, MK_TEST_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_0r, miner_account, miner_account, MK_TEST_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;
bool r = fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
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_0r, miner_account, miner_account, MK_TEST_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 = boost::get<uint64_t>(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_TEST_COINS(60) + 1, blk_1r);
MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_TEST_COINS(60) + 1, blk_1r);
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_TEST_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_TEST_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().out_reference;
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_0r, miner_account, miner_account, MK_TEST_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_TEST_COINS(1) + TESTS_DEFAULT_FEE, blk_1r);
MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_TEST_COINS(1) + TESTS_DEFAULT_FEE, blk_1r);
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_TEST_COINS(1), TESTS_DEFAULT_FEE, 1, sources, destinations);
sources.front().outputs[(sources.front().real_output + 1) % 2].out_reference = 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_0r, miner_account, miner_account, MK_TEST_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);
boost::get<currency::NLSAG_sig>(builder.m_tx.signatures[0]).s.resize(1);
boost::get<currency::NLSAG_sig>(builder.m_tx.signatures[0]).s[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_0r, miner_account, miner_account, MK_TEST_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 = tx_builder::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);
boost::get<currency::NLSAG_sig>(builder.m_tx.signatures[0]).s.resize(1);
boost::get<currency::NLSAG_sig>(builder.m_tx.signatures[0]).s[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(tx_builder::make_simple_tx_with_unlock_time(events, blk_1r, miner_account, accounts[acc_idx],
MK_TEST_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 + 10 + 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);
REWIND_BLOCKS(events, blk_3, blk_2, miner_account);
std::list<transaction> txs_1;
auto make_tx_from_acc = [&](size_t acc_idx, bool invalid)
{
transaction tx = tx_builder::make_simple_tx_with_unlock_time(events, blk_3, accounts[acc_idx], miner_account, MK_TEST_COINS(1), 0, false);
if (invalid)
{
DO_CALLBACK(events, "mark_invalid_tx");
}
else
{
txs_1.push_back(tx);
}
events.push_back(tx);
LOG_PRINT_L0("[gen_tx_check_input_unlock_time]make_tx_from_acc(" << acc_idx << ", " << invalid << ") as event[" << events.size() - 1 << "]");
};
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_4, blk_3, 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_0r, miner_account, miner_account, MK_TEST_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>(boost::get<tx_out_bare>(builder.m_tx.vout.front()).target);
out_to_key.key = tx_builder::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_0r, miner_account, miner_account, MK_TEST_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);
boost::get<tx_out_bare>(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);
return true;
// this test is not needed anymore, to be deleted
/*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_0r, miner_account, miner_account, MK_TEST_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;
//TODO: fix test
//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;
//TODO: fix test
//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;
*/
}
void check_broken_tx(std::vector<test_event_entry>& events, const transaction& broken_tx, const block& head_block, const account_base& miner_acc, test_generator& generator)
{
// 1. make sure broken_tx will be rejected by the tx_pool
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(broken_tx);
// 2. make sure it will be rejected by blockchain_storage even if it is accepted by the pool
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block | event_visitor_settings::set_skip_txs_blobsize_check, true, true)); // to avoid many checks in the pool
events.push_back(broken_tx);
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block | event_visitor_settings::set_skip_txs_blobsize_check, false, false));
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, broken_block, head_block, miner_acc, broken_tx);
// clear the pool of broken_tx
DO_CALLBACK(events, "clear_tx_pool");
}
bool gen_tx_signatures_are_invalid::generate(std::vector<test_event_entry>& events) const
{
GENERATE_ACCOUNT(preminer_account);
GENERATE_ACCOUNT(miner_account);
GENERATE_ACCOUNT(alice_account);
GENERATE_ACCOUNT(bob_account);
GENERATE_ACCOUNT(carol_account);
GENERATE_ACCOUNT(dan_account);
MAKE_GENESIS_BLOCK(events, blk_0, preminer_account, test_core_time::get_time());
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_account, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
uint64_t amount = TESTS_DEFAULT_FEE * 80;
// prepare needed outputs by transferring them to Carol and Dan
MAKE_TX(events, tx_a, miner_account, carol_account, amount / 2, blk_1r);
MAKE_NEXT_BLOCK_TX1(events, blk_1r_a, blk_1r, miner_account, tx_a);
MAKE_TX(events, tx_b, miner_account, carol_account, amount / 2, blk_1r_a);
MAKE_NEXT_BLOCK_TX1(events, blk_1r_b, blk_1r_a, miner_account, tx_b);
MAKE_TX(events, tx_c, miner_account, carol_account, amount / 2, blk_1r_b);
MAKE_NEXT_BLOCK_TX1(events, blk_1r_c, blk_1r_b, miner_account, tx_c);
MAKE_TX(events, tx_d, miner_account, dan_account, amount / 2, blk_1r_c);
MAKE_NEXT_BLOCK_TX1(events, blk_1r_d, blk_1r_c, miner_account, tx_d);
MAKE_TX(events, tx_e, miner_account, dan_account, amount / 2, blk_1r_d);
MAKE_NEXT_BLOCK_TX1(events, blk_1r_e, blk_1r_d, miner_account, tx_e);
MAKE_TX(events, tx_f, miner_account, dan_account, amount / 2, blk_1r_e);
MAKE_NEXT_BLOCK_TX1(events, blk_1r_f, blk_1r_e, miner_account, tx_f);
// create reference transaction tx_0, Carol->Alice, nmix=0
MAKE_TX(events, tx_0, carol_account, alice_account, amount, blk_1r_f);
events.pop_back();
CHECK_AND_ASSERT_MES(tx_0.vin.size() > 1 && tx_0.vout.size() > 1 && tx_0.signatures.size() > 1, false, "tx_0 is incorrect"); // need > 1 for this test
// create reference transaction tx_1, Dan->Alice, nmix=1
MAKE_TX_MIX(events, tx_1, dan_account, alice_account, amount, 1, blk_1r_f);
events.pop_back();
CHECK_AND_ASSERT_MES(tx_1.vin.size() > 1 && tx_1.vout.size() > 1 && tx_1.signatures.size() > 1, false, "tx_1 is incorrect"); // need > 1 for this test
const block& prev_block = blk_1r_f;
transaction broken_tx;
// Tx with nmix = 0 without signatures
broken_tx = tx_0;
broken_tx.signatures.clear();
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 0 have a few inputs, and not enough signatures
broken_tx = tx_0;
broken_tx.signatures.resize(broken_tx.signatures.size() - 1);
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 0 have a few inputs, and too many signatures (1/2)
broken_tx = tx_0;
boost::get<currency::NLSAG_sig>(broken_tx.signatures.back()).s.push_back(invalid_signature);
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 0 have a few inputs, and too many signatures (2/2)
broken_tx = tx_0;
broken_tx.signatures.push_back(currency::NLSAG_sig());
boost::get<currency::NLSAG_sig>(broken_tx.signatures.back()).s.push_back(invalid_signature);
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 1 without signatures
broken_tx = tx_1;
broken_tx.signatures.clear();
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 1 have not enough signatures (1/2)
broken_tx = tx_1;
broken_tx.signatures.resize(broken_tx.signatures.size() - 1);
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 1 have not enough signatures (2/2)
broken_tx = tx_1;
boost::get<currency::NLSAG_sig>(broken_tx.signatures.back()).s.resize(boost::get<currency::NLSAG_sig>(broken_tx.signatures.back()).s.size() - 1);
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 1 have too many signatures (1/2)
broken_tx = tx_1;
boost::get<currency::NLSAG_sig>(broken_tx.signatures.back()).s.push_back(invalid_signature);
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Tx with nmix = 1 have too many signatures (2/2)
broken_tx = tx_1;
broken_tx.signatures.push_back(currency::NLSAG_sig());
boost::get<currency::NLSAG_sig>(broken_tx.signatures.back()).s.push_back(invalid_signature);
check_broken_tx(events, broken_tx, prev_block, miner_account, generator);
// Finally check that the original transactions are accepted and passed
events.push_back(tx_0);
events.push_back(tx_1);
MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, prev_block, miner_account, std::list<transaction>({ tx_0, tx_1 }));
// spend all Alice's money to make sure she has successfully received it with 'tx'
MAKE_TX(events, tx_2, alice_account, bob_account, amount * 2 - TESTS_DEFAULT_FEE, blk_3);
MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_account, tx_2);
return true;
}
void fill_test_offer_(bc_services::offer_details& od)
{
od.offer_type = OFFER_TYPE_PRIMARY_TO_TARGET;
od.amount_primary = 1000000000;
od.amount_target = 22222222;
od.target = "USD";
od.location_country = "US";
od.location_city = "New York City";
od.contacts = "skype: zina; icq: 12313212; email: zina@zina.com; mobile: +621234567834";
od.comment = "The best ever rate in NYC!!!";
od.payment_types = "BTC;BANK;CASH";
od.expiration_time = 10;
}
bool gen_broken_attachments::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);
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account);
MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_account);
MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_account);
REWIND_BLOCKS(events, blk_5, blk_4, miner_account);
REWIND_BLOCKS_N(events, blk_5r, blk_5, miner_account, 20);
bc_services::offer_details od = AUTO_VAL_INIT(od);
fill_test_offer_(od);
std::vector<currency::attachment_v> attachments;
bc_services::put_offer_into_attachment(od, attachments);
std::list<currency::transaction> txs_set;
DO_CALLBACK(events, "mark_invalid_tx");
construct_broken_tx(txs_set, events, miner_account, miner_account, blk_5r, attachments, [](transaction& tx)
{
//don't put attachments info into
return true;
});
DO_CALLBACK(events, "mark_invalid_tx");
construct_broken_tx(txs_set, events, miner_account, miner_account, blk_5r, std::vector<currency::attachment_v>(), [&](transaction& tx)
{
extra_attachment_info eai = extra_attachment_info();
//put hash into extra
std::stringstream ss;
binary_archive<true> oar(ss);
bool r = ::do_serialize(oar, const_cast<std::vector<attachment_v>&>(attachments));
CHECK_AND_ASSERT_MES(r, false, "do_serialize failed");
std::string buff = ss.str();
eai.sz = buff.size();
eai.hsh = get_blob_hash(buff);
tx.extra.push_back(eai);
return true;
});
return true;
}
//------------------------------------------------------------------------------
gen_crypted_attachments::gen_crypted_attachments()
{
REGISTER_CALLBACK("check_crypted_tx", gen_crypted_attachments::check_crypted_tx);
REGISTER_CALLBACK("set_blockchain_height", gen_crypted_attachments::set_blockchain_height);
REGISTER_CALLBACK("set_crypted_tx_height", gen_crypted_attachments::set_crypted_tx_height);
REGISTER_CALLBACK("check_offers_count_befor_cancel", gen_crypted_attachments::check_offers_count_befor_cancel);
REGISTER_CALLBACK("check_offers_count_after_cancel", gen_crypted_attachments::check_offers_count_after_cancel);
m_hardforks.set_hardfork_height(1, 0);
m_hardforks.set_hardfork_height(2, 0); // tx_payer is allowed only after HF2
}
bool gen_crypted_attachments::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);
set_hard_fork_heights_to_generator(generator);
DO_CALLBACK(events, "configure_core");
MAKE_ACCOUNT(events, bob_account);
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account);
MAKE_NEXT_BLOCK(events, blk_4, blk_1, miner_account);
REWIND_BLOCKS_N_WITH_TIME(events, blk_5, blk_4, miner_account, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
DO_CALLBACK(events, "set_blockchain_height");
pr.acc_addr = miner_account.get_keys().account_address;
cm.comment = "Comandante Che Guevara";
//ms.msg = "Hasta Siempre, Comandante";
std::vector<currency::attachment_v> attachments;
attachments.push_back(pr);
attachments.push_back(cm);
//attachments.push_back(ms);
MAKE_TX_LIST_START(events, txs_list, miner_account, bob_account, MK_TEST_COINS(1), blk_5);
MAKE_TX_LIST_ATTACH(events, txs_list, miner_account, bob_account, MK_TEST_COINS(1), blk_5, attachments);
DO_CALLBACK(events, "set_crypted_tx_height");
MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5, miner_account, txs_list);
DO_CALLBACK(events, "check_crypted_tx");
MAKE_TX_LIST_START(events, txs_list2, miner_account, bob_account, MK_TEST_COINS(1), blk_6);
//create two offers
currency::transaction tx;
std::vector<currency::attachment_v> attachments2;
bc_services::offer_details od = AUTO_VAL_INIT(od);
fill_test_offer_(od);
bc_services::put_offer_into_attachment(od, attachments2);
bc_services::put_offer_into_attachment(od, attachments2);
construct_tx_to_key(m_hardforks, events, tx, blk_6, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, CURRENCY_TO_KEY_OUT_RELAXED, std::vector<currency::extra_v>(), attachments2);
txs_list2.push_back(tx);
events.push_back(tx);
MAKE_NEXT_BLOCK_TX_LIST(events, blk_7, blk_6, miner_account, txs_list2);
DO_CALLBACK(events, "check_offers_count_befor_cancel");
//create two cancel offers
MAKE_TX_LIST_START(events, txs_list3, miner_account, bob_account, MK_TEST_COINS(1), blk_7);
std::vector<currency::attachment_v> attachments3;
bc_services::cancel_offer co = AUTO_VAL_INIT(co);
co.tx_id = get_transaction_hash(tx);
co.offer_index = 0;
crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(tx);
crypto::public_key related_tx_offer_pub_key = bc_services::get_offer_secure_key_by_index_from_tx(tx, 0);
currency::keypair ephemeral;
bool r = currency::derive_ephemeral_key_helper(miner_account.get_keys(), related_tx_pub_key, 0, ephemeral);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to derive_ephemeral_key_helper");
CHECK_AND_ASSERT_THROW_MES(ephemeral.pub == related_tx_offer_pub_key, "Failed to derive_ephemeral_key_helper(pub keys missmatch)");
blobdata bl_for_sig = bc_services::make_offer_sig_blob(co);
crypto::generate_signature(crypto::cn_fast_hash(bl_for_sig.data(), bl_for_sig.size()), ephemeral.pub, ephemeral.sec, co.sig);
// blobdata bl_for_sig = currency::make_offer_sig_blob(co);
// crypto::generate_signature(crypto::cn_fast_hash(bl_for_sig.data(), bl_for_sig.size()),
// currency::get_tx_pub_key_from_extra(tx),
// off_key_pair.sec, co.sig);
bc_services::put_offer_into_attachment(co, attachments3);
co.offer_index = 1;
crypto::public_key related_tx_pub_key_1 = get_tx_pub_key_from_extra(tx);
crypto::public_key related_tx_offer_pub_key_1 = bc_services::get_offer_secure_key_by_index_from_tx(tx, 1);
currency::keypair ephemeral_1;
r = currency::derive_ephemeral_key_helper(miner_account.get_keys(), related_tx_pub_key_1, 1, ephemeral_1);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to derive_ephemeral_key_helper");
CHECK_AND_ASSERT_THROW_MES(ephemeral_1.pub == related_tx_offer_pub_key_1, "Failed to derive_ephemeral_key_helper(pub keys missmatch)");
bl_for_sig = bc_services::make_offer_sig_blob(co);
crypto::generate_signature(crypto::cn_fast_hash(bl_for_sig.data(), bl_for_sig.size()), ephemeral_1.pub, ephemeral_1.sec, co.sig);
// bl_for_sig = currency::make_offer_sig_blob(co);
// crypto::generate_signature(crypto::cn_fast_hash(bl_for_sig.data(), bl_for_sig.size()),
// currency::get_tx_pub_key_from_extra(tx),
// off_key_pair.sec, co.sig);
bc_services::put_offer_into_attachment(co, attachments3);
construct_tx_to_key(m_hardforks, events, tx, blk_7, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, CURRENCY_TO_KEY_OUT_RELAXED, std::vector<currency::extra_v>(), attachments3);
txs_list3.push_back(tx);
events.push_back(tx);
MAKE_NEXT_BLOCK_TX_LIST(events, blk_8, blk_7, miner_account, txs_list3);
DO_CALLBACK(events, "check_offers_count_after_cancel");
return true;
}
bool gen_crypted_attachments::set_blockchain_height(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
bc_height_before = c.get_current_blockchain_size();
return true;
}
bool gen_crypted_attachments::set_crypted_tx_height(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
crypted_tx_height = ev_index - 1;
return true;
}
bool gen_crypted_attachments::check_offers_count_befor_cancel(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
std::list<bc_services::offer_details_ex> od;
get_all_offers(c, od);
offers_before_canel = od.size();
return true;
}
bool gen_crypted_attachments::check_offers_count_after_cancel(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
std::list<bc_services::offer_details_ex> od;
get_all_offers(c, od);
CHECK_EQ(offers_before_canel - 2, od.size());
return true;
}
bool gen_crypted_attachments::check_crypted_tx(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
//// check cha cha
std::string test_key = "ssssss";
std::vector<std::string> test_array;
test_array.push_back("qweqweqwqw");
test_array.push_back("4g45");
test_array.push_back("qweqwe56575qwqw");
test_array.push_back("q3f34f4");
std::vector<std::string> test_array2 = test_array;
for (auto& v : test_array2)
chacha_crypt(v, test_key);
for (auto& v : test_array2)
chacha_crypt(v, test_key);
if (test_array2 != test_array)
{
LOG_ERROR("cha cha broken");
return false;
}
//
const currency::transaction& tx = boost::get<currency::transaction>(events[crypted_tx_height]);
const currency::account_base& bob_acc = boost::get<currency::account_base>(events[2]);
CHECK_EQ(c.get_current_blockchain_size(), bc_height_before+1);
auto ptx_from_bc = c.get_blockchain_storage().get_tx(currency::get_transaction_hash(tx));
CHECK_TEST_CONDITION(ptx_from_bc );
std::vector<payload_items_v> at;
bool r = currency::decrypt_payload_items(true, *ptx_from_bc, bob_acc.get_keys(), at);
CHECK_EQ(r, true);
if (at.size() != 16)
{
std::stringstream ss;
for(auto& el : at)
ss << " " << el.type().name() << ENDL;
LOG_PRINT_RED("at.size() = " << at.size() << " : " << ENDL << ss.str(), LOG_LEVEL_0);
// expected items:
// public_key
// etc_tx_flags16_t
// tx_derivation_hint x 10
// extra_attachment_info
// tx_payer
// tx_comment
// tx_crypto_checksum
//
// total: 16
CHECK_AND_ASSERT_MES(false, false, "unexpected number of decrypted items");
}
currency::tx_payer decrypted_pr = AUTO_VAL_INIT(decrypted_pr);
r = get_type_in_variant_container(at, decrypted_pr);
CHECK_AND_ASSERT_MES(r, false, "get_type_in_variant_container failed");
if (pr.acc_addr != decrypted_pr.acc_addr)
{
LOG_ERROR("decrypted wrong data: "
<< epee::string_tools::pod_to_hex(decrypted_pr.acc_addr)
<< "expected:" << epee::string_tools::pod_to_hex(pr.acc_addr));
return false;
}
currency::tx_comment decrypted_comment = AUTO_VAL_INIT(decrypted_comment);
r = get_type_in_variant_container(at, decrypted_comment);
CHECK_AND_ASSERT_MES(r, false, "get_type_in_variant_container failed");
if (cm.comment != decrypted_comment.comment)
{
LOG_ERROR("decrypted wrong data: "
<< decrypted_comment.comment
<< "expected:" << cm.comment);
return false;
}
// currency::tx_message decrypted_message = AUTO_VAL_INIT(decrypted_message);
// r = get_type_in_variant_container(at, decrypted_message);
// CHECK_AND_ASSERT_MES(r, false, "get_type_in_variant_container failed");
// if (ms.msg != decrypted_message.msg)
// {
// LOG_ERROR("decrypted wrong data: "
// << decrypted_message.msg
// << "expected:" << ms.msg);
// return false;
// }
// now cancel attachment
return true;
}
gen_tx_extra_double_entry::gen_tx_extra_double_entry()
{
REGISTER_CALLBACK_METHOD(gen_tx_extra_double_entry, configure_core);
}
bool gen_tx_extra_double_entry::configure_core(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
currency::core_runtime_config pc = c.get_blockchain_storage().get_core_runtime_config();
pc.min_coinstake_age = TESTS_POS_CONFIG_MIN_COINSTAKE_AGE;
pc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH;
pc.hard_forks.set_hardfork_height(1, 0);
pc.hard_forks.set_hardfork_height(2, 0);
c.get_blockchain_storage().set_core_runtime_config(pc);
return true;
}
bool gen_tx_extra_double_entry::generate(std::vector<test_event_entry>& events) const
{
uint64_t ts_start = 1450000000;
bool r = false;
GENERATE_ACCOUNT(miner_account);
MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start);
generator.set_hardfork_height(1, 0);
generator.set_hardfork_height(2, 0); // extra_alias_entry is only allowed after HF2, so switch it on here
DO_CALLBACK(events, "configure_core");
REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2);
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
r = fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
tx_builder builder;
// 1. double pubkey
builder.step1_init();
builder.step2_fill_inputs(miner_account.get_keys(), sources);
builder.step3_fill_outputs(destinations);
add_tx_pub_key_to_extra(builder.m_tx, builder.m_tx_key.pub); // add redundant second pubkey to tx extra
builder.step4_calc_hash();
builder.step5_sign(sources);
// EXPECTED: tx and blk_1 are rejected
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(builder.m_tx);
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, builder.m_tx);
// 2. double attachment
bc_services::offer_details od = AUTO_VAL_INIT(od);
fill_test_offer_(od);
//std::vector<currency::attachment_v> attachments;
builder.step1_init();
builder.step2_fill_inputs(miner_account.get_keys(), sources);
builder.step3_fill_outputs(destinations);
bc_services::put_offer_into_attachment(od, builder.m_tx.attachment);
add_attachments_info_to_extra(builder.m_tx.extra, builder.m_tx.attachment);
add_attachments_info_to_extra(builder.m_tx.extra, builder.m_tx.attachment); // two attachment entry
builder.step4_calc_hash();
builder.step5_sign(sources);
// EXPECTED: tx and blk_2 are rejected
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(builder.m_tx);
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_0r, miner_account, builder.m_tx);
// 3. double alias
extra_alias_entry ai;
ai.m_alias = "some-alias";
ai.m_address = miner_account.get_public_address();
ai.m_text_comment = "Mandame unas monedas, por favor!";
uint64_t alias_reward = currency::get_alias_coast_from_fee(ai.m_alias, TESTS_DEFAULT_FEE);
sources.clear();
destinations.clear();
r = fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE + alias_reward, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
tx_destination_entry de;
de.addr.push_back(null_pub_addr);
de.amount = alias_reward;
destinations.push_back(de);
builder.step1_init();
builder.step2_fill_inputs(miner_account.get_keys(), sources);
builder.step3_fill_outputs(destinations);
add_tx_extra_alias(builder.m_tx, ai);
add_tx_extra_alias(builder.m_tx, ai); // second alias entry in the extra
builder.step4_calc_hash();
builder.step5_sign(sources);
// EXPECTED: tx and blk_3 are rejected
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(builder.m_tx);
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_0r, miner_account, builder.m_tx);
// 4. double extra_user_data
sources.clear();
destinations.clear();
r = fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
builder.step1_init();
builder.step2_fill_inputs(miner_account.get_keys(), sources);
builder.step3_fill_outputs(destinations);
extra_user_data eud;
eud.buff = std::string(2049, '$');
builder.m_tx.extra.push_back(eud);
builder.m_tx.extra.push_back(eud); // second extra user data entry
builder.step4_calc_hash();
builder.step5_sign(sources);
// EXPECTED: tx and blk_4 are rejected
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(builder.m_tx);
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_0r, miner_account, builder.m_tx);
// 5. double extra_padding
builder.step1_init();
builder.step2_fill_inputs(miner_account.get_keys(), sources);
builder.step3_fill_outputs(destinations);
extra_padding padding;
padding.buff.resize(2049);
builder.m_tx.extra.push_back(padding);
builder.m_tx.extra.push_back(padding); // extra_padding entry
builder.step4_calc_hash();
builder.step5_sign(sources);
// EXPECTED: tx and blk_5 are rejected
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(builder.m_tx);
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_0r, miner_account, builder.m_tx);
// 6. each entry is added only once
ai = AUTO_VAL_INIT(ai);
ai.m_alias = "uwndjckforptifmslf";
ai.m_address = miner_account.get_public_address();
ai.m_text_comment = "Mandame unas monedas, por favor!";
sources.clear();
destinations.clear();
r = fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE + alias_reward, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
de = AUTO_VAL_INIT(de);
de.addr.push_back(currency::account_public_address());
de.amount = alias_reward;
destinations.push_back(de);
builder.step1_init();
builder.step2_fill_inputs(miner_account.get_keys(), sources);
builder.step3_fill_outputs(destinations);
bc_services::put_offer_into_attachment(od, builder.m_tx.attachment);
add_attachments_info_to_extra(builder.m_tx.extra, builder.m_tx.attachment); // attachment
add_tx_extra_alias(builder.m_tx, ai); // alias
builder.m_tx.extra.push_back(eud); // user data
builder.m_tx.extra.push_back(padding); // padding
builder.step4_calc_hash();
builder.step5_sign(sources);
// EXPECTED: tx and blk_6 are accepted
events.push_back(builder.m_tx);
MAKE_NEXT_BLOCK_TX1(events, blk_65, blk_0r, miner_account, builder.m_tx);
return true;
}
bool gen_tx_double_key_image::generate(std::vector<test_event_entry>& events) const
{
// Make a tx with correct, but used more then once key_image in inputs. Should be invalid tx.
uint64_t ts_start = test_core_time::get_time();
GENERATE_ACCOUNT(miner_account);
MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start);
REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_account, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
sources.push_back(sources.front()); // dobule source entry => double imput => double key image
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 tx_expiration_time::generate(std::vector<test_event_entry>& events) const
{
bool r = false;
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
GENERATE_ACCOUNT(bob_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// transfer to Alice some coins in chunks
uint64_t alice_amount = MK_TEST_COINS(10);
transaction tx_1 = AUTO_VAL_INIT(tx_1);
r = construct_tx_with_many_outputs(m_hardforks, events, blk_0r, miner_acc.get_keys(), alice_acc.get_public_address(), alice_amount, 10, TESTS_DEFAULT_FEE, tx_1);
CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed");
events.push_back(tx_1);
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1);
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc);
// check Alice's balance
std::shared_ptr<tools::wallet2> alice_wlt;
r = generator.init_test_wallet(alice_acc, get_block_hash(blk_0), alice_wlt);
CHECK_AND_ASSERT_MES(r, false, "init_test_wallet failed");
r = generator.refresh_test_wallet(events, alice_wlt.get(), get_block_hash(blk_2), CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2);
CHECK_AND_ASSERT_MES(r, false, "refresh_test_wallet failed");
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "alice", alice_amount, 0, 0, 0, 0), false, "");
uint64_t ts_median = generator.get_timestamps_median(get_block_hash(blk_2), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW);
LOG_PRINT_YELLOW("%%%%%%% ts_median = " << ts_median, LOG_LEVEL_0);
/// core tx expiration condition:
/// expiration_time - TX_EXPIRATION_MEDIAN_SHIFT > get_last_n_blocks_timestamps_median(TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW)
// 1/3
// create normal tx, then add expiration time entry to the extra and then resign
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
r = fill_tx_sources_and_destinations(events, blk_2, alice_acc.get_keys(), bob_acc.get_public_address(), MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
transaction tx_2{};
r = construct_tx(alice_acc.get_keys(), sources, destinations, events, this, tx_2);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
set_tx_expiration_time(tx_2, ts_median + TX_EXPIRATION_MEDIAN_SHIFT + 1); // one second greather than minimum allowed
r = resign_tx(alice_acc.get_keys(), sources, tx_2);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
// should pass normally
events.push_back(tx_2);
MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_2);
// 2/3
// normal tx with expiration time entry equal to median minus shift - should not pass at tx_pool
ts_median = generator.get_timestamps_median(get_block_hash(blk_3), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW);
sources.clear();
destinations.clear();
r = fill_tx_sources_and_destinations(events, blk_3, alice_acc.get_keys(), bob_acc.get_public_address(), MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
transaction tx_3{};
r = construct_tx(alice_acc.get_keys(), sources, destinations, events, this, tx_3);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
set_tx_expiration_time(tx_3, ts_median + TX_EXPIRATION_MEDIAN_SHIFT + 0); // exact expiration time, should not pass (see core condition above)
r = resign_tx(alice_acc.get_keys(), sources, tx_3);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
// should be rejected at tx_pool
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(tx_3);
// 3/3
// normal tx with expiration time entry equal to median minus shift - should not pass at the core
// create a new block, but do not add it to the events vector for a while
MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_acc);
events.pop_back();
// update median
ts_median = generator.get_timestamps_median(get_block_hash(blk_4), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW);
// prepare transaction - use tx_3 change it's expiration time and resign
set_tx_expiration_time(tx_3, ts_median + TX_EXPIRATION_MEDIAN_SHIFT + 0); // exact expiration time, should not pass (see core condition above)
r = resign_tx(alice_acc.get_keys(), sources, tx_3);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
// should be accepted by the pool
events.push_back(tx_3);
// add blk_4, this step was deferred in order to skip expiration condition at tx_memory_pool::add_tx
events.push_back(blk_4);
// blk_5 contain already expired tx_3 and thus should be rejected by the core
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4, miner_acc, tx_3);
return true;
}
//------------------------------------------------------------------
tx_expiration_time_and_block_template::tx_expiration_time_and_block_template()
{
REGISTER_CALLBACK_METHOD(tx_expiration_time_and_block_template, c1);
}
bool tx_expiration_time_and_block_template::generate(std::vector<test_event_entry>& events) const
{
// Test idea: make sure expired transaction, waiting in tx pool, won't be included into a block template, making it invalid
GENERATE_ACCOUNT(miner_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
uint64_t ts_median = generator.get_timestamps_median(get_block_hash(blk_0r), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW);
LOG_PRINT_YELLOW("%%%%%%% ts_median = " << ts_median, LOG_LEVEL_0);
/// core tx expiration condition:
/// expiration_time - TX_EXPIRATION_MEDIAN_SHIFT > get_last_n_blocks_timestamps_median(TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW)
// create normal tx, then add expiration time entry to the extra and then resign
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
bool r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), miner_acc.get_public_address(), MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
transaction tx_1{};
r = construct_tx(miner_acc.get_keys(), sources, destinations, events, this, tx_1);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
uint64_t tx_1_expiration_time = ts_median + TX_EXPIRATION_MEDIAN_SHIFT + 1; // one second greather than minimum allowed
set_tx_expiration_time(tx_1, tx_1_expiration_time);
r = resign_tx(miner_acc.get_keys(), sources, tx_1);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
events.push_back(tx_1); // should normally pass into tx pool
MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner_acc);
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc);
ts_median = generator.get_timestamps_median(get_block_hash(blk_2), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW);
// make sure tx_1 is expired with this median
CHECK_AND_ASSERT_MES(tx_1_expiration_time - TX_EXPIRATION_MEDIAN_SHIFT <= ts_median, false, "tx_1 didn't expire yet as expected");
// okay, now we have expired transaction in tx pool, try to construct block_template and mine a block
DO_CALLBACK(events, "c1");
return true;
}
bool tx_expiration_time_and_block_template::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect tx count in the pool: " << c.get_pool_transactions_count());
account_public_address addr = AUTO_VAL_INIT(addr);
bool r = mine_next_pow_block_in_playtime(addr, c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
// tx MAY stay in the pool, check it as forced condition (may change in future)
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect tx count in the pool: " << c.get_pool_transactions_count());
LOG_PRINT("tx pool:" << ENDL << c.get_tx_pool().print_pool(true), LOG_LEVEL_0);
LOG_PRINT_YELLOW("tx_pool::on_idle()", LOG_LEVEL_0);
c.get_tx_pool().on_idle();
LOG_PRINT("tx pool:" << ENDL << c.get_tx_pool().print_pool(true), LOG_LEVEL_0);
return true;
}
//------------------------------------------------------------------
bool tx_expiration_time_and_chain_switching::generate(std::vector<test_event_entry>& events) const
{
// test idea: check border case when tx with expiration time are moved back from blockchain to the pool
bool r = false;
GENERATE_ACCOUNT(miner_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
//
// chain A
//
// 0 ... 20 21 22 23 24 <- height
// (0 )- (0r)- (1 )- (2 )- <- chain A
// tx_1
REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, static_cast<size_t>(std::max(CURRENCY_MINED_MONEY_UNLOCK_WINDOW, TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW)));
uint64_t ts_median = generator.get_timestamps_median(get_block_hash(blk_0r), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW);
LOG_PRINT_YELLOW("%%%%%%% ts_median = " << ts_median << " for the last " << TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW << " blocks", LOG_LEVEL_0);
/// core tx expiration GOOD condition:
/// expiration_time - TX_EXPIRATION_MEDIAN_SHIFT > get_last_n_blocks_timestamps_median(TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW)
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), miner_acc.get_public_address(), MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
transaction tx_0{};
r = construct_tx(miner_acc.get_keys(), sources, destinations, events, this, tx_0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
uint64_t tx_0_expiration_time = ts_median + TX_EXPIRATION_MEDIAN_SHIFT + 0; // one second less than minimum allowed (see condition above)
set_tx_expiration_time(tx_0, tx_0_expiration_time);
r = resign_tx(miner_acc.get_keys(), sources, tx_0);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(tx_0); // should not pass at tx pool as it has expiration time exactly at the right boundary of allowed period
// tx_1 is the same except the expiration time
transaction tx_1 = tx_0;
uint64_t tx_1_expiration_time = ts_median + TX_EXPIRATION_MEDIAN_SHIFT + 1; // minimum allowed ts (see condition above)
set_tx_expiration_time(tx_1, tx_1_expiration_time);
r = resign_tx(miner_acc.get_keys(), sources, tx_1);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
events.push_back(tx_1); // should pass
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); // tx_1 and blk_1 should pass
//
// chain B
//
// 0 ... 20 21 22 23 24 <- height
// (0 )- (0r)- (1 )- <- chain A
// | tx_1
// \
// \ (1b)- (2b)- <- chain B (became main after 2b)
//
MAKE_NEXT_BLOCK(events, blk_1b, blk_0r, miner_acc);
MAKE_NEXT_BLOCK(events, blk_2b, blk_1b, miner_acc); // <-- should trigger chain switching
// make sure the chain switching was ok
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_2b));
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1))
// 0 ... 20 21 22 23 24 <- height
// (0 )- (0r)- (1 )- <- chain A
// | tx_1
// \
// \ (1b)- (2b)- !3b!- <- chain B, block 3b is rejected because tx_1 is already expired
// tx_1
// tx_1 is still in the pool, but can't be added to any block anymore as it was already expired
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_3b_bad, blk_2b, miner_acc, tx_1);
// make sure top block wasn't changed
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_2b));
// make sure tx_1 is still in the pool
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1))
//
// chain C
//
// 0 ... 20 21 22 23 24 <- height
// (0 )- (0r)- (1 )- <- chain A
// | tx_1
// |\
// | \ (1b)- (2b)- <- chain B
// |
// \- (1c)- (2c)- (3c)- <- chain C
// tx_1
// should pass as tx_1 can be added to any block on height 21 (it's not yet expired on that height)
MAKE_NEXT_BLOCK_TX1(events, blk_1c, blk_0r, miner_acc, tx_1);
MAKE_NEXT_BLOCK(events, blk_2c, blk_1c, miner_acc);
MAKE_NEXT_BLOCK(events, blk_3c, blk_2c, miner_acc); // <- should trigger chain switching
// make sure the chain switching was ok and the pool is empty
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_3c));
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(0))
//
// switch to chain B again
//
// 0 ... 20 21 22 23 24 <- height
// (0 )- (0r)- (1 )- <- chain A
// | tx_1
// |\
// | \ (1b)- (2b)- (3b)- (4b)- <- chain B
// |
// \- (1c)- (2c)- (3c)- <- chain C
// tx_1
// should pass as tx_1 can be added to any block on height 21 (it's not yet expired on that height)
MAKE_NEXT_BLOCK(events, blk_3b, blk_2b, miner_acc);
MAKE_NEXT_BLOCK(events, blk_4b, blk_3b, miner_acc); // <- should trigger chain switching
// make sure the chain switching was ok
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_4b));
// tx_1 should be popped out to the pool again
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1))
return true;
}
//------------------------------------------------------------------
bool tx_key_image_pool_conflict::generate(std::vector<test_event_entry>& events) const
{
// Test idea: check tx that is stuck in tx pool because one on its key images is already spent in the blockchain
// 1) if it's linked to an alt block -- tx will not be removed as long as linked alt block exists (in order to be able to switch)
// 2) if it's not linked to an alt block -- it will be removed after CONFLICT_KEY_IMAGE_SPENT_DEPTH_TO_REMOVE_TX_FROM_POOL confirmations of conflicted tx
// or it will be removed once tx is old enough (CURRENCY_MEMPOOL_TX_LIVETIME)
bool r = false;
m_miner_acc.generate();
GENERATE_ACCOUNT(bob_acc);
MAKE_GENESIS_BLOCK(events, blk_0, m_miner_acc, test_core_time::get_time());
REWIND_BLOCKS_N(events, blk_0r, blk_0, m_miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3);
// make tx_0 : miner -> bob
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
r = fill_tx_sources_and_destinations(events, blk_0r, m_miner_acc.get_keys(), bob_acc.get_public_address(), MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
transaction tx_0{};
r = construct_tx(m_miner_acc.get_keys(), sources, destinations, events, this, tx_0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
LOG_PRINT_YELLOW("tx_0 = " << get_transaction_hash(tx_0), LOG_LEVEL_0);
// do not push tx_0 into events yet
// tx_1 spends the same key image as tx_0
transaction tx_1 = tx_0;
keypair kp = keypair::generate();
// change tx pub key to end up with different tx hash
update_or_add_field_to_extra(tx_1.extra, kp.pub);
r = resign_tx(m_miner_acc.get_keys(), sources, tx_1);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
LOG_PRINT_YELLOW("tx_1 = " << get_transaction_hash(tx_1), LOG_LEVEL_0);
// tx_2 spends the same key image as tx_0
transaction tx_2 = tx_0;
kp = keypair::generate();
// change tx pub key to end up with different tx hash
update_or_add_field_to_extra(tx_2.extra, kp.pub);
r = resign_tx(m_miner_acc.get_keys(), sources, tx_2);
CHECK_AND_ASSERT_MES(r, false, "resign_tx failed");
LOG_PRINT_YELLOW("tx_2 = " << get_transaction_hash(tx_2), LOG_LEVEL_0);
events.push_back(tx_1);
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
// as long as tx_0 is using the same key image as tx_1, tx_0 and tx_2 can't be added to the pool atm
// make sure that it's true
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(tx_0);
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(tx_2);
// however, tx_0 and tx_2 can be added with kept_by_block flag (to simulate it's going with blk_1)
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true));
events.push_back(tx_0);
events.push_back(tx_2);
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
// make a block with tx_0 and put tx_0 to the blockchain
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, m_miner_acc, tx_0);
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
// tx_1 and tx_2 is still in the pool
// it can never be added to any block as long as blk_1 is in the blockchain due to key image conflict
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_2_bad, blk_1, m_miner_acc, tx_1);
// add tx_1 to alt block, it should go well
MAKE_NEXT_BLOCK_TX1(events, blk_1a, blk_0r, m_miner_acc, tx_1);
// however, it does not remove tx from the pool
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
//
// make sure stuck tx will be removed from the pool when it's too old
//
MAKE_NEXT_BLOCK(events, blk_2, blk_1, m_miner_acc);
// remove_stuck_txs should not remove anything, tx_1 and tx_2 should be in the pool
DO_CALLBACK(events, "remove_stuck_txs");
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
// shift time by CURRENCY_MEMPOOL_TX_LIVETIME
events.push_back(event_core_time(CURRENCY_MEMPOOL_TX_LIVETIME + 1, true));
// remove_stuck_txs should have removed tx_2 because it's too old
DO_CALLBACK(events, "remove_stuck_txs");
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
//
// make sure stuck tx will be removed from the pool as soon as one of its key images is spent deep enough in the blockchain
// (even if it's not too old to be removed by age)
//
MAKE_NEXT_BLOCK(events, blk_3, blk_2, m_miner_acc);
// re-add tx_2 with kept_by_block flag
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true));
events.push_back(tx_2);
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
// remove_stuck_txs should not remove anything, tx_1 and tx_2 should be in the pool
DO_CALLBACK(events, "remove_stuck_txs");
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
// rewind 50 blocks so tx_0 spending its key image will be deep enough
REWIND_BLOCKS_N_WITH_TIME(events, blk_3r, blk_3, m_miner_acc, 50);
// remove_stuck_txs should remove only tx_2 and left tx_1 (linked to alt block)
DO_CALLBACK(events, "remove_stuck_txs");
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
DO_CALLBACK(events, "print_tx_pool");
return true;
}
//------------------------------------------------------------------
bool tx_version_against_hardfork::generate(std::vector<test_event_entry>& events) const
{
// Test idea: make sure that tx with incorrect for the activated HF version won't be accepted.
bool r = false;
GENERATE_ACCOUNT(miner_acc);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
DO_CALLBACK(events, "configure_core"); // default configure_core callback will initialize core runtime config with m_hardforks
REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// 0 ... 10 11 12 13 14 <- height
// (0 )- (0r)- (1 )- !2b!- <- chain A, block 2b is invalid
// tx_0 tx_1 tx_0 is accepted, tx_1 is rejected
// \
// \(2 ) <- chain B
// tx_1 tx_1 is accepted
uint64_t tx_version_good = 0, tx_version_bad = 0;
size_t tx_hardfork_id = 0;
// select good and bad tx versions based on active hardfork
size_t most_recent_hardfork_id = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_0r));
switch(most_recent_hardfork_id)
{
case ZANO_HARDFORK_04_ZARCANUM:
tx_hardfork_id = 0;
[[fallthrough]];
case ZANO_HARDFORK_05:
tx_version_good = TRANSACTION_VERSION_POST_HF4;
tx_version_bad = TRANSACTION_VERSION_PRE_HF4;
tx_hardfork_id = ZANO_HARDFORK_05;
break;
default:
LOG_ERROR("hardfork " << most_recent_hardfork_id << " is not supported by this test");
return false;
}
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
//
// 1/2 tx with good version, should go okay
//
r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), miner_acc.get_public_address(), MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
transaction tx_0{};
r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_0, tx_version_good, tx_hardfork_id, 0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
events.push_back(tx_0);
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0);
sources.clear();
destinations.clear();
//
// 2/2 tx with bad version, should be rejected by tx pool and by the core
//
r = fill_tx_sources_and_destinations(events, blk_0, miner_acc.get_keys(), miner_acc.get_public_address(), MK_TEST_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed");
transaction tx_1{};
r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_0, tx_version_bad, tx_hardfork_id, 0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
// check tx pool rejection
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(tx_1);
// now add tx_1 as onboard transaction (directly to a block, skipping tx pool)
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true));
events.push_back(tx_1);
events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));
// make sure the block with tx_1 is invalid
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_2b, blk_1, miner_acc, tx_1);
// just one more block to make sure everyting is okay
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc);
return true;
}
bool tx_pool_semantic_validation::generate(std::vector<test_event_entry>& events) const
{
// Test idea: ensure that the checks contained in the function "validate_tx_semantic" body are performed.
GENERATE_ACCOUNT(miner);
MAKE_GENESIS_BLOCK(events, blk_0, miner, test_core_time::get_time());
DO_CALLBACK(events, "configure_core");
CHECK_AND_ASSERT_EQ(validate_tx_semantic(transaction{}, CURRENCY_MAX_TRANSACTION_BLOB_SIZE), false);
// No inputs.
{
transaction tx{};
tx.vin = {};
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Unsupported input type.
{
transaction tx{};
tx.vin.emplace_back(txin_gen{});
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Unsupported output type.
{
transaction tx{};
tx.vin.push_back(txin_to_key{});
tx.vout.emplace_back();
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Inputs amount overflow.
{
point_t point_public_key{};
txout_to_key target{};
std::array<txin_to_key, 2> inputs{};
transaction tx{};
CHECK_AND_ASSERT_EQ(point_public_key.from_string("499790c3302b9f0514e2db09b390679283d43d971383d33dc24c7991ea4cf6d7"), true);
target.key = point_public_key.to_public_key();
inputs.at(0).amount = 1;
inputs.at(1).amount = UINT64_MAX;
for (const auto& input : inputs)
{
tx.vin.push_back(input);
}
CHECK_AND_ASSERT_GREATER(inputs.at(0).amount, inputs.at(0).amount + inputs.at(1).amount);
CHECK_AND_ASSERT_GREATER(inputs.at(1).amount, inputs.at(0).amount + inputs.at(1).amount);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Outputs amount overflow.
{
point_t point_public_key{};
txout_to_key target{};
std::array<tx_out_bare, 2> outputs{};
transaction tx{};
CHECK_AND_ASSERT_EQ(point_public_key.from_string("78ef3d9af7b5e3d09556d57820cf68c2b3553a9d8205c01fe40fc70aae86bb4f"), true);
target.key = point_public_key.to_public_key();
outputs.at(0).amount = 1;
outputs.at(1).amount = UINT64_MAX;
for (auto& output : outputs)
{
output.target = target;
tx.vout.push_back(output);
}
tx.vin.push_back(txin_to_key{});
CHECK_AND_ASSERT_GREATER(outputs.at(0).amount, outputs.at(0).amount + outputs.at(1).amount);
CHECK_AND_ASSERT_GREATER(outputs.at(1).amount, outputs.at(0).amount + outputs.at(1).amount);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Equal key images in inputs.
{
transaction tx{};
{
txin_zc_input input_zc{};
txin_htlc input_htlc{};
txin_to_key input_to_key{};
point_t point_key_image{};
CHECK_AND_ASSERT_EQ(point_key_image.from_string("93fa59f43fb9cff98e6867d20cf200c98b29cae406acdbde798ffb3e30d3503a"), true);
input_zc.k_image = point_key_image.to_key_image();
CHECK_AND_ASSERT_EQ(point_key_image.from_string("ad1226e3fd1be15e26b119fa80380e580a498e5fa3421b63fded89672b526a44"), true);
input_htlc.k_image = point_key_image.to_key_image();
CHECK_AND_ASSERT_EQ(point_key_image.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true);
input_to_key.k_image = point_key_image.to_key_image();
tx.vin.push_back(input_zc);
tx.vin.push_back(input_htlc);
tx.vin.push_back(input_to_key);
}
{
txin_to_key input{};
input.amount = 0;
tx.vin.push_back(input);
}
for (int8_t step{}; step < 3; ++step)
{
tx.vin.push_back(tx.vin.front());
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
tx.vin.erase(tx.vin.begin(), tx.vin.begin() + 1);
}
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
}
// Two entries of the same type in extra.
{
transaction tx{};
std::array<txin_to_key, 2> inputs{};
std::array<point_t, 2> key_image_points{};
CHECK_AND_ASSERT_EQ(key_image_points.at(0).from_string("de3c22a62f15e6de8abe6b217085b2aead196daf5ddd67d9c4b366330736fbeb"), true);
CHECK_AND_ASSERT_EQ(key_image_points.at(1).from_string("9f3eef913921ca35239e696725595e3686bb0d69e3e805791c5aa93d5754aa5c"), true);
for (int position{}; position < 2; ++position)
{
inputs.at(position).k_image = key_image_points.at(position).to_key_image();
tx.vin.push_back(inputs.at(position));
}
tx.extra.push_back(null_pkey);
tx.extra.push_back(null_pkey);
CHECK_AND_ASSERT_EQ(tx.extra.size(), 2);
CHECK_AND_ASSERT_EQ(typeid(tx.extra.front()), typeid(tx.extra.back()));
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// tx.version <= TRANSACTION_VERSION_PRE_HF4. Balance check fail: sum of inputs <= sum of outputs.
{
tx_out_bare output{};
transaction tx{};
std::array<point_t, 2> key_image_points{};
std::array<txin_to_key, 2> inputs{};
output.amount = 3;
tx.vout.push_back(output);
tx.version = 0;
CHECK_AND_ASSERT_EQ(key_image_points.at(0).from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true);
CHECK_AND_ASSERT_EQ(key_image_points.at(1).from_string("dc48b741dacda5ac026ad0a7d193b816049eb08724907a1ff6f95839cfb0efa5"), true);
for (int position{}; position < 2; ++position)
{
auto& input{inputs.at(position)};
input.amount = 1;
input.k_image = key_image_points.at(position).to_key_image();
tx.vin.push_back(input);
}
const uint64_t sum_inputs{std::accumulate(inputs.begin(), inputs.end(), std::uint64_t{}, [](uint64_t sum, const txin_to_key& input) { return sum + input.amount; })};
CHECK_AND_ASSERT_LESS(sum_inputs, output.amount);
CHECK_AND_ASSERT_EQ(output.amount - sum_inputs, 1);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Semantically valid transaction.
{
tx_out_bare output;
transaction tx{};
std::array<point_t, 2> key_image_points{};
std::array<txin_to_key, 2> inputs{};
output.amount = 3'000'000'000'000;
tx.version = 0;
tx.vout.push_back(output);
CHECK_AND_ASSERT_EQ(key_image_points.at(0).from_string("fe1ef4a48a69804652324dc071cb72f49c22cd97479583950eaff746c936f72c"), true);
CHECK_AND_ASSERT_EQ(key_image_points.at(1).from_string("0bf1d8bb0988069f2c2b4f0dc89c81830c1178e04450d1da31ef020660732279"), true);
for (int position{}; position < 2; ++position)
{
auto& input{inputs.at(position)};
input.amount = 2'000'000'000'000;
input.k_image = key_image_points.at(position).to_key_image();
tx.vin.push_back(input);
}
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
REWIND_BLOCKS_N(events, blk_0r, blk_0, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// Construct a valid transaction and then modify it so that the transaction is no longer semantically correct.
// No inputs.
{
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
tx.vin = {};
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Unsupported input type.
{
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
tx.vin.emplace_back(txin_gen{});
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Unsupported output type.
{
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
tx.vout.emplace_back();
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Inputs amount overflow.
{
point_t point_public_key{};
txout_to_key target{};
std::array<txin_to_key, 2> inputs{};
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
CHECK_AND_ASSERT_EQ(point_public_key.from_string("499790c3302b9f0514e2db09b390679283d43d971383d33dc24c7991ea4cf6d7"), true);
target.key = point_public_key.to_public_key();
inputs.at(0).amount = 1;
inputs.at(1).amount = UINT64_MAX;
for (const auto& input : inputs)
{
tx.vin.push_back(input);
}
CHECK_AND_ASSERT_GREATER(inputs.at(0).amount, inputs.at(0).amount + inputs.at(1).amount);
CHECK_AND_ASSERT_GREATER(inputs.at(1).amount, inputs.at(0).amount + inputs.at(1).amount);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Outputs amount overflow.
{
point_t point_public_key{};
txout_to_key target{};
std::array<tx_out_bare, 2> outputs{};
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
CHECK_AND_ASSERT_EQ(point_public_key.from_string("78ef3d9af7b5e3d09556d57820cf68c2b3553a9d8205c01fe40fc70aae86bb4f"), true);
target.key = point_public_key.to_public_key();
outputs.at(0).amount = 1;
outputs.at(1).amount = UINT64_MAX;
for (auto& output : outputs)
{
output.target = target;
tx.vout.push_back(output);
}
tx.vin.push_back(txin_to_key{});
CHECK_AND_ASSERT_GREATER(outputs.at(0).amount, outputs.at(0).amount + outputs.at(1).amount);
CHECK_AND_ASSERT_GREATER(outputs.at(1).amount, outputs.at(0).amount + outputs.at(1).amount);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// Equal key images in inputs.
{
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r);
{
txin_zc_input input_zc{};
txin_htlc input_htlc{};
txin_to_key input_to_key{};
point_t point_key_image{};
CHECK_AND_ASSERT_EQ(point_key_image.from_string("93fa59f43fb9cff98e6867d20cf200c98b29cae406acdbde798ffb3e30d3503a"), true);
input_zc.k_image = point_key_image.to_key_image();
CHECK_AND_ASSERT_EQ(point_key_image.from_string("ad1226e3fd1be15e26b119fa80380e580a498e5fa3421b63fded89672b526a44"), true);
input_htlc.k_image = point_key_image.to_key_image();
CHECK_AND_ASSERT_EQ(point_key_image.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true);
input_to_key.k_image = point_key_image.to_key_image();
tx.vin.push_back(input_zc);
tx.vin.push_back(input_htlc);
tx.vin.push_back(input_to_key);
}
for (int8_t step{}; step < 3; ++step)
{
tx.vin.push_back(tx.vin.front());
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
tx.vin.erase(tx.vin.begin(), tx.vin.begin() + 1);
}
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
}
// Two entries of the same type in extra.
{
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
tx.extra.push_back(null_pkey);
tx.extra.push_back(null_pkey);
CHECK_AND_ASSERT_GREATER(tx.extra.size(), 2);
CHECK_AND_ASSERT_EQ(typeid(tx.extra.back()), typeid(tx.extra.at(tx.extra.size() - 2)));
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
// tx.version <= TRANSACTION_VERSION_PRE_HF4. Balance check fail: sum of inputs <= sum of outputs.
{
tx_out_bare output{};
std::array<point_t, 2> key_image_points{};
std::array<txin_to_key, 2> inputs{};
MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, blk_0r);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true);
output.amount = 10'000'000'003;
tx.vout.push_back(output);
tx.version = 0;
CHECK_AND_ASSERT_EQ(key_image_points.at(0).from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true);
CHECK_AND_ASSERT_EQ(key_image_points.at(1).from_string("dc48b741dacda5ac026ad0a7d193b816049eb08724907a1ff6f95839cfb0efa5"), true);
for (int position{}; position < 2; ++position)
{
auto& input{inputs.at(position)};
input.amount = 1;
input.k_image = key_image_points.at(position).to_key_image();
tx.vin.push_back(input);
}
const auto inputs_sum{[](const uint64_t sum, const txin_v& input) -> uint64_t
{
if (input.type() == typeid(txin_to_key))
{
return sum + boost::get<txin_to_key>(input).amount;
}
if (input.type() == typeid(txin_multisig))
{
return sum + boost::get<txin_multisig>(input).amount;
}
return sum;
}
};
const auto outputs_sum{[](const uint64_t sum, const tx_out_v& output) -> uint64_t
{
if (output.type() == typeid(tx_out_bare))
{
return sum + boost::get<tx_out_bare>(output).amount;
}
return sum;
}
};
const uint64_t inputs_amount{std::accumulate(tx.vin.begin(), tx.vin.end(), std::uint64_t{}, inputs_sum)};
const uint64_t outputs_amount{std::accumulate(tx.vout.begin(), tx.vout.end(), std::uint64_t{}, outputs_sum)};
CHECK_AND_ASSERT_LESS(inputs_amount, outputs_amount);
CHECK_AND_ASSERT_EQ(outputs_amount - inputs_amount, 1);
CHECK_AND_ASSERT(tx.version <= TRANSACTION_VERSION_PRE_HF4, false);
CHECK_AND_ASSERT_EQ(get_block_height(blk_0r), 10);
CHECK_AND_ASSERT_EQ(m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_03, 10), true);
CHECK_AND_ASSERT_EQ(m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, 10), false);
CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx);
}
return true;
}
input_refers_to_incompatible_by_type_output::input_refers_to_incompatible_by_type_output()
{
REGISTER_CALLBACK_METHOD(input_refers_to_incompatible_by_type_output, assert_htlc_input_refers_to_key_output_is_wrong);
REGISTER_CALLBACK_METHOD(input_refers_to_incompatible_by_type_output, assert_to_key_input_refers_zarcanum_output_is_wrong);
REGISTER_CALLBACK_METHOD(input_refers_to_incompatible_by_type_output, assert_htlc_input_refers_zarcanum_output_is_wrong);
REGISTER_CALLBACK_METHOD(input_refers_to_incompatible_by_type_output, assert_zc_input_refers_bare_output_is_wrong);
m_hardforks.set_hardfork_height(ZANO_HARDFORK_04_ZARCANUM, 14);
}
bool input_refers_to_incompatible_by_type_output::generate(std::vector<test_event_entry>& events) const
{
// Test idea: ensure that input and output compatibility checks work.
GENERATE_ACCOUNT(miner);
MAKE_GENESIS_BLOCK(events, blk_0, miner, test_core_time::get_time());
DO_CALLBACK(events, "configure_core");
REWIND_BLOCKS(events, blk_0r, blk_0, miner);
block& top{blk_0r};
MAKE_TX_FEE(events, tx_00, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk_1, top, miner, tx_00);
top = blk_1;
}
// An input of the type "txin_htlc" refers by a "ref_by_id" object to an output with a target of the type "txout_to_key".
{
MAKE_TX_FEE(events, tx_0, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_0);
transaction tx_1{};
top = blk;
{
txin_htlc input{};
{
ref_by_id reference{};
reference.tx_id = get_transaction_hash(tx_0);
reference.n = get_tx_out_index_by_amount(tx_0, MK_TEST_COINS(7));
CHECK_AND_ASSERT_NEQ(reference.n, UINT64_MAX);
input.key_offsets.push_back(reference);
CHECK_AND_ASSERT_EQ(input.key_offsets.size(), 1);
}
input.k_image = crypto::point_t{{0x59, 0x01, 0xed, 0xcc, 0x3a, 0xe7, 0xaa, 0x83, 0x6c, 0x79, 0xfb, 0xed, 0x5d, 0x60, 0x02, 0xc5, 0xd0, 0xbf, 0x65, 0x85, 0x7b, 0x65, 0x25, 0x0e, 0x22, 0xcb, 0x63,
0x64, 0x3b, 0x3b, 0x47, 0x30}}.to_key_image();
input.amount = MK_TEST_COINS(7);
tx_1.vin.push_back(input);
CHECK_AND_ASSERT_EQ(tx_1.vin.size(), 1);
}
{
tx_out_bare output_bare{};
txout_to_key output_to_key{};
output_bare.amount = MK_TEST_COINS(2);
output_to_key.key = crypto::point_t{{0x2c, 0xdc, 0xc4, 0x7c, 0x38, 0x69, 0xe5, 0xe2, 0x4c, 0x5e, 0x10, 0xb2, 0xbe, 0x57, 0xe9, 0x42, 0x72, 0xd8, 0xf8, 0xb5, 0x97, 0xb9, 0x02, 0x41, 0xba, 0xea,
0x82, 0xb3, 0xaf, 0x0c, 0xf0, 0x09}}.to_public_key();
output_bare.target = output_to_key;
tx_1.vout.push_back(output_bare);
CHECK_AND_ASSERT_EQ(tx_1.vout.size(), 1);
}
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx_1);
DO_CALLBACK_PARAMS_STR(events, "assert_htlc_input_refers_to_key_output_is_wrong", t_serializable_object_to_blob(tx_1));
}
// An input of the type "txin_htlc" refers by a global output index to an output with a target of the type "txout_to_key".
{
MAKE_TX_FEE(events, tx_0, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_0);
transaction tx_1{};
top = blk;
{
txin_htlc input{};
{
uint64_t global_output_index{};
CHECK_AND_ASSERT_EQ(find_global_index_for_output(events, get_block_hash(top), tx_0, get_tx_out_index_by_amount(tx_0, MK_TEST_COINS(7)), global_output_index), true);
CHECK_AND_ASSERT_NEQ(global_output_index, UINT64_MAX);
input.key_offsets.push_back(global_output_index);
CHECK_AND_ASSERT_EQ(input.key_offsets.size(), 1);
}
input.k_image = crypto::point_t{{0xc6, 0x1c, 0xda, 0xf7, 0x9e, 0xb7, 0xd9, 0xc2, 0x46, 0x90, 0x29, 0xc8, 0x8a, 0x8f, 0xb4, 0x3e, 0x8e, 0xa8, 0x3b, 0x33, 0x4c, 0x75, 0xdf, 0xcb, 0x8b, 0x77, 0xf7,
0x39, 0xa7, 0x17, 0xc9, 0xb4}}.to_key_image();
input.amount = MK_TEST_COINS(7);
tx_1.vin.push_back(input);
CHECK_AND_ASSERT_EQ(tx_1.vin.size(), 1);
}
{
tx_out_bare output_bare{};
txout_to_key output_to_key{};
output_bare.amount = MK_TEST_COINS(2);
output_to_key.key = crypto::point_t{{0xc4, 0x17, 0xc7, 0x7f, 0xb2, 0x5d, 0xcb, 0x4b, 0x29, 0xdf, 0xea, 0x53, 0x70, 0x11, 0xbb, 0x42, 0x33, 0x0d, 0xf1, 0x22, 0x2d, 0xe4, 0x84, 0x24, 0x36, 0xc0,
0x06, 0xd5, 0x8c, 0xf8, 0x23, 0x62}}.to_public_key();
output_bare.target = output_to_key;
tx_1.vout.push_back(output_bare);
}
tx_1.signatures.push_back(NLSAG_sig{{crypto::signature{}}});
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx_1);
DO_CALLBACK_PARAMS_STR(events, "assert_htlc_input_refers_to_key_output_is_wrong", t_serializable_object_to_blob(tx_1));
}
DO_CALLBACK_PARAMS(events, "check_hardfork_inactive", size_t{ZANO_HARDFORK_04_ZARCANUM});
{
REWIND_BLOCKS_N(events, blk, top, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
top = blk;
}
DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM});
MAKE_TX_FEE(events, tx_01, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_01);
top = blk;
}
MAKE_TX_FEE(events, tx_02, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_02);
top = blk;
}
// An input of the type "txin_to_key" refers by a "ref_by_id" object to an output of the type "tx_out_zarcanum".
{
DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM});
MAKE_TX_FEE(events, tx_0, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_0);
top = blk;
}
{
REWIND_BLOCKS(events, blk_r, top, miner);
top = blk_r;
}
transaction tx_1{};
tx_1.version = 2;
{
txin_to_key input{};
input.key_offsets.emplace_back(ref_by_id{get_transaction_hash(tx_0), 0});
CHECK_AND_ASSERT_EQ(tx_0.vout.at(boost::get<ref_by_id>(input.key_offsets.front()).n).type(), typeid(tx_out_zarcanum));
CHECK_AND_ASSERT_EQ(input.key_offsets.size(), 1);
input.k_image = crypto::point_t({0x59,0x01, 0xed, 0xcc, 0x3a, 0xe7, 0xaa, 0x83, 0x6c, 0x79, 0xfb, 0xed, 0x5d, 0x60, 0x02, 0xc5, 0xd0, 0xbf, 0x65, 0x85, 0x7b, 0x65, 0x25, 0x0e, 0x22, 0xcb, 0x63,
0x64, 0x3b, 0x3b, 0x47, 0x30}).to_key_image();
input.amount = MK_TEST_COINS(2);
tx_1.vin.push_back(input);
CHECK_AND_ASSERT_EQ(tx_1.vin.size(), 1);
}
tx_1.vout.push_back(tx_out_zarcanum{});
tx_1.vout.push_back(tx_out_zarcanum{});
CHECK_AND_ASSERT_EQ(tx_1.vout.size(), 2);
tx_1.extra.push_back(zarcanum_tx_data_v1{TESTS_DEFAULT_FEE});
CHECK_AND_ASSERT_EQ(tx_1.extra.size(), 1);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx_1);
DO_CALLBACK_PARAMS_STR(events, "assert_to_key_input_refers_zarcanum_output_is_wrong", t_serializable_object_to_blob(tx_1));
}
// An input of the type "txin_to_key" refers by a global output index to an output of the type "tx_out_zarcanum".
{
DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM});
MAKE_TX_FEE(events, tx_0, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_0);
top = blk;
}
{
REWIND_BLOCKS(events, blk_r, top, miner);
top = blk_r;
}
transaction tx_1{};
tx_1.version = 2;
{
txin_to_key input{};
{
uint64_t global_output_index{};
CHECK_AND_ASSERT_EQ(tx_0.vout.front().type(), typeid(tx_out_zarcanum));
CHECK_AND_ASSERT_EQ(find_global_index_for_output(events, get_block_hash(top), tx_0, 0, global_output_index), true);
input.key_offsets.push_back(global_output_index);
}
CHECK_AND_ASSERT_EQ(input.key_offsets.size(), 1);
input.k_image = crypto::point_t{{0xbc, 0x2d, 0xdc, 0xc5, 0x93, 0x03, 0x9f, 0x0e, 0xce, 0x76, 0xee, 0xef, 0xd9, 0x1c, 0x2c, 0x3e, 0x8c, 0x4a, 0xca, 0x87, 0x9b, 0x6e, 0x3a, 0xda, 0xaf, 0x0c,
0x92, 0x88, 0xda, 0x88, 0xc0, 0xf0}}.to_key_image();
// A container is selected by an amount specified in an input. ZC outputs have .amount equals to 0. Thus, the input has .amount equals to 0.
input.amount = 0;
tx_1.vin.push_back(input);
CHECK_AND_ASSERT_EQ(tx_1.vin.size(), 1);
}
tx_1.vout.push_back(tx_out_zarcanum{});
tx_1.vout.push_back(tx_out_zarcanum{});
CHECK_AND_ASSERT_EQ(tx_1.vout.size(), 2);
tx_1.extra.push_back(zarcanum_tx_data_v1{TESTS_DEFAULT_FEE});
CHECK_AND_ASSERT_EQ(tx_1.extra.size(), 1);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx_1);
DO_CALLBACK_PARAMS_STR(events, "assert_to_key_input_refers_zarcanum_output_is_wrong", t_serializable_object_to_blob(tx_1));
}
// An input of the type "txin_zc_input" refers by a "ref_by_id" object to an output with a target of the type "txout_to_key".
{
DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM});
MAKE_TX_FEE(events, tx_0, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_0);
top = blk;
}
{
REWIND_BLOCKS(events, blk_r, top, miner);
top = blk_r;
}
transaction tx_1{};
tx_1.version = 2;
{
txin_zc_input input{};
input.key_offsets.emplace_back(ref_by_id{get_transaction_hash(tx_00), 0});
{
const auto& output{tx_00.vout.at(boost::get<ref_by_id>(input.key_offsets.front()).n)};
CHECK_AND_ASSERT_EQ(output.type(), typeid(tx_out_bare));
CHECK_AND_ASSERT_EQ(boost::get<tx_out_bare>(output).target.type(), typeid(txout_to_key));
}
CHECK_AND_ASSERT_EQ(input.key_offsets.size(), 1);
input.key_offsets.emplace_back(ref_by_id{get_transaction_hash(tx_01), 0});
{
const auto& output{tx_01.vout.at(boost::get<ref_by_id>(input.key_offsets.at(1)).n)};
CHECK_AND_ASSERT_EQ(output.type(), typeid(tx_out_zarcanum));
}
CHECK_AND_ASSERT_EQ(input.key_offsets.size(), 2);
input.k_image = crypto::point_t{{0x31, 0x31, 0xd0, 0xf7, 0x13, 0x73, 0xff, 0x21, 0x14, 0xe8, 0x17, 0x4d, 0x18, 0x20, 0x12, 0x2d, 0x80, 0x31, 0xd5, 0x11, 0x82, 0xc0, 0x37, 0xad, 0xd2, 0x7b, 0x8c,
0xf2, 0xdd, 0xd4, 0x34, 0x9a}}.to_key_image();
tx_1.vin.push_back(input);
CHECK_AND_ASSERT_EQ(tx_1.vin.size(), 1);
}
tx_1.vout.push_back(tx_out_zarcanum{});
tx_1.vout.push_back(tx_out_zarcanum{});
CHECK_AND_ASSERT_EQ(tx_1.vout.size(), 2);
tx_1.extra.push_back(zarcanum_tx_data_v1{TESTS_DEFAULT_FEE});
CHECK_AND_ASSERT_EQ(tx_1.extra.size(), 1);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx_1);
DO_CALLBACK_PARAMS_STR(events, "assert_zc_input_refers_bare_output_is_wrong", t_serializable_object_to_blob(tx_1));
}
// An input of the type "txin_htlc" refers by a "ref_by_id" object to an output of the type "tx_out_zarcanum".
{
DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM});
MAKE_TX_FEE(events, tx_0, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_0);
top = blk;
}
{
REWIND_BLOCKS_N_WITH_TIME(events, blk_r, top, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
top = blk_r;
}
transaction tx_1{};
tx_1.version = 2;
{
txin_htlc input{};
input.key_offsets.push_back(ref_by_id{get_transaction_hash(tx_0), 0});
CHECK_AND_ASSERT_EQ(tx_0.vout.at(boost::get<ref_by_id>(input.key_offsets.front()).n).type(), typeid(tx_out_zarcanum));
input.k_image = crypto::point_t{{0x7b, 0xf5, 0x28, 0x09, 0xe8, 0x7e, 0x9c, 0x71, 0x0b, 0xad, 0x24, 0xa1, 0x9d, 0xb4, 0xc8, 0xd7, 0x96, 0x72, 0x18, 0xe6, 0x4b, 0x8f, 0x31, 0x01, 0xb0, 0x43, 0xa0,
0xcc, 0xce, 0x72, 0x8c, 0x7e}}.to_key_image();
input.amount = MK_TEST_COINS(2);
tx_1.vin.push_back(input);
CHECK_AND_ASSERT_EQ(tx_1.vin.size(), 1);
}
tx_1.vout.push_back(tx_out_zarcanum{});
tx_1.vout.push_back(tx_out_zarcanum{});
CHECK_AND_ASSERT_EQ(tx_1.vout.size(), 2);
tx_1.extra.push_back(zarcanum_tx_data_v1{TESTS_DEFAULT_FEE});
CHECK_AND_ASSERT_EQ(tx_1.extra.size(), 1);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx_1);
DO_CALLBACK_PARAMS_STR(events, "assert_htlc_input_refers_zarcanum_output_is_wrong", t_serializable_object_to_blob(tx_1));
}
// An input of the type "txin_htlc" refers by a global output index to an output of the type "tx_out_zarcanum".
{
MAKE_TX_FEE(events, tx_0, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, top);
{
MAKE_NEXT_BLOCK_TX1(events, blk, top, miner, tx_0);
top = blk;
}
DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM});
{
REWIND_BLOCKS_N_WITH_TIME(events, blk_r, top, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
top = blk_r;
}
transaction tx_1{};
tx_1.version = 2;
{
txin_htlc input{};
uint64_t global_output_index{};
CHECK_AND_ASSERT_EQ(tx_0.vout.front().type(), typeid(tx_out_zarcanum));
CHECK_AND_ASSERT_EQ(find_global_index_for_output(events, get_block_hash(top), tx_0, 0, global_output_index), true);
input.key_offsets.push_back(global_output_index);
CHECK_AND_ASSERT_EQ(input.key_offsets.size(), 1);
input.k_image = crypto::point_t{{0xbc, 0x2d, 0xdc, 0xc5, 0x93, 0x03, 0x9f, 0x0e, 0xce, 0x76, 0xee, 0xef, 0xd9, 0x1c, 0x2c, 0x3e, 0x8c, 0x4a, 0xca, 0x87, 0x9b, 0x6e, 0x3a, 0xda, 0xaf, 0x0c, 0x92,
0x88, 0xda, 0x88, 0xc0, 0xf0}}.to_key_image();
// A container is selected by an amount specified in an input. ZC outputs have .amount equals to 0. Thus, the input has .amount equals to 0.
input.amount = 0;
tx_1.vin.push_back(input);
CHECK_AND_ASSERT_EQ(tx_1.vin.size(), 1);
}
tx_1.vout.push_back(tx_out_zarcanum{});
tx_1.vout.push_back(tx_out_zarcanum{});
CHECK_AND_ASSERT_EQ(tx_1.vout.size(), 2);
tx_1.extra.push_back(zarcanum_tx_data_v1{TESTS_DEFAULT_FEE});
CHECK_AND_ASSERT_EQ(tx_1.extra.size(), 1);
DO_CALLBACK(events, "mark_invalid_tx");
ADD_CUSTOM_EVENT(events, tx_1);
DO_CALLBACK_PARAMS_STR(events, "assert_htlc_input_refers_zarcanum_output_is_wrong", t_serializable_object_to_blob(tx_1));
}
return true;
}
bool input_refers_to_incompatible_by_type_output::assert_htlc_input_refers_to_key_output_is_wrong(const currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events) const
{
transaction tx{};
CHECK_AND_ASSERT_EQ(t_unserializable_object_from_blob(tx, boost::get<const callback_entry>(events.at(ev_index)).callback_params), true);
CHECK_AND_ASSERT_EQ(tx.vin.front().type(), typeid(txin_htlc));
{
uint64_t max_related_block_height{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().check_tx_input(tx, 0, boost::get<const txin_htlc>(tx.vin.front()), get_transaction_hash(tx), max_related_block_height), false);
}
{
std::vector<public_key> keys{};
uint64_t max_related_block_height{};
uint64_t source_max_unlock_time_for_pos_coinbase{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().get_output_keys_for_input_with_checks(tx, tx.vin.front(), keys, max_related_block_height, source_max_unlock_time_for_pos_coinbase), false);
}
return true;
}
bool input_refers_to_incompatible_by_type_output::assert_to_key_input_refers_zarcanum_output_is_wrong(const currency::core& c, const size_t ev_index, const std::vector<test_event_entry>& events) const
{
transaction tx{};
CHECK_AND_ASSERT_EQ(t_unserializable_object_from_blob(tx, boost::get<const callback_entry>(events.at(ev_index)).callback_params), true);
CHECK_AND_ASSERT_EQ(tx.vin.front().type(), typeid(txin_to_key));
{
uint64_t max_related_block_height{};
uint64_t source_max_unlock_time_for_pos_coinbase{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().check_tx_input(tx, 0, boost::get<const txin_to_key>(tx.vin.front()), get_transaction_hash(tx), max_related_block_height,
source_max_unlock_time_for_pos_coinbase), false);
}
{
std::vector<public_key> keys{};
uint64_t max_related_block_height{};
uint64_t source_max_unlock_time_for_pos_coinbase{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().get_output_keys_for_input_with_checks(tx, tx.vin.front(), keys, max_related_block_height, source_max_unlock_time_for_pos_coinbase), false);
}
return true;
}
bool input_refers_to_incompatible_by_type_output::assert_zc_input_refers_bare_output_is_wrong(const currency::core& c, const size_t ev_index, const std::vector<test_event_entry>& events) const
{
transaction tx{};
CHECK_AND_ASSERT_EQ(t_unserializable_object_from_blob(tx, boost::get<const callback_entry>(events.at(ev_index)).callback_params), true);
CHECK_AND_ASSERT_EQ(tx.vin.front().type(), typeid(txin_zc_input));
{
std::vector<public_key> keys{};
uint64_t max_related_block_height{};
uint64_t source_max_unlock_time_for_pos_coinbase{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().get_output_keys_for_input_with_checks(tx, tx.vin.front(), keys, max_related_block_height, source_max_unlock_time_for_pos_coinbase), true);
}
{
blockchain_storage::check_tx_inputs_context ctic{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().check_tx_input(tx, 0, boost::get<const txin_zc_input>(tx.vin.front()), get_transaction_hash(tx), ctic), false);
}
return true;
}
bool input_refers_to_incompatible_by_type_output::assert_htlc_input_refers_zarcanum_output_is_wrong(const currency::core& c, const size_t ev_index, const std::vector<test_event_entry>& events) const
{
transaction tx{};
CHECK_AND_ASSERT_EQ(t_unserializable_object_from_blob(tx, boost::get<const callback_entry>(events.at(ev_index)).callback_params), true);
CHECK_AND_ASSERT_EQ(tx.vin.front().type(), typeid(txin_htlc));
{
uint64_t max_related_block_height{};
uint64_t source_max_unlock_time_for_pos_coinbase{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().check_tx_input(tx, 0, boost::get<const txin_htlc>(tx.vin.front()), get_transaction_hash(tx), max_related_block_height,
source_max_unlock_time_for_pos_coinbase), false);
}
{
std::vector<public_key> keys{};
uint64_t max_related_block_height{};
uint64_t source_max_unlock_time_for_pos_coinbase{};
CHECK_AND_ASSERT_EQ(c.get_blockchain_storage().get_output_keys_for_input_with_checks(tx, tx.vin.front(), keys, max_related_block_height, source_max_unlock_time_for_pos_coinbase), false);
}
return true;
}
//------------------------------------------------------------------
tx_pool_validation_and_chain_switch::tx_pool_validation_and_chain_switch()
{
REGISTER_CALLBACK_METHOD(tx_pool_validation_and_chain_switch, c1);
}
bool tx_pool_validation_and_chain_switch::generate(std::vector<test_event_entry>& events) const
{
// Test idea: make shure that if a tx in the tx pool doesn't satisfy HF4 coinage rule, it won't be added to the block template
// UNTIL the current height is enough so the rule is obeyed.
uint64_t ts = test_core_time::get_time();
m_accounts.resize(TOTAL_ACCS_COUNT);
account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate();
account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate();
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts);
DO_CALLBACK(events, "configure_core");
std::list<currency::account_base> miner_stake_sources( {miner_acc} );
REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2);
MAKE_TX(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(100), blk_0r);
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0);
// 0 ... 20 21 22 23 24 25 26 27 28 29 30 31 <- height
// (0 )- (0r)- {1 }- {2 }- {3 }- {4 }- {5 }- {6 }- {7 }- {8 }- {9 }- {10}- {11}- <- chain M
// tx_0 \ tx_a
// - (4a)- {5a}- (6a)- {7a}- <- chain A
MAKE_NEXT_POS_BLOCK(events, blk_2, blk_1, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_3, blk_2, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_4, blk_3, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_5, blk_4, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_6, blk_5, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_7, blk_6, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_8, blk_7, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_9, blk_8, miner_acc, miner_stake_sources);
MAKE_NEXT_POS_BLOCK(events, blk_10, blk_9, miner_acc, miner_stake_sources);
// now Alice should be able to spend her coins
MAKE_TX(events, tx_a, alice_acc, miner_acc, MK_TEST_COINS(100) - TESTS_DEFAULT_FEE, blk_10);
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
MAKE_NEXT_POS_BLOCK_TX1(events, blk_11, blk_10, miner_acc, miner_stake_sources, tx_a);
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(0));
// construct chain A as PoW-PoS-PoW-PoS, it should win
MAKE_NEXT_BLOCK(events, blk_4a, blk_3, miner_acc);
MAKE_NEXT_POS_BLOCK(events, blk_5a, blk_4a, miner_acc, miner_stake_sources);
MAKE_NEXT_BLOCK(events, blk_6a, blk_5a, miner_acc);
MAKE_NEXT_POS_BLOCK(events, blk_7a, blk_6a, miner_acc, miner_stake_sources);
// make sure it won
DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(blk_7a));
// now tx_a should have been put back to the tx pool
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(1));
// the next block should be rejected, because of max_related_block_height=21 and the current height is 28
DO_CALLBACK(events, "mark_invalid_block");
MAKE_NEXT_BLOCK_TX1(events, blk_8a, blk_7a, miner_acc, tx_a);
// and if we clear tx pool ...
DO_CALLBACK(events, "clear_tx_pool");
DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast<size_t>(0));
// and re-add the transaction on this height, it should be added
ADD_CUSTOM_EVENT(events, tx_a);
// however, we need to make sure that tx pool won't be use tx_a in a block template until the height is good enough
DO_CALLBACK(events, "c1");
return true;
}
bool tx_pool_validation_and_chain_switch::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
// 0 ... 20 21 22 23 24 25 26 27 28 29 30 31 <- height
// (0 )- (0r)- {1 }- {2 }- {3 }- {4 }- {5 }- {6 }- {7 }- {8 }- {9 }- {10}- {11}- <- chain M
// tx_0 \ tx_a
// - (4a)- {5a}- (6a)- {7a}- ( )- ( )- ( )- ( )- <- chain A
// | tx_a tx_a re-added in A chain (in mine_next_pow_block_in_playtime() call)
// c1 <- callback called
std::shared_ptr<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX);
std::shared_ptr<tools::wallet2> alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX);
bool r = false;
alice_wlt->refresh();
// make sure tx_a is still in the pool
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// and Alice's unconfirmed balance is nonzero
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(100)), false, "");
// mine a block, make sure it is possible and the tx is still in the pool afterwards
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// make sure tx_a is still in the pool
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// and Alice's unconfirmed balance is nonzero
alice_wlt->refresh();
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(100)), false, "");
// mine more
r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 2);
// make sure tx_a is still in the pool
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// and Alice's unconfirmed balance is nonzero
alice_wlt->refresh();
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(100)), false, "");
// the next blocktemplate should include it
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
// make sure it did
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// and Alice's unconfirmed balance is zero
alice_wlt->refresh();
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(0)), false, "");
return true;
}
// Сoinbase transactions must NOT allow the TX_FLAG_SIGNATURE_MODE_SEPARATE flag.
// Сhecks that setting this flag for coinbase fails, while a default coinbase (without the flag) succeeds.
bool tx_coinbase_separate_sig_flag::generate(std::vector<test_event_entry>& events) const
{
GENERATE_ACCOUNT(miner);
uint64_t ts = test_core_time::get_time();
MAKE_GENESIS_BLOCK(events, blk_0, miner, ts);
DO_CALLBACK(events, "configure_core");
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1);
block blk_2;
auto coinbase_default_cb = [](transaction& miner_tx, const keypair&) -> bool { return true; };
auto coinbase_separate_cb = [](transaction& miner_tx, const keypair&) -> bool
{
set_tx_flags(miner_tx, get_tx_flags(miner_tx) | TX_FLAG_SIGNATURE_MODE_SEPARATE);
return true;
};
// сonstruct a block with the forbidden flag, should fail after hf4
bool with_separate_flag = generator.construct_block_gentime_with_coinbase_cb(blk_1r, miner, coinbase_separate_cb, blk_2);
CHECK_AND_ASSERT_MES(with_separate_flag, false, "expected failure because TX_FLAG_SIGNATURE_MODE_SEPARATE is forbidden for coinbase after HF4");
DO_CALLBACK(events, "mark_invalid_block");
events.push_back(blk_2);
// construct a default coinbase block, should succeed
bool default_tx = generator.construct_block_gentime_with_coinbase_cb(blk_1r, miner, coinbase_default_cb, blk_2);
CHECK_AND_ASSERT_MES(default_tx, true, "default coinbase must succeed");
events.push_back(blk_2);
return true;
}
tx_input_mixins::tx_input_mixins()
{
REGISTER_CALLBACK_METHOD(tx_input_mixins, configure_core);
}
bool tx_input_mixins::configure_core(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
currency::core_runtime_config pc = c.get_blockchain_storage().get_core_runtime_config();
pc.min_coinstake_age = TESTS_POS_CONFIG_MIN_COINSTAKE_AGE;
pc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH;
pc.hf4_minimum_mixins = 5;
pc.hard_forks.set_hardfork_height(1, 0);
pc.hard_forks.set_hardfork_height(2, 1);
pc.hard_forks.set_hardfork_height(3, 1);
pc.hard_forks.set_hardfork_height(4, 31);
c.get_blockchain_storage().set_core_runtime_config(pc);
return true;
}
// Tests that post-HF4, legacy txin_to_key inputs accept any mixin count, while new txin_zc inputs enforce a >= 15 mixin minimum
// are in the same vin and work together
bool tx_input_mixins::generate(std::vector<test_event_entry>& events) const
{
uint64_t ts = test_core_time::get_time();
GENERATE_ACCOUNT(miner_acc);
GENERATE_ACCOUNT(alice_acc);
GENERATE_ACCOUNT(bob_acc);
m_hardforks.set_hardfork_height(1, 0);
m_hardforks.set_hardfork_height(2, 1);
m_hardforks.set_hardfork_height(3, 1);
m_hardforks.set_hardfork_height(4, 31);
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts);
DO_CALLBACK(events, "configure_core");
// mine one block, then rewind enough to unlock initial coinbase funds
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW*2);
// build tx_a_1: single txin_to_key output of 15 coins to Alice
transaction tx_a_1;
bool r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(),
alice_acc.get_public_address(), MK_TEST_COINS(15), 1, TESTS_DEFAULT_FEE, tx_a_1);
CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs 1 failed");
events.push_back(tx_a_1);
// build tx_a_2: 16 outputs of 15 coins each to Bob for mixins
transaction tx_a_2;
r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(),
bob_acc.get_public_address(), MK_TEST_COINS(15*16), 16, TESTS_DEFAULT_FEE, tx_a_2);
CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs 2 failed");
events.push_back(tx_a_2);
MAKE_NEXT_BLOCK_TX_LIST(events, blk_1r_1, blk_1r, miner_acc, std::list<transaction>({ tx_a_1, tx_a_2 }));
// mine a couple more blocks and rewind to generate new spendable outputs
MAKE_NEXT_BLOCK(events, blk_2, blk_1r_1, miner_acc);
REWIND_BLOCKS_N(events, blk_2r, blk_2, miner_acc, 4);
MAKE_NEXT_BLOCK(events, blk_3, blk_2r, miner_acc);
REWIND_BLOCKS_N(events, blk_4r, blk_3, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
// make sure HF4 is active at 31 height
DO_CALLBACK_PARAMS(events, "check_hardfork_active", size_t{ZANO_HARDFORK_04_ZARCANUM});
// build tx_b with zc hf4 input, send 15 coins from miner -> Alice
std::vector<currency::tx_source_entry> sources_b;
std::vector<currency::tx_destination_entry> destinations_b;
CHECK_AND_ASSERT_MES(fill_tx_sources_and_destinations(
events, blk_4r, miner_acc, alice_acc, MK_TEST_COINS(15), TESTS_DEFAULT_FEE, 2, sources_b, destinations_b),
false, "fill_tx_sources_and_destinations failed");
currency::transaction tx_b{};
r = construct_tx(miner_acc.get_keys(), sources_b, destinations_b, events, this, tx_b);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
events.push_back(tx_b);
// mine tx_b and rewind to unlock its outputs
MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_acc, tx_b);
REWIND_BLOCKS_N(events, blk_5r, blk_5, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
std::vector<currency::tx_source_entry> sources_c;
std::vector<currency::tx_destination_entry> destinations_c;
// For input #0 use 5 mixins (old-style ring-CT allows any mixins)
// For input #1 use 16 mixins (new-style txin_zc enforces >=15 once HF4 is live)
mixins_per_input nmix_map = { {0, 5}, {1, 16} };
CHECK_AND_ASSERT_MES(fill_tx_sources_and_destinations(
events, blk_5r, alice_acc, bob_acc, MK_TEST_COINS(29), TESTS_DEFAULT_FEE, 5, sources_c, destinations_c,
true, true, false, &nmix_map), false, "fill_tx_sources_and_destinations failed");
currency::transaction tx_c{};
r = construct_tx(alice_acc.get_keys(), sources_c, destinations_c, events, this, tx_c);
LOG_PRINT_GREEN("tx_c alice -> bob \n" << obj_to_json_str(tx_c), LOG_LEVEL_0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
events.push_back(tx_c);
MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5r, miner_acc, tx_c);
return true;
}