// Copyright (c) 2011-2016 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 #include #include #include #include #include #include "Common/CommandLine.h" #include "CryptoNoteCore/CryptoNoteBasic.h" #include "CryptoNoteCore/CryptoNoteBasicImpl.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "CryptoNoteCore/CryptoNoteTools.h" #include "CryptoNoteCore/Core.h" #include "CryptoNoteCore/Currency.h" //#include "AccountBoostSerialization.h" //#include "cryptonote_boost_serialization.h" using namespace std; using namespace CryptoNote; struct output_index { const CryptoNote::TransactionOutputTarget 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 uint32_t idx; bool spent; const CryptoNote::Block *p_blk; const CryptoNote::Transaction *p_tx; output_index(const CryptoNote::TransactionOutputTarget &_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 > map_output_t; typedef std::map > map_output_idx_t; typedef pair outloc_t; namespace { uint64_t get_inputs_amount(const vector &s) { uint64_t r = 0; BOOST_FOREACH(const TransactionSourceEntry &e, s) { r += e.amount; } return r; } } bool init_output_indices(map_output_idx_t& outs, std::map >& outs_mine, const std::vector& blockchain, const map_hash2tx_t& mtx, const CryptoNote::AccountBase& from) { BOOST_FOREACH (const Block& blk, blockchain) { vector vtx; vtx.push_back(&blk.baseTransaction); for (const Crypto::Hash& h : blk.transactionHashes) { 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]; size_t keyIndex = 0; for (size_t j = 0; j < tx.outputs.size(); ++j) { const TransactionOutput &out = tx.outputs[j]; if (out.target.type() == typeid(KeyOutput)) { output_index oi(out.target, out.amount, boost::get(*blk.baseTransaction.inputs.begin()).blockIndex, i, j, &blk, vtx[i]); outs[out.amount].push_back(oi); uint32_t tx_global_idx = static_cast(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.getAccountKeys(), boost::get(out.target), getTransactionPublicKeyFromExtra(tx.extra), keyIndex)) { outs_mine[out.amount].push_back(tx_global_idx); } ++keyIndex; } else if (out.target.type() == typeid(MultisignatureOutput)) { keyIndex += boost::get(out.target).keys.size(); } } } } return true; } bool init_spent_output_indices(map_output_idx_t& outs, map_output_t& outs_mine, const std::vector& blockchain, const map_hash2tx_t& mtx, const CryptoNote::AccountBase& from) { for (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::KeyImage img; KeyPair in_ephemeral; generate_key_image_helper(from.getAccountKeys(), getTransactionPublicKeyFromExtra(oi.p_tx->extra), oi.out_no, in_ephemeral, img); // lookup for this key image in the events vector for (auto& tx_pair : mtx) { const Transaction& tx = *tx_pair.second; for (const auto& in : tx.inputs) { if (in.type() == typeid(KeyInput)) { const KeyInput &itk = boost::get(in); if (itk.keyImage == img) { oi.spent = true; } } } } } } return true; } bool fill_output_entries(std::vector& out_indices, size_t sender_out, size_t nmix, size_t& real_entry_idx, std::vector& 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 KeyOutput& otk = boost::get(oi.out); output_entries.push_back(TransactionSourceEntry::OutputEntry(oi.idx, otk.key)); } } return 0 == rest && sender_out_found; } bool fill_tx_sources(std::vector& sources, const std::vector& events, const Block& blk_head, const CryptoNote::AccountBase& from, uint64_t amount, size_t nmix) { map_output_idx_t outs; map_output_t outs_mine; std::vector 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::TransactionSourceEntry ts; ts.amount = oi.amount; ts.realOutputIndexInTransaction = oi.out_no; ts.realTransactionPublicKey = getTransactionPublicKeyFromExtra(oi.p_tx->extra); // incoming tx public key size_t realOutput; if (!fill_output_entries(outs[o.first], sender_out, nmix, realOutput, ts.outputs)) continue; ts.realOutput = realOutput; sources.push_back(ts); sources_amount += ts.amount; sources_found = amount <= sources_amount; } if (sources_found) break; } return sources_found; } bool fill_tx_destination(TransactionDestinationEntry &de, const CryptoNote::AccountBase &to, uint64_t amount) { de.addr = to.getAccountKeys().address; de.amount = amount; return true; } void fill_tx_sources_and_destinations(const std::vector& events, const Block& blk_head, const CryptoNote::AccountBase& from, const CryptoNote::AccountBase& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector& sources, std::vector& 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"); TransactionDestinationEntry de; if (!fill_tx_destination(de, to, amount)) throw std::runtime_error("couldn't fill transaction destination"); destinations.push_back(de); TransactionDestinationEntry 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); } } bool construct_tx_to_key(Logging::ILogger& logger, const std::vector& events, CryptoNote::Transaction& tx, const Block& blk_head, const CryptoNote::AccountBase& from, const CryptoNote::AccountBase& to, uint64_t amount, uint64_t fee, size_t nmix) { vector sources; vector destinations; fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, nmix, sources, destinations); return constructTransaction(from.getAccountKeys(), sources, destinations, std::vector(), tx, 0, logger); } Transaction construct_tx_with_fee(Logging::ILogger& logger, std::vector& events, const Block& blk_head, const AccountBase& acc_from, const AccountBase& acc_to, uint64_t amount, uint64_t fee) { Transaction tx; construct_tx_to_key(logger, events, tx, blk_head, acc_from, acc_to, amount, fee, 0); events.push_back(tx); return tx; } uint64_t get_balance(const CryptoNote::AccountBase& addr, const std::vector& blockchain, const map_hash2tx_t& mtx) { uint64_t res = 0; std::map > outs; std::map > 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& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) { std::unordered_set confirmed_hashes; for (const Block& blk : blockchain) { for (const Crypto::Hash& tx_hash : blk.transactionHashes) { 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& events, std::vector& blockchain, map_hash2tx_t& mtx, const Crypto::Hash& head) { std::unordered_map block_index; BOOST_FOREACH(const test_event_entry& ev, events) { if (typeid(Block) == ev.type()) { const Block* blk = &boost::get(ev); block_index[get_block_hash(*blk)] = blk; } else if (typeid(Transaction) == ev.type()) { const Transaction& tx = boost::get(ev); mtx[getObjectHash(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->previousBlockHash; if (NULL_HASH == id) { b_success = true; break; } } reverse(blockchain.begin(), blockchain.end()); return b_success; } const CryptoNote::Currency& test_chain_unit_base::currency() const { return m_currency; } 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 &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); }