diff --git a/crypto/xmrcore/CMakeLists.txt b/crypto/xmrcore/CMakeLists.txt new file mode 100644 index 0000000000..0be8b544e4 --- /dev/null +++ b/crypto/xmrcore/CMakeLists.txt @@ -0,0 +1,69 @@ +# Copyright (c) 2014-2020, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(cryptonote_core_sources + blockchain.cpp + cryptonote_core.cpp + tx_pool.cpp + tx_sanity_check.cpp + cryptonote_tx_utils.cpp) + +set(cryptonote_core_headers) + +set(cryptonote_core_private_headers + blockchain_storage_boost_serialization.h + blockchain.h + cryptonote_core.h + tx_pool.h + tx_sanity_check.h + cryptonote_tx_utils.h) + +monero_private_headers(cryptonote_core + ${cryptonote_core_private_headers}) +monero_add_library(cryptonote_core + ${cryptonote_core_sources} + ${cryptonote_core_headers} + ${cryptonote_core_private_headers}) +target_link_libraries(cryptonote_core + PUBLIC + version + common + cncrypto + blockchain_db + multisig + ringct + device + hardforks + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) diff --git a/crypto/xmrcore/cryptonote_core.cpp b/crypto/xmrcore/cryptonote_core.cpp new file mode 100644 index 0000000000..4c6536318f --- /dev/null +++ b/crypto/xmrcore/cryptonote_core.cpp @@ -0,0 +1,2077 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include +#include + +#include "string_tools.h" +using namespace epee; + +#include +#include "cryptonote_core.h" +#include "common/util.h" +#include "common/updates.h" +#include "common/download.h" +#include "common/threadpool.h" +#include "common/command_line.h" +#include "cryptonote_basic/events.h" +#include "warnings.h" +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "misc_language.h" +#include "file_io_utils.h" +#include +#include "checkpoints/checkpoints.h" +#include "ringct/rctTypes.h" +#include "blockchain_db/blockchain_db.h" +#include "ringct/rctSigs.h" +#include "rpc/zmq_pub.h" +#include "common/notify.h" +#include "hardforks/hardforks.h" +#include "version.h" + +#include + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn" + +DISABLE_VS_WARNINGS(4355) + +#define MERROR_VER(x) MCERROR("verify", x) + +#define BAD_SEMANTICS_TXES_MAX_SIZE 100 + +// basically at least how many bytes the block itself serializes to without the miner tx +#define BLOCK_SIZE_SANITY_LEEWAY 100 + +namespace cryptonote +{ + const command_line::arg_descriptor arg_testnet_on = { + "testnet" + , "Run on testnet. The wallet must be launched with --testnet flag." + , false + }; + const command_line::arg_descriptor arg_stagenet_on = { + "stagenet" + , "Run on stagenet. The wallet must be launched with --stagenet flag." + , false + }; + const command_line::arg_descriptor arg_regtest_on = { + "regtest" + , "Run in a regression testing mode." + , false + }; + const command_line::arg_descriptor arg_keep_fakechain = { + "keep-fakechain" + , "Don't delete any existing database when in fakechain mode." + , false + }; + const command_line::arg_descriptor arg_fixed_difficulty = { + "fixed-difficulty" + , "Fixed difficulty used for testing." + , 0 + }; + const command_line::arg_descriptor arg_data_dir = { + "data-dir" + , "Specify data directory" + , tools::get_default_data_dir() + , {{ &arg_testnet_on, &arg_stagenet_on }} + , [](std::array testnet_stagenet, bool defaulted, std::string val)->std::string { + if (testnet_stagenet[0]) + return (boost::filesystem::path(val) / "testnet").string(); + else if (testnet_stagenet[1]) + return (boost::filesystem::path(val) / "stagenet").string(); + return val; + } + }; + const command_line::arg_descriptor arg_offline = { + "offline" + , "Do not listen for peers, nor connect to any" + }; + const command_line::arg_descriptor arg_disable_dns_checkpoints = { + "disable-dns-checkpoints" + , "Do not retrieve checkpoints from DNS" + }; + const command_line::arg_descriptor arg_block_download_max_size = { + "block-download-max-size" + , "Set maximum size of block download queue in bytes (0 for default)" + , 0 + }; + const command_line::arg_descriptor arg_sync_pruned_blocks = { + "sync-pruned-blocks" + , "Allow syncing from nodes with only pruned blocks" + }; + + static const command_line::arg_descriptor arg_test_drop_download = { + "test-drop-download" + , "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)" + }; + static const command_line::arg_descriptor arg_test_drop_download_height = { + "test-drop-download-height" + , "Like test-drop-download but discards only after around certain height" + , 0 + }; + static const command_line::arg_descriptor arg_test_dbg_lock_sleep = { + "test-dbg-lock-sleep" + , "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests." + , 0 + }; + static const command_line::arg_descriptor arg_dns_checkpoints = { + "enforce-dns-checkpointing" + , "checkpoints from DNS server will be enforced" + , false + }; + static const command_line::arg_descriptor arg_fast_block_sync = { + "fast-block-sync" + , "Sync up most of the way by using embedded, known block hashes." + , 1 + }; + static const command_line::arg_descriptor arg_prep_blocks_threads = { + "prep-blocks-threads" + , "Max number of threads to use when preparing block hashes in groups." + , 4 + }; + static const command_line::arg_descriptor arg_show_time_stats = { + "show-time-stats" + , "Show time-stats when processing blocks/txs and disk synchronization." + , 0 + }; + static const command_line::arg_descriptor arg_block_sync_size = { + "block-sync-size" + , "How many blocks to sync at once during chain synchronization (0 = adaptive)." + , 0 + }; + static const command_line::arg_descriptor arg_check_updates = { + "check-updates" + , "Check for new versions of monero: [disabled|notify|download|update]" + , "notify" + }; + static const command_line::arg_descriptor arg_fluffy_blocks = { + "fluffy-blocks" + , "Relay blocks as fluffy blocks (obsolete, now default)" + , true + }; + static const command_line::arg_descriptor arg_no_fluffy_blocks = { + "no-fluffy-blocks" + , "Relay blocks as normal blocks" + , false + }; + static const command_line::arg_descriptor arg_max_txpool_weight = { + "max-txpool-weight" + , "Set maximum txpool weight in bytes." + , DEFAULT_TXPOOL_MAX_WEIGHT + }; + static const command_line::arg_descriptor arg_block_notify = { + "block-notify" + , "Run a program for each new block, '%s' will be replaced by the block hash" + , "" + }; + static const command_line::arg_descriptor arg_prune_blockchain = { + "prune-blockchain" + , "Prune blockchain" + , false + }; + static const command_line::arg_descriptor arg_reorg_notify = { + "reorg-notify" + , "Run a program for each reorg, '%s' will be replaced by the split height, " + "'%h' will be replaced by the new blockchain height, '%n' will be " + "replaced by the number of new blocks in the new chain, and '%d' will be " + "replaced by the number of blocks discarded from the old chain" + , "" + }; + static const command_line::arg_descriptor arg_block_rate_notify = { + "block-rate-notify" + , "Run a program when the block rate undergoes large fluctuations. This might " + "be a sign of large amounts of hash rate going on and off the Monero network, " + "and thus be of potential interest in predicting attacks. %t will be replaced " + "by the number of minutes for the observation window, %b by the number of " + "blocks observed within that window, and %e by the number of blocks that was " + "expected in that window. It is suggested that this notification is used to " + "automatically increase the number of confirmations required before a payment " + "is acted upon." + , "" + }; + static const command_line::arg_descriptor arg_keep_alt_blocks = { + "keep-alt-blocks" + , "Keep alternative blocks on restart" + , false + }; + + //----------------------------------------------------------------------------------------------- + core::core(i_cryptonote_protocol* pprotocol): + m_mempool(m_blockchain_storage), + m_blockchain_storage(m_mempool), + m_miner(this, [this](const cryptonote::block &b, uint64_t height, const crypto::hash *seed_hash, unsigned int threads, crypto::hash &hash) { + return cryptonote::get_block_longhash(&m_blockchain_storage, b, hash, height, seed_hash, threads); + }), + m_starter_message_showed(false), + m_target_blockchain_height(0), + m_checkpoints_path(""), + m_last_dns_checkpoints_update(0), + m_last_json_checkpoints_update(0), + m_disable_dns_checkpoints(false), + m_update_download(0), + m_nettype(UNDEFINED), + m_update_available(false) + { + m_checkpoints_updating.clear(); + set_cryptonote_protocol(pprotocol); + } + void core::set_cryptonote_protocol(i_cryptonote_protocol* pprotocol) + { + if(pprotocol) + m_pprotocol = pprotocol; + else + m_pprotocol = &m_protocol_stub; + } + //----------------------------------------------------------------------------------- + void core::set_checkpoints(checkpoints&& chk_pts) + { + m_blockchain_storage.set_checkpoints(std::move(chk_pts)); + } + //----------------------------------------------------------------------------------- + void core::set_checkpoints_file_path(const std::string& path) + { + m_checkpoints_path = path; + } + //----------------------------------------------------------------------------------- + void core::set_enforce_dns_checkpoints(bool enforce_dns) + { + m_blockchain_storage.set_enforce_dns_checkpoints(enforce_dns); + } + //----------------------------------------------------------------------------------- + void core::set_txpool_listener(boost::function)> zmq_pub) + { + CRITICAL_REGION_LOCAL(m_incoming_tx_lock); + m_zmq_pub = std::move(zmq_pub); + } + + //----------------------------------------------------------------------------------------------- + bool core::update_checkpoints(const bool skip_dns /* = false */) + { + if (m_nettype != MAINNET || m_disable_dns_checkpoints) return true; + + if (m_checkpoints_updating.test_and_set()) return true; + + bool res = true; + if (!skip_dns && time(NULL) - m_last_dns_checkpoints_update >= 3600) + { + res = m_blockchain_storage.update_checkpoints(m_checkpoints_path, true); + m_last_dns_checkpoints_update = time(NULL); + m_last_json_checkpoints_update = time(NULL); + } + else if (time(NULL) - m_last_json_checkpoints_update >= 600) + { + res = m_blockchain_storage.update_checkpoints(m_checkpoints_path, false); + m_last_json_checkpoints_update = time(NULL); + } + + m_checkpoints_updating.clear(); + + // if anything fishy happened getting new checkpoints, bring down the house + if (!res) + { + graceful_exit(); + } + return res; + } + //----------------------------------------------------------------------------------- + void core::stop() + { + m_miner.stop(); + m_blockchain_storage.cancel(); + + tools::download_async_handle handle; + { + boost::lock_guard lock(m_update_mutex); + handle = m_update_download; + m_update_download = 0; + } + if (handle) + tools::download_cancel(handle); + } + //----------------------------------------------------------------------------------- + void core::init_options(boost::program_options::options_description& desc) + { + command_line::add_arg(desc, arg_data_dir); + + command_line::add_arg(desc, arg_test_drop_download); + command_line::add_arg(desc, arg_test_drop_download_height); + + command_line::add_arg(desc, arg_testnet_on); + command_line::add_arg(desc, arg_stagenet_on); + command_line::add_arg(desc, arg_regtest_on); + command_line::add_arg(desc, arg_keep_fakechain); + command_line::add_arg(desc, arg_fixed_difficulty); + command_line::add_arg(desc, arg_dns_checkpoints); + command_line::add_arg(desc, arg_prep_blocks_threads); + command_line::add_arg(desc, arg_fast_block_sync); + command_line::add_arg(desc, arg_show_time_stats); + command_line::add_arg(desc, arg_block_sync_size); + command_line::add_arg(desc, arg_check_updates); + command_line::add_arg(desc, arg_fluffy_blocks); + command_line::add_arg(desc, arg_no_fluffy_blocks); + command_line::add_arg(desc, arg_test_dbg_lock_sleep); + command_line::add_arg(desc, arg_offline); + command_line::add_arg(desc, arg_disable_dns_checkpoints); + command_line::add_arg(desc, arg_block_download_max_size); + command_line::add_arg(desc, arg_sync_pruned_blocks); + command_line::add_arg(desc, arg_max_txpool_weight); + command_line::add_arg(desc, arg_block_notify); + command_line::add_arg(desc, arg_prune_blockchain); + command_line::add_arg(desc, arg_reorg_notify); + command_line::add_arg(desc, arg_block_rate_notify); + command_line::add_arg(desc, arg_keep_alt_blocks); + + miner::init_options(desc); + BlockchainDB::init_options(desc); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_command_line(const boost::program_options::variables_map& vm) + { + if (m_nettype != FAKECHAIN) + { + const bool testnet = command_line::get_arg(vm, arg_testnet_on); + const bool stagenet = command_line::get_arg(vm, arg_stagenet_on); + m_nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; + } + + m_config_folder = command_line::get_arg(vm, arg_data_dir); + + auto data_dir = boost::filesystem::path(m_config_folder); + + if (m_nettype == MAINNET) + { + cryptonote::checkpoints checkpoints; + if (!checkpoints.init_default_checkpoints(m_nettype)) + { + throw std::runtime_error("Failed to initialize checkpoints"); + } + set_checkpoints(std::move(checkpoints)); + + boost::filesystem::path json(JSON_HASH_FILE_NAME); + boost::filesystem::path checkpoint_json_hashfile_fullpath = data_dir / json; + + set_checkpoints_file_path(checkpoint_json_hashfile_fullpath.string()); + } + + + set_enforce_dns_checkpoints(command_line::get_arg(vm, arg_dns_checkpoints)); + test_drop_download_height(command_line::get_arg(vm, arg_test_drop_download_height)); + m_fluffy_blocks_enabled = !get_arg(vm, arg_no_fluffy_blocks); + m_offline = get_arg(vm, arg_offline); + m_disable_dns_checkpoints = get_arg(vm, arg_disable_dns_checkpoints); + + if (!command_line::is_arg_defaulted(vm, arg_fluffy_blocks)) + MWARNING(arg_fluffy_blocks.name << " is obsolete, it is now default"); + + if (command_line::get_arg(vm, arg_test_drop_download) == true) + test_drop_download(); + + epee::debug::g_test_dbg_lock_sleep() = command_line::get_arg(vm, arg_test_dbg_lock_sleep); + + return true; + } + //----------------------------------------------------------------------------------------------- + uint64_t core::get_current_blockchain_height() const + { + return m_blockchain_storage.get_current_blockchain_height(); + } + //----------------------------------------------------------------------------------------------- + void core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const + { + top_id = m_blockchain_storage.get_tail_id(height); + } + //----------------------------------------------------------------------------------------------- + bool core::get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks, std::vector& txs) const + { + return m_blockchain_storage.get_blocks(start_offset, count, blocks, txs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks) const + { + return m_blockchain_storage.get_blocks(start_offset, count, blocks); + } + //----------------------------------------------------------------------------------------------- + bool core::get_blocks(uint64_t start_offset, size_t count, std::vector& blocks) const + { + std::vector> bs; + if (!m_blockchain_storage.get_blocks(start_offset, count, bs)) + return false; + for (const auto &b: bs) + blocks.push_back(b.second); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs, bool pruned) const + { + return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs, pruned); + } + //----------------------------------------------------------------------------------------------- + bool core::get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::vector& missed_txs) const + { + return m_blockchain_storage.get_split_transactions_blobs(txs_ids, txs, missed_txs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_txpool_backlog(std::vector& backlog, bool include_sensitive_txes) const + { + m_mempool.get_transaction_backlog(backlog, include_sensitive_txes); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs, bool pruned) const + { + return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs, pruned); + } + //----------------------------------------------------------------------------------------------- + bool core::get_alternative_blocks(std::vector& blocks) const + { + return m_blockchain_storage.get_alternative_blocks(blocks); + } + //----------------------------------------------------------------------------------------------- + size_t core::get_alternative_blocks_count() const + { + return m_blockchain_storage.get_alternative_blocks_count(); + } + //----------------------------------------------------------------------------------------------- + bool core::init(const boost::program_options::variables_map& vm, const cryptonote::test_options *test_options, const GetCheckpointsCallback& get_checkpoints/* = nullptr */, bool allow_dns) + { + start_time = std::time(nullptr); + + const bool regtest = command_line::get_arg(vm, arg_regtest_on); + if (test_options != NULL || regtest) + { + m_nettype = FAKECHAIN; + } + bool r = handle_command_line(vm); + CHECK_AND_ASSERT_MES(r, false, "Failed to handle command line"); + m_disable_dns_checkpoints |= not allow_dns; + + std::string db_sync_mode = command_line::get_arg(vm, cryptonote::arg_db_sync_mode); + bool db_salvage = command_line::get_arg(vm, cryptonote::arg_db_salvage) != 0; + bool fast_sync = command_line::get_arg(vm, arg_fast_block_sync) != 0; + uint64_t blocks_threads = command_line::get_arg(vm, arg_prep_blocks_threads); + std::string check_updates_string = command_line::get_arg(vm, arg_check_updates); + size_t max_txpool_weight = command_line::get_arg(vm, arg_max_txpool_weight); + bool prune_blockchain = command_line::get_arg(vm, arg_prune_blockchain); + bool keep_alt_blocks = command_line::get_arg(vm, arg_keep_alt_blocks); + bool keep_fakechain = command_line::get_arg(vm, arg_keep_fakechain); + + boost::filesystem::path folder(m_config_folder); + if (m_nettype == FAKECHAIN) + folder /= "fake"; + + // make sure the data directory exists, and try to lock it + CHECK_AND_ASSERT_MES (boost::filesystem::exists(folder) || boost::filesystem::create_directories(folder), false, + std::string("Failed to create directory ").append(folder.string()).c_str()); + + // check for blockchain.bin + try + { + const boost::filesystem::path old_files = folder; + if (boost::filesystem::exists(old_files / "blockchain.bin")) + { + MWARNING("Found old-style blockchain.bin in " << old_files.string()); + MWARNING("Monero now uses a new format. You can either remove blockchain.bin to start syncing"); + MWARNING("the blockchain anew, or use monero-blockchain-export and monero-blockchain-import to"); + MWARNING("convert your existing blockchain.bin to the new format. See README.md for instructions."); + return false; + } + } + // folder might not be a directory, etc, etc + catch (...) { } + + std::unique_ptr db(new_db()); + if (db == NULL) + { + LOG_ERROR("Failed to initialize a database"); + return false; + } + + folder /= db->get_db_name(); + MGINFO("Loading blockchain from folder " << folder.string() << " ..."); + + const std::string filename = folder.string(); + // default to fast:async:1 if overridden + blockchain_db_sync_mode sync_mode = db_defaultsync; + bool sync_on_blocks = true; + uint64_t sync_threshold = 1; + + if (m_nettype == FAKECHAIN && !keep_fakechain) + { + // reset the db by removing the database file before opening it + if (!db->remove_data_file(filename)) + { + MERROR("Failed to remove data file in " << filename); + return false; + } + } + + try + { + uint64_t db_flags = 0; + + std::vector options; + boost::trim(db_sync_mode); + boost::split(options, db_sync_mode, boost::is_any_of(" :")); + const bool db_sync_mode_is_default = command_line::is_arg_defaulted(vm, cryptonote::arg_db_sync_mode); + + for(const auto &option : options) + MDEBUG("option: " << option); + + // default to fast:async:1 + uint64_t DEFAULT_FLAGS = DBF_FAST; + + if(options.size() == 0) + { + // default to fast:async:1 + db_flags = DEFAULT_FLAGS; + } + + bool safemode = false; + if(options.size() >= 1) + { + if(options[0] == "safe") + { + safemode = true; + db_flags = DBF_SAFE; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_nosync; + } + else if(options[0] == "fast") + { + db_flags = DBF_FAST; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; + } + else if(options[0] == "fastest") + { + db_flags = DBF_FASTEST; + sync_threshold = 1000; // default to fastest:async:1000 + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; + } + else + db_flags = DEFAULT_FLAGS; + } + + if(options.size() >= 2 && !safemode) + { + if(options[1] == "sync") + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_sync; + else if(options[1] == "async") + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; + } + + if(options.size() >= 3 && !safemode) + { + char *endptr; + uint64_t threshold = strtoull(options[2].c_str(), &endptr, 0); + if (*endptr == '\0' || !strcmp(endptr, "blocks")) + { + sync_on_blocks = true; + sync_threshold = threshold; + } + else if (!strcmp(endptr, "bytes")) + { + sync_on_blocks = false; + sync_threshold = threshold; + } + else + { + LOG_ERROR("Invalid db sync mode: " << options[2]); + return false; + } + } + + if (db_salvage) + db_flags |= DBF_SALVAGE; + + db->open(filename, db_flags); + if(!db->m_open) + return false; + } + catch (const DB_ERROR& e) + { + LOG_ERROR("Error opening database: " << e.what()); + return false; + } + + m_blockchain_storage.set_user_options(blocks_threads, + sync_on_blocks, sync_threshold, sync_mode, fast_sync); + + try + { + if (!command_line::is_arg_defaulted(vm, arg_block_notify)) + { + struct hash_notify + { + tools::Notify cmdline; + + void operator()(std::uint64_t, epee::span blocks) const + { + for (const block& bl : blocks) + cmdline.notify("%s", epee::string_tools::pod_to_hex(get_block_hash(bl)).c_str(), NULL); + } + }; + + m_blockchain_storage.add_block_notify(hash_notify{{command_line::get_arg(vm, arg_block_notify).c_str()}}); + } + } + catch (const std::exception &e) + { + MERROR("Failed to parse block notify spec: " << e.what()); + } + + try + { + if (!command_line::is_arg_defaulted(vm, arg_reorg_notify)) + m_blockchain_storage.set_reorg_notify(std::shared_ptr(new tools::Notify(command_line::get_arg(vm, arg_reorg_notify).c_str()))); + } + catch (const std::exception &e) + { + MERROR("Failed to parse reorg notify spec: " << e.what()); + } + + try + { + if (!command_line::is_arg_defaulted(vm, arg_block_rate_notify)) + m_block_rate_notify.reset(new tools::Notify(command_line::get_arg(vm, arg_block_rate_notify).c_str())); + } + catch (const std::exception &e) + { + MERROR("Failed to parse block rate notify spec: " << e.what()); + } + + const std::pair regtest_hard_forks[3] = {std::make_pair(1, 0), std::make_pair(mainnet_hard_forks[num_mainnet_hard_forks-1].version, 1), std::make_pair(0, 0)}; + const cryptonote::test_options regtest_test_options = { + regtest_hard_forks, + 0 + }; + const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty); + r = m_blockchain_storage.init(db.release(), m_nettype, m_offline, regtest ? ®test_test_options : test_options, fixed_difficulty, get_checkpoints); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); + + r = m_mempool.init(max_txpool_weight, m_nettype == FAKECHAIN); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); + + // now that we have a valid m_blockchain_storage, we can clean out any + // transactions in the pool that do not conform to the current fork + m_mempool.validate(m_blockchain_storage.get_current_hard_fork_version()); + + bool show_time_stats = command_line::get_arg(vm, arg_show_time_stats) != 0; + m_blockchain_storage.set_show_time_stats(show_time_stats); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); + + block_sync_size = command_line::get_arg(vm, arg_block_sync_size); + if (block_sync_size > BLOCKS_SYNCHRONIZING_MAX_COUNT) + MERROR("Error --block-sync-size cannot be greater than " << BLOCKS_SYNCHRONIZING_MAX_COUNT); + + MGINFO("Loading checkpoints"); + + // load json & DNS checkpoints, and verify them + // with respect to what blocks we already have + const bool skip_dns_checkpoints = !command_line::get_arg(vm, arg_dns_checkpoints); + CHECK_AND_ASSERT_MES(update_checkpoints(skip_dns_checkpoints), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + + // DNS versions checking + if (check_updates_string == "disabled" || not allow_dns) + check_updates_level = UPDATES_DISABLED; + else if (check_updates_string == "notify") + check_updates_level = UPDATES_NOTIFY; + else if (check_updates_string == "download") + check_updates_level = UPDATES_DOWNLOAD; + else if (check_updates_string == "update") + check_updates_level = UPDATES_UPDATE; + else { + MERROR("Invalid argument to --dns-versions-check: " << check_updates_string); + return false; + } + + r = m_miner.init(vm, m_nettype); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance"); + + if (!keep_alt_blocks && !m_blockchain_storage.get_db().is_read_only()) + m_blockchain_storage.get_db().drop_alt_blocks(); + + if (prune_blockchain) + { + // display a message if the blockchain is not pruned yet + if (!m_blockchain_storage.get_blockchain_pruning_seed()) + { + MGINFO("Pruning blockchain..."); + CHECK_AND_ASSERT_MES(m_blockchain_storage.prune_blockchain(), false, "Failed to prune blockchain"); + } + else + { + CHECK_AND_ASSERT_MES(m_blockchain_storage.update_blockchain_pruning(), false, "Failed to update blockchain pruning"); + } + } + + return load_state_data(); + } + //----------------------------------------------------------------------------------------------- + bool core::set_genesis_block(const block& b) + { + return m_blockchain_storage.reset_and_set_genesis_block(b); + } + //----------------------------------------------------------------------------------------------- + bool core::load_state_data() + { + // may be some code later + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::deinit() + { + m_miner.stop(); + m_mempool.deinit(); + m_blockchain_storage.deinit(); + return true; + } + //----------------------------------------------------------------------------------------------- + void core::test_drop_download() + { + m_test_drop_download = false; + } + //----------------------------------------------------------------------------------------------- + void core::test_drop_download_height(uint64_t height) + { + m_test_drop_download_height = height; + } + //----------------------------------------------------------------------------------------------- + bool core::get_test_drop_download() const + { + return m_test_drop_download; + } + //----------------------------------------------------------------------------------------------- + bool core::get_test_drop_download_height() const + { + if (m_test_drop_download_height == 0) + return true; + + if (get_blockchain_storage().get_current_blockchain_height() <= m_test_drop_download_height) + return true; + + return false; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash) + { + tvc = {}; + + if(tx_blob.blob.size() > get_max_tx_size()) + { + LOG_PRINT_L1("WRONG TRANSACTION BLOB, too big size " << tx_blob.blob.size() << ", rejected"); + tvc.m_verifivation_failed = true; + tvc.m_too_big = true; + return false; + } + + tx_hash = crypto::null_hash; + + bool r; + if (tx_blob.prunable_hash == crypto::null_hash) + { + r = parse_tx_from_blob(tx, tx_hash, tx_blob.blob); + } + else + { + r = parse_and_validate_tx_base_from_blob(tx_blob.blob, tx); + if (r) + { + tx.set_prunable_hash(tx_blob.prunable_hash); + tx_hash = cryptonote::get_pruned_transaction_hash(tx, tx_blob.prunable_hash); + tx.set_hash(tx_hash); + } + } + + if (!r) + { + LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to parse, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + //std::cout << "!"<< tx.vin.size() << std::endl; + + bad_semantics_txes_lock.lock(); + for (int idx = 0; idx < 2; ++idx) + { + if (bad_semantics_txes[idx].find(tx_hash) != bad_semantics_txes[idx].end()) + { + bad_semantics_txes_lock.unlock(); + LOG_PRINT_L1("Transaction already seen with bad semantics, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + } + bad_semantics_txes_lock.unlock(); + + uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); + const size_t max_tx_version = version == 1 ? 1 : 2; + if (tx.version == 0 || tx.version > max_tx_version) + { + // v2 is the latest one we know + MERROR_VER("Bad tx version (" << tx.version << ", max is " << max_tx_version << ")"); + tvc.m_verifivation_failed = true; + return false; + } + + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash) + { + if(!check_tx_syntax(tx)) + { + LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + + return true; + } + //----------------------------------------------------------------------------------------------- + void core::set_semantics_failed(const crypto::hash &tx_hash) + { + LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected"); + bad_semantics_txes_lock.lock(); + bad_semantics_txes[0].insert(tx_hash); + if (bad_semantics_txes[0].size() >= BAD_SEMANTICS_TXES_MAX_SIZE) + { + std::swap(bad_semantics_txes[0], bad_semantics_txes[1]); + bad_semantics_txes[0].clear(); + } + bad_semantics_txes_lock.unlock(); + } + //----------------------------------------------------------------------------------------------- + static bool is_canonical_bulletproof_layout(const std::vector &proofs) + { + if (proofs.size() != 1) + return false; + const size_t sz = proofs[0].V.size(); + if (sz == 0 || sz > BULLETPROOF_MAX_OUTPUTS) + return false; + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx_accumulated_batch(std::vector &tx_info, bool keeped_by_block) + { + bool ret = true; + if (keeped_by_block && get_blockchain_storage().is_within_compiled_block_hash_area()) + { + MTRACE("Skipping semantics check for tx kept by block in embedded hash area"); + return true; + } + + std::vector rvv; + for (size_t n = 0; n < tx_info.size(); ++n) + { + if (!check_tx_semantic(*tx_info[n].tx, keeped_by_block)) + { + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + + if (tx_info[n].tx->version < 2) + continue; + const rct::rctSig &rv = tx_info[n].tx->rct_signatures; + switch (rv.type) { + case rct::RCTTypeNull: + // coinbase should not come here, so we reject for all other types + MERROR_VER("Unexpected Null rctSig type"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + case rct::RCTTypeSimple: + if (!rct::verRctSemanticsSimple(rv)) + { + MERROR_VER("rct signature semantics check failed"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + break; + case rct::RCTTypeFull: + if (!rct::verRct(rv, true)) + { + MERROR_VER("rct signature semantics check failed"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + break; + case rct::RCTTypeBulletproof: + case rct::RCTTypeBulletproof2: + case rct::RCTTypeCLSAG: + if (!is_canonical_bulletproof_layout(rv.p.bulletproofs)) + { + MERROR_VER("Bulletproof does not have canonical form"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + rvv.push_back(&rv); // delayed batch verification + break; + default: + MERROR_VER("Unknown rct type: " << rv.type); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + } + if (!rvv.empty() && !rct::verRctSemanticsSimple(rvv)) + { + LOG_PRINT_L1("One transaction among this group has bad semantics, verifying one at a time"); + ret = false; + const bool assumed_bad = rvv.size() == 1; // if there's only one tx, it must be the bad one + for (size_t n = 0; n < tx_info.size(); ++n) + { + if (!tx_info[n].result) + continue; + if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG) + continue; + if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures)) + { + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + } + } + } + + return ret; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_txs(const epee::span tx_blobs, epee::span tvc, relay_method tx_relay, bool relayed) + { + TRY_ENTRY(); + + if (tx_blobs.size() != tvc.size()) + { + MERROR("tx_blobs and tx_verification_context spans must have equal size"); + return false; + } + + std::vector results(tx_blobs.size()); + + CRITICAL_REGION_LOCAL(m_incoming_tx_lock); + + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter(tpool); + epee::span::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + tpool.submit(&waiter, [&, i, it] { + try + { + results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash); + } + catch (const std::exception &e) + { + MERROR_VER("Exception in handle_incoming_tx_pre: " << e.what()); + tvc[i].m_verifivation_failed = true; + results[i].res = false; + } + }); + } + if (!waiter.wait()) + return false; + it = tx_blobs.begin(); + std::vector already_have(tx_blobs.size(), false); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + continue; + if(m_mempool.have_tx(results[i].hash, relay_category::legacy)) + { + LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); + already_have[i] = true; + } + else if(m_blockchain_storage.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); + already_have[i] = true; + } + else + { + tpool.submit(&waiter, [&, i, it] { + try + { + results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash); + } + catch (const std::exception &e) + { + MERROR_VER("Exception in handle_incoming_tx_post: " << e.what()); + tvc[i].m_verifivation_failed = true; + results[i].res = false; + } + }); + } + } + if (!waiter.wait()) + return false; + + std::vector tx_info; + tx_info.reserve(tx_blobs.size()); + for (size_t i = 0; i < tx_blobs.size(); i++) { + if (!results[i].res || already_have[i]) + continue; + tx_info.push_back({&results[i].tx, results[i].hash, tvc[i], results[i].res}); + } + if (!tx_info.empty()) + handle_incoming_tx_accumulated_batch(tx_info, tx_relay == relay_method::block); + + bool valid_events = false; + bool ok = true; + it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + { + ok = false; + continue; + } + if (tx_relay == relay_method::block) + get_blockchain_storage().on_new_tx_from_block(results[i].tx); + if (already_have[i]) + continue; + + results[i].blob_size = it->blob.size(); + results[i].weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, it->blob.size()); + ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, results[i].weight, tvc[i], tx_relay, relayed); + + if(tvc[i].m_verifivation_failed) + {MERROR_VER("Transaction verification failed: " << results[i].hash);} + else if(tvc[i].m_verifivation_impossible) + {MERROR_VER("Transaction verification impossible: " << results[i].hash);} + + if(tvc[i].m_added_to_pool) + { + MDEBUG("tx added: " << results[i].hash); + valid_events = true; + } + else + results[i].res = false; + } + + if (valid_events && m_zmq_pub && matches_category(tx_relay, relay_category::legacy)) + m_zmq_pub(std::move(results)); + + return ok; + CATCH_ENTRY_L0("core::handle_incoming_txs()", false); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed) + { + return handle_incoming_txs({std::addressof(tx_blob), 1}, {std::addressof(tvc), 1}, tx_relay, relayed); + } + //----------------------------------------------------------------------------------------------- + bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) const + { + if(!tx.vin.size()) + { + MERROR_VER("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if(!check_inputs_types_supported(tx)) + { + MERROR_VER("unsupported input types for tx id= " << get_transaction_hash(tx)); + return false; + } + + if(!check_outs_valid(tx)) + { + MERROR_VER("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + if (tx.version > 1) + { + if (tx.rct_signatures.outPk.size() != tx.vout.size()) + { + MERROR_VER("tx with mismatched vout/outPk count, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + } + + if(!check_money_overflow(tx)) + { + MERROR_VER("tx has money overflow, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if (tx.version == 1) + { + uint64_t amount_in = 0; + get_inputs_money_amount(tx, amount_in); + uint64_t amount_out = get_outs_money_amount(tx); + + if(amount_in <= amount_out) + { + MERROR_VER("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + } + // for version > 1, ringct signatures check verifies amounts match + + if(!keeped_by_block && get_transaction_weight(tx) >= m_blockchain_storage.get_current_cumulative_block_weight_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) + { + MERROR_VER("tx is too large " << get_transaction_weight(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_cumulative_block_weight_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + return false; + } + + //check if tx use different key images + if(!check_tx_inputs_keyimages_diff(tx)) + { + MERROR_VER("tx uses a single key image more than once"); + return false; + } + + if (!check_tx_inputs_ring_members_diff(tx)) + { + MERROR_VER("tx uses duplicate ring members"); + return false; + } + + if (!check_tx_inputs_keyimages_domain(tx)) + { + MERROR_VER("tx uses key image not in the valid domain"); + return false; + } + + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::is_key_image_spent(const crypto::key_image &key_image) const + { + return m_blockchain_storage.have_tx_keyimg_as_spent(key_image); + } + //----------------------------------------------------------------------------------------------- + bool core::are_key_images_spent(const std::vector& key_im, std::vector &spent) const + { + spent.clear(); + for(auto& ki: key_im) + { + spent.push_back(m_blockchain_storage.have_tx_keyimg_as_spent(ki)); + } + return true; + } + //----------------------------------------------------------------------------------------------- + size_t core::get_block_sync_size(uint64_t height) const + { + static const uint64_t quick_height = m_nettype == TESTNET ? 801219 : m_nettype == MAINNET ? 1220516 : 0; + size_t res = 0; + if (block_sync_size > 0) + res = block_sync_size; + else if (height >= quick_height) + res = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; + else + res = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT_PRE_V4; + + static size_t max_block_size = 0; + if (max_block_size == 0) + { + const char *env = getenv("SEEDHASH_EPOCH_BLOCKS"); + if (env) + { + int n = atoi(env); + if (n <= 0) + n = BLOCKS_SYNCHRONIZING_MAX_COUNT; + size_t p = 1; + while (p < (size_t)n) + p <<= 1; + max_block_size = p; + } + else + max_block_size = BLOCKS_SYNCHRONIZING_MAX_COUNT; + } + if (res > max_block_size) + { + static bool warned = false; + if (!warned) + { + MWARNING("Clamping block sync size to " << max_block_size); + warned = true; + } + res = max_block_size; + } + return res; + } + //----------------------------------------------------------------------------------------------- + bool core::are_key_images_spent_in_pool(const std::vector& key_im, std::vector &spent) const + { + spent.clear(); + + return m_mempool.check_for_key_images(key_im, spent); + } + //----------------------------------------------------------------------------------------------- + std::pair core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) + { + boost::multiprecision::uint128_t emission_amount = 0; + boost::multiprecision::uint128_t total_fee_amount = 0; + if (count) + { + const uint64_t end = start_offset + count - 1; + m_blockchain_storage.for_blocks_range(start_offset, end, + [this, &emission_amount, &total_fee_amount](uint64_t, const crypto::hash& hash, const block& b){ + std::vector txs; + std::vector missed_txs; + uint64_t coinbase_amount = get_outs_money_amount(b.miner_tx); + this->get_transactions(b.tx_hashes, txs, missed_txs, true); + uint64_t tx_fee_amount = 0; + for(const auto& tx: txs) + { + tx_fee_amount += get_tx_fee(tx); + } + + emission_amount += coinbase_amount - tx_fee_amount; + total_fee_amount += tx_fee_amount; + return true; + }); + } + + return std::pair(emission_amount, total_fee_amount); + } + //----------------------------------------------------------------------------------------------- + bool core::check_tx_inputs_keyimages_diff(const transaction& tx) const + { + std::unordered_set ki; + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + if(!ki.insert(tokey_in.k_image).second) + return false; + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const + { + const uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); + if (version >= 6) + { + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + for (size_t n = 1; n < tokey_in.key_offsets.size(); ++n) + if (tokey_in.key_offsets[n] == 0) + return false; + } + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::check_tx_inputs_keyimages_domain(const transaction& tx) const + { + std::unordered_set ki; + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + if (!(rct::scalarmultKey(rct::ki2rct(tokey_in.k_image), rct::curveOrder()) == rct::identity())) + return false; + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::add_new_tx(transaction& tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed) + { + crypto::hash tx_hash = get_transaction_hash(tx); + blobdata bl; + t_serializable_object_to_blob(tx, bl); + size_t tx_weight = get_transaction_weight(tx, bl.size()); + return add_new_tx(tx, tx_hash, bl, tx_weight, tvc, tx_relay, relayed); + } + //----------------------------------------------------------------------------------------------- + size_t core::get_blockchain_total_transactions() const + { + return m_blockchain_storage.get_total_transactions(); + } + //----------------------------------------------------------------------------------------------- + bool core::add_new_tx(transaction& tx, const crypto::hash& tx_hash, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed) + { + if(m_mempool.have_tx(tx_hash, relay_category::legacy)) + { + LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool"); + return true; + } + + if(m_blockchain_storage.have_tx(tx_hash)) + { + LOG_PRINT_L2("tx " << tx_hash << " already have transaction in blockchain"); + return true; + } + + uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); + return m_mempool.add_tx(tx, tx_hash, blob, tx_weight, tvc, tx_relay, relayed, version); + } + //----------------------------------------------------------------------------------------------- + bool core::relay_txpool_transactions() + { + // we attempt to relay txes that should be relayed, but were not + std::vector> txs; + if (m_mempool.get_relayable_transactions(txs) && !txs.empty()) + { + NOTIFY_NEW_TRANSACTIONS::request public_req{}; + NOTIFY_NEW_TRANSACTIONS::request private_req{}; + NOTIFY_NEW_TRANSACTIONS::request stem_req{}; + for (auto& tx : txs) + { + switch (std::get<2>(tx)) + { + default: + case relay_method::none: + break; + case relay_method::local: + private_req.txs.push_back(std::move(std::get<1>(tx))); + break; + case relay_method::forward: + stem_req.txs.push_back(std::move(std::get<1>(tx))); + break; + case relay_method::block: + case relay_method::fluff: + case relay_method::stem: + public_req.txs.push_back(std::move(std::get<1>(tx))); + break; + } + } + + /* All txes are sent on randomized timers per connection in + `src/cryptonote_protocol/levin_notify.cpp.` They are either sent with + "white noise" delays or via diffusion (Dandelion++ fluff). So + re-relaying public and private _should_ be acceptable here. */ + const boost::uuids::uuid source = boost::uuids::nil_uuid(); + if (!public_req.txs.empty()) + get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_, relay_method::fluff); + if (!private_req.txs.empty()) + get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid, relay_method::local); + if (!stem_req.txs.empty()) + get_protocol()->relay_transactions(stem_req, source, epee::net_utils::zone::public_, relay_method::stem); + } + return true; + } + //----------------------------------------------------------------------------------------------- + void core::on_transactions_relayed(const epee::span tx_blobs, const relay_method tx_relay) + { + std::vector tx_hashes{}; + tx_hashes.resize(tx_blobs.size()); + + for (std::size_t i = 0; i < tx_blobs.size(); ++i) + { + cryptonote::transaction tx{}; + if (!parse_and_validate_tx_from_blob(tx_blobs[i], tx, tx_hashes[i])) + { + LOG_ERROR("Failed to parse relayed transaction"); + return; + } + } + m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay); + } + //----------------------------------------------------------------------------------------------- + bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash) + { + return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce, seed_height, seed_hash); + } + //----------------------------------------------------------------------------------------------- + bool core::get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash) + { + return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce, seed_height, seed_hash); + } + //----------------------------------------------------------------------------------------------- + bool core::get_miner_data(uint8_t& major_version, uint64_t& height, crypto::hash& prev_id, crypto::hash& seed_hash, difficulty_type& difficulty, uint64_t& median_weight, uint64_t& already_generated_coins, std::vector& tx_backlog) + { + return m_blockchain_storage.get_miner_data(major_version, height, prev_id, seed_hash, difficulty, median_weight, already_generated_coins, tx_backlog); + } + //----------------------------------------------------------------------------------------------- + bool core::find_blockchain_supplement(const std::list& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const + { + return m_blockchain_storage.find_blockchain_supplement(qblock_ids, clip_pruned, resp); + } + //----------------------------------------------------------------------------------------------- + bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::vector, std::vector > > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_block_count, size_t max_tx_count) const + { + return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, start_height, pruned, get_miner_tx_hash, max_block_count, max_tx_count); + } + //----------------------------------------------------------------------------------------------- + bool core::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const + { + return m_blockchain_storage.get_outs(req, res); + } + //----------------------------------------------------------------------------------------------- + bool core::get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector &distribution, uint64_t &base) const + { + return m_blockchain_storage.get_output_distribution(amount, from_height, to_height, start_height, distribution, base); + } + //----------------------------------------------------------------------------------------------- + bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const + { + return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, size_t n_txes, std::vector>& indexs) const + { + return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, n_txes, indexs); + } + //----------------------------------------------------------------------------------------------- + void core::pause_mine() + { + m_miner.pause(); + } + //----------------------------------------------------------------------------------------------- + void core::resume_mine() + { + m_miner.resume(); + } + //----------------------------------------------------------------------------------------------- + block_complete_entry get_block_complete_entry(block& b, tx_memory_pool &pool) + { + block_complete_entry bce; + bce.block = cryptonote::block_to_blob(b); + bce.block_weight = 0; // we can leave it to 0, those txes aren't pruned + for (const auto &tx_hash: b.tx_hashes) + { + cryptonote::blobdata txblob; + CHECK_AND_ASSERT_THROW_MES(pool.get_transaction(tx_hash, txblob, relay_category::all), "Transaction not found in pool"); + bce.txs.push_back({txblob, crypto::null_hash}); + } + return bce; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_block_found(block& b, block_verification_context &bvc) + { + bvc = {}; + m_miner.pause(); + std::vector blocks; + try + { + blocks.push_back(get_block_complete_entry(b, m_mempool)); + } + catch (const std::exception &e) + { + m_miner.resume(); + return false; + } + std::vector pblocks; + if (!prepare_handle_incoming_blocks(blocks, pblocks)) + { + MERROR("Block found, but failed to prepare to add"); + m_miner.resume(); + return false; + } + m_blockchain_storage.add_new_block(b, bvc); + cleanup_handle_incoming_blocks(true); + //anyway - update miner template + update_miner_block_template(); + m_miner.resume(); + + + CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "mined block failed verification"); + if(bvc.m_added_to_main_chain) + { + cryptonote_connection_context exclude_context = {}; + NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); + arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); + std::vector missed_txs; + std::vector txs; + m_blockchain_storage.get_transactions_blobs(b.tx_hashes, txs, missed_txs); + if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) + { + LOG_PRINT_L1("Block found but, seems that reorganize just happened after that, do not relay this block"); + return true; + } + CHECK_AND_ASSERT_MES(txs.size() == b.tx_hashes.size() && !missed_txs.size(), false, "can't find some transactions in found block:" << get_block_hash(b) << " txs.size()=" << txs.size() + << ", b.tx_hashes.size()=" << b.tx_hashes.size() << ", missed_txs.size()" << missed_txs.size()); + + block_to_blob(b, arg.b.block); + //pack transactions + for(auto& tx: txs) + arg.b.txs.push_back({tx, crypto::null_hash}); + + m_pprotocol->relay_block(arg, exclude_context); + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::is_synchronized() const + { + return m_pprotocol != nullptr && m_pprotocol->is_synchronized(); + } + //----------------------------------------------------------------------------------------------- + void core::on_synchronized() + { + m_miner.on_synchronized(); + } + //----------------------------------------------------------------------------------------------- + void core::safesyncmode(const bool onoff) + { + m_blockchain_storage.safesyncmode(onoff); + } + //----------------------------------------------------------------------------------------------- + bool core::add_new_block(const block& b, block_verification_context& bvc) + { + return m_blockchain_storage.add_new_block(b, bvc); + } + + //----------------------------------------------------------------------------------------------- + bool core::prepare_handle_incoming_blocks(const std::vector &blocks_entry, std::vector &blocks) + { + m_incoming_tx_lock.lock(); + if (!m_blockchain_storage.prepare_handle_incoming_blocks(blocks_entry, blocks)) + { + cleanup_handle_incoming_blocks(false); + return false; + } + return true; + } + + //----------------------------------------------------------------------------------------------- + bool core::cleanup_handle_incoming_blocks(bool force_sync) + { + bool success = false; + try { + success = m_blockchain_storage.cleanup_handle_incoming_blocks(force_sync); + } + catch (...) {} + m_incoming_tx_lock.unlock(); + return success; + } + + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, bool update_miner_blocktemplate) + { + TRY_ENTRY(); + + bvc = {}; + + if (!check_incoming_block_size(block_blob)) + { + bvc.m_verifivation_failed = true; + return false; + } + + if (((size_t)-1) <= 0xffffffff && block_blob.size() >= 0x3fffffff) + MWARNING("This block's size is " << block_blob.size() << ", closing on the 32 bit limit"); + + // load json & DNS checkpoints every 10min/hour respectively, + // and verify them with respect to what blocks we already have + CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + + block lb; + if (!b) + { + crypto::hash block_hash; + if(!parse_and_validate_block_from_blob(block_blob, lb, block_hash)) + { + LOG_PRINT_L1("Failed to parse and validate new block"); + bvc.m_verifivation_failed = true; + return false; + } + b = &lb; + } + add_new_block(*b, bvc); + if(update_miner_blocktemplate && bvc.m_added_to_main_chain) + update_miner_block_template(); + return true; + + CATCH_ENTRY_L0("core::handle_incoming_block()", false); + } + //----------------------------------------------------------------------------------------------- + // Used by the RPC server to check the size of an incoming + // block_blob + bool core::check_incoming_block_size(const blobdata& block_blob) const + { + // note: we assume block weight is always >= block blob size, so we check incoming + // blob size against the block weight limit, which acts as a sanity check without + // having to parse/weigh first; in fact, since the block blob is the block header + // plus the tx hashes, the weight will typically be much larger than the blob size + if(block_blob.size() > m_blockchain_storage.get_current_cumulative_block_weight_limit() + BLOCK_SIZE_SANITY_LEEWAY) + { + LOG_PRINT_L1("WRONG BLOCK BLOB, sanity check failed on size " << block_blob.size() << ", rejected"); + return false; + } + return true; + } + //----------------------------------------------------------------------------------------------- + crypto::hash core::get_tail_id() const + { + return m_blockchain_storage.get_tail_id(); + } + //----------------------------------------------------------------------------------------------- + difficulty_type core::get_block_cumulative_difficulty(uint64_t height) const + { + return m_blockchain_storage.get_db().get_block_cumulative_difficulty(height); + } + //----------------------------------------------------------------------------------------------- + size_t core::get_pool_transactions_count(bool include_sensitive_txes) const + { + return m_mempool.get_transactions_count(include_sensitive_txes); + } + //----------------------------------------------------------------------------------------------- + bool core::have_block_unlocked(const crypto::hash& id, int *where) const + { + return m_blockchain_storage.have_block_unlocked(id, where); + } + //----------------------------------------------------------------------------------------------- + bool core::have_block(const crypto::hash& id, int *where) const + { + return m_blockchain_storage.have_block(id, where); + } + //----------------------------------------------------------------------------------------------- + bool core::parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, const blobdata& blob) const + { + return parse_and_validate_tx_from_blob(blob, tx, tx_hash); + } + //----------------------------------------------------------------------------------------------- + bool core::check_tx_syntax(const transaction& tx) const + { + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_transactions(std::vector& txs, bool include_sensitive_data) const + { + m_mempool.get_transactions(txs, include_sensitive_data); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_transaction_hashes(std::vector& txs, bool include_sensitive_data) const + { + m_mempool.get_transaction_hashes(txs, include_sensitive_data); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_transaction_stats(struct txpool_stats& stats, bool include_sensitive_data) const + { + m_mempool.get_transaction_stats(stats, include_sensitive_data); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_transaction(const crypto::hash &id, cryptonote::blobdata& tx, relay_category tx_category) const + { + return m_mempool.get_transaction(id, tx, tx_category); + } + //----------------------------------------------------------------------------------------------- + bool core::pool_has_tx(const crypto::hash &id) const + { + return m_mempool.have_tx(id, relay_category::legacy); + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos, bool include_sensitive_data) const + { + return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos, include_sensitive_data); + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_for_rpc(std::vector& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + return m_mempool.get_pool_for_rpc(tx_infos, key_image_infos); + } + //----------------------------------------------------------------------------------------------- + bool core::get_short_chain_history(std::list& ids) const + { + return m_blockchain_storage.get_short_chain_history(ids); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context) + { + return m_blockchain_storage.handle_get_objects(arg, rsp); + } + //----------------------------------------------------------------------------------------------- + crypto::hash core::get_block_id_by_height(uint64_t height) const + { + return m_blockchain_storage.get_block_id_by_height(height); + } + //----------------------------------------------------------------------------------------------- + bool core::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan) const + { + return m_blockchain_storage.get_block_by_hash(h, blk, orphan); + } + //----------------------------------------------------------------------------------------------- + std::string core::print_pool(bool short_format) const + { + return m_mempool.print_pool(short_format); + } + //----------------------------------------------------------------------------------------------- + bool core::update_miner_block_template() + { + m_miner.on_block_chain_update(); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::on_idle() + { + if(!m_starter_message_showed) + { + std::string main_message; + if (m_offline) + main_message = "The daemon is running offline and will not attempt to sync to the Monero network."; + else + main_message = "The daemon will start synchronizing with the network. This may take a long time to complete."; + MGINFO_YELLOW(ENDL << "**********************************************************************" << ENDL + << main_message << ENDL + << ENDL + << "You can set the level of process detailization through \"set_log \" command," << ENDL + << "where is between 0 (no details) and 4 (very verbose), or custom category based levels (eg, *:WARNING)." << ENDL + << ENDL + << "Use the \"help\" command to see the list of available commands." << ENDL + << "Use \"help \" to see a command's documentation." << ENDL + << "**********************************************************************" << ENDL); + m_starter_message_showed = true; + } + + relay_txpool_transactions(); // txpool handles periodic DB checking + m_check_updates_interval.do_call(boost::bind(&core::check_updates, this)); + m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this)); + m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this)); + m_blockchain_pruning_interval.do_call(boost::bind(&core::update_blockchain_pruning, this)); + m_diff_recalc_interval.do_call(boost::bind(&core::recalculate_difficulties, this)); + m_miner.on_idle(); + m_mempool.on_idle(); + return true; + } + //----------------------------------------------------------------------------------------------- + uint8_t core::get_ideal_hard_fork_version() const + { + return get_blockchain_storage().get_ideal_hard_fork_version(); + } + //----------------------------------------------------------------------------------------------- + uint8_t core::get_ideal_hard_fork_version(uint64_t height) const + { + return get_blockchain_storage().get_ideal_hard_fork_version(height); + } + //----------------------------------------------------------------------------------------------- + uint8_t core::get_hard_fork_version(uint64_t height) const + { + return get_blockchain_storage().get_hard_fork_version(height); + } + //----------------------------------------------------------------------------------------------- + uint64_t core::get_earliest_ideal_height_for_version(uint8_t version) const + { + return get_blockchain_storage().get_earliest_ideal_height_for_version(version); + } + //----------------------------------------------------------------------------------------------- + bool core::check_updates() + { + static const char software[] = "monero"; +#ifdef BUILD_TAG + static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); + static const char subdir[] = "cli"; // because it can never be simple +#else + static const char buildtag[] = "source"; + static const char subdir[] = "source"; // because it can never be simple +#endif + + if (m_offline) + return true; + + if (check_updates_level == UPDATES_DISABLED) + return true; + + std::string version, hash; + MCDEBUG("updates", "Checking for a new " << software << " version for " << buildtag); + if (!tools::check_updates(software, buildtag, version, hash)) + return false; + + if (tools::vercmp(version.c_str(), MONERO_VERSION) <= 0) + { + m_update_available = false; + return true; + } + + std::string url = tools::get_update_url(software, subdir, buildtag, version, true); + MCLOG_CYAN(el::Level::Info, "global", "Version " << version << " of " << software << " for " << buildtag << " is available: " << url << ", SHA256 hash " << hash); + m_update_available = true; + + if (check_updates_level == UPDATES_NOTIFY) + return true; + + url = tools::get_update_url(software, subdir, buildtag, version, false); + std::string filename; + const char *slash = strrchr(url.c_str(), '/'); + if (slash) + filename = slash + 1; + else + filename = std::string(software) + "-update-" + version; + boost::filesystem::path path(epee::string_tools::get_current_module_folder()); + path /= filename; + + boost::unique_lock lock(m_update_mutex); + + if (m_update_download != 0) + { + MCDEBUG("updates", "Already downloading update"); + return true; + } + + crypto::hash file_hash; + if (!tools::sha256sum(path.string(), file_hash) || (hash != epee::string_tools::pod_to_hex(file_hash))) + { + MCDEBUG("updates", "We don't have that file already, downloading"); + const std::string tmppath = path.string() + ".tmp"; + if (epee::file_io_utils::is_file_exist(tmppath)) + { + MCDEBUG("updates", "We have part of the file already, resuming download"); + } + m_last_update_length = 0; + m_update_download = tools::download_async(tmppath, url, [this, hash, path](const std::string &tmppath, const std::string &uri, bool success) { + bool remove = false, good = true; + if (success) + { + crypto::hash file_hash; + if (!tools::sha256sum(tmppath, file_hash)) + { + MCERROR("updates", "Failed to hash " << tmppath); + remove = true; + good = false; + } + else if (hash != epee::string_tools::pod_to_hex(file_hash)) + { + MCERROR("updates", "Download from " << uri << " does not match the expected hash"); + remove = true; + good = false; + } + } + else + { + MCERROR("updates", "Failed to download " << uri); + good = false; + } + boost::unique_lock lock(m_update_mutex); + m_update_download = 0; + if (success && !remove) + { + std::error_code e = tools::replace_file(tmppath, path.string()); + if (e) + { + MCERROR("updates", "Failed to rename downloaded file"); + good = false; + } + } + else if (remove) + { + if (!boost::filesystem::remove(tmppath)) + { + MCERROR("updates", "Failed to remove invalid downloaded file"); + good = false; + } + } + if (good) + MCLOG_CYAN(el::Level::Info, "updates", "New version downloaded to " << path.string()); + }, [this](const std::string &path, const std::string &uri, size_t length, ssize_t content_length) { + if (length >= m_last_update_length + 1024 * 1024 * 10) + { + m_last_update_length = length; + MCDEBUG("updates", "Downloaded " << length << "/" << (content_length ? std::to_string(content_length) : "unknown")); + } + return true; + }); + } + else + { + MCDEBUG("updates", "We already have " << path << " with expected hash"); + } + + lock.unlock(); + + if (check_updates_level == UPDATES_DOWNLOAD) + return true; + + MCERROR("updates", "Download/update not implemented yet"); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::check_disk_space() + { + uint64_t free_space = get_free_space(); + if (free_space < 1ull * 1024 * 1024 * 1024) // 1 GB + { + const el::Level level = el::Level::Warning; + MCLOG_RED(level, "global", "Free space is below 1 GB on " << m_config_folder); + } + return true; + } + //----------------------------------------------------------------------------------------------- + double factorial(unsigned int n) + { + if (n <= 1) + return 1.0; + double f = n; + while (n-- > 1) + f *= n; + return f; + } + //----------------------------------------------------------------------------------------------- + static double probability1(unsigned int blocks, unsigned int expected) + { + // https://www.umass.edu/wsp/resources/poisson/#computing + return pow(expected, blocks) / (factorial(blocks) * exp(expected)); + } + //----------------------------------------------------------------------------------------------- + static double probability(unsigned int blocks, unsigned int expected) + { + double p = 0.0; + if (blocks <= expected) + { + for (unsigned int b = 0; b <= blocks; ++b) + p += probability1(b, expected); + } + else if (blocks > expected) + { + for (unsigned int b = blocks; b <= expected * 3 /* close enough */; ++b) + p += probability1(b, expected); + } + return p; + } + //----------------------------------------------------------------------------------------------- + bool core::check_block_rate() + { + if (m_offline || m_nettype == FAKECHAIN || m_target_blockchain_height > get_current_blockchain_height() || m_target_blockchain_height == 0) + { + MDEBUG("Not checking block rate, offline or syncing"); + return true; + } + + static constexpr double threshold = 1. / (864000 / DIFFICULTY_TARGET_V2); // one false positive every 10 days + static constexpr unsigned int max_blocks_checked = 150; + + const time_t now = time(NULL); + const std::vector timestamps = m_blockchain_storage.get_last_block_timestamps(max_blocks_checked); + + static const unsigned int seconds[] = { 5400, 3600, 1800, 1200, 600 }; + for (size_t n = 0; n < sizeof(seconds)/sizeof(seconds[0]); ++n) + { + unsigned int b = 0; + const time_t time_boundary = now - static_cast(seconds[n]); + for (time_t ts: timestamps) b += ts >= time_boundary; + const double p = probability(b, seconds[n] / DIFFICULTY_TARGET_V2); + MDEBUG("blocks in the last " << seconds[n] / 60 << " minutes: " << b << " (probability " << p << ")"); + if (p < threshold) + { + MWARNING("There were " << b << (b == max_blocks_checked ? " or more" : "") << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack, or your computer's time is off. Or it could be just sheer bad luck."); + + std::shared_ptr block_rate_notify = m_block_rate_notify; + if (block_rate_notify) + { + auto expected = seconds[n] / DIFFICULTY_TARGET_V2; + block_rate_notify->notify("%t", std::to_string(seconds[n] / 60).c_str(), "%b", std::to_string(b).c_str(), "%e", std::to_string(expected).c_str(), NULL); + } + + break; // no need to look further + } + } + + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::recalculate_difficulties() + { + m_blockchain_storage.recalculate_difficulties(); + return true; + } + //----------------------------------------------------------------------------------------------- + void core::flush_bad_txs_cache() + { + bad_semantics_txes_lock.lock(); + for (int idx = 0; idx < 2; ++idx) + bad_semantics_txes[idx].clear(); + bad_semantics_txes_lock.unlock(); + } + //----------------------------------------------------------------------------------------------- + void core::flush_invalid_blocks() + { + m_blockchain_storage.flush_invalid_blocks(); + } + //----------------------------------------------------------------------------------------------- + bool core::get_txpool_complement(const std::vector &hashes, std::vector &txes) + { + return m_mempool.get_complement(hashes, txes); + } + //----------------------------------------------------------------------------------------------- + bool core::update_blockchain_pruning() + { + return m_blockchain_storage.update_blockchain_pruning(); + } + //----------------------------------------------------------------------------------------------- + bool core::check_blockchain_pruning() + { + return m_blockchain_storage.check_blockchain_pruning(); + } + //----------------------------------------------------------------------------------------------- + void core::set_target_blockchain_height(uint64_t target_blockchain_height) + { + m_target_blockchain_height = target_blockchain_height; + } + //----------------------------------------------------------------------------------------------- + uint64_t core::get_target_blockchain_height() const + { + return m_target_blockchain_height; + } + //----------------------------------------------------------------------------------------------- + uint64_t core::prevalidate_block_hashes(uint64_t height, const std::vector &hashes, const std::vector &weights) + { + return get_blockchain_storage().prevalidate_block_hashes(height, hashes, weights); + } + //----------------------------------------------------------------------------------------------- + uint64_t core::get_free_space() const + { + boost::filesystem::path path(m_config_folder); + boost::filesystem::space_info si = boost::filesystem::space(path); + return si.available; + } + //----------------------------------------------------------------------------------------------- + uint32_t core::get_blockchain_pruning_seed() const + { + return get_blockchain_storage().get_blockchain_pruning_seed(); + } + //----------------------------------------------------------------------------------------------- + bool core::prune_blockchain(uint32_t pruning_seed) + { + return get_blockchain_storage().prune_blockchain(pruning_seed); + } + //----------------------------------------------------------------------------------------------- + bool core::is_within_compiled_block_hash_area(uint64_t height) const + { + return get_blockchain_storage().is_within_compiled_block_hash_area(height); + } + //----------------------------------------------------------------------------------------------- + bool core::has_block_weights(uint64_t height, uint64_t nblocks) const + { + return get_blockchain_storage().has_block_weights(height, nblocks); + } + //----------------------------------------------------------------------------------------------- + std::time_t core::get_start_time() const + { + return start_time; + } + //----------------------------------------------------------------------------------------------- + void core::graceful_exit() + { + raise(SIGTERM); + } +} diff --git a/crypto/xmrcore/cryptonote_core.h b/crypto/xmrcore/cryptonote_core.h new file mode 100644 index 0000000000..d2bffdaee0 --- /dev/null +++ b/crypto/xmrcore/cryptonote_core.h @@ -0,0 +1,1137 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include + +#include +#include +#include + +#include "cryptonote_basic/fwd.h" +#include "cryptonote_core/i_core_events.h" +#include "cryptonote_protocol/cryptonote_protocol_handler_common.h" +#include "cryptonote_protocol/enums.h" +#include "storages/portable_storage_template_helper.h" +#include "common/download.h" +#include "common/command_line.h" +#include "tx_pool.h" +#include "blockchain.h" +#include "cryptonote_basic/miner.h" +#include "cryptonote_basic/connection_context.h" +#include "warnings.h" +#include "crypto/hash.h" +#include "span.h" +#include "rpc/fwd.h" + +PUSH_WARNINGS +DISABLE_VS_WARNINGS(4355) + +enum { HAVE_BLOCK_MAIN_CHAIN, HAVE_BLOCK_ALT_CHAIN, HAVE_BLOCK_INVALID }; + +namespace cryptonote +{ + struct test_options { + const std::pair *hard_forks; + const size_t long_term_block_weight_window; + }; + + extern const command_line::arg_descriptor arg_data_dir; + extern const command_line::arg_descriptor arg_testnet_on; + extern const command_line::arg_descriptor arg_stagenet_on; + extern const command_line::arg_descriptor arg_regtest_on; + extern const command_line::arg_descriptor arg_fixed_difficulty; + extern const command_line::arg_descriptor arg_offline; + extern const command_line::arg_descriptor arg_block_download_max_size; + extern const command_line::arg_descriptor arg_sync_pruned_blocks; + + /************************************************************************/ + /* */ + /************************************************************************/ + + /** + * @brief handles core cryptonote functionality + * + * This class coordinates cryptonote functionality including, but not + * limited to, communication among the Blockchain, the transaction pool, + * any miners, and the network. + */ + class core final: public i_miner_handler, public i_core_events + { + public: + + /** + * @brief constructor + * + * sets member variables into a usable state + * + * @param pprotocol pre-constructed protocol object to store and use + */ + core(i_cryptonote_protocol* pprotocol); + + /** + * @copydoc Blockchain::handle_get_objects + * + * @note see Blockchain::handle_get_objects() + * @param context connection context associated with the request + */ + bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); + + /** + * @brief calls various idle routines + * + * @note see miner::on_idle and tx_memory_pool::on_idle + * + * @return true + */ + bool on_idle(); + + /** + * @brief handles an incoming transaction + * + * Parses an incoming transaction and, if nothing is obviously wrong, + * passes it along to the transaction pool + * + * @param tx_blob the tx to handle + * @param tvc metadata about the transaction's validity + * @param tx_relay how the transaction was received + * @param relayed whether or not the transaction was relayed to us + * + * @return true if the transaction was accepted, false otherwise + */ + bool handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed); + + /** + * @brief handles a list of incoming transactions + * + * Parses incoming transactions and, if nothing is obviously wrong, + * passes them along to the transaction pool + * + * @pre `tx_blobs.size() == tvc.size()` + * + * @param tx_blobs the txs to handle + * @param tvc metadata about the transactions' validity + * @param tx_relay how the transaction was received. + * @param relayed whether or not the transactions were relayed to us + * + * @return true if the transactions were accepted, false otherwise + */ + bool handle_incoming_txs(epee::span tx_blobs, epee::span tvc, relay_method tx_relay, bool relayed); + + /** + * @brief handles a list of incoming transactions + * + * Parses incoming transactions and, if nothing is obviously wrong, + * passes them along to the transaction pool + * + * @param tx_blobs the txs to handle + * @param tvc metadata about the transactions' validity + * @param tx_relay how the transaction was received. + * @param relayed whether or not the transactions were relayed to us + * + * @return true if the transactions were accepted, false otherwise + */ + bool handle_incoming_txs(const std::vector& tx_blobs, std::vector& tvc, relay_method tx_relay, bool relayed) + { + tvc.resize(tx_blobs.size()); + return handle_incoming_txs(epee::to_span(tx_blobs), epee::to_mut_span(tvc), tx_relay, relayed); + } + + /** + * @brief handles an incoming block + * + * periodic update to checkpoints is triggered here + * Attempts to add the block to the Blockchain and, on success, + * optionally updates the miner's block template. + * + * @param block_blob the block to be added + * @param block the block to be added, or NULL + * @param bvc return-by-reference metadata context about the block's validity + * @param update_miner_blocktemplate whether or not to update the miner's block template + * + * @return false if loading new checkpoints fails, or the block is not + * added, otherwise true + */ + bool handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, bool update_miner_blocktemplate = true); + + /** + * @copydoc Blockchain::prepare_handle_incoming_blocks + * + * @note see Blockchain::prepare_handle_incoming_blocks + */ + bool prepare_handle_incoming_blocks(const std::vector &blocks_entry, std::vector &blocks); + + /** + * @copydoc Blockchain::cleanup_handle_incoming_blocks + * + * @note see Blockchain::cleanup_handle_incoming_blocks + */ + bool cleanup_handle_incoming_blocks(bool force_sync = false); + + /** + * @brief check the size of a block against the current maximum + * + * @param block_blob the block to check + * + * @return whether or not the block is too big + */ + bool check_incoming_block_size(const blobdata& block_blob) const; + + /** + * @brief get the cryptonote protocol instance + * + * @return the instance + */ + i_cryptonote_protocol* get_protocol(){return m_pprotocol;} + + //-------------------- i_miner_handler ----------------------- + + /** + * @brief stores and relays a block found by a miner + * + * Updates the miner's target block, attempts to store the found + * block in Blockchain, and -- on success -- relays that block to + * the network. + * + * @param b the block found + * @param bvc returns the block verification flags + * + * @return true if the block was added to the main chain, otherwise false + */ + virtual bool handle_block_found(block& b, block_verification_context &bvc) override; + + /** + * @copydoc Blockchain::create_block_template + * + * @note see Blockchain::create_block_template + */ + virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash) override; + virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash); + + /** + * @copydoc Blockchain::get_miner_data + * + * @note see Blockchain::get_miner_data + */ + bool get_miner_data(uint8_t& major_version, uint64_t& height, crypto::hash& prev_id, crypto::hash& seed_hash, difficulty_type& difficulty, uint64_t& median_weight, uint64_t& already_generated_coins, std::vector& tx_backlog); + + /** + * @brief called when a transaction is relayed. + * @note Should only be invoked from `levin_notify`. + */ + virtual void on_transactions_relayed(epee::span tx_blobs, relay_method tx_relay) final; + + + /** + * @brief gets the miner instance + * + * @return a reference to the miner instance + */ + miner& get_miner(){return m_miner;} + + /** + * @brief gets the miner instance (const) + * + * @return a const reference to the miner instance + */ + const miner& get_miner()const{return m_miner;} + + /** + * @brief adds command line options to the given options set + * + * As of now, there are no command line options specific to core, + * so this function simply returns. + * + * @param desc return-by-reference the command line options set to add to + */ + static void init_options(boost::program_options::options_description& desc); + + /** + * @brief initializes the core as needed + * + * This function initializes the transaction pool, the Blockchain, and + * a miner instance with parameters given on the command line (or defaults) + * + * @param vm command line parameters + * @param test_options configuration options for testing + * @param get_checkpoints if set, will be called to get checkpoints data, must return checkpoints data pointer and size or nullptr if there ain't any checkpoints for specific network type + * @param allow_dns whether or not to allow DNS requests + * + * @return false if one of the init steps fails, otherwise true + */ + bool init(const boost::program_options::variables_map& vm, const test_options *test_options = NULL, const GetCheckpointsCallback& get_checkpoints = nullptr, bool allow_dns = true); + + /** + * @copydoc Blockchain::reset_and_set_genesis_block + * + * @note see Blockchain::reset_and_set_genesis_block + */ + bool set_genesis_block(const block& b); + + /** + * @brief performs safe shutdown steps for core and core components + * + * Uninitializes the miner instance, transaction pool, and Blockchain + * + * @return true + */ + bool deinit(); + + /** + * @brief sets to drop blocks downloaded (for testing) + */ + void test_drop_download(); + + /** + * @brief sets to drop blocks downloaded below a certain height + * + * @param height height below which to drop blocks + */ + void test_drop_download_height(uint64_t height); + + /** + * @brief gets whether or not to drop blocks (for testing) + * + * @return whether or not to drop blocks + */ + bool get_test_drop_download() const; + + /** + * @brief gets whether or not to drop blocks + * + * If the current blockchain height <= our block drop threshold + * and test drop blocks is set, return true + * + * @return see above + */ + bool get_test_drop_download_height() const; + + /** + * @copydoc Blockchain::get_current_blockchain_height + * + * @note see Blockchain::get_current_blockchain_height() + */ + virtual uint64_t get_current_blockchain_height() const final; + + /** + * @brief get the hash and height of the most recent block + * + * @param height return-by-reference height of the block + * @param top_id return-by-reference hash of the block + */ + void get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::vector>&, std::vector&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::vector>&, std::vector&) const + */ + bool get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks, std::vector& txs) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::vector>&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::vector>&) const + */ + bool get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::vector>&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::vector>&) const + */ + bool get_blocks(uint64_t start_offset, size_t count, std::vector& blocks) const; + + /** + * @copydoc Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const + * + * @note see Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const + */ + template + bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const + { + return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); + } + + /** + * @copydoc Blockchain::get_block_id_by_height + * + * @note see Blockchain::get_block_id_by_height + */ + crypto::hash get_block_id_by_height(uint64_t height) const; + + /** + * @copydoc Blockchain::get_transactions + * + * @note see Blockchain::get_transactions + */ + bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs, bool pruned = false) const; + + /** + * @copydoc Blockchain::get_transactions + * + * @note see Blockchain::get_transactions + */ + bool get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::vector& missed_txs) const; + + /** + * @copydoc Blockchain::get_transactions + * + * @note see Blockchain::get_transactions + */ + bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs, bool pruned = false) const; + + /** + * @copydoc Blockchain::get_block_by_hash + * + * @note see Blockchain::get_block_by_hash + */ + bool get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan = NULL) const; + + /** + * @copydoc Blockchain::get_alternative_blocks + * + * @note see Blockchain::get_alternative_blocks(std::vector&) const + */ + bool get_alternative_blocks(std::vector& blocks) const; + + /** + * @copydoc Blockchain::get_alternative_blocks_count + * + * @note see Blockchain::get_alternative_blocks_count() const + */ + size_t get_alternative_blocks_count() const; + + /** + * @brief set the pointer to the cryptonote protocol object to use + * + * @param pprotocol the pointer to set ours as + */ + void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); + + /** + * @copydoc Blockchain::set_checkpoints + * + * @note see Blockchain::set_checkpoints() + */ + void set_checkpoints(checkpoints&& chk_pts); + + /** + * @brief set the file path to read from when loading checkpoints + * + * @param path the path to set ours as + */ + void set_checkpoints_file_path(const std::string& path); + + /** + * @brief set whether or not we enforce DNS checkpoints + * + * @param enforce_dns enforce DNS checkpoints or not + */ + void set_enforce_dns_checkpoints(bool enforce_dns); + + /** + * @brief set a listener for txes being added to the txpool + * + * @param callable to notify, or empty function to disable. + */ + void set_txpool_listener(boost::function)> zmq_pub); + + /** + * @brief set whether or not to enable or disable DNS checkpoints + * + * @param disble whether to disable DNS checkpoints + */ + void disable_dns_checkpoints(bool disable = true) { m_disable_dns_checkpoints = disable; } + + /** + * @copydoc tx_memory_pool::have_tx + * + * @note see tx_memory_pool::have_tx + */ + bool pool_has_tx(const crypto::hash &txid) const; + + /** + * @copydoc tx_memory_pool::get_transactions + * @param include_sensitive_txes include private transactions + * + * @note see tx_memory_pool::get_transactions + */ + bool get_pool_transactions(std::vector& txs, bool include_sensitive_txes = false) const; + + /** + * @copydoc tx_memory_pool::get_txpool_backlog + * @param include_sensitive_txes include private transactions + * + * @note see tx_memory_pool::get_txpool_backlog + */ + bool get_txpool_backlog(std::vector& backlog, bool include_sensitive_txes = false) const; + + /** + * @copydoc tx_memory_pool::get_transactions + * @param include_sensitive_txes include private transactions + * + * @note see tx_memory_pool::get_transactions + */ + bool get_pool_transaction_hashes(std::vector& txs, bool include_sensitive_txes = false) const; + + /** + * @copydoc tx_memory_pool::get_transactions + * @param include_sensitive_txes include private transactions + * + * @note see tx_memory_pool::get_transactions + */ + bool get_pool_transaction_stats(struct txpool_stats& stats, bool include_sensitive_txes = false) const; + + /** + * @copydoc tx_memory_pool::get_transaction + * + * @note see tx_memory_pool::get_transaction + */ + bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx, relay_category tx_category) const; + + /** + * @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info + * @param include_sensitive_txes include private transactions + * + * @note see tx_memory_pool::get_pool_transactions_and_spent_keys_info + */ + bool get_pool_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos, bool include_sensitive_txes = false) const; + + /** + * @copydoc tx_memory_pool::get_pool_for_rpc + * + * @note see tx_memory_pool::get_pool_for_rpc + */ + bool get_pool_for_rpc(std::vector& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** + * @copydoc tx_memory_pool::get_transactions_count + * @param include_sensitive_txes include private transactions + * + * @note see tx_memory_pool::get_transactions_count + */ + size_t get_pool_transactions_count(bool include_sensitive_txes = false) const; + + /** + * @copydoc Blockchain::get_total_transactions + * + * @note see Blockchain::get_total_transactions + */ + size_t get_blockchain_total_transactions() const; + + /** + * @copydoc Blockchain::have_block + * + * @note see Blockchain::have_block + */ + bool have_block_unlocked(const crypto::hash& id, int *where = NULL) const; + bool have_block(const crypto::hash& id, int *where = NULL) const; + + /** + * @copydoc Blockchain::get_short_chain_history + * + * @note see Blockchain::get_short_chain_history + */ + bool get_short_chain_history(std::list& ids) const; + + /** + * @copydoc Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const + * + * @note see Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const + */ + bool find_blockchain_supplement(const std::list& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; + + /** + * @copydoc Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::vector > >&, uint64_t&, uint64_t&, size_t) const + * + * @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::vector > >&, uint64_t&, uint64_t&, size_t) const + */ + bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::vector, std::vector > > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_block_count, size_t max_tx_count) const; + + /** + * @copydoc Blockchain::get_tx_outputs_gindexs + * + * @note see Blockchain::get_tx_outputs_gindexs + */ + bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; + bool get_tx_outputs_gindexs(const crypto::hash& tx_id, size_t n_txes, std::vector>& indexs) const; + + /** + * @copydoc Blockchain::get_tail_id + * + * @note see Blockchain::get_tail_id + */ + crypto::hash get_tail_id() const; + + /** + * @copydoc Blockchain::get_block_cumulative_difficulty + * + * @note see Blockchain::get_block_cumulative_difficulty + */ + difficulty_type get_block_cumulative_difficulty(uint64_t height) const; + + /** + * @copydoc Blockchain::get_outs + * + * @note see Blockchain::get_outs + */ + bool get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const; + + /** + * @copydoc Blockchain::get_output_distribution + * + * @brief get per block distribution of outputs of a given amount + */ + bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector &distribution, uint64_t &base) const; + + /** + * @copydoc miner::pause + * + * @note see miner::pause + */ + void pause_mine(); + + /** + * @copydoc miner::resume + * + * @note see miner::resume + */ + void resume_mine(); + + /** + * @brief gets the Blockchain instance + * + * @return a reference to the Blockchain instance + */ + Blockchain& get_blockchain_storage(){return m_blockchain_storage;} + + /** + * @brief gets the Blockchain instance (const) + * + * @return a const reference to the Blockchain instance + */ + const Blockchain& get_blockchain_storage()const{return m_blockchain_storage;} + + /** + * @copydoc tx_memory_pool::print_pool + * + * @note see tx_memory_pool::print_pool + */ + std::string print_pool(bool short_format) const; + + /** + * @brief gets the core synchronization status + * + * @return core synchronization status + */ + virtual bool is_synchronized() const final; + + /** + * @copydoc miner::on_synchronized + * + * @note see miner::on_synchronized + */ + void on_synchronized(); + + /** + * @copydoc Blockchain::safesyncmode + * + * 2note see Blockchain::safesyncmode + */ + void safesyncmode(const bool onoff); + + /** + * @brief sets the target blockchain height + * + * @param target_blockchain_height the height to set + */ + void set_target_blockchain_height(uint64_t target_blockchain_height); + + /** + * @brief gets the target blockchain height + * + * @param target_blockchain_height the target height + */ + uint64_t get_target_blockchain_height() const; + + /** + * @brief returns the newest hardfork version known to the blockchain + * + * @return the version + */ + uint8_t get_ideal_hard_fork_version() const; + + /** + * @brief return the ideal hard fork version for a given block height + * + * @return what it says above + */ + uint8_t get_ideal_hard_fork_version(uint64_t height) const; + + /** + * @brief return the hard fork version for a given block height + * + * @return what it says above + */ + uint8_t get_hard_fork_version(uint64_t height) const; + + /** + * @brief return the earliest block a given version may activate + * + * @return what it says above + */ + uint64_t get_earliest_ideal_height_for_version(uint8_t version) const; + + /** + * @brief gets start_time + * + */ + std::time_t get_start_time() const; + + /** + * @brief tells the Blockchain to update its checkpoints + * + * This function will check if enough time has passed since the last + * time checkpoints were updated and tell the Blockchain to update + * its checkpoints if it is time. If updating checkpoints fails, + * the daemon is told to shut down. + * + * @note see Blockchain::update_checkpoints() + */ + bool update_checkpoints(const bool skip_dns = false); + + /** + * @brief tells the daemon to wind down operations and stop running + * + * Currently this function raises SIGTERM, allowing the installed signal + * handlers to do the actual stopping. + */ + void graceful_exit(); + + /** + * @brief stops the daemon running + * + * @note see graceful_exit() + */ + void stop(); + + /** + * @copydoc Blockchain::have_tx_keyimg_as_spent + * + * @note see Blockchain::have_tx_keyimg_as_spent + */ + bool is_key_image_spent(const crypto::key_image& key_im) const; + + /** + * @brief check if multiple key images are spent + * + * plural version of is_key_image_spent() + * + * @param key_im list of key images to check + * @param spent return-by-reference result for each image checked + * + * @return true + */ + bool are_key_images_spent(const std::vector& key_im, std::vector &spent) const; + + /** + * @brief check if multiple key images are spent in the transaction pool + * + * @param key_im list of key images to check + * @param spent return-by-reference result for each image checked + * + * @return true + */ + bool are_key_images_spent_in_pool(const std::vector& key_im, std::vector &spent) const; + + /** + * @brief get the number of blocks to sync in one go + * + * @return the number of blocks to sync in one go + */ + size_t get_block_sync_size(uint64_t height) const; + + /** + * @brief get the sum of coinbase tx amounts between blocks + * + * @return the number of blocks to sync in one go + */ + std::pair get_coinbase_tx_sum(const uint64_t start_offset, const size_t count); + + /** + * @brief get the network type we're on + * + * @return which network are we on? + */ + network_type get_nettype() const { return m_nettype; }; + + /** + * @brief check whether an update is known to be available or not + * + * This does not actually trigger a check, but returns the result + * of the last check + * + * @return whether an update is known to be available or not + */ + bool is_update_available() const { return m_update_available; } + + /** + * @brief get whether fluffy blocks are enabled + * + * @return whether fluffy blocks are enabled + */ + bool fluffy_blocks_enabled() const { return m_fluffy_blocks_enabled; } + + /** + * @brief check a set of hashes against the precompiled hash set + * + * @return number of usable blocks + */ + uint64_t prevalidate_block_hashes(uint64_t height, const std::vector &hashes, const std::vector &weights); + + /** + * @brief get free disk space on the blockchain partition + * + * @return free space in bytes + */ + uint64_t get_free_space() const; + + /** + * @brief get whether the core is running offline + * + * @return whether the core is running offline + */ + bool offline() const { return m_offline; } + + /** + * @brief get the blockchain pruning seed + * + * @return the blockchain pruning seed + */ + uint32_t get_blockchain_pruning_seed() const; + + /** + * @brief prune the blockchain + * + * @param pruning_seed the seed to use to prune the chain (0 for default, highly recommended) + * + * @return true iff success + */ + bool prune_blockchain(uint32_t pruning_seed = 0); + + /** + * @brief incrementally prunes blockchain + * + * @return true on success, false otherwise + */ + bool update_blockchain_pruning(); + + /** + * @brief checks the blockchain pruning if enabled + * + * @return true on success, false otherwise + */ + bool check_blockchain_pruning(); + + /** + * @brief checks whether a given block height is included in the precompiled block hash area + * + * @param height the height to check for + */ + bool is_within_compiled_block_hash_area(uint64_t height) const; + + /** + * @brief checks whether block weights are known for the given range + */ + bool has_block_weights(uint64_t height, uint64_t nblocks) const; + + /** + * @brief flushes the bad txs cache + */ + void flush_bad_txs_cache(); + + /** + * @brief flushes the invalid block cache + */ + void flush_invalid_blocks(); + + /** + * @brief returns the set of transactions in the txpool which are not in the argument + * + * @param hashes hashes of transactions to exclude from the result + * + * @return true iff success, false otherwise + */ + bool get_txpool_complement(const std::vector &hashes, std::vector &txes); + + private: + + /** + * @copydoc add_new_tx(transaction&, tx_verification_context&, bool) + * + * @param tx_hash the transaction's hash + * @param blob the transaction as a blob + * @param tx_weight the weight of the transaction + * @param tx_relay how the transaction was received + * @param relayed whether or not the transaction was relayed to us + * + */ + bool add_new_tx(transaction& tx, const crypto::hash& tx_hash, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed); + + /** + * @brief add a new transaction to the transaction pool + * + * Adds a new transaction to the transaction pool. + * + * @param tx the transaction to add + * @param tvc return-by-reference metadata about the transaction's verification process + * @param tx_relay how the transaction was received + * @param relayed whether or not the transaction was relayed to us + * + * @return true if the transaction is already in the transaction pool, + * is already in a block on the Blockchain, or is successfully added + * to the transaction pool + */ + bool add_new_tx(transaction& tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed); + + /** + * @copydoc Blockchain::add_new_block + * + * @note see Blockchain::add_new_block + */ + bool add_new_block(const block& b, block_verification_context& bvc); + + /** + * @brief load any core state stored on disk + * + * currently does nothing, but may have state to load in the future. + * + * @return true + */ + bool load_state_data(); + + /** + * @copydoc parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const + * + * @note see parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const + */ + bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, const blobdata& blob) const; + + /** + * @brief check a transaction's syntax + * + * For now this does nothing, but it may check something about the tx + * in the future. + * + * @param tx the transaction to check + * + * @return true + */ + bool check_tx_syntax(const transaction& tx) const; + + /** + * @brief validates some simple properties of a transaction + * + * Currently checks: tx has inputs, + * tx inputs all of supported type(s), + * tx outputs valid (type, key, amount), + * input and output total amounts don't overflow, + * output amount <= input amount, + * tx not too large, + * each input has a different key image. + * + * @param tx the transaction to check + * @param keeped_by_block if the transaction has been in a block + * + * @return true if all the checks pass, otherwise false + */ + bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; + void set_semantics_failed(const crypto::hash &tx_hash); + + bool handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash); + bool handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash); + struct tx_verification_batch_info { const cryptonote::transaction *tx; crypto::hash tx_hash; tx_verification_context &tvc; bool &result; }; + bool handle_incoming_tx_accumulated_batch(std::vector &tx_info, bool keeped_by_block); + + /** + * @copydoc miner::on_block_chain_update + * + * @note see miner::on_block_chain_update + * + * @return true + */ + bool update_miner_block_template(); + + /** + * @brief act on a set of command line options given + * + * @param vm the command line options + * + * @return true + */ + bool handle_command_line(const boost::program_options::variables_map& vm); + + /** + * @brief verify that each input key image in a transaction is unique + * + * @param tx the transaction to check + * + * @return false if any key image is repeated, otherwise true + */ + bool check_tx_inputs_keyimages_diff(const transaction& tx) const; + + /** + * @brief verify that each ring uses distinct members + * + * @param tx the transaction to check + * + * @return false if any ring uses duplicate members, true otherwise + */ + bool check_tx_inputs_ring_members_diff(const transaction& tx) const; + + /** + * @brief verify that each input key image in a transaction is in + * the valid domain + * + * @param tx the transaction to check + * + * @return false if any key image is not in the valid domain, otherwise true + */ + bool check_tx_inputs_keyimages_domain(const transaction& tx) const; + + /** + * @brief attempts to relay any transactions in the mempool which need it + * + * @return true + */ + bool relay_txpool_transactions(); + + /** + * @brief checks DNS versions + * + * @return true on success, false otherwise + */ + bool check_updates(); + + /** + * @brief checks free disk space + * + * @return true on success, false otherwise + */ + bool check_disk_space(); + + /** + * @brief checks block rate, and warns if it's too slow + * + * @return true on success, false otherwise + */ + bool check_block_rate(); + + /** + * @brief recalculate difficulties after the last difficulty checklpoint to circumvent the annoying 'difficulty drift' bug + * + * @return true + */ + bool recalculate_difficulties(); + + bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) + + uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so + + tx_memory_pool m_mempool; //!< transaction pool instance + Blockchain m_blockchain_storage; //!< Blockchain instance + + i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance + + epee::critical_section m_incoming_tx_lock; //!< incoming transaction lock + + //m_miner and m_miner_addres are probably temporary here + miner m_miner; //!< miner instance + + std::string m_config_folder; //!< folder to look in for configs and other files + + cryptonote_protocol_stub m_protocol_stub; //!< cryptonote protocol stub instance + + epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; //!< interval for manual storing of Blockchain, if enabled + epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; //!< interval for checking HardFork status + epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions + epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space + epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate + epee::math_helper::once_a_time_seconds<60*60*5, true> m_blockchain_pruning_interval; //!< interval for incremental blockchain pruning + epee::math_helper::once_a_time_seconds<60*60*24*7, false> m_diff_recalc_interval; //!< interval for recalculating difficulties + + std::atomic m_starter_message_showed; //!< has the "daemon will sync now" message been shown? + + uint64_t m_target_blockchain_height; //!< blockchain height target + + network_type m_nettype; //!< which network are we on? + + std::atomic m_update_available; + + std::string m_checkpoints_path; //!< path to json checkpoints file + time_t m_last_dns_checkpoints_update; //!< time when dns checkpoints were last updated + time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated + + std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once + bool m_disable_dns_checkpoints; + + size_t block_sync_size; + + time_t start_time; + + std::unordered_set bad_semantics_txes[2]; + boost::mutex bad_semantics_txes_lock; + + enum { + UPDATES_DISABLED, + UPDATES_NOTIFY, + UPDATES_DOWNLOAD, + UPDATES_UPDATE, + } check_updates_level; + + tools::download_async_handle m_update_download; + size_t m_last_update_length; + boost::mutex m_update_mutex; + + bool m_fluffy_blocks_enabled; + bool m_offline; + + /* `boost::function` is used because the implementation never allocates if + the callable object has a single `std::shared_ptr` or `std::weap_ptr` + internally. Whereas, the libstdc++ `std::function` will allocate. */ + + std::shared_ptr m_block_rate_notify; + boost::function)> m_zmq_pub; + }; +} + +POP_WARNINGS diff --git a/crypto/xmrcore/cryptonote_tx_utils.cpp b/crypto/xmrcore/cryptonote_tx_utils.cpp new file mode 100644 index 0000000000..f41c63a4b4 --- /dev/null +++ b/crypto/xmrcore/cryptonote_tx_utils.cpp @@ -0,0 +1,730 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include +#include +#include "include_base_utils.h" +#include "string_tools.h" +using namespace epee; + +#include "common/apply_permutation.h" +#include "cryptonote_tx_utils.h" +#include "cryptonote_config.h" +#include "blockchain.h" +#include "cryptonote_basic/miner.h" +#include "cryptonote_basic/tx_extra.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "ringct/rctSigs.h" +#include "multisig/multisig.h" + +using namespace crypto; + +namespace cryptonote +{ + //--------------------------------------------------------------- + void classify_addresses(const std::vector &destinations, const boost::optional& change_addr, size_t &num_stdaddresses, size_t &num_subaddresses, account_public_address &single_dest_subaddress) + { + num_stdaddresses = 0; + num_subaddresses = 0; + std::unordered_set unique_dst_addresses; + for(const tx_destination_entry& dst_entr: destinations) + { + if (change_addr && dst_entr.addr == change_addr) + continue; + if (unique_dst_addresses.count(dst_entr.addr) == 0) + { + unique_dst_addresses.insert(dst_entr.addr); + if (dst_entr.is_subaddress) + { + ++num_subaddresses; + single_dest_subaddress = dst_entr.addr; + } + else + { + ++num_stdaddresses; + } + } + } + LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << " subaddresses"); + } + //--------------------------------------------------------------- + bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { + tx.vin.clear(); + tx.vout.clear(); + tx.extra.clear(); + + keypair txkey = keypair::generate(hw::get_device("default")); + add_tx_pub_key_to_extra(tx, txkey.pub); + if(!extra_nonce.empty()) + if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) + return false; + if (!sort_tx_extra(tx.extra, tx.extra)) + return false; + + txin_gen in; + in.height = height; + + uint64_t block_reward; + if(!get_block_reward(median_weight, current_block_weight, already_generated_coins, block_reward, hard_fork_version)) + { + LOG_PRINT_L0("Block is too big"); + return false; + } + +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: reward " << block_reward << + ", fee " << fee); +#endif + block_reward += fee; + + // from hard fork 2, we cut out the low significant digits. This makes the tx smaller, and + // keeps the paid amount almost the same. The unpaid remainder gets pushed back to the + // emission schedule + // from hard fork 4, we use a single "dusty" output. This makes the tx even smaller, + // and avoids the quantization. These outputs will be added as rct outputs with identity + // masks, to they can be used as rct inputs. + if (hard_fork_version >= 2 && hard_fork_version < 4) { + block_reward = block_reward - block_reward % ::config::BASE_REWARD_CLAMP_THRESHOLD; + } + + std::vector out_amounts; + decompose_amount_into_digits(block_reward, hard_fork_version >= 2 ? 0 : ::config::DEFAULT_DUST_THRESHOLD, + [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, + [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); + + CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); + if (height == 0 || hard_fork_version >= 4) + { + // the genesis block was not decomposed, for unknown reasons + while (max_outs < out_amounts.size()) + { + //out_amounts[out_amounts.size() - 2] += out_amounts.back(); + //out_amounts.resize(out_amounts.size() - 1); + out_amounts[1] += out_amounts[0]; + for (size_t n = 1; n < out_amounts.size(); ++n) + out_amounts[n - 1] = out_amounts[n]; + out_amounts.pop_back(); + } + } + else + { + CHECK_AND_ASSERT_MES(max_outs >= out_amounts.size(), false, "max_out exceeded"); + } + + uint64_t summary_amounts = 0; + for (size_t no = 0; no < out_amounts.size(); no++) + { + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); + + r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")"); + + txout_to_key tk; + tk.key = out_eph_public_key; + + tx_out out; + summary_amounts += out.amount = out_amounts[no]; + out.target = tk; + tx.vout.push_back(out); + } + + CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); + + if (hard_fork_version >= 4) + tx.version = 2; + else + tx.version = 1; + + //lock + tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + tx.vin.push_back(in); + + tx.invalidate_hashes(); + + //LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee) + // << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2); + return true; + } + //--------------------------------------------------------------- + crypto::public_key get_destination_view_key_pub(const std::vector &destinations, const boost::optional& change_addr) + { + account_public_address addr = {null_pkey, null_pkey}; + size_t count = 0; + for (const auto &i : destinations) + { + if (i.amount == 0) + continue; + if (change_addr && i.addr == *change_addr) + continue; + if (i.addr == addr) + continue; + if (count > 0) + return null_pkey; + addr = i.addr; + ++count; + } + if (count == 0 && change_addr) + return change_addr->m_view_public_key; + return addr.m_view_public_key; + } + //--------------------------------------------------------------- + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, std::vector& destinations, const boost::optional& change_addr, const std::vector &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs) + { + hw::device &hwdev = sender_account_keys.get_device(); + + if (sources.empty()) + { + LOG_ERROR("Empty sources"); + return false; + } + + std::vector amount_keys; + tx.set_null(); + amount_keys.clear(); + if (msout) + { + msout->c.clear(); + } + + tx.version = rct ? 2 : 1; + tx.unlock_time = unlock_time; + + tx.extra = extra; + crypto::public_key txkey_pub; + + // if we have a stealth payment id, find it and encrypt it with the tx key now + std::vector tx_extra_fields; + if (parse_tx_extra(tx.extra, tx_extra_fields)) + { + bool add_dummy_payment_id = true; + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id = null_hash; + crypto::hash8 payment_id8 = null_hash8; + if (get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + LOG_PRINT_L2("Encrypting payment id " << payment_id8); + crypto::public_key view_key_pub = get_destination_view_key_pub(destinations, change_addr); + if (view_key_pub == null_pkey) + { + LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids"); + return false; + } + + if (!hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_key)) + { + LOG_ERROR("Failed to encrypt payment id"); + return false; + } + + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8); + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_nonce)); + if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) + { + LOG_ERROR("Failed to add encrypted payment id to tx extra"); + return false; + } + LOG_PRINT_L1("Encrypted payment ID: " << payment_id8); + add_dummy_payment_id = false; + } + else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + add_dummy_payment_id = false; + } + } + + // we don't add one if we've got more than the usual 1 destination plus change + if (destinations.size() > 2) + add_dummy_payment_id = false; + + if (add_dummy_payment_id) + { + // if we have neither long nor short payment id, add a dummy short one, + // this should end up being the vast majority of txes as time goes on + std::string extra_nonce; + crypto::hash8 payment_id8 = null_hash8; + crypto::public_key view_key_pub = get_destination_view_key_pub(destinations, change_addr); + if (view_key_pub == null_pkey) + { + LOG_ERROR("Failed to get key to encrypt dummy payment id with"); + } + else + { + hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_key); + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8); + if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) + { + LOG_ERROR("Failed to add dummy encrypted payment id to tx extra"); + // continue anyway + } + } + } + } + else + { + MWARNING("Failed to parse tx extra"); + tx_extra_fields.clear(); + } + + struct input_generation_context_data + { + keypair in_ephemeral; + }; + std::vector in_contexts; + + uint64_t summary_inputs_money = 0; + //fill inputs + int idx = -1; + for(const tx_source_entry& src_entr: sources) + { + ++idx; + if(src_entr.real_output >= src_entr.outputs.size()) + { + LOG_ERROR("real_output index (" << src_entr.real_output << ")bigger than output_keys.size()=" << src_entr.outputs.size()); + return false; + } + summary_inputs_money += src_entr.amount; + + //key_derivation recv_derivation; + in_contexts.push_back(input_generation_context_data()); + keypair& in_ephemeral = in_contexts.back().in_ephemeral; + crypto::key_image img; + const auto& out_key = reinterpret_cast(src_entr.outputs[src_entr.real_output].second.dest); + if(!generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral,img, hwdev)) + { + LOG_ERROR("Key image generation failed!"); + return false; + } + + //check that derivated key is equal with real output key (if non multisig) + if(!msout && !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + { + LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:" + << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" + << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second.dest) ); + LOG_ERROR("amount " << src_entr.amount << ", rct " << src_entr.rct); + LOG_ERROR("tx pubkey " << src_entr.real_out_tx_key << ", real_output_in_tx_index " << src_entr.real_output_in_tx_index); + return false; + } + + //put key image into tx input + txin_to_key input_to_key; + input_to_key.amount = src_entr.amount; + input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img; + + //fill outputs array and use relative offsets + for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) + input_to_key.key_offsets.push_back(out_entry.first); + + input_to_key.key_offsets = absolute_output_offsets_to_relative(input_to_key.key_offsets); + tx.vin.push_back(input_to_key); + } + + if (shuffle_outs) + { + std::shuffle(destinations.begin(), destinations.end(), crypto::random_device{}); + } + + // sort ins by their key image + std::vector ins_order(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + ins_order[n] = n; + std::sort(ins_order.begin(), ins_order.end(), [&](const size_t i0, const size_t i1) { + const txin_to_key &tk0 = boost::get(tx.vin[i0]); + const txin_to_key &tk1 = boost::get(tx.vin[i1]); + return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) > 0; + }); + tools::apply_permutation(ins_order, [&] (size_t i0, size_t i1) { + std::swap(tx.vin[i0], tx.vin[i1]); + std::swap(in_contexts[i0], in_contexts[i1]); + std::swap(sources[i0], sources[i1]); + }); + + // figure out if we need to make additional tx pubkeys + size_t num_stdaddresses = 0; + size_t num_subaddresses = 0; + account_public_address single_dest_subaddress; + classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress); + + // if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D + if (num_stdaddresses == 0 && num_subaddresses == 1) + { + txkey_pub = rct::rct2pk(hwdev.scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(tx_key))); + } + else + { + txkey_pub = rct::rct2pk(hwdev.scalarmultBase(rct::sk2rct(tx_key))); + } + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); + add_tx_pub_key_to_extra(tx, txkey_pub); + + std::vector additional_tx_public_keys; + + // we don't need to include additional tx keys if: + // - all the destinations are standard addresses + // - there's only one destination which is a subaddress + bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); + if (need_additional_txkeys) + CHECK_AND_ASSERT_MES(destinations.size() == additional_tx_keys.size(), false, "Wrong amount of additional tx keys"); + + uint64_t summary_outs_money = 0; + //fill outputs + size_t output_index = 0; + for(const tx_destination_entry& dst_entr: destinations) + { + CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); + crypto::public_key out_eph_public_key; + + hwdev.generate_output_ephemeral_keys(tx.version,sender_account_keys, txkey_pub, tx_key, + dst_entr, change_addr, output_index, + need_additional_txkeys, additional_tx_keys, + additional_tx_public_keys, amount_keys, out_eph_public_key); + + tx_out out; + out.amount = dst_entr.amount; + txout_to_key tk; + tk.key = out_eph_public_key; + out.target = tk; + tx.vout.push_back(out); + output_index++; + summary_outs_money += dst_entr.amount; + } + CHECK_AND_ASSERT_MES(additional_tx_public_keys.size() == additional_tx_keys.size(), false, "Internal error creating additional public keys"); + + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys)); + + LOG_PRINT_L2("tx pubkey: " << txkey_pub); + if (need_additional_txkeys) + { + LOG_PRINT_L2("additional tx pubkeys: "); + for (size_t i = 0; i < additional_tx_public_keys.size(); ++i) + LOG_PRINT_L2(additional_tx_public_keys[i]); + add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); + } + + if (!sort_tx_extra(tx.extra, tx.extra)) + return false; + + //check money + if(summary_outs_money > summary_inputs_money ) + { + LOG_ERROR("Transaction inputs money ("<< summary_inputs_money << ") less than outputs money (" << summary_outs_money << ")"); + return false; + } + + // check for watch only wallet + bool zero_secret_key = true; + for (size_t i = 0; i < sizeof(sender_account_keys.m_spend_secret_key); ++i) + zero_secret_key &= (sender_account_keys.m_spend_secret_key.data[i] == 0); + if (zero_secret_key) + { + MDEBUG("Null secret key, skipping signatures"); + } + + if (tx.version == 1) + { + //generate ring signatures + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash); + + std::stringstream ss_ring_s; + size_t i = 0; + for(const tx_source_entry& src_entr: sources) + { + ss_ring_s << "pub_keys:" << ENDL; + std::vector keys_ptrs; + std::vector keys(src_entr.outputs.size()); + size_t ii = 0; + for(const tx_source_entry::output_entry& o: src_entr.outputs) + { + keys[ii] = rct2pk(o.second.dest); + keys_ptrs.push_back(&keys[ii]); + ss_ring_s << o.second.dest << ENDL; + ++ii; + } + + tx.signatures.push_back(std::vector()); + std::vector& sigs = tx.signatures.back(); + sigs.resize(src_entr.outputs.size()); + if (!zero_secret_key) + crypto::generate_ring_signature(tx_prefix_hash, boost::get(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); + ss_ring_s << "signatures:" << ENDL; + std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); + ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output << ENDL; + i++; + } + + MCINFO("construct_tx", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str()); + } + else + { + size_t n_total_outs = sources[0].outputs.size(); // only for non-simple rct + + // the non-simple version is slightly smaller, but assumes all real inputs + // are on the same index, so can only be used if there just one ring. + bool use_simple_rct = sources.size() > 1 || rct_config.range_proof_type != rct::RangeProofBorromean; + + if (!use_simple_rct) + { + // non simple ringct requires all real inputs to be at the same index for all inputs + for(const tx_source_entry& src_entr: sources) + { + if(src_entr.real_output != sources.begin()->real_output) + { + LOG_ERROR("All inputs must have the same index for non-simple ringct"); + return false; + } + } + + // enforce same mixin for all outputs + for (size_t i = 1; i < sources.size(); ++i) { + if (n_total_outs != sources[i].outputs.size()) { + LOG_ERROR("Non-simple ringct transaction has varying ring size"); + return false; + } + } + } + + uint64_t amount_in = 0, amount_out = 0; + rct::ctkeyV inSk; + inSk.reserve(sources.size()); + // mixRing indexing is done the other way round for simple + rct::ctkeyM mixRing(use_simple_rct ? sources.size() : n_total_outs); + rct::keyV destinations; + std::vector inamounts, outamounts; + std::vector index; + std::vector kLRki; + for (size_t i = 0; i < sources.size(); ++i) + { + rct::ctkey ctkey; + amount_in += sources[i].amount; + inamounts.push_back(sources[i].amount); + index.push_back(sources[i].real_output); + // inSk: (secret key, mask) + ctkey.dest = rct::sk2rct(in_contexts[i].in_ephemeral.sec); + ctkey.mask = sources[i].mask; + inSk.push_back(ctkey); + memwipe(&ctkey, sizeof(rct::ctkey)); + // inPk: (public key, commitment) + // will be done when filling in mixRing + if (msout) + { + kLRki.push_back(sources[i].multisig_kLRki); + } + } + for (size_t i = 0; i < tx.vout.size(); ++i) + { + destinations.push_back(rct::pk2rct(boost::get(tx.vout[i].target).key)); + outamounts.push_back(tx.vout[i].amount); + amount_out += tx.vout[i].amount; + } + + if (use_simple_rct) + { + // mixRing indexing is done the other way round for simple + for (size_t i = 0; i < sources.size(); ++i) + { + mixRing[i].resize(sources[i].outputs.size()); + for (size_t n = 0; n < sources[i].outputs.size(); ++n) + { + mixRing[i][n] = sources[i].outputs[n].second; + } + } + } + else + { + for (size_t i = 0; i < n_total_outs; ++i) // same index assumption + { + mixRing[i].resize(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + { + mixRing[i][n] = sources[n].outputs[i].second; + } + } + } + + // fee + if (!use_simple_rct && amount_in > amount_out) + outamounts.push_back(amount_in - amount_out); + + // zero out all amounts to mask rct outputs, real amounts are now encrypted + for (size_t i = 0; i < tx.vin.size(); ++i) + { + if (sources[i].rct) + boost::get(tx.vin[i]).amount = 0; + } + for (size_t i = 0; i < tx.vout.size(); ++i) + tx.vout[i].amount = 0; + + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash, hwdev); + rct::ctkeyV outSk; + if (use_simple_rct) + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, rct_config, hwdev); + else + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption + memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey)); + + CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); + + MCINFO("construct_tx", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL); + } + + tx.invalidate_hashes(); + + return true; + } + //--------------------------------------------------------------- + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, std::vector& destinations, const boost::optional& change_addr, const std::vector &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout) + { + hw::device &hwdev = sender_account_keys.get_device(); + hwdev.open_tx(tx_key); + try { + // figure out if we need to make additional tx pubkeys + size_t num_stdaddresses = 0; + size_t num_subaddresses = 0; + account_public_address single_dest_subaddress; + classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress); + bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); + if (need_additional_txkeys) + { + additional_tx_keys.clear(); + for (size_t i = 0; i < destinations.size(); ++i) + { + additional_tx_keys.push_back(keypair::generate(sender_account_keys.get_device()).sec); + } + } + + bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout); + hwdev.close_tx(); + return r; + } catch(...) { + hwdev.close_tx(); + throw; + } + } + //--------------------------------------------------------------- + bool construct_tx(const account_keys& sender_account_keys, std::vector& sources, const std::vector& destinations, const boost::optional& change_addr, const std::vector &extra, transaction& tx, uint64_t unlock_time) + { + std::unordered_map subaddresses; + subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0}; + crypto::secret_key tx_key; + std::vector additional_tx_keys; + std::vector destinations_copy = destinations; + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL); + } + //--------------------------------------------------------------- + bool generate_genesis_block( + block& bl + , std::string const & genesis_tx + , uint32_t nonce + ) + { + //genesis block + bl = {}; + + blobdata tx_bl; + bool r = string_tools::parse_hexstr_to_binbuff(genesis_tx, tx_bl); + CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); + r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); + CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); + bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; + bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; + bl.timestamp = 0; + bl.nonce = nonce; + miner::find_nonce_for_given_block([](const cryptonote::block &b, uint64_t height, const crypto::hash *seed_hash, unsigned int threads, crypto::hash &hash){ + return cryptonote::get_block_longhash(NULL, b, hash, height, seed_hash, threads); + }, bl, 1, 0, NULL); + bl.invalidate_hashes(); + return true; + } + //--------------------------------------------------------------- + void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height, const uint64_t seed_height, const crypto::hash& seed_hash) + { + blobdata bd = get_block_hashing_blob(b); + rx_slow_hash(main_height, seed_height, seed_hash.data, bd.data(), bd.size(), res.data, 0, 1); + } + + bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners) + { + // block 202612 bug workaround + if (height == 202612) + { + static const std::string longhash_202612 = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000"; + epee::string_tools::hex_to_pod(longhash_202612, res); + return true; + } + blobdata bd = get_block_hashing_blob(b); + if (b.major_version >= RX_BLOCK_VERSION) + { + uint64_t seed_height, main_height; + crypto::hash hash; + if (pbc != NULL) + { + seed_height = rx_seedheight(height); + hash = seed_hash ? *seed_hash : pbc->get_pending_block_id_by_height(seed_height); + main_height = pbc->get_current_blockchain_height(); + } else + { + memset(&hash, 0, sizeof(hash)); // only happens when generating genesis block + seed_height = 0; + main_height = 0; + } + rx_slow_hash(main_height, seed_height, hash.data, bd.data(), bd.size(), res.data, seed_hash ? 0 : miners, !!seed_hash); + } else { + const int pow_variant = b.major_version >= 7 ? b.major_version - 6 : 0; + crypto::cn_slow_hash(bd.data(), bd.size(), res, pow_variant, height); + } + return true; + } + + bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const int miners) + { + return get_block_longhash(pbc, b, res, height, NULL, miners); + } + + crypto::hash get_block_longhash(const Blockchain *pbc, const block& b, const uint64_t height, const int miners) + { + crypto::hash p = crypto::null_hash; + get_block_longhash(pbc, b, p, height, miners); + return p; + } + + void get_block_longhash_reorg(const uint64_t split_height) + { + rx_reorg(split_height); + } +} diff --git a/crypto/xmrcore/cryptonote_tx_utils.h b/crypto/xmrcore/cryptonote_tx_utils.h new file mode 100644 index 0000000000..06412d6bf7 --- /dev/null +++ b/crypto/xmrcore/cryptonote_tx_utils.h @@ -0,0 +1,194 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "cryptonote_basic/cryptonote_format_utils.h" +#include +#include +#include "ringct/rctOps.h" + +namespace cryptonote +{ + //--------------------------------------------------------------- + bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); + + struct tx_source_entry + { + typedef std::pair output_entry; + + std::vector outputs; //index + key + optional ringct commitment + uint64_t real_output; //index in outputs vector of real output_entry + crypto::public_key real_out_tx_key; //incoming real tx public key + std::vector real_out_additional_tx_keys; //incoming real tx additional public keys + uint64_t real_output_in_tx_index; //index in transaction outputs vector + uint64_t amount; //money + bool rct; //true if the output is rct + rct::key mask; //ringct amount mask + rct::multisig_kLRki multisig_kLRki; //multisig info + + void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } + + BEGIN_SERIALIZE_OBJECT() + FIELD(outputs) + FIELD(real_output) + FIELD(real_out_tx_key) + FIELD(real_out_additional_tx_keys) + FIELD(real_output_in_tx_index) + FIELD(amount) + FIELD(rct) + FIELD(mask) + FIELD(multisig_kLRki) + + if (real_output >= outputs.size()) + return false; + END_SERIALIZE() + }; + + struct tx_destination_entry + { + std::string original; + uint64_t amount; //money + account_public_address addr; //destination address + bool is_subaddress; + bool is_integrated; + + tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)), is_subaddress(false), is_integrated(false) { } + tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { } + tx_destination_entry(const std::string &o, uint64_t a, const account_public_address &ad, bool is_subaddress) : original(o), amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { } + + std::string address(network_type nettype, const crypto::hash &payment_id) const + { + if (!original.empty()) + { + return original; + } + + if (is_integrated) + { + return get_account_integrated_address_as_str(nettype, addr, reinterpret_cast(payment_id)); + } + + return get_account_address_as_str(nettype, is_subaddress, addr); + } + + BEGIN_SERIALIZE_OBJECT() + FIELD(original) + VARINT_FIELD(amount) + FIELD(addr) + FIELD(is_subaddress) + FIELD(is_integrated) + END_SERIALIZE() + }; + + //--------------------------------------------------------------- + + struct tx_block_template_backlog_entry + { + crypto::hash id; + uint64_t weight; + uint64_t fee; + }; + + //--------------------------------------------------------------- + crypto::public_key get_destination_view_key_pub(const std::vector &destinations, const boost::optional& change_addr); + bool construct_tx(const account_keys& sender_account_keys, std::vector &sources, const std::vector& destinations, const boost::optional& change_addr, const std::vector &extra, transaction& tx, uint64_t unlock_time); + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, std::vector& destinations, const boost::optional& change_addr, const std::vector &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map& subaddresses, std::vector& sources, std::vector& destinations, const boost::optional& change_addr, const std::vector &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL); + bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, + const cryptonote::tx_destination_entry &dst_entr, const boost::optional &change_addr, const size_t output_index, + const bool &need_additional_txkeys, const std::vector &additional_tx_keys, + std::vector &additional_tx_public_keys, + std::vector &amount_keys, + crypto::public_key &out_eph_public_key) ; + + bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, + const cryptonote::tx_destination_entry &dst_entr, const boost::optional &change_addr, const size_t output_index, + const bool &need_additional_txkeys, const std::vector &additional_tx_keys, + std::vector &additional_tx_public_keys, + std::vector &amount_keys, + crypto::public_key &out_eph_public_key) ; + + bool generate_genesis_block( + block& bl + , std::string const & genesis_tx + , uint32_t nonce + ); + + class Blockchain; + bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const int miners); + bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners); + void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height, + const uint64_t seed_height, const crypto::hash& seed_hash); + crypto::hash get_block_longhash(const Blockchain *pb, const block& b, const uint64_t height, const int miners); + void get_block_longhash_reorg(const uint64_t split_height); + +} + +BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 1) +BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 2) + +namespace boost +{ + namespace serialization + { + template + inline void serialize(Archive &a, cryptonote::tx_source_entry &x, const boost::serialization::version_type ver) + { + a & x.outputs; + a & x.real_output; + a & x.real_out_tx_key; + a & x.real_output_in_tx_index; + a & x.amount; + a & x.rct; + a & x.mask; + if (ver < 1) + return; + a & x.multisig_kLRki; + a & x.real_out_additional_tx_keys; + } + + template + inline void serialize(Archive& a, cryptonote::tx_destination_entry& x, const boost::serialization::version_type ver) + { + a & x.amount; + a & x.addr; + if (ver < 1) + return; + a & x.is_subaddress; + if (ver < 2) + { + x.is_integrated = false; + return; + } + a & x.original; + a & x.is_integrated; + } + } +} diff --git a/crypto/xmrcore/i_core_events.h b/crypto/xmrcore/i_core_events.h new file mode 100644 index 0000000000..5d00858b5e --- /dev/null +++ b/crypto/xmrcore/i_core_events.h @@ -0,0 +1,46 @@ +// Copyright (c) 2019-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "cryptonote_basic/blobdatatype.h" +#include "cryptonote_protocol/enums.h" +#include "span.h" + +namespace cryptonote +{ + struct i_core_events + { + virtual ~i_core_events() noexcept + {} + + virtual uint64_t get_current_blockchain_height() const = 0; + virtual bool is_synchronized() const = 0; + virtual void on_transactions_relayed(epee::span tx_blobs, relay_method tx_relay) = 0; + }; +} diff --git a/crypto/xmrcore/tx_pool.cpp b/crypto/xmrcore/tx_pool.cpp new file mode 100644 index 0000000000..84605d6f57 --- /dev/null +++ b/crypto/xmrcore/tx_pool.cpp @@ -0,0 +1,1699 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include +#include +#include +#include + +#include "tx_pool.h" +#include "cryptonote_tx_utils.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" +#include "cryptonote_config.h" +#include "blockchain.h" +#include "blockchain_db/locked_txn.h" +#include "blockchain_db/blockchain_db.h" +#include "common/boost_serialization_helper.h" +#include "int-util.h" +#include "misc_language.h" +#include "warnings.h" +#include "common/perf_timer.h" +#include "crypto/hash.h" +#include "crypto/duration.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "txpool" + +DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated + +using namespace crypto; + +namespace cryptonote +{ + namespace + { + /*! The Dandelion++ has formula for calculating the average embargo timeout: + (-k*(k-1)*hop)/(2*log(1-ep)) + where k is the number of hops before this node and ep is the probability + that one of the k hops hits their embargo timer, and hop is the average + time taken between hops. So decreasing ep will make it more probable + that "this" node is the first to expire the embargo timer. Increasing k + will increase the number of nodes that will be "hidden" as a prior + recipient of the tx. + + As example, k=5 and ep=0.1 means "this" embargo timer has a 90% + probability of being the first to expire amongst 5 nodes that saw the + tx before "this" one. These values are independent to the fluff + probability, but setting a low k with a low p (fluff probability) is + not ideal since a blackhole is more likely to reveal earlier nodes in + the chain. + + This value was calculated with k=5, ep=0.10, and hop = 175 ms. A + testrun from a recent Intel laptop took ~80ms to + receive+parse+proces+send transaction. At least 50ms will be added to + the latency if crossing an ocean. So 175ms is the fudge factor for + a single hop with 39s being the embargo timer. */ + constexpr const std::chrono::seconds dandelionpp_embargo_average{CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE}; + + //TODO: constants such as these should at least be in the header, + // but probably somewhere more accessible to the rest of the + // codebase. As it stands, it is at best nontrivial to test + // whether or not changing these parameters (or adding new) + // will work correctly. + time_t const MIN_RELAY_TIME = (60 * 5); // only start re-relaying transactions after that many seconds + time_t const MAX_RELAY_TIME = (60 * 60 * 4); // at most that many seconds between resends + float const ACCEPT_THRESHOLD = 1.0f; + + //! Max DB check interval for relayable txes + constexpr const std::chrono::minutes max_relayable_check{2}; + + constexpr const std::chrono::seconds forward_delay_average{CRYPTONOTE_FORWARD_DELAY_AVERAGE}; + + // a kind of increasing backoff within min/max bounds + uint64_t get_relay_delay(time_t now, time_t received) + { + time_t d = (now - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME; + if (d > MAX_RELAY_TIME) + d = MAX_RELAY_TIME; + return d; + } + + uint64_t template_accept_threshold(uint64_t amount) + { + return amount * ACCEPT_THRESHOLD; + } + + uint64_t get_transaction_weight_limit(uint8_t version) + { + // from v8, limit a tx to 50% of the minimum block weight + if (version >= 8) + return get_min_block_weight(version) / 2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + else + return get_min_block_weight(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + } + + // external lock must be held for the comparison+set to work properly + void set_if_less(std::atomic& next_check, const time_t candidate) noexcept + { + if (candidate < next_check.load(std::memory_order_relaxed)) + next_check = candidate; + } + } + //--------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------- + tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_cookie(0), m_txpool_max_weight(DEFAULT_TXPOOL_MAX_WEIGHT), m_txpool_weight(0), m_mine_stem_txes(false), m_next_check(std::time(nullptr)) + { + // class code expects unsigned values throughout + if (m_next_check < time_t(0)) + throw std::runtime_error{"Unexpected time_t (system clock) value"}; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::add_tx(transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version) + { + const bool kept_by_block = (tx_relay == relay_method::block); + + // this should already be called with that lock, but let's make it explicit for clarity + CRITICAL_REGION_LOCAL(m_transactions_lock); + + PERF_TIMER(add_tx); + if (tx.version == 0) + { + // v0 never accepted + LOG_PRINT_L1("transaction version 0 is invalid"); + tvc.m_verifivation_failed = true; + return false; + } + + // we do not accept transactions that timed out before, unless they're + // kept_by_block + if (!kept_by_block && m_timed_out_transactions.find(id) != m_timed_out_transactions.end()) + { + // not clear if we should set that, since verifivation (sic) did not fail before, since + // the tx was accepted before timing out. + tvc.m_verifivation_failed = true; + return false; + } + + if(!check_inputs_types_supported(tx)) + { + tvc.m_verifivation_failed = true; + tvc.m_invalid_input = true; + return false; + } + + // fee per kilobyte, size rounded up. + uint64_t fee; + + if (tx.version == 1) + { + uint64_t inputs_amount = 0; + if(!get_inputs_money_amount(tx, inputs_amount)) + { + tvc.m_verifivation_failed = true; + return false; + } + + uint64_t outputs_amount = get_outs_money_amount(tx); + if(outputs_amount > inputs_amount) + { + LOG_PRINT_L1("transaction use more money than it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount)); + tvc.m_verifivation_failed = true; + tvc.m_overspend = true; + return false; + } + else if(outputs_amount == inputs_amount) + { + LOG_PRINT_L1("transaction fee is zero: outputs_amount == inputs_amount, rejecting."); + tvc.m_verifivation_failed = true; + tvc.m_fee_too_low = true; + return false; + } + + fee = inputs_amount - outputs_amount; + } + else + { + fee = tx.rct_signatures.txnFee; + } + + if (!kept_by_block && !m_blockchain.check_fee(tx_weight, fee)) + { + tvc.m_verifivation_failed = true; + tvc.m_fee_too_low = true; + return false; + } + + size_t tx_weight_limit = get_transaction_weight_limit(version); + if ((!kept_by_block || version >= HF_VERSION_PER_BYTE_FEE) && tx_weight > tx_weight_limit) + { + LOG_PRINT_L1("transaction is too heavy: " << tx_weight << " bytes, maximum weight: " << tx_weight_limit); + tvc.m_verifivation_failed = true; + tvc.m_too_big = true; + return false; + } + + // if the transaction came from a block popped from the chain, + // don't check if we have its key images as spent. + // TODO: Investigate why not? + if(!kept_by_block) + { + if(have_tx_keyimges_as_spent(tx, id)) + { + mark_double_spend(tx); + LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images"); + tvc.m_verifivation_failed = true; + tvc.m_double_spend = true; + return false; + } + } + + if (!m_blockchain.check_tx_outputs(tx, tvc)) + { + LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid output"); + tvc.m_verifivation_failed = true; + tvc.m_invalid_output = true; + return false; + } + + // assume failure during verification steps until success is certain + tvc.m_verifivation_failed = true; + + time_t receive_time = time(nullptr); + + crypto::hash max_used_block_id = null_hash; + uint64_t max_used_block_height = 0; + cryptonote::txpool_tx_meta_t meta{}; + bool ch_inp_res = check_tx_inputs([&tx]()->cryptonote::transaction&{ return tx; }, id, max_used_block_height, max_used_block_id, tvc, kept_by_block); + if(!ch_inp_res) + { + // if the transaction was valid before (kept_by_block), then it + // may become valid again, so ignore the failed inputs check. + if(kept_by_block) + { + meta.weight = tx_weight; + meta.fee = fee; + meta.max_used_block_id = null_hash; + meta.max_used_block_height = 0; + meta.last_failed_height = 0; + meta.last_failed_id = null_hash; + meta.receive_time = receive_time; + meta.last_relayed_time = time(NULL); + meta.relayed = relayed; + meta.set_relay_method(tx_relay); + meta.double_spend_seen = have_tx_keyimges_as_spent(tx, id); + meta.pruned = tx.pruned; + meta.bf_padding = 0; + memset(meta.padding, 0, sizeof(meta.padding)); + try + { + if (kept_by_block) + m_parsed_tx_cache.insert(std::make_pair(id, tx)); + CRITICAL_REGION_LOCAL1(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); + if (!insert_key_images(tx, id, tx_relay)) + return false; + + m_blockchain.add_txpool_tx(id, blob, meta); + m_txs_by_fee_and_receive_time.emplace(std::pair(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); + lock.commit(); + } + catch (const std::exception &e) + { + MERROR("Error adding transaction to txpool: " << e.what()); + return false; + } + tvc.m_verifivation_impossible = true; + tvc.m_added_to_pool = true; + }else + { + LOG_PRINT_L1("tx used wrong inputs, rejected"); + tvc.m_verifivation_failed = true; + tvc.m_invalid_input = true; + return false; + } + }else + { + try + { + if (kept_by_block) + m_parsed_tx_cache.insert(std::make_pair(id, tx)); + CRITICAL_REGION_LOCAL1(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); + + const bool existing_tx = m_blockchain.get_txpool_tx_meta(id, meta); + if (existing_tx) + { + /* If Dandelion++ loop. Do not use txes in the `local` state in the + loop detection - txes in that state should be outgoing over i2p/tor + then routed back via public dandelion++ stem. Pretend to be + another stem node in that situation, a loop over the public + network hasn't been hit yet. */ + if (tx_relay == relay_method::stem && meta.dandelionpp_stem) + tx_relay = relay_method::fluff; + } + else + meta.set_relay_method(relay_method::none); + + if (meta.upgrade_relay_method(tx_relay) || !existing_tx) // synchronize with embargo timer or stem/fluff out-of-order messages + { + using clock = std::chrono::system_clock; + auto last_relayed_time = std::numeric_limits::max(); + if (tx_relay == relay_method::forward) + { + last_relayed_time = clock::to_time_t(clock::now() + crypto::random_poisson_seconds{forward_delay_average}()); + set_if_less(m_next_check, time_t(last_relayed_time)); + } + // else the `set_relayed` function will adjust the time accordingly later + + //update transactions container + meta.last_relayed_time = last_relayed_time; + meta.receive_time = receive_time; + meta.weight = tx_weight; + meta.fee = fee; + meta.max_used_block_id = max_used_block_id; + meta.max_used_block_height = max_used_block_height; + meta.last_failed_height = 0; + meta.last_failed_id = null_hash; + meta.relayed = relayed; + meta.double_spend_seen = false; + meta.pruned = tx.pruned; + meta.bf_padding = 0; + memset(meta.padding, 0, sizeof(meta.padding)); + + if (!insert_key_images(tx, id, tx_relay)) + return false; + + m_blockchain.remove_txpool_tx(id); + m_blockchain.add_txpool_tx(id, blob, meta); + m_txs_by_fee_and_receive_time.emplace(std::pair(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); + } + lock.commit(); + } + catch (const std::exception &e) + { + MERROR("internal error: error adding transaction to txpool: " << e.what()); + return false; + } + tvc.m_added_to_pool = true; + + static_assert(unsigned(relay_method::none) == 0, "expected relay_method::none value to be zero"); + if(meta.fee > 0 && tx_relay != relay_method::forward) + tvc.m_relay = tx_relay; + } + + tvc.m_verifivation_failed = false; + m_txpool_weight += tx_weight; + + ++m_cookie; + + MINFO("Transaction added to pool: txid " << id << " weight: " << tx_weight << " fee/byte: " << (fee / (double)(tx_weight ? tx_weight : 1))); + + prune(m_txpool_max_weight); + + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version) + { + crypto::hash h = null_hash; + cryptonote::blobdata bl; + t_serializable_object_to_blob(tx, bl); + if (bl.size() == 0 || !get_transaction_hash(tx, h)) + return false; + return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, tx_relay, relayed, version); + } + //--------------------------------------------------------------------------------- + size_t tx_memory_pool::get_txpool_weight() const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + return m_txpool_weight; + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::set_txpool_max_weight(size_t bytes) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + m_txpool_max_weight = bytes; + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::prune(size_t bytes) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + if (bytes == 0) + bytes = m_txpool_max_weight; + CRITICAL_REGION_LOCAL1(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); + bool changed = false; + + // this will never remove the first one, but we don't care + auto it = --m_txs_by_fee_and_receive_time.end(); + while (it != m_txs_by_fee_and_receive_time.begin()) + { + if (m_txpool_weight <= bytes) + break; + try + { + const crypto::hash &txid = it->second; + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(txid, meta)) + { + MERROR("Failed to find tx_meta in txpool"); + return; + } + // don't prune the kept_by_block ones, they're likely added because we're adding a block with those + if (meta.kept_by_block) + { + --it; + continue; + } + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); + cryptonote::transaction_prefix tx; + if (!parse_and_validate_tx_prefix_from_blob(txblob, tx)) + { + MERROR("Failed to parse tx from txpool"); + return; + } + // remove first, in case this throws, so key images aren't removed + MINFO("Pruning tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first); + m_blockchain.remove_txpool_tx(txid); + m_txpool_weight -= meta.weight; + remove_transaction_keyimages(tx, txid); + MINFO("Pruned tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first); + m_txs_by_fee_and_receive_time.erase(it--); + changed = true; + } + catch (const std::exception &e) + { + MERROR("Error while pruning txpool: " << e.what()); + return; + } + } + lock.commit(); + if (changed) + ++m_cookie; + if (m_txpool_weight > bytes) + MINFO("Pool weight after pruning is larger than limit: " << m_txpool_weight << "/" << bytes); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::insert_key_images(const transaction_prefix &tx, const crypto::hash &id, relay_method tx_relay) + { + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); + std::unordered_set& kei_image_set = m_spent_key_images[txin.k_image]; + + // Only allow multiple txes per key-image if kept-by-block. Only allow + // the same txid if going from local/stem->fluff. + + if (tx_relay != relay_method::block) + { + const bool one_txid = + (kei_image_set.empty() || (kei_image_set.size() == 1 && *(kei_image_set.cbegin()) == id)); + CHECK_AND_ASSERT_MES(one_txid, false, "internal error: tx_relay=" << unsigned(tx_relay) + << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL + << "tx_id=" << id); + } + + const bool new_or_previously_private = + kei_image_set.insert(id).second || + !m_blockchain.txpool_tx_matches_category(id, relay_category::legacy); + CHECK_AND_ASSERT_MES(new_or_previously_private, false, "internal error: try to insert duplicate iterator in key_image set"); + } + ++m_cookie; + return true; + } + //--------------------------------------------------------------------------------- + //FIXME: Can return early before removal of all of the key images. + // At the least, need to make sure that a false return here + // is treated properly. Should probably not return early, however. + bool tx_memory_pool::remove_transaction_keyimages(const transaction_prefix& tx, const crypto::hash &actual_hash) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + // ND: Speedup + for(const txin_v& vi: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(vi, const txin_to_key, txin, false); + auto it = m_spent_key_images.find(txin.k_image); + CHECK_AND_ASSERT_MES(it != m_spent_key_images.end(), false, "failed to find transaction input in key images. img=" << txin.k_image << ENDL + << "transaction id = " << actual_hash); + std::unordered_set& key_image_set = it->second; + CHECK_AND_ASSERT_MES(key_image_set.size(), false, "empty key_image set, img=" << txin.k_image << ENDL + << "transaction id = " << actual_hash); + + auto it_in_set = key_image_set.find(actual_hash); + CHECK_AND_ASSERT_MES(it_in_set != key_image_set.end(), false, "transaction id not found in key_image set, img=" << txin.k_image << ENDL + << "transaction id = " << actual_hash); + key_image_set.erase(it_in_set); + if(!key_image_set.size()) + { + //it is now empty hash container for this key_image + m_spent_key_images.erase(it); + } + + } + ++m_cookie; + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + auto sorted_it = find_tx_in_sorted_container(id); + + try + { + LockedTXN lock(m_blockchain.get_db()); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(id, meta)) + { + MERROR("Failed to find tx_meta in txpool"); + return false; + } + txblob = m_blockchain.get_txpool_tx_blob(id, relay_category::all); + auto ci = m_parsed_tx_cache.find(id); + if (ci != m_parsed_tx_cache.end()) + { + tx = ci->second; + } + else if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(txblob, tx) : parse_and_validate_tx_from_blob(txblob, tx))) + { + MERROR("Failed to parse tx from txpool"); + return false; + } + else + { + tx.set_hash(id); + } + tx_weight = meta.weight; + fee = meta.fee; + relayed = meta.relayed; + do_not_relay = meta.do_not_relay; + double_spend_seen = meta.double_spend_seen; + pruned = meta.pruned; + + // remove first, in case this throws, so key images aren't removed + m_blockchain.remove_txpool_tx(id); + m_txpool_weight -= tx_weight; + remove_transaction_keyimages(tx, id); + lock.commit(); + } + catch (const std::exception &e) + { + MERROR("Failed to remove tx from txpool: " << e.what()); + return false; + } + + if (sorted_it != m_txs_by_fee_and_receive_time.end()) + m_txs_by_fee_and_receive_time.erase(sorted_it); + ++m_cookie; + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_transaction_info(const crypto::hash &txid, tx_details &td) const + { + PERF_TIMER(get_transaction_info); + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + try + { + LockedTXN lock(m_blockchain.get_db()); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(txid, meta)) + { + MERROR("Failed to find tx in txpool"); + return false; + } + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); + auto ci = m_parsed_tx_cache.find(txid); + if (ci != m_parsed_tx_cache.end()) + { + td.tx = ci->second; + } + else if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(txblob, td.tx) : parse_and_validate_tx_from_blob(txblob, td.tx))) + { + MERROR("Failed to parse tx from txpool"); + return false; + } + else + { + td.tx.set_hash(txid); + } + td.blob_size = txblob.size(); + td.weight = meta.weight; + td.fee = meta.fee; + td.max_used_block_id = meta.max_used_block_id; + td.max_used_block_height = meta.max_used_block_height; + td.kept_by_block = meta.kept_by_block; + td.last_failed_height = meta.last_failed_height; + td.last_failed_id = meta.last_failed_id; + td.receive_time = meta.receive_time; + td.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time; + td.relayed = meta.relayed; + td.do_not_relay = meta.do_not_relay; + td.double_spend_seen = meta.double_spend_seen; + } + catch (const std::exception &e) + { + MERROR("Failed to get tx from txpool: " << e.what()); + return false; + } + + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_complement(const std::vector &hashes, std::vector &txes) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + m_blockchain.for_all_txpool_txes([this, &hashes, &txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) { + const auto tx_relay_method = meta.get_relay_method(); + if (tx_relay_method != relay_method::block && tx_relay_method != relay_method::fluff) + return true; + const auto i = std::find(hashes.begin(), hashes.end(), txid); + if (i == hashes.end()) + { + cryptonote::blobdata bd; + try + { + if (!m_blockchain.get_txpool_tx_blob(txid, bd, cryptonote::relay_category::broadcasted)) + { + MERROR("Failed to get blob for txpool transaction " << txid); + return true; + } + txes.emplace_back(std::move(bd)); + } + catch (const std::exception &e) + { + MERROR("Failed to get blob for txpool transaction " << txid << ": " << e.what()); + return true; + } + } + return true; + }, false); + return true; + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::on_idle() + { + m_remove_stuck_tx_interval.do_call([this](){return remove_stuck_transactions();}); + } + //--------------------------------------------------------------------------------- + sorted_tx_container::iterator tx_memory_pool::find_tx_in_sorted_container(const crypto::hash& id) const + { + return std::find_if( m_txs_by_fee_and_receive_time.begin(), m_txs_by_fee_and_receive_time.end() + , [&](const sorted_tx_container::value_type& a){ + return a.second == id; + } + ); + } + //--------------------------------------------------------------------------------- + //TODO: investigate whether boolean return is appropriate + bool tx_memory_pool::remove_stuck_transactions() + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + std::list> remove; + m_blockchain.for_all_txpool_txes([this, &remove](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) { + uint64_t tx_age = time(nullptr) - meta.receive_time; + + if((tx_age > CRYPTONOTE_MEMPOOL_TX_LIVETIME && !meta.kept_by_block) || + (tx_age > CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME && meta.kept_by_block) ) + { + LOG_PRINT_L1("Tx " << txid << " removed from tx pool due to outdated, age: " << tx_age ); + auto sorted_it = find_tx_in_sorted_container(txid); + if (sorted_it == m_txs_by_fee_and_receive_time.end()) + { + LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!"); + } + else + { + m_txs_by_fee_and_receive_time.erase(sorted_it); + } + m_timed_out_transactions.insert(txid); + remove.push_back(std::make_pair(txid, meta.weight)); + } + return true; + }, false, relay_category::all); + + if (!remove.empty()) + { + LockedTXN lock(m_blockchain.get_db()); + for (const std::pair &entry: remove) + { + const crypto::hash &txid = entry.first; + try + { + cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); + cryptonote::transaction_prefix tx; + if (!parse_and_validate_tx_prefix_from_blob(bd, tx)) + { + MERROR("Failed to parse tx from txpool"); + // continue + } + else + { + // remove first, so we only remove key images if the tx removal succeeds + m_blockchain.remove_txpool_tx(txid); + m_txpool_weight -= entry.second; + remove_transaction_keyimages(tx, txid); + } + } + catch (const std::exception &e) + { + MWARNING("Failed to remove stuck transaction: " << txid); + // ignore error + } + } + lock.commit(); + ++m_cookie; + } + return true; + } + //--------------------------------------------------------------------------------- + //TODO: investigate whether boolean return is appropriate + bool tx_memory_pool::get_relayable_transactions(std::vector> &txs) + { + using clock = std::chrono::system_clock; + + const uint64_t now = time(NULL); + if (uint64_t{std::numeric_limits::max()} < now || time_t(now) < m_next_check) + return false; + + uint64_t next_check = clock::to_time_t(clock::from_time_t(time_t(now)) + max_relayable_check); + std::vector> change_timestamps; + + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); + txs.reserve(m_blockchain.get_txpool_tx_count()); + m_blockchain.for_all_txpool_txes([this, now, &txs, &change_timestamps, &next_check](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *){ + // 0 fee transactions are never relayed + if(!meta.pruned && meta.fee > 0 && !meta.do_not_relay) + { + const relay_method tx_relay = meta.get_relay_method(); + switch (tx_relay) + { + case relay_method::stem: + case relay_method::forward: + if (meta.last_relayed_time > now) + { + next_check = std::min(next_check, meta.last_relayed_time); + return true; // continue to next tx + } + change_timestamps.emplace_back(txid, meta); + break; + default: + case relay_method::none: + return true; + case relay_method::local: + case relay_method::fluff: + case relay_method::block: + if (now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time)) + return true; // continue to next tx + break; + } + + // if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem + // mentioned by smooth where nodes would flush txes at slightly different times, causing + // flushed txes to be re-added when received from a node which was just about to flush it + uint64_t max_age = (tx_relay == relay_method::block) ? CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME : CRYPTONOTE_MEMPOOL_TX_LIVETIME; + if (now - meta.receive_time <= max_age / 2) + { + try + { + txs.emplace_back(txid, m_blockchain.get_txpool_tx_blob(txid, relay_category::all), tx_relay); + } + catch (const std::exception &e) + { + MERROR("Failed to get transaction blob from db"); + // ignore error + } + } + } + return true; + }, false, relay_category::relayable); + + for (auto& elem : change_timestamps) + { + /* These transactions are still in forward or stem state, so the field + represents the next time a relay should be attempted. Will be + overwritten when the state is upgraded to stem, fluff or block. This + function is only called every ~2 minutes, so this resetting should be + unnecessary, but is primarily a precaution against potential changes + to the callback routines. */ + elem.second.last_relayed_time = now + get_relay_delay(now, elem.second.receive_time); + m_blockchain.update_txpool_tx(elem.first, elem.second); + } + + m_next_check = time_t(next_check); + return true; + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::set_relayed(const epee::span hashes, const relay_method method) + { + crypto::random_poisson_seconds embargo_duration{dandelionpp_embargo_average}; + const auto now = std::chrono::system_clock::now(); + uint64_t next_relay = uint64_t{std::numeric_limits::max()}; + + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); + for (const auto& hash : hashes) + { + try + { + txpool_tx_meta_t meta; + if (m_blockchain.get_txpool_tx_meta(hash, meta)) + { + // txes can be received as "stem" or "fluff" in either order + meta.upgrade_relay_method(method); + meta.relayed = true; + + if (meta.dandelionpp_stem) + { + meta.last_relayed_time = std::chrono::system_clock::to_time_t(now + embargo_duration()); + next_relay = std::min(next_relay, meta.last_relayed_time); + } + else + meta.last_relayed_time = std::chrono::system_clock::to_time_t(now); + + m_blockchain.update_txpool_tx(hash, meta); + } + } + catch (const std::exception &e) + { + MERROR("Failed to update txpool transaction metadata: " << e.what()); + // continue + } + } + lock.commit(); + set_if_less(m_next_check, time_t(next_relay)); + } + //--------------------------------------------------------------------------------- + size_t tx_memory_pool::get_transactions_count(bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + return m_blockchain.get_txpool_tx_count(include_sensitive); + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::get_transactions(std::vector& txs, bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + txs.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); + m_blockchain.for_all_txpool_txes([&txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + transaction tx; + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + tx.set_hash(txid); + txs.push_back(std::move(tx)); + return true; + }, true, category); + } + //------------------------------------------------------------------ + void tx_memory_pool::get_transaction_hashes(std::vector& txs, bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + txs.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); + m_blockchain.for_all_txpool_txes([&txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + txs.push_back(txid); + return true; + }, false, category); + } + //------------------------------------------------------------------ + void tx_memory_pool::get_transaction_backlog(std::vector& backlog, bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const uint64_t now = time(NULL); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); + m_blockchain.for_all_txpool_txes([&backlog, now](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + backlog.push_back({meta.weight, meta.fee, meta.receive_time - now}); + return true; + }, false, category); + } + //------------------------------------------------------------------ + void tx_memory_pool::get_block_template_backlog(std::vector& backlog, bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); + txpool_tx_meta_t tmp_meta; + m_blockchain.for_all_txpool_txes([this, &backlog, &tmp_meta](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + transaction tx; + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + tx.set_hash(txid); + + tmp_meta = meta; + + if (is_transaction_ready_to_go(tmp_meta, txid, *bd, tx)) + backlog.push_back({txid, meta.weight, meta.fee}); + + return true; + }, true, category); + } + //------------------------------------------------------------------ + void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const uint64_t now = time(NULL); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + std::map agebytes; + stats.txs_total = m_blockchain.get_txpool_tx_count(include_sensitive); + std::vector weights; + weights.reserve(stats.txs_total); + m_blockchain.for_all_txpool_txes([&stats, &weights, now, &agebytes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + weights.push_back(meta.weight); + stats.bytes_total += meta.weight; + if (!stats.bytes_min || meta.weight < stats.bytes_min) + stats.bytes_min = meta.weight; + if (meta.weight > stats.bytes_max) + stats.bytes_max = meta.weight; + if (!meta.relayed) + stats.num_not_relayed++; + stats.fee_total += meta.fee; + if (!stats.oldest || meta.receive_time < stats.oldest) + stats.oldest = meta.receive_time; + if (meta.receive_time < now - 600) + stats.num_10m++; + if (meta.last_failed_height) + stats.num_failing++; + uint64_t age = now - meta.receive_time + (now == meta.receive_time); + agebytes[age].txs++; + agebytes[age].bytes += meta.weight; + if (meta.double_spend_seen) + ++stats.num_double_spends; + return true; + }, false, category); + + stats.bytes_med = epee::misc_utils::median(weights); + if (stats.txs_total > 1) + { + /* looking for 98th percentile */ + size_t end = stats.txs_total * 0.02; + uint64_t delta, factor; + std::map::iterator it, i2; + if (end) + { + /* If enough txs, spread the first 98% of results across + * the first 9 bins, drop final 2% in last bin. + */ + it = agebytes.end(); + size_t cumulative_num = 0; + /* Since agebytes is not empty and end is nonzero, the + * below loop can always run at least once. + */ + do { + --it; + cumulative_num += it->second.txs; + } while (it != agebytes.begin() && cumulative_num < end); + stats.histo_98pc = it->first; + factor = 9; + delta = it->first; + stats.histo.resize(10); + } else + { + /* If not enough txs, don't reserve the last slot; + * spread evenly across all 10 bins. + */ + stats.histo_98pc = 0; + it = agebytes.end(); + factor = stats.txs_total > 9 ? 10 : stats.txs_total; + delta = now - stats.oldest; + stats.histo.resize(factor); + } + if (!delta) + delta = 1; + for (i2 = agebytes.begin(); i2 != it; i2++) + { + size_t i = (i2->first * factor - 1) / delta; + stats.histo[i].txs += i2->second.txs; + stats.histo[i].bytes += i2->second.bytes; + } + for (; i2 != agebytes.end(); i2++) + { + stats.histo[factor].txs += i2->second.txs; + stats.histo[factor].bytes += i2->second.bytes; + } + } + } + //------------------------------------------------------------------ + //TODO: investigate whether boolean return is appropriate + bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos, bool include_sensitive_data) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const relay_category category = include_sensitive_data ? relay_category::all : relay_category::broadcasted; + const size_t count = m_blockchain.get_txpool_tx_count(include_sensitive_data); + tx_infos.reserve(count); + key_image_infos.reserve(count); + m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos, include_sensitive_data](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + tx_info txi; + txi.id_hash = epee::string_tools::pod_to_hex(txid); + txi.tx_blob = blobdata(bd->data(), bd->size()); + transaction tx; + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + tx.set_hash(txid); + txi.tx_json = obj_to_json_str(tx); + txi.blob_size = bd->size(); + txi.weight = meta.weight; + txi.fee = meta.fee; + txi.kept_by_block = meta.kept_by_block; + txi.max_used_block_height = meta.max_used_block_height; + txi.max_used_block_id_hash = epee::string_tools::pod_to_hex(meta.max_used_block_id); + txi.last_failed_height = meta.last_failed_height; + txi.last_failed_id_hash = epee::string_tools::pod_to_hex(meta.last_failed_id); + // In restricted mode we do not include this data: + txi.receive_time = include_sensitive_data ? meta.receive_time : 0; + txi.relayed = meta.relayed; + // In restricted mode we do not include this data: + txi.last_relayed_time = (include_sensitive_data && !meta.dandelionpp_stem) ? meta.last_relayed_time : 0; + txi.do_not_relay = meta.do_not_relay; + txi.double_spend_seen = meta.double_spend_seen; + tx_infos.push_back(std::move(txi)); + return true; + }, true, category); + + for (const key_images_container::value_type& kee : m_spent_key_images) { + const crypto::key_image& k_image = kee.first; + const std::unordered_set& kei_image_set = kee.second; + spent_key_image_info ki; + ki.id_hash = epee::string_tools::pod_to_hex(k_image); + for (const crypto::hash& tx_id_hash : kei_image_set) + { + if (m_blockchain.txpool_tx_matches_category(tx_id_hash, category)) + ki.txs_hashes.push_back(epee::string_tools::pod_to_hex(tx_id_hash)); + } + + // Only return key images for which we have at least one tx that we can show for them + if (!ki.txs_hashes.empty()) + key_image_infos.push_back(std::move(ki)); + } + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_pool_for_rpc(std::vector& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + tx_infos.reserve(m_blockchain.get_txpool_tx_count()); + key_image_infos.reserve(m_blockchain.get_txpool_tx_count()); + m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + cryptonote::rpc::tx_in_pool txi; + txi.tx_hash = txid; + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, txi.tx) : parse_and_validate_tx_from_blob(*bd, txi.tx))) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + txi.tx.set_hash(txid); + txi.blob_size = bd->size(); + txi.weight = meta.weight; + txi.fee = meta.fee; + txi.kept_by_block = meta.kept_by_block; + txi.max_used_block_height = meta.max_used_block_height; + txi.max_used_block_hash = meta.max_used_block_id; + txi.last_failed_block_height = meta.last_failed_height; + txi.last_failed_block_hash = meta.last_failed_id; + txi.receive_time = meta.receive_time; + txi.relayed = meta.relayed; + txi.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time; + txi.do_not_relay = meta.do_not_relay; + txi.double_spend_seen = meta.double_spend_seen; + tx_infos.push_back(txi); + return true; + }, true, relay_category::broadcasted); + + for (const key_images_container::value_type& kee : m_spent_key_images) { + std::vector tx_hashes; + const std::unordered_set& kei_image_set = kee.second; + for (const crypto::hash& tx_id_hash : kei_image_set) + { + if (m_blockchain.txpool_tx_matches_category(tx_id_hash, relay_category::broadcasted)) + tx_hashes.push_back(tx_id_hash); + } + + if (!tx_hashes.empty()) + key_image_infos[kee.first] = std::move(tx_hashes); + } + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::check_for_key_images(const std::vector& key_images, std::vector& spent) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + spent.clear(); + + for (const auto& image : key_images) + { + bool is_spent = false; + const auto found = m_spent_key_images.find(image); + if (found != m_spent_key_images.end()) + { + for (const crypto::hash& tx_hash : found->second) + is_spent |= m_blockchain.txpool_tx_matches_category(tx_hash, relay_category::broadcasted); + } + spent.push_back(is_spent); + } + + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_transaction(const crypto::hash& id, cryptonote::blobdata& txblob, relay_category tx_category) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + try + { + return m_blockchain.get_txpool_tx_blob(id, txblob, tx_category); + } + catch (const std::exception &e) + { + return false; + } + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + m_input_cache.clear(); + m_parsed_tx_cache.clear(); + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + m_input_cache.clear(); + m_parsed_tx_cache.clear(); + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_tx(const crypto::hash &id, relay_category tx_category) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + return m_blockchain.get_db().txpool_has_tx(id, tx_category); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail + if(have_tx_keyimg_as_spent(tokey_in.k_image, txid)) + return true; + } + return false; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + const auto found = m_spent_key_images.find(key_im); + if (found != m_spent_key_images.end() && !found->second.empty()) + { + // If another tx is using the key image, always return as spent. + // See `insert_key_images`. + if (1 < found->second.size() || *(found->second.cbegin()) != txid) + return true; + return m_blockchain.txpool_tx_matches_category(txid, relay_category::legacy); + } + return false; + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::lock() const + { + m_transactions_lock.lock(); + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::unlock() const + { + m_transactions_lock.unlock(); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::check_tx_inputs(const std::function &get_tx, const crypto::hash &txid, uint64_t &max_used_block_height, crypto::hash &max_used_block_id, tx_verification_context &tvc, bool kept_by_block) const + { + if (!kept_by_block) + { + const std::unordered_map>::const_iterator i = m_input_cache.find(txid); + if (i != m_input_cache.end()) + { + max_used_block_height = std::get<2>(i->second); + max_used_block_id = std::get<3>(i->second); + tvc = std::get<1>(i->second); + return std::get<0>(i->second); + } + } + bool ret = m_blockchain.check_tx_inputs(get_tx(), max_used_block_height, max_used_block_id, tvc, kept_by_block); + if (!kept_by_block) + m_input_cache.insert(std::make_pair(txid, std::make_tuple(ret, tvc, max_used_block_height, max_used_block_id))); + return ret; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata_ref& txblob, transaction &tx) const + { + struct transaction_parser + { + transaction_parser(const cryptonote::blobdata_ref &txblob, const crypto::hash &txid, transaction &tx): txblob(txblob), txid(txid), tx(tx), parsed(false) {} + cryptonote::transaction &operator()() + { + if (!parsed) + { + if (!parse_and_validate_tx_from_blob(txblob, tx)) + throw std::runtime_error("failed to parse transaction blob"); + tx.set_hash(txid); + parsed = true; + } + return tx; + } + const cryptonote::blobdata_ref &txblob; + const crypto::hash &txid; + transaction &tx; + bool parsed; + } lazy_tx(txblob, txid, tx); + + //not the best implementation at this time, sorry :( + //check is ring_signature already checked ? + if(txd.max_used_block_id == null_hash) + {//not checked, lets try to check + + if(txd.last_failed_id != null_hash && m_blockchain.get_current_blockchain_height() > txd.last_failed_height && txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height)) + return false;//we already sure that this tx is broken for this height + + tx_verification_context tvc; + if(!check_tx_inputs([&lazy_tx]()->cryptonote::transaction&{ return lazy_tx(); }, txid, txd.max_used_block_height, txd.max_used_block_id, tvc)) + { + txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1; + txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height); + return false; + } + }else + { + if(txd.max_used_block_height >= m_blockchain.get_current_blockchain_height()) + return false; + if(true) + { + //if we already failed on this height and id, skip actual ring signature check + if(txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height)) + return false; + //check ring signature again, it is possible (with very small chance) that this transaction become again valid + tx_verification_context tvc; + if(!check_tx_inputs([&lazy_tx]()->cryptonote::transaction&{ return lazy_tx(); }, txid, txd.max_used_block_height, txd.max_used_block_id, tvc)) + { + txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1; + txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height); + return false; + } + } + } + //if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure + if(m_blockchain.have_tx_keyimges_as_spent(lazy_tx())) + { + txd.double_spend_seen = true; + return false; + } + + //transaction is ok. + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata& txblob, transaction &tx) const + { + return is_transaction_ready_to_go(txd, txid, cryptonote::blobdata_ref{txblob.data(), txblob.size()}, tx); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_key_images(const std::unordered_set& k_images, const transaction_prefix& tx) + { + for(size_t i = 0; i!= tx.vin.size(); i++) + { + CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, false); + if(k_images.count(itk.k_image)) + return true; + } + return false; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::append_key_images(std::unordered_set& k_images, const transaction_prefix& tx) + { + for(size_t i = 0; i!= tx.vin.size(); i++) + { + CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, false); + auto i_res = k_images.insert(itk.k_image); + CHECK_AND_ASSERT_MES(i_res.second, false, "internal error: key images pool cache - inserted duplicate image in set: " << itk.k_image); + } + return true; + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::mark_double_spend(const transaction &tx) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + bool changed = false; + LockedTXN lock(m_blockchain.get_db()); + for(size_t i = 0; i!= tx.vin.size(); i++) + { + CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, void()); + const key_images_container::const_iterator it = m_spent_key_images.find(itk.k_image); + if (it != m_spent_key_images.end()) + { + for (const crypto::hash &txid: it->second) + { + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(txid, meta)) + { + MERROR("Failed to find tx meta in txpool"); + // continue, not fatal + continue; + } + if (!meta.double_spend_seen) + { + MDEBUG("Marking " << txid << " as double spending " << itk.k_image); + meta.double_spend_seen = true; + changed = true; + try + { + m_blockchain.update_txpool_tx(txid, meta); + } + catch (const std::exception &e) + { + MERROR("Failed to update tx meta: " << e.what()); + // continue, not fatal + } + } + } + } + } + lock.commit(); + if (changed) + ++m_cookie; + } + //--------------------------------------------------------------------------------- + std::string tx_memory_pool::print_pool(bool short_format) const + { + std::stringstream ss; + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + m_blockchain.for_all_txpool_txes([&ss, short_format](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *txblob) { + ss << "id: " << txid << std::endl; + if (!short_format) { + cryptonote::transaction tx; + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*txblob, tx) : parse_and_validate_tx_from_blob(*txblob, tx))) + { + MERROR("Failed to parse tx from txpool"); + return true; // continue + } + ss << obj_to_json_str(tx) << std::endl; + } + ss << "blob_size: " << (short_format ? "-" : std::to_string(txblob->size())) << std::endl + << "weight: " << meta.weight << std::endl + << "fee: " << print_money(meta.fee) << std::endl + << "kept_by_block: " << (meta.kept_by_block ? 'T' : 'F') << std::endl + << "is_local" << (meta.is_local ? 'T' : 'F') << std::endl + << "double_spend_seen: " << (meta.double_spend_seen ? 'T' : 'F') << std::endl + << "max_used_block_height: " << meta.max_used_block_height << std::endl + << "max_used_block_id: " << meta.max_used_block_id << std::endl + << "last_failed_height: " << meta.last_failed_height << std::endl + << "last_failed_id: " << meta.last_failed_id << std::endl; + return true; + }, !short_format, relay_category::all); + + return ss.str(); + } + //--------------------------------------------------------------------------------- + //TODO: investigate whether boolean return is appropriate + bool tx_memory_pool::fill_block_template(block &bl, size_t median_weight, uint64_t already_generated_coins, size_t &total_weight, uint64_t &fee, uint64_t &expected_reward, uint8_t version) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + uint64_t best_coinbase = 0, coinbase = 0; + total_weight = 0; + fee = 0; + + //baseline empty block + if (!get_block_reward(median_weight, total_weight, already_generated_coins, best_coinbase, version)) + { + MERROR("Failed to get block reward for empty block"); + return false; + } + + + size_t max_total_weight_pre_v5 = (130 * median_weight) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + size_t max_total_weight_v5 = 2 * median_weight - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + size_t max_total_weight = version >= 5 ? max_total_weight_v5 : max_total_weight_pre_v5; + std::unordered_set k_images; + + LOG_PRINT_L2("Filling block template, median weight " << median_weight << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool"); + + LockedTXN lock(m_blockchain.get_db()); + + auto sorted_it = m_txs_by_fee_and_receive_time.begin(); + for (; sorted_it != m_txs_by_fee_and_receive_time.end(); ++sorted_it) + { + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(sorted_it->second, meta)) + { + static bool warned = false; + if (!warned) + MERROR(" failed to find tx meta: " << sorted_it->second << " (will only print once)"); + warned = true; + continue; + } + LOG_PRINT_L2("Considering " << sorted_it->second << ", weight " << meta.weight << ", current block weight " << total_weight << "/" << max_total_weight << ", current coinbase " << print_money(best_coinbase) << ", relay method " << (unsigned)meta.get_relay_method()); + + if (!meta.matches(relay_category::legacy) && !(m_mine_stem_txes && meta.get_relay_method() == relay_method::stem)) + { + LOG_PRINT_L2(" tx relay method is " << (unsigned)meta.get_relay_method()); + continue; + } + if (meta.pruned) + { + LOG_PRINT_L2(" tx is pruned"); + continue; + } + + // Can not exceed maximum block weight + if (max_total_weight < total_weight + meta.weight) + { + LOG_PRINT_L2(" would exceed maximum block weight"); + continue; + } + + // start using the optimal filling algorithm from v5 + if (version >= 5) + { + // If we're getting lower coinbase tx, + // stop including more tx + uint64_t block_reward; + if(!get_block_reward(median_weight, total_weight + meta.weight, already_generated_coins, block_reward, version)) + { + LOG_PRINT_L2(" would exceed maximum block weight"); + continue; + } + coinbase = block_reward + fee + meta.fee; + if (coinbase < template_accept_threshold(best_coinbase)) + { + LOG_PRINT_L2(" would decrease coinbase to " << print_money(coinbase)); + continue; + } + } + else + { + // If we've exceeded the penalty free weight, + // stop including more tx + if (total_weight > median_weight) + { + LOG_PRINT_L2(" would exceed median block weight"); + break; + } + } + + // "local" and "stem" txes are filtered above + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(sorted_it->second, relay_category::all); + + cryptonote::transaction tx; + + // Skip transactions that are not ready to be + // included into the blockchain or that are + // missing key images + const cryptonote::txpool_tx_meta_t original_meta = meta; + bool ready = false; + try + { + ready = is_transaction_ready_to_go(meta, sorted_it->second, txblob, tx); + } + catch (const std::exception &e) + { + MERROR("Failed to check transaction readiness: " << e.what()); + // continue, not fatal + } + if (memcmp(&original_meta, &meta, sizeof(meta))) + { + try + { + m_blockchain.update_txpool_tx(sorted_it->second, meta); + } + catch (const std::exception &e) + { + MERROR("Failed to update tx meta: " << e.what()); + // continue, not fatal + } + } + if (!ready) + { + LOG_PRINT_L2(" not ready to go"); + continue; + } + if (have_key_images(k_images, tx)) + { + LOG_PRINT_L2(" key images already seen"); + continue; + } + + bl.tx_hashes.push_back(sorted_it->second); + total_weight += meta.weight; + fee += meta.fee; + best_coinbase = coinbase; + append_key_images(k_images, tx); + LOG_PRINT_L2(" added, new block weight " << total_weight << "/" << max_total_weight << ", coinbase " << print_money(best_coinbase)); + } + lock.commit(); + + expected_reward = best_coinbase; + LOG_PRINT_L2("Block template filled with " << bl.tx_hashes.size() << " txes, weight " + << total_weight << "/" << max_total_weight << ", coinbase " << print_money(best_coinbase) + << " (including " << print_money(fee) << " in fees)"); + return true; + } + //--------------------------------------------------------------------------------- + size_t tx_memory_pool::validate(uint8_t version) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + size_t tx_weight_limit = get_transaction_weight_limit(version); + std::unordered_set remove; + + m_txpool_weight = 0; + m_blockchain.for_all_txpool_txes([this, &remove, tx_weight_limit](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) { + m_txpool_weight += meta.weight; + if (meta.weight > tx_weight_limit) { + LOG_PRINT_L1("Transaction " << txid << " is too big (" << meta.weight << " bytes), removing it from pool"); + remove.insert(txid); + } + else if (m_blockchain.have_tx(txid)) { + LOG_PRINT_L1("Transaction " << txid << " is in the blockchain, removing it from pool"); + remove.insert(txid); + } + return true; + }, false, relay_category::all); + + size_t n_removed = 0; + if (!remove.empty()) + { + LockedTXN lock(m_blockchain.get_db()); + for (const crypto::hash &txid: remove) + { + try + { + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); + cryptonote::transaction tx; + if (!parse_and_validate_tx_from_blob(txblob, tx)) // remove pruned ones on startup, they're meant to be temporary + { + MERROR("Failed to parse tx from txpool"); + continue; + } + // remove tx from db first + m_blockchain.remove_txpool_tx(txid); + m_txpool_weight -= get_transaction_weight(tx, txblob.size()); + remove_transaction_keyimages(tx, txid); + auto sorted_it = find_tx_in_sorted_container(txid); + if (sorted_it == m_txs_by_fee_and_receive_time.end()) + { + LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!"); + } + else + { + m_txs_by_fee_and_receive_time.erase(sorted_it); + } + ++n_removed; + } + catch (const std::exception &e) + { + MERROR("Failed to remove invalid tx from pool"); + // continue + } + } + lock.commit(); + } + if (n_removed > 0) + ++m_cookie; + return n_removed; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::init(size_t max_txpool_weight, bool mine_stem_txes) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + m_txpool_max_weight = max_txpool_weight ? max_txpool_weight : DEFAULT_TXPOOL_MAX_WEIGHT; + m_txs_by_fee_and_receive_time.clear(); + m_spent_key_images.clear(); + m_txpool_weight = 0; + std::vector remove; + + // first add the not kept by block, then the kept by block, + // to avoid rejection due to key image collision + for (int pass = 0; pass < 2; ++pass) + { + const bool kept = pass == 1; + bool r = m_blockchain.for_all_txpool_txes([this, &remove, kept](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd) { + if (!!kept != !!meta.kept_by_block) + return true; + cryptonote::transaction_prefix tx; + if (!parse_and_validate_tx_prefix_from_blob(*bd, tx)) + { + MWARNING("Failed to parse tx from txpool, removing"); + remove.push_back(txid); + return true; + } + if (!insert_key_images(tx, txid, meta.get_relay_method())) + { + MFATAL("Failed to insert key images from txpool tx"); + return false; + } + m_txs_by_fee_and_receive_time.emplace(std::pair(meta.fee / (double)meta.weight, meta.receive_time), txid); + m_txpool_weight += meta.weight; + return true; + }, true, relay_category::all); + if (!r) + return false; + } + if (!remove.empty()) + { + LockedTXN lock(m_blockchain.get_db()); + for (const auto &txid: remove) + { + try + { + m_blockchain.remove_txpool_tx(txid); + } + catch (const std::exception &e) + { + MWARNING("Failed to remove corrupt transaction: " << txid); + // ignore error + } + } + lock.commit(); + } + + m_mine_stem_txes = mine_stem_txes; + m_cookie = 0; + + // Ignore deserialization error + return true; + } + + //--------------------------------------------------------------------------------- + bool tx_memory_pool::deinit() + { + return true; + } +} diff --git a/crypto/xmrcore/tx_pool.h b/crypto/xmrcore/tx_pool.h new file mode 100644 index 0000000000..80b38c51d1 --- /dev/null +++ b/crypto/xmrcore/tx_pool.h @@ -0,0 +1,665 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "include_base_utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "span.h" +#include "string_tools.h" +#include "syncobj.h" +#include "math_helper.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/verification_context.h" +#include "cryptonote_protocol/enums.h" +#include "blockchain_db/blockchain_db.h" +#include "crypto/hash.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/message_data_structs.h" + +namespace cryptonote +{ + class Blockchain; + /************************************************************************/ + /* */ + /************************************************************************/ + + //! pair of for organization + typedef std::pair, crypto::hash> tx_by_fee_and_receive_time_entry; + + class txCompare + { + public: + bool operator()(const tx_by_fee_and_receive_time_entry& a, const tx_by_fee_and_receive_time_entry& b) const + { + // sort by greatest first, not least + if (a.first.first > b.first.first) return true; + else if (a.first.first < b.first.first) return false; + else if (a.first.second < b.first.second) return true; + else if (a.first.second > b.first.second) return false; + else if (a.second != b.second) return true; + else return false; + } + }; + + //! container for sorting transactions by fee per unit size + typedef std::set sorted_tx_container; + + /** + * @brief Transaction pool, handles transactions which are not part of a block + * + * This class handles all transactions which have been received, but not as + * part of a block. + * + * This handling includes: + * storing the transactions + * organizing the transactions by fee per weight unit + * taking/giving transactions to and from various other components + * saving the transactions to disk on shutdown + * helping create a new block template by choosing transactions for it + * + */ + class tx_memory_pool: boost::noncopyable + { + public: + /** + * @brief Constructor + * + * @param bchs a Blockchain class instance, for getting chain info + */ + tx_memory_pool(Blockchain& bchs); + + + /** + * @copydoc add_tx(transaction&, tx_verification_context&, bool, bool, uint8_t) + * + * @param id the transaction's hash + * @tx_relay how the transaction was received + * @param tx_weight the transaction's weight + */ + bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version); + + /** + * @brief add a transaction to the transaction pool + * + * Most likely the transaction will come from the network, but it is + * also possible for transactions to come from popped blocks during + * a reorg, or from local clients creating a transaction and + * submitting it to the network + * + * @param tx the transaction to be added + * @param tvc return-by-reference status about the transaction verification + * @tx_relay how the transaction was received + * @param relayed was this transaction from the network or a local client? + * @param version the version used to create the transaction + * + * @return true if the transaction passes validations, otherwise false + */ + bool add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version); + + /** + * @brief takes a transaction with the given hash from the pool + * + * @param id the hash of the transaction + * @param tx return-by-reference the transaction taken + * @param txblob return-by-reference the transaction as a blob + * @param tx_weight return-by-reference the transaction's weight + * @param fee the transaction fee + * @param relayed return-by-reference was transaction relayed to us by the network? + * @param do_not_relay return-by-reference is transaction not to be relayed to the network? + * @param double_spend_seen return-by-reference was a double spend seen for that transaction? + * @param pruned return-by-reference is the tx pruned + * + * @return true unless the transaction cannot be found in the pool + */ + bool take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned); + + /** + * @brief checks if the pool has a transaction with the given hash + * + * @param id the hash to look for + * @param tx_category a filter for txes + * + * @return true if the transaction is in the pool and meets tx_category requirements + */ + bool have_tx(const crypto::hash &id, relay_category tx_category) const; + + /** + * @brief action to take when notified of a block added to the blockchain + * + * Currently does nothing + * + * @param new_block_height the height of the blockchain after the change + * @param top_block_id the hash of the new top block + * + * @return true + */ + bool on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id); + + /** + * @brief action to take when notified of a block removed from the blockchain + * + * Currently does nothing + * + * @param new_block_height the height of the blockchain after the change + * @param top_block_id the hash of the new top block + * + * @return true + */ + bool on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id); + + /** + * @brief action to take periodically + * + * Currently checks transaction pool for stale ("stuck") transactions + */ + void on_idle(); + + /** + * @brief locks the transaction pool + */ + void lock() const; + + /** + * @brief unlocks the transaction pool + */ + void unlock() const; + + // load/store operations + + /** + * @brief loads pool state (if any) from disk, and initializes pool + * + * @param max_txpool_weight the max weight in bytes + * @param mine_stem_txes whether to mine txes in stem relay mode + * + * @return true + */ + bool init(size_t max_txpool_weight = 0, bool mine_stem_txes = false); + + /** + * @brief attempts to save the transaction pool state to disk + * + * Currently fails (returns false) if the data directory from init() + * does not exist and cannot be created, but returns true even if + * saving to disk is unsuccessful. + * + * @return true in most cases (see above) + */ + bool deinit(); + + /** + * @brief Chooses transactions for a block to include + * + * @param bl return-by-reference the block to fill in with transactions + * @param median_weight the current median block weight + * @param already_generated_coins the current total number of coins "minted" + * @param total_weight return-by-reference the total weight of the new block + * @param fee return-by-reference the total of fees from the included transactions + * @param expected_reward return-by-reference the total reward awarded to the miner finding this block, including transaction fees + * @param version hard fork version to use for consensus rules + * + * @return true + */ + bool fill_block_template(block &bl, size_t median_weight, uint64_t already_generated_coins, size_t &total_weight, uint64_t &fee, uint64_t &expected_reward, uint8_t version); + + /** + * @brief get a list of all transactions in the pool + * + * @param txs return-by-reference the list of transactions + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes + * + */ + void get_transactions(std::vector& txs, bool include_sensitive = false) const; + + /** + * @brief get a list of all transaction hashes in the pool + * + * @param txs return-by-reference the list of transactions + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes + * + */ + void get_transaction_hashes(std::vector& txs, bool include_sensitive = false) const; + + /** + * @brief get (weight, fee, receive time) for all transaction in the pool + * + * @param txs return-by-reference that data + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes + * + */ + void get_transaction_backlog(std::vector& backlog, bool include_sensitive = false) const; + + /** + * @brief get (hash, weight, fee) for all transactions in the pool - the minimum required information to create a block template + * + * @param backlog return-by-reference that data + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes + * + */ + void get_block_template_backlog(std::vector& backlog, bool include_sensitive = false) const; + + /** + * @brief get a summary statistics of all transaction hashes in the pool + * + * @param stats return-by-reference the pool statistics + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes + * + */ + void get_transaction_stats(struct txpool_stats& stats, bool include_sensitive = false) const; + + /** + * @brief get information about all transactions and key images in the pool + * + * see documentation on tx_info and spent_key_image_info for more details + * + * @param tx_infos return-by-reference the transactions' information + * @param key_image_infos return-by-reference the spent key images' information + * @param include_sensitive_data return stempool, anonymity-pool, and unrelayed + * txes and fields that are sensitive to the node privacy + * + * @return true + */ + bool get_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos, bool include_sensitive_data = false) const; + + /** + * @brief get information about all transactions and key images in the pool + * + * see documentation on tx_in_pool and key_images_with_tx_hashes for more details + * + * @param tx_infos [out] the transactions' information + * @param key_image_infos [out] the spent key images' information + * + * @return true + */ + bool get_pool_for_rpc(std::vector& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** + * @brief check for presence of key images in the pool + * + * @param key_images [in] vector of key images to check + * @param spent [out] vector of bool to return + * + * @return true + */ + bool check_for_key_images(const std::vector& key_images, std::vector& spent) const; + + /** + * @brief get a specific transaction from the pool + * + * @param h the hash of the transaction to get + * @param tx return-by-reference the transaction blob requested + * @param tx_relay last relay method us + * + * @return true if the transaction is found, otherwise false + */ + bool get_transaction(const crypto::hash& h, cryptonote::blobdata& txblob, relay_category tx_category) const; + + /** + * @brief get a list of all relayable transactions and their hashes + * + * "relayable" in this case means: + * nonzero fee + * hasn't been relayed too recently + * isn't old enough that relaying it is considered harmful + * Note a transaction can be "relayable" even if do_not_relay is true + * + * This function will skip all DB checks if an insufficient amount of + * time since the last call. + * + * @param txs return-by-reference the transactions and their hashes + * + * @return True if DB was checked, false if DB checks skipped. + */ + bool get_relayable_transactions(std::vector>& txs); + + /** + * @brief tell the pool that certain transactions were just relayed + * + * @param hashes list of tx hashes that are about to be relayed + * @param tx_relay update how the tx left this node + */ + void set_relayed(epee::span hashes, relay_method tx_relay); + + /** + * @brief get the total number of transactions in the pool + * + * @return the number of transactions in the pool + */ + size_t get_transactions_count(bool include_sensitive = false) const; + + /** + * @brief get a string containing human-readable pool information + * + * @param short_format whether to use a shortened format for the info + * + * @return the string + */ + std::string print_pool(bool short_format) const; + + /** + * @brief remove transactions from the pool which are no longer valid + * + * With new versions of the currency, what conditions render a transaction + * invalid may change. This function clears those which were received + * before a version change and no longer conform to requirements. + * + * @param version the version the transactions must conform to + * + * @return the number of transactions removed + */ + size_t validate(uint8_t version); + + /** + * @brief return the cookie + * + * @return the cookie + */ + uint64_t cookie() const { return m_cookie; } + + /** + * @brief get the cumulative txpool weight in bytes + * + * @return the cumulative txpool weight in bytes + */ + size_t get_txpool_weight() const; + + /** + * @brief set the max cumulative txpool weight in bytes + * + * @param bytes the max cumulative txpool weight in bytes + */ + void set_txpool_max_weight(size_t bytes); + +#define CURRENT_MEMPOOL_ARCHIVE_VER 11 +#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 13 + + /** + * @brief information about a single transaction + */ + struct tx_details + { + transaction tx; //!< the transaction + size_t blob_size; //!< the transaction's size + size_t weight; //!< the transaction's weight + uint64_t fee; //!< the transaction's fee amount + crypto::hash max_used_block_id; //!< the hash of the highest block referenced by an input + uint64_t max_used_block_height; //!< the height of the highest block referenced by an input + + //! whether or not the transaction has been in a block before + /*! if the transaction was returned to the pool from the blockchain + * due to a reorg, then this will be true + */ + bool kept_by_block; + + //! the highest block the transaction referenced when last checking it failed + /*! if verifying a transaction's inputs fails, it's possible this is due + * to a reorg since it was created (if it used recently created outputs + * as inputs). + */ + uint64_t last_failed_height; + + //! the hash of the highest block the transaction referenced when last checking it failed + /*! if verifying a transaction's inputs fails, it's possible this is due + * to a reorg since it was created (if it used recently created outputs + * as inputs). + */ + crypto::hash last_failed_id; + + time_t receive_time; //!< the time when the transaction entered the pool + + time_t last_relayed_time; //!< the last time the transaction was relayed to the network + bool relayed; //!< whether or not the transaction has been relayed to the network + bool do_not_relay; //!< to avoid relay this transaction to the network + + bool double_spend_seen; //!< true iff another tx was seen double spending this one + }; + + /** + * @brief get infornation about a single transaction + */ + bool get_transaction_info(const crypto::hash &txid, tx_details &td) const; + + /** + * @brief get transactions not in the passed set + */ + bool get_complement(const std::vector &hashes, std::vector &txes) const; + + private: + + /** + * @brief insert key images into m_spent_key_images + * + * @return true on success, false on error + */ + bool insert_key_images(const transaction_prefix &tx, const crypto::hash &txid, relay_method tx_relay); + + /** + * @brief remove old transactions from the pool + * + * After a certain time, it is assumed that a transaction which has not + * yet been mined will likely not be mined. These transactions are removed + * from the pool to avoid buildup. + * + * @return true + */ + bool remove_stuck_transactions(); + + /** + * @brief check if a transaction in the pool has a given spent key image + * + * @param key_im the spent key image to look for + * @param txid hash of the new transaction where `key_im` was seen. + * + * @return true if the spent key image is present, otherwise false + */ + bool have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const; + + /** + * @brief check if any spent key image in a transaction is in the pool + * + * Checks if any of the spent key images in a given transaction are present + * in any of the transactions in the transaction pool. + * + * @note see tx_pool::have_tx_keyimg_as_spent + * + * @param tx the transaction to check spent key images of + * @param txid hash of `tx`. + * + * @return true if any spent key images are present in the pool, otherwise false + */ + bool have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const; + + /** + * @brief forget a transaction's spent key images + * + * Spent key images are stored separately from transactions for + * convenience/speed, so this is part of the process of removing + * a transaction from the pool. + * + * @param tx the transaction + * @param txid the transaction's hash + * + * @return false if any key images to be removed cannot be found, otherwise true + */ + bool remove_transaction_keyimages(const transaction_prefix& tx, const crypto::hash &txid); + + /** + * @brief check if any of a transaction's spent key images are present in a given set + * + * @param kic the set of key images to check against + * @param tx the transaction to check + * + * @return true if any key images present in the set, otherwise false + */ + static bool have_key_images(const std::unordered_set& kic, const transaction_prefix& tx); + + /** + * @brief append the key images from a transaction to the given set + * + * @param kic the set of key images to append to + * @param tx the transaction + * + * @return false if any append fails, otherwise true + */ + static bool append_key_images(std::unordered_set& kic, const transaction_prefix& tx); + + /** + * @brief check if a transaction is a valid candidate for inclusion in a block + * + * @param txd the transaction to check (and info about it) + * @param txid the txid of the transaction to check + * @param txblob the transaction blob to check + * @param tx the parsed transaction, if successful + * + * @return true if the transaction is good to go, otherwise false + */ + bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata_ref &txblob, transaction&tx) const; + bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata &txblob, transaction&tx) const; + + /** + * @brief mark all transactions double spending the one passed + */ + void mark_double_spend(const transaction &tx); + + /** + * @brief prune lowest fee/byte txes till we're not above bytes + * + * if bytes is 0, use m_txpool_max_weight + */ + void prune(size_t bytes = 0); + + //TODO: confirm the below comments and investigate whether or not this + // is the desired behavior + //! map key images to transactions which spent them + /*! this seems odd, but it seems that multiple transactions can exist + * in the pool which both have the same spent key. This would happen + * in the event of a reorg where someone creates a new/different + * transaction on the assumption that the original will not be in a + * block again. + */ + typedef std::unordered_map> key_images_container; + +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) +public: +#endif + mutable epee::critical_section m_transactions_lock; //!< lock for the pool +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) +private: +#endif + + //! container for spent key images from the transactions in the pool + key_images_container m_spent_key_images; + + //TODO: this time should be a named constant somewhere, not hard-coded + //! interval on which to check for stale/"stuck" transactions + epee::math_helper::once_a_time_seconds<30> m_remove_stuck_tx_interval; + + //TODO: look into doing this better + //!< container for transactions organized by fee per size and receive time + sorted_tx_container m_txs_by_fee_and_receive_time; + + std::atomic m_cookie; //!< incremented at each change + + /** + * @brief get an iterator to a transaction in the sorted container + * + * @param id the hash of the transaction to look for + * + * @return an iterator, possibly to the end of the container if not found + */ + sorted_tx_container::iterator find_tx_in_sorted_container(const crypto::hash& id) const; + + //! cache/call Blockchain::check_tx_inputs results + bool check_tx_inputs(const std::function &get_tx, const crypto::hash &txid, uint64_t &max_used_block_height, crypto::hash &max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false) const; + + //! transactions which are unlikely to be included in blocks + /*! These transactions are kept in RAM in case they *are* included + * in a block eventually, but this container is not saved to disk. + */ + std::unordered_set m_timed_out_transactions; + + Blockchain& m_blockchain; //!< reference to the Blockchain object + + size_t m_txpool_max_weight; + size_t m_txpool_weight; + bool m_mine_stem_txes; + + mutable std::unordered_map> m_input_cache; + + std::unordered_map m_parsed_tx_cache; + + //! Next timestamp that a DB check for relayable txes is allowed + std::atomic m_next_check; + }; +} + +namespace boost +{ + namespace serialization + { + template + void serialize(archive_t & ar, cryptonote::tx_memory_pool::tx_details& td, const unsigned int version) + { + ar & td.blob_size; + ar & td.fee; + ar & td.tx; + ar & td.max_used_block_height; + ar & td.max_used_block_id; + ar & td.last_failed_height; + ar & td.last_failed_id; + ar & td.receive_time; + ar & td.last_relayed_time; + ar & td.relayed; + if (version < 11) + return; + ar & td.kept_by_block; + if (version < 12) + return; + ar & td.do_not_relay; + if (version < 13) + return; + ar & td.weight; + } + } +} +BOOST_CLASS_VERSION(cryptonote::tx_memory_pool, CURRENT_MEMPOOL_ARCHIVE_VER) +BOOST_CLASS_VERSION(cryptonote::tx_memory_pool::tx_details, CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER) + + + diff --git a/crypto/xmrcore/tx_sanity_check.cpp b/crypto/xmrcore/tx_sanity_check.cpp new file mode 100644 index 0000000000..ca870779ea --- /dev/null +++ b/crypto/xmrcore/tx_sanity_check.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2019-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "blockchain.h" +#include "tx_sanity_check.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "verify" + +namespace cryptonote +{ + +bool tx_sanity_check(const cryptonote::blobdata &tx_blob, uint64_t rct_outs_available) +{ + cryptonote::transaction tx; + + if (!cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx)) + { + MERROR("Failed to parse transaction"); + return false; + } + + if (cryptonote::is_coinbase(tx)) + { + MERROR("Transaction is coinbase"); + return false; + } + std::set rct_indices; + size_t n_indices = 0; + + for (const auto &txin : tx.vin) + { + if (txin.type() != typeid(cryptonote::txin_to_key)) + continue; + const cryptonote::txin_to_key &in_to_key = boost::get(txin); + if (in_to_key.amount != 0) + continue; + const std::vector absolute = cryptonote::relative_output_offsets_to_absolute(in_to_key.key_offsets); + for (uint64_t offset: absolute) + rct_indices.insert(offset); + n_indices += in_to_key.key_offsets.size(); + } + + return tx_sanity_check(rct_indices, n_indices, rct_outs_available); +} + +bool tx_sanity_check(const std::set &rct_indices, size_t n_indices, uint64_t rct_outs_available) +{ + if (n_indices <= 10) + { + MDEBUG("n_indices is only " << n_indices << ", not checking"); + return true; + } + + if (rct_outs_available < 10000) + return true; + + if (rct_indices.size() < n_indices * 8 / 10) + { + MERROR("amount of unique indices is too low (amount of rct indices is " << rct_indices.size() << ", out of total " << n_indices << "indices."); + return false; + } + + std::vector offsets(rct_indices.begin(), rct_indices.end()); + uint64_t median = epee::misc_utils::median(offsets); + if (median < rct_outs_available * 6 / 10) + { + MERROR("median offset index is too low (median is " << median << " out of total " << rct_outs_available << "offsets). Transactions should contain a higher fraction of recent outputs."); + return false; + } + + return true; +} + +} diff --git a/crypto/xmrcore/tx_sanity_check.h b/crypto/xmrcore/tx_sanity_check.h new file mode 100644 index 0000000000..15c5476ee9 --- /dev/null +++ b/crypto/xmrcore/tx_sanity_check.h @@ -0,0 +1,36 @@ +// Copyright (c) 2019-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include "cryptonote_basic/blobdatatype.h" + +namespace cryptonote +{ + bool tx_sanity_check(const cryptonote::blobdata &tx_blob, uint64_t rct_outs_available); + bool tx_sanity_check(const std::set &rct_indices, size_t n_indices, uint64_t rct_outs_available); +}