diff options
Diffstat (limited to 'tests/core_tests/chaingen.cpp')
-rw-r--r-- | tests/core_tests/chaingen.cpp | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp new file mode 100644 index 000000000..c876e3a75 --- /dev/null +++ b/tests/core_tests/chaingen.cpp @@ -0,0 +1,637 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <vector> +#include <iostream> +#include <sstream> + +#include "include_base_utils.h" + +#include "console_handler.h" + +#include "p2p/net_node.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/miner.h" + +#include "chaingen.h" + +using namespace std; + +using namespace epee; +using namespace cryptonote; + + +void test_generator::get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const +{ + crypto::hash curr = head; + while (null_hash != curr && blockchain.size() < n) + { + auto it = m_blocks_info.find(curr); + if (m_blocks_info.end() == it) + { + throw std::runtime_error("block hash wasn't found"); + } + + blockchain.push_back(it->second); + curr = it->second.prev_id; + } + + std::reverse(blockchain.begin(), blockchain.end()); +} + +void test_generator::get_last_n_block_sizes(std::vector<size_t>& block_sizes, const crypto::hash& head, size_t n) const +{ + std::vector<block_info> blockchain; + get_block_chain(blockchain, head, n); + BOOST_FOREACH(auto& bi, blockchain) + { + block_sizes.push_back(bi.block_size); + } +} + +uint64_t test_generator::get_already_generated_coins(const crypto::hash& blk_id) const +{ + auto it = m_blocks_info.find(blk_id); + if (it == m_blocks_info.end()) + throw std::runtime_error("block hash wasn't found"); + + return it->second.already_generated_coins; +} + +uint64_t test_generator::get_already_generated_coins(const cryptonote::block& blk) const +{ + crypto::hash blk_hash; + get_block_hash(blk, blk_hash); + return get_already_generated_coins(blk_hash); +} + +void test_generator::add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins) +{ + const size_t block_size = tsx_size + get_object_blobsize(blk.miner_tx); + bool block_too_big; + uint64_t block_reward = get_block_reward(block_sizes, block_size, block_too_big, already_generated_coins); + m_blocks_info[get_block_hash(blk)] = block_info(blk.prev_id, already_generated_coins + block_reward, block_size); +} + +bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id, + const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins, + std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list) +{ + blk.major_version = CURRENT_BLOCK_MAJOR_VERSION; + blk.minor_version = CURRENT_BLOCK_MINOR_VERSION; + blk.timestamp = timestamp; + blk.prev_id = prev_id; + + blk.tx_hashes.reserve(tx_list.size()); + BOOST_FOREACH(const transaction &tx, tx_list) + { + crypto::hash tx_hash; + get_transaction_hash(tx, tx_hash); + blk.tx_hashes.push_back(tx_hash); + } + + uint64_t total_fee = 0; + size_t txs_size = 0; + BOOST_FOREACH(auto& tx, tx_list) + { + uint64_t fee = 0; + bool r = get_tx_fee(tx, fee); + CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); + total_fee += fee; + txs_size += get_object_blobsize(tx); + } + + blk.miner_tx = AUTO_VAL_INIT(blk.miner_tx); + size_t target_block_size = txs_size + get_object_blobsize(blk.miner_tx); + while (true) + { + if (!construct_miner_tx(height, already_generated_coins, miner_acc.get_keys().m_account_address, blk.miner_tx, total_fee, block_sizes, target_block_size, 10)) + return false; + + size_t actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); + if (target_block_size < actual_block_size) + { + target_block_size = actual_block_size; + } + else if (actual_block_size < target_block_size) + { + size_t delta = target_block_size - actual_block_size; + blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); + actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); + if (actual_block_size == target_block_size) + { + break; + } + else + { + CHECK_AND_ASSERT_MES(target_block_size < actual_block_size, false, "Unexpected block size"); + delta = actual_block_size - target_block_size; + blk.miner_tx.extra.resize(blk.miner_tx.extra.size() - delta); + actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); + if (actual_block_size == target_block_size) + { + break; + } + else + { + CHECK_AND_ASSERT_MES(actual_block_size < target_block_size, false, "Unexpected block size"); + blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0); + target_block_size = txs_size + get_object_blobsize(blk.miner_tx); + } + } + } + else + { + break; + } + } + + //blk.tree_root_hash = get_tx_tree_hash(blk); + + // Nonce search... + blk.nonce = 0; + while (!miner::find_nonce_for_given_block(blk, get_test_difficulty(), height)) + blk.timestamp++; + + add_block(blk, txs_size, block_sizes, already_generated_coins); + + return true; +} + +bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::account_base& miner_acc, uint64_t timestamp) +{ + std::vector<size_t> block_sizes; + std::list<cryptonote::transaction> tx_list; + return construct_block(blk, 0, null_hash, miner_acc, timestamp, 0, block_sizes, tx_list); +} + +bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::block& blk_prev, + const cryptonote::account_base& miner_acc, + const std::list<cryptonote::transaction>& tx_list/* = std::list<cryptonote::transaction>()*/) +{ + uint64_t height = boost::get<txin_gen>(blk_prev.miner_tx.vin.front()).height + 1; + crypto::hash prev_id = get_block_hash(blk_prev); + // Keep difficulty unchanged + uint64_t timestamp = blk_prev.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN; + uint64_t already_generated_coins = get_already_generated_coins(prev_id); + std::vector<size_t> block_sizes; + get_last_n_block_sizes(block_sizes, prev_id, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + + return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, block_sizes, tx_list); +} + +bool test_generator::construct_block_manually(block& blk, const block& prev_block, const account_base& miner_acc, + int actual_params/* = bf_none*/, uint8_t major_ver/* = 0*/, + uint8_t minor_ver/* = 0*/, uint64_t timestamp/* = 0*/, + const crypto::hash& prev_id/* = crypto::hash()*/, const difficulty_type& diffic/* = 1*/, + const transaction& miner_tx/* = transaction()*/, + const std::vector<crypto::hash>& tx_hashes/* = std::vector<crypto::hash>()*/, + size_t txs_sizes/* = 0*/) +{ + blk.major_version = actual_params & bf_major_ver ? major_ver : CURRENT_BLOCK_MAJOR_VERSION; + blk.minor_version = actual_params & bf_minor_ver ? minor_ver : CURRENT_BLOCK_MINOR_VERSION; + blk.timestamp = actual_params & bf_timestamp ? timestamp : prev_block.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN; // Keep difficulty unchanged + blk.prev_id = actual_params & bf_prev_id ? prev_id : get_block_hash(prev_block); + blk.tx_hashes = actual_params & bf_tx_hashes ? tx_hashes : std::vector<crypto::hash>(); + + size_t height = get_block_height(prev_block) + 1; + uint64_t already_generated_coins = get_already_generated_coins(prev_block); + std::vector<size_t> block_sizes; + get_last_n_block_sizes(block_sizes, get_block_hash(prev_block), CRYPTONOTE_REWARD_BLOCKS_WINDOW); + if (actual_params & bf_miner_tx) + { + blk.miner_tx = miner_tx; + } + else + { + size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx); + // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + if (!construct_miner_tx(height, already_generated_coins, miner_acc.get_keys().m_account_address, blk.miner_tx, 0, block_sizes, current_block_size, 1)) + return false; + } + + //blk.tree_root_hash = get_tx_tree_hash(blk); + + difficulty_type a_diffic = actual_params & bf_diffic ? diffic : get_test_difficulty(); + fill_nonce(blk, a_diffic, height); + + add_block(blk, txs_sizes, block_sizes, already_generated_coins); + + return true; +} + +bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, + const cryptonote::account_base& miner_acc, + const std::vector<crypto::hash>& tx_hashes, size_t txs_size) +{ + return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, transaction(), tx_hashes, txs_size); +} + + +struct output_index { + const cryptonote::txout_target_v out; + uint64_t amount; + size_t blk_height; // block height + size_t tx_no; // index of transaction in block + size_t out_no; // index of out in transaction + size_t idx; + bool spent; + const cryptonote::block *p_blk; + const cryptonote::transaction *p_tx; + + output_index(const cryptonote::txout_target_v &_out, uint64_t _a, size_t _h, size_t tno, size_t ono, const cryptonote::block *_pb, const cryptonote::transaction *_pt) + : out(_out), amount(_a), blk_height(_h), tx_no(tno), out_no(ono), idx(0), spent(false), p_blk(_pb), p_tx(_pt) { } + + output_index(const output_index &other) + : out(other.out), amount(other.amount), blk_height(other.blk_height), tx_no(other.tx_no), out_no(other.out_no), idx(other.idx), spent(other.spent), p_blk(other.p_blk), p_tx(other.p_tx) { } + + const std::string toString() const { + std::stringstream ss; + + ss << "output_index{blk_height=" << blk_height + << " tx_no=" << tx_no + << " out_no=" << out_no + << " amount=" << amount + << " idx=" << idx + << " spent=" << spent + << "}"; + + return ss.str(); + } + + output_index& operator=(const output_index& other) + { + new(this) output_index(other); + return *this; + } +}; + +typedef std::map<uint64_t, std::vector<size_t> > map_output_t; +typedef std::map<uint64_t, std::vector<output_index> > map_output_idx_t; +typedef pair<uint64_t, size_t> outloc_t; + +namespace +{ + uint64_t get_inputs_amount(const vector<tx_source_entry> &s) + { + uint64_t r = 0; + BOOST_FOREACH(const tx_source_entry &e, s) + { + r += e.amount; + } + + return r; + } +} + +bool init_output_indices(map_output_idx_t& outs, std::map<uint64_t, std::vector<size_t> >& outs_mine, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { + + BOOST_FOREACH (const block& blk, blockchain) { + vector<const transaction*> vtx; + vtx.push_back(&blk.miner_tx); + + BOOST_FOREACH(const crypto::hash &h, blk.tx_hashes) { + const map_hash2tx_t::const_iterator cit = mtx.find(h); + if (mtx.end() == cit) + throw std::runtime_error("block contains an unknown tx hash"); + + vtx.push_back(cit->second); + } + + //vtx.insert(vtx.end(), blk.); + // TODO: add all other txes + for (size_t i = 0; i < vtx.size(); i++) { + const transaction &tx = *vtx[i]; + + for (size_t j = 0; j < tx.vout.size(); ++j) { + const tx_out &out = tx.vout[j]; + + output_index oi(out.target, out.amount, boost::get<txin_gen>(*blk.miner_tx.vin.begin()).height, i, j, &blk, vtx[i]); + + if (2 == out.target.which()) { // out_to_key + outs[out.amount].push_back(oi); + size_t tx_global_idx = outs[out.amount].size() - 1; + outs[out.amount][tx_global_idx].idx = tx_global_idx; + // Is out to me? + if (is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), j)) { + outs_mine[out.amount].push_back(tx_global_idx); + } + } + } + } + } + + return true; +} + +bool init_spent_output_indices(map_output_idx_t& outs, map_output_t& outs_mine, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, const cryptonote::account_base& from) { + + BOOST_FOREACH (const map_output_t::value_type &o, outs_mine) { + for (size_t i = 0; i < o.second.size(); ++i) { + output_index &oi = outs[o.first][o.second[i]]; + + // construct key image for this output + crypto::key_image img; + keypair in_ephemeral; + generate_key_image_helper(from.get_keys(), get_tx_pub_key_from_extra(*oi.p_tx), oi.out_no, in_ephemeral, img); + + // lookup for this key image in the events vector + BOOST_FOREACH(auto& tx_pair, mtx) { + const transaction& tx = *tx_pair.second; + BOOST_FOREACH(const txin_v &in, tx.vin) { + if (typeid(txin_to_key) == in.type()) { + const txin_to_key &itk = boost::get<txin_to_key>(in); + if (itk.k_image == img) { + oi.spent = true; + } + } + } + } + } + } + + return true; +} + +bool fill_output_entries(std::vector<output_index>& out_indices, size_t sender_out, size_t nmix, size_t& real_entry_idx, std::vector<tx_source_entry::output_entry>& output_entries) +{ + if (out_indices.size() <= nmix) + return false; + + bool sender_out_found = false; + size_t rest = nmix; + for (size_t i = 0; i < out_indices.size() && (0 < rest || !sender_out_found); ++i) + { + const output_index& oi = out_indices[i]; + if (oi.spent) + continue; + + bool append = false; + if (i == sender_out) + { + append = true; + sender_out_found = true; + real_entry_idx = output_entries.size(); + } + else if (0 < rest) + { + --rest; + append = true; + } + + if (append) + { + const txout_to_key& otk = boost::get<txout_to_key>(oi.out); + output_entries.push_back(tx_source_entry::output_entry(oi.idx, otk.key)); + } + } + + return 0 == rest && sender_out_found; +} + +bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<test_event_entry>& events, + const block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix) +{ + map_output_idx_t outs; + map_output_t outs_mine; + + std::vector<cryptonote::block> blockchain; + map_hash2tx_t mtx; + if (!find_block_chain(events, blockchain, mtx, get_block_hash(blk_head))) + return false; + + if (!init_output_indices(outs, outs_mine, blockchain, mtx, from)) + return false; + + if (!init_spent_output_indices(outs, outs_mine, blockchain, mtx, from)) + return false; + + // Iterate in reverse is more efficiency + uint64_t sources_amount = 0; + bool sources_found = false; + BOOST_REVERSE_FOREACH(const map_output_t::value_type o, outs_mine) + { + for (size_t i = 0; i < o.second.size() && !sources_found; ++i) + { + size_t sender_out = o.second[i]; + const output_index& oi = outs[o.first][sender_out]; + if (oi.spent) + continue; + + cryptonote::tx_source_entry ts; + ts.amount = oi.amount; + ts.real_output_in_tx_index = oi.out_no; + ts.real_out_tx_key = get_tx_pub_key_from_extra(*oi.p_tx); // incoming tx public key + if (!fill_output_entries(outs[o.first], sender_out, nmix, ts.real_output, ts.outputs)) + continue; + + sources.push_back(ts); + + sources_amount += ts.amount; + sources_found = amount <= sources_amount; + } + + if (sources_found) + break; + } + + return sources_found; +} + +bool fill_tx_destination(tx_destination_entry &de, const cryptonote::account_base &to, uint64_t amount) { + de.addr = to.get_keys().m_account_address; + de.amount = amount; + return true; +} + +void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const block& blk_head, + const cryptonote::account_base& from, const cryptonote::account_base& to, + uint64_t amount, uint64_t fee, size_t nmix, std::vector<tx_source_entry>& sources, + std::vector<tx_destination_entry>& destinations) +{ + sources.clear(); + destinations.clear(); + + if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix)) + throw std::runtime_error("couldn't fill transaction sources"); + + tx_destination_entry de; + if (!fill_tx_destination(de, to, amount)) + throw std::runtime_error("couldn't fill transaction destination"); + destinations.push_back(de); + + tx_destination_entry de_change; + uint64_t cache_back = get_inputs_amount(sources) - (amount + fee); + if (0 < cache_back) + { + if (!fill_tx_destination(de_change, from, cache_back)) + throw std::runtime_error("couldn't fill transaction cache back destination"); + destinations.push_back(de_change); + } +} + +void fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) +{ + blk.nonce = 0; + while (!miner::find_nonce_for_given_block(blk, diffic, height)) + blk.timestamp++; +} + +bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins, + const account_public_address& miner_address, transaction& tx, uint64_t fee, + keypair* p_txkey/* = 0*/) +{ + keypair txkey; + txkey = keypair::generate(); + add_tx_pub_key_to_extra(tx, txkey.pub); + + if (0 != p_txkey) + *p_txkey = txkey; + + txin_gen in; + in.height = height; + tx.vin.push_back(in); + + // This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + std::vector<size_t> block_sizes; + bool block_too_big = false; + uint64_t block_reward = get_block_reward(block_sizes, 0, block_too_big, already_generated_coins) + fee; + if (block_too_big) + { + LOG_PRINT_L0("Block is too big"); + return false; + } + + crypto::key_derivation derivation; + crypto::public_key out_eph_public_key; + crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); + crypto::derive_public_key(derivation, 0, miner_address.m_spend_public_key, out_eph_public_key); + + tx_out out; + out.amount = block_reward; + out.target = txout_to_key(out_eph_public_key); + tx.vout.push_back(out); + + tx.version = CURRENT_TRANSACTION_VERSION; + tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + + return true; +} + +bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const block& blk_head, + const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, + uint64_t fee, size_t nmix) +{ + vector<tx_source_entry> sources; + vector<tx_destination_entry> destinations; + fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, nmix, sources, destinations); + + return construct_tx(from.get_keys(), sources, destinations, tx, 0); +} + +transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head, + const account_base& acc_from, const account_base& acc_to, uint64_t amount, uint64_t fee) +{ + transaction tx; + construct_tx_to_key(events, tx, blk_head, acc_from, acc_to, amount, fee, 0); + events.push_back(tx); + return tx; +} + +uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx) { + uint64_t res = 0; + std::map<uint64_t, std::vector<output_index> > outs; + std::map<uint64_t, std::vector<size_t> > outs_mine; + + map_hash2tx_t confirmed_txs; + get_confirmed_txs(blockchain, mtx, confirmed_txs); + + if (!init_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) + return false; + + if (!init_spent_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr)) + return false; + + BOOST_FOREACH (const map_output_t::value_type &o, outs_mine) { + for (size_t i = 0; i < o.second.size(); ++i) { + if (outs[o.first][o.second[i]].spent) + continue; + + res += outs[o.first][o.second[i]].amount; + } + } + + return res; +} + +void get_confirmed_txs(const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) +{ + std::unordered_set<crypto::hash> confirmed_hashes; + BOOST_FOREACH(const block& blk, blockchain) + { + BOOST_FOREACH(const crypto::hash& tx_hash, blk.tx_hashes) + { + confirmed_hashes.insert(tx_hash); + } + } + + BOOST_FOREACH(const auto& tx_pair, mtx) + { + if (0 != confirmed_hashes.count(tx_pair.first)) + { + confirmed_txs.insert(tx_pair); + } + } +} + +bool find_block_chain(const std::vector<test_event_entry>& events, std::vector<cryptonote::block>& blockchain, map_hash2tx_t& mtx, const crypto::hash& head) { + std::unordered_map<crypto::hash, const block*> block_index; + BOOST_FOREACH(const test_event_entry& ev, events) + { + if (typeid(block) == ev.type()) + { + const block* blk = &boost::get<block>(ev); + block_index[get_block_hash(*blk)] = blk; + } + else if (typeid(transaction) == ev.type()) + { + const transaction& tx = boost::get<transaction>(ev); + mtx[get_transaction_hash(tx)] = &tx; + } + } + + bool b_success = false; + crypto::hash id = head; + for (auto it = block_index.find(id); block_index.end() != it; it = block_index.find(id)) + { + blockchain.push_back(*it->second); + id = it->second->prev_id; + if (null_hash == id) + { + b_success = true; + break; + } + } + reverse(blockchain.begin(), blockchain.end()); + + return b_success; +} + + +void test_chain_unit_base::register_callback(const std::string& cb_name, verify_callback cb) +{ + m_callbacks[cb_name] = cb; +} +bool test_chain_unit_base::verify(const std::string& cb_name, cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events) +{ + auto cb_it = m_callbacks.find(cb_name); + if(cb_it == m_callbacks.end()) + { + LOG_ERROR("Failed to find callback " << cb_name); + return false; + } + return cb_it->second(c, ev_index, events); +} |