Merge pull request #5965

5d7ae2d Adding support for hidden (anonymity) txpool (vtnerd)
pull/235/head
luigi1111 5 years ago
commit 3531cc5fd2
No known key found for this signature in database
GPG Key ID: F4ACA0183641E010

@ -110,7 +110,8 @@ namespace epee
constexpr std::size_t size() const noexcept { return len; }
constexpr std::size_t size_bytes() const noexcept { return size() * sizeof(value_type); }
const T &operator[](size_t idx) const { return ptr[idx]; }
T &operator[](size_t idx) noexcept { return ptr[idx]; }
const T &operator[](size_t idx) const noexcept { return ptr[idx]; }
private:
T* ptr;

@ -44,6 +44,71 @@ using epee::string_tools::pod_to_hex;
namespace cryptonote
{
bool matches_category(relay_method method, relay_category category) noexcept
{
switch (category)
{
default:
return false;
case relay_category::all:
return true;
case relay_category::relayable:
if (method == relay_method::none)
return false;
return true;
case relay_category::broadcasted:
case relay_category::legacy:
break;
}
// check for "broadcasted" or "legacy" methods:
switch (method)
{
default:
case relay_method::local:
return false;
case relay_method::block:
case relay_method::flood:
return true;
case relay_method::none:
break;
}
return category == relay_category::legacy;
}
void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept
{
kept_by_block = 0;
do_not_relay = 0;
is_local = 0;
switch (method)
{
case relay_method::none:
do_not_relay = 1;
break;
case relay_method::local:
is_local = 1;
break;
default:
case relay_method::flood:
break;
case relay_method::block:
kept_by_block = 1;
break;
}
}
relay_method txpool_tx_meta_t::get_relay_method() const noexcept
{
if (kept_by_block)
return relay_method::block;
if (do_not_relay)
return relay_method::none;
if (is_local)
return relay_method::local;
return relay_method::flood;
}
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
"db-sync-mode"
, "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]."
@ -924,4 +989,23 @@ void BlockchainDB::fixup()
batch_stop();
}
bool BlockchainDB::txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category)
{
try
{
txpool_tx_meta_t meta{};
if (!get_txpool_tx_meta(tx_hash, meta))
{
MERROR("Failed to get tx meta from txpool");
return false;
}
return meta.matches(category);
}
catch (const std::exception &e)
{
MERROR("Failed to get tx meta from txpool: " << e.what());
}
return false;
}
} // namespace cryptonote

@ -39,6 +39,7 @@
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/difficulty.h"
#include "cryptonote_basic/hardfork.h"
#include "cryptonote_protocol/enums.h"
/** \file
* Cryptonote Blockchain Database Interface
@ -105,6 +106,16 @@ typedef std::pair<crypto::hash, uint64_t> tx_out_index;
extern const command_line::arg_descriptor<std::string> arg_db_sync_mode;
extern const command_line::arg_descriptor<bool, false> arg_db_salvage;
enum class relay_category : uint8_t
{
broadcasted = 0,//!< Public txes received via block/flooding/fluff
relayable, //!< Every tx not marked `relay_method::none`
legacy, //!< `relay_category::broadcasted` + `relay_method::none` for rpc relay requests or historical reasons
all //!< Everything in the db
};
bool matches_category(relay_method method, relay_category category) noexcept;
#pragma pack(push, 1)
/**
@ -156,11 +167,22 @@ struct txpool_tx_meta_t
uint8_t do_not_relay;
uint8_t double_spend_seen: 1;
uint8_t pruned: 1;
uint8_t bf_padding: 6;
uint8_t is_local: 1;
uint8_t bf_padding: 5;
uint8_t padding[76]; // till 192 bytes
void set_relay_method(relay_method method) noexcept;
relay_method get_relay_method() const noexcept;
//! See `relay_category` description
bool matches(const relay_category category) const noexcept
{
return matches_category(get_relay_method(), category);
}
};
#define DBF_SAFE 1
#define DBF_FAST 2
#define DBF_FASTEST 4
@ -1465,12 +1487,12 @@ public:
/**
* @brief get the number of transactions in the txpool
*/
virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const = 0;
virtual uint64_t get_txpool_tx_count(relay_category tx_category = relay_category::broadcasted) const = 0;
/**
* @brief check whether a txid is in the txpool
* @brief check whether a txid is in the txpool and meets tx_category requirements
*/
virtual bool txpool_has_tx(const crypto::hash &txid) const = 0;
virtual bool txpool_has_tx(const crypto::hash &txid, relay_category tx_category) const = 0;
/**
* @brief remove a txpool transaction
@ -1494,10 +1516,11 @@ public:
*
* @param txid the transaction id of the transation to lookup
* @param bd the blob to return
* @param tx_category for filtering out hidden/private txes
*
* @return true if the txid was in the txpool, false otherwise
* @return True iff `txid` is in the pool and meets `tx_category` requirements
*/
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const = 0;
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const = 0;
/**
* @brief get a txpool transaction's blob
@ -1506,7 +1529,17 @@ public:
*
* @return the blob for that transaction
*/
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const = 0;
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const = 0;
/**
* @brief Check if `tx_hash` relay status is in `category`.
*
* @param tx_hash hash of the transaction to lookup
* @param category relay status category to test against
*
* @return True if `tx_hash` latest relay status is in `category`.
*/
bool txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category);
/**
* @brief prune output data for the given amount
@ -1604,7 +1637,7 @@ public:
*
* @return false if the function returns false for any transaction, otherwise true
*/
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const = 0;
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, relay_category category = relay_category::broadcasted) const = 0;
/**
* @brief runs a function over all key images stored

@ -1771,7 +1771,7 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash &txid, const txpool_tx_
}
}
uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const
uint64_t BlockchainLMDB::get_txpool_tx_count(relay_category category) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
@ -1781,7 +1781,7 @@ uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const
TXN_PREFIX_RDONLY();
if (include_unrelayed_txes)
if (category == relay_category::all)
{
// No filtering, we can get the number of tx the "fast" way
MDB_stat db_stats;
@ -1807,7 +1807,7 @@ uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const
if (result)
throw0(DB_ERROR(lmdb_error("Failed to enumerate txpool tx metadata: ", result).c_str()));
const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data;
if (!meta.do_not_relay)
if (meta.matches(category))
++num_entries;
}
}
@ -1816,7 +1816,7 @@ uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const
return num_entries;
}
bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const
bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid, relay_category tx_category) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
@ -1825,11 +1825,21 @@ bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const
RCURSOR(txpool_meta)
MDB_val k = {sizeof(txid), (void *)&txid};
auto result = mdb_cursor_get(m_cur_txpool_meta, &k, NULL, MDB_SET);
MDB_val v;
auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET);
if (result != 0 && result != MDB_NOTFOUND)
throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str()));
if (result == MDB_NOTFOUND)
return false;
bool found = true;
if (tx_category != relay_category::all)
{
const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data;
found = meta.matches(tx_category);
}
TXN_POSTFIX_RDONLY();
return result != MDB_NOTFOUND;
return found;
}
void BlockchainLMDB::remove_txpool_tx(const crypto::hash& txid)
@ -1883,7 +1893,7 @@ bool BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta
return true;
}
bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const
bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
@ -1893,6 +1903,21 @@ bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::bl
MDB_val k = {sizeof(txid), (void *)&txid};
MDB_val v;
// if filtering, make sure those requirements are met before copying blob
if (tx_category != relay_category::all)
{
auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET);
if (result == MDB_NOTFOUND)
return false;
if (result != 0)
throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str()));
const txpool_tx_meta_t& meta = *(const txpool_tx_meta_t*)v.mv_data;
if (!meta.matches(tx_category))
return false;
}
auto result = mdb_cursor_get(m_cur_txpool_blob, &k, &v, MDB_SET);
if (result == MDB_NOTFOUND)
return false;
@ -1904,10 +1929,10 @@ bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::bl
return true;
}
cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid) const
cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const
{
cryptonote::blobdata bd;
if (!get_txpool_tx_blob(txid, bd))
if (!get_txpool_tx_blob(txid, bd, tx_category))
throw1(DB_ERROR("Tx not found in txpool: "));
return bd;
}
@ -2245,7 +2270,7 @@ bool BlockchainLMDB::check_pruning()
return prune_worker(prune_mode_check, 0);
}
bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const
bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, relay_category category) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
@ -2269,8 +2294,7 @@ bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&,
throw0(DB_ERROR(lmdb_error("Failed to enumerate txpool tx metadata: ", result).c_str()));
const crypto::hash txid = *(const crypto::hash*)k.mv_data;
const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data;
if (!include_unrelayed_txes && meta.do_not_relay)
// Skipping that tx
if (!meta.matches(category))
continue;
const cryptonote::blobdata *passed_bd = NULL;
cryptonote::blobdata bd;

@ -281,12 +281,12 @@ public:
virtual void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const txpool_tx_meta_t& meta);
virtual void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t& meta);
virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const;
virtual bool txpool_has_tx(const crypto::hash &txid) const;
virtual uint64_t get_txpool_tx_count(relay_category category = relay_category::broadcasted) const;
virtual bool txpool_has_tx(const crypto::hash &txid, relay_category tx_category) const;
virtual void remove_txpool_tx(const crypto::hash& txid);
virtual bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const;
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const;
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const;
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata& bd, relay_category tx_category) const;
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const;
virtual uint32_t get_blockchain_pruning_seed() const;
virtual bool prune_blockchain(uint32_t pruning_seed = 0);
virtual bool update_pruning();
@ -298,7 +298,7 @@ public:
virtual uint64_t get_alt_block_count();
virtual void drop_alt_blocks();
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, bool include_unrelayed_txes = true) const;
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, relay_category category = relay_category::broadcasted) const;
virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const;
virtual bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const;

@ -126,14 +126,14 @@ public:
virtual void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const cryptonote::txpool_tx_meta_t& details) override {}
virtual void update_txpool_tx(const crypto::hash &txid, const cryptonote::txpool_tx_meta_t& details) override {}
virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const override { return 0; }
virtual bool txpool_has_tx(const crypto::hash &txid) const override { return false; }
virtual uint64_t get_txpool_tx_count(relay_category tx_relay = relay_category::broadcasted) const override { return 0; }
virtual bool txpool_has_tx(const crypto::hash &txid, relay_category tx_category) const override { return false; }
virtual void remove_txpool_tx(const crypto::hash& txid) override {}
virtual bool get_txpool_tx_meta(const crypto::hash& txid, cryptonote::txpool_tx_meta_t &meta) const override { return false; }
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const override { return false; }
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const override { return false; }
virtual uint64_t get_database_size() const override { return 0; }
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const override { return ""; }
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = false) const override { return false; }
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const override { return ""; }
virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, relay_category category = relay_category::broadcasted) const override { return false; }
virtual void add_block( const cryptonote::block& blk
, size_t block_weight

@ -174,7 +174,7 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block
for(auto& tx_blob: block_entry.txs)
{
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
core.handle_incoming_tx(tx_blob, tvc, true, true, false);
core.handle_incoming_tx(tx_blob, tvc, relay_method::block, true);
if(tvc.m_verifivation_failed)
{
MERROR("transaction verification failed, tx_id = "

@ -616,7 +616,7 @@ block Blockchain::pop_block_from_blockchain()
// that might not be always true. Unlikely though, and always relaying
// these again might cause a spike of traffic as many nodes re-relay
// all the transactions in a popped block when a reorg happens.
bool r = m_tx_pool.add_tx(tx, tvc, true, true, false, version);
bool r = m_tx_pool.add_tx(tx, tvc, relay_method::block, true, version);
if (!r)
{
LOG_ERROR("Error returning transaction to tx_pool");
@ -1765,7 +1765,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
{
cryptonote::tx_memory_pool::tx_details td;
cryptonote::blobdata blob;
if (m_tx_pool.have_tx(txid))
if (m_tx_pool.have_tx(txid, relay_category::legacy))
{
if (m_tx_pool.get_transaction_info(txid, td))
{
@ -3641,7 +3641,7 @@ void Blockchain::return_tx_to_pool(std::vector<std::pair<transaction, blobdata>>
// all the transactions in a popped block when a reorg happens.
const size_t weight = get_transaction_weight(tx.first, tx.second.size());
const crypto::hash tx_hash = get_transaction_hash(tx.first);
if (!m_tx_pool.add_tx(tx.first, tx_hash, tx.second, weight, tvc, true, true, false, version))
if (!m_tx_pool.add_tx(tx.first, tx_hash, tx.second, weight, tvc, relay_method::block, true, version))
{
MERROR("Failed to return taken transaction with hash: " << get_transaction_hash(tx.first) << " to tx_pool");
}
@ -3661,7 +3661,7 @@ bool Blockchain::flush_txes_from_pool(const std::vector<crypto::hash> &txids)
uint64_t fee;
bool relayed, do_not_relay, double_spend_seen, pruned;
MINFO("Removing txid " << txid << " from the pool");
if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned))
if(!m_tx_pool.have_tx(txid, relay_category::all) && !m_tx_pool.take_tx(txid, tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned))
{
MERROR("Failed to remove txid " << txid << " from the pool");
res = false;
@ -4895,9 +4895,9 @@ void Blockchain::remove_txpool_tx(const crypto::hash &txid)
m_db->remove_txpool_tx(txid);
}
uint64_t Blockchain::get_txpool_tx_count(bool include_unrelayed_txes) const
uint64_t Blockchain::get_txpool_tx_count(bool include_sensitive) const
{
return m_db->get_txpool_tx_count(include_unrelayed_txes);
return m_db->get_txpool_tx_count(include_sensitive ? relay_category::all : relay_category::broadcasted);
}
bool Blockchain::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const
@ -4905,19 +4905,24 @@ bool Blockchain::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &
return m_db->get_txpool_tx_meta(txid, meta);
}
bool Blockchain::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const
bool Blockchain::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const
{
return m_db->get_txpool_tx_blob(txid, bd);
return m_db->get_txpool_tx_blob(txid, bd, tx_category);
}
cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid) const
cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const
{
return m_db->get_txpool_tx_blob(txid);
return m_db->get_txpool_tx_blob(txid, tx_category);
}
bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const
bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, relay_category tx_category) const
{
return m_db->for_all_txpool_txes(f, include_blob, include_unrelayed_txes);
return m_db->for_all_txpool_txes(f, include_blob, tx_category);
}
bool Blockchain::txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category)
{
return m_db->txpool_tx_matches_category(tx_hash, category);
}
void Blockchain::set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint64_t sync_threshold, blockchain_db_sync_mode sync_mode, bool fast_sync)
@ -5098,7 +5103,7 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get
CRITICAL_REGION_LOCAL(m_tx_pool);
std::vector<transaction> txs;
m_tx_pool.get_transactions(txs);
m_tx_pool.get_transactions(txs, true);
size_t tx_weight;
uint64_t fee;

@ -964,11 +964,12 @@ namespace cryptonote
void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const txpool_tx_meta_t &meta);
void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t &meta);
void remove_txpool_tx(const crypto::hash &txid);
uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const;
uint64_t get_txpool_tx_count(bool include_sensitive = false) const;
bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const;
bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const;
cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const;
bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const;
bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const;
cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const;
bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, relay_category tx_category = relay_category::broadcasted) const;
bool txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category);
bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); }
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes, const std::vector<uint64_t> &weights);

@ -29,6 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include <boost/algorithm/string.hpp>
#include <boost/uuid/nil_generator.hpp>
#include "string_tools.h"
using namespace epee;
@ -753,7 +754,7 @@ namespace cryptonote
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, bool keeped_by_block, bool relayed, bool do_not_relay)
bool core::handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash)
{
tvc = {};
@ -817,7 +818,7 @@ namespace cryptonote
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, bool keeped_by_block, bool relayed, bool do_not_relay)
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))
{
@ -946,23 +947,29 @@ namespace cryptonote
return ret;
}
//-----------------------------------------------------------------------------------------------
bool core::handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
bool core::handle_incoming_txs(const epee::span<const tx_blob_entry> tx_blobs, epee::span<tx_verification_context> tvc, relay_method tx_relay, bool relayed)
{
TRY_ENTRY();
CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
if (tx_blobs.size() != tvc.size())
{
MERROR("tx_blobs and tx_verification_context spans must have equal size");
return false;
}
struct result { bool res; cryptonote::transaction tx; crypto::hash hash; };
std::vector<result> results(tx_blobs.size());
tvc.resize(tx_blobs.size());
CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
std::vector<tx_blob_entry>::const_iterator it = tx_blobs.begin();
epee::span<tx_blob_entry>::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, keeped_by_block, relayed, do_not_relay);
results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash);
}
catch (const std::exception &e)
{
@ -978,7 +985,7 @@ namespace cryptonote
for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
if (!results[i].res)
continue;
if(m_mempool.have_tx(results[i].hash))
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;
@ -993,7 +1000,7 @@ namespace cryptonote
tpool.submit(&waiter, [&, i, it] {
try
{
results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, keeped_by_block, relayed, do_not_relay);
results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash);
}
catch (const std::exception &e)
{
@ -1014,7 +1021,7 @@ namespace cryptonote
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, keeped_by_block);
handle_incoming_tx_accumulated_batch(tx_info, tx_relay == relay_method::block);
bool ok = true;
it = tx_blobs.begin();
@ -1024,13 +1031,14 @@ namespace cryptonote
ok = false;
continue;
}
if (keeped_by_block)
if (tx_relay == relay_method::block)
get_blockchain_storage().on_new_tx_from_block(results[i].tx);
if (already_have[i])
continue;
const uint64_t 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, weight, tvc[i], keeped_by_block, relayed, do_not_relay);
ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, 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)
@ -1044,19 +1052,9 @@ namespace cryptonote
CATCH_ENTRY_L0("core::handle_incoming_txs()", false);
}
//-----------------------------------------------------------------------------------------------
bool core::handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
{
std::vector<tx_blob_entry> tx_blobs;
tx_blobs.push_back(tx_blob);
std::vector<tx_verification_context> tvcv(1);
bool r = handle_incoming_txs(tx_blobs, tvcv, keeped_by_block, relayed, do_not_relay);
tvc = tvcv[0];
return r;
}
//-----------------------------------------------------------------------------------------------
bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
bool core::handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed)
{
return handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, keeped_by_block, relayed, do_not_relay);
return handle_incoming_txs({std::addressof(tx_blob), 1}, {std::addressof(tvc), 1}, tx_relay, relayed);
}
//-----------------------------------------------------------------------------------------------
bool core::get_stat_info(core_stat_info& st_inf) const
@ -1246,13 +1244,13 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
bool core::add_new_tx(transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
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, keeped_by_block, relayed, do_not_relay);
return add_new_tx(tx, tx_hash, bl, tx_weight, tvc, tx_relay, relayed);
}
//-----------------------------------------------------------------------------------------------
size_t core::get_blockchain_total_transactions() const
@ -1260,9 +1258,9 @@ namespace cryptonote
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, bool keeped_by_block, bool relayed, bool do_not_relay)
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))
if(m_mempool.have_tx(tx_hash, relay_category::legacy))
{
LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool");
return true;
@ -1275,40 +1273,62 @@ namespace cryptonote
}
uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
return m_mempool.add_tx(tx, tx_hash, blob, tx_weight, tvc, keeped_by_block, relayed, do_not_relay, 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<std::pair<crypto::hash, cryptonote::blobdata>> txs;
std::vector<std::tuple<crypto::hash, cryptonote::blobdata, relay_method>> txs;
if (m_mempool.get_relayable_transactions(txs) && !txs.empty())
{
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
NOTIFY_NEW_TRANSACTIONS::request r;
for (auto it = txs.begin(); it != txs.end(); ++it)
NOTIFY_NEW_TRANSACTIONS::request public_req{};
NOTIFY_NEW_TRANSACTIONS::request private_req{};
for (auto& tx : txs)
{
r.txs.push_back(it->second);
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::block:
case relay_method::flood:
public_req.txs.push_back(std::move(std::get<1>(tx)));
break;
}
}
get_protocol()->relay_transactions(r, fake_context);
m_mempool.set_relayed(txs);
/* 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_);
if (!private_req.txs.empty())
get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid);
}
return true;
}
//-----------------------------------------------------------------------------------------------
void core::on_transaction_relayed(const cryptonote::blobdata& tx_blob)
void core::on_transactions_relayed(const epee::span<const cryptonote::blobdata> tx_blobs, const relay_method tx_relay)
{
std::vector<std::pair<crypto::hash, cryptonote::blobdata>> txs;
cryptonote::transaction tx;
crypto::hash tx_hash;
if (!parse_and_validate_tx_from_blob(tx_blob, tx, tx_hash))
std::vector<crypto::hash> tx_hashes{};
tx_hashes.resize(tx_blobs.size());
cryptonote::transaction tx{};
for (std::size_t i = 0; i < tx_blobs.size(); ++i)
{
LOG_ERROR("Failed to parse relayed transaction");
return;
if (!parse_and_validate_tx_from_blob(tx_blobs[i], tx, tx_hashes[i]))
{
LOG_ERROR("Failed to parse relayed transaction");
return;
}
}
txs.push_back(std::make_pair(tx_hash, std::move(tx_blob)));
m_mempool.set_relayed(txs);
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)
@ -1369,7 +1389,7 @@ namespace cryptonote
for (const auto &tx_hash: b.tx_hashes)
{
cryptonote::blobdata txblob;
CHECK_AND_ASSERT_THROW_MES(pool.get_transaction(tx_hash, txblob), "Transaction not found in pool");
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;
@ -1573,14 +1593,14 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
bool core::get_pool_transaction(const crypto::hash &id, cryptonote::blobdata& tx) const
bool core::get_pool_transaction(const crypto::hash &id, cryptonote::blobdata& tx, relay_category tx_category) const
{
return m_mempool.get_transaction(id, tx);
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);
return m_mempool.have_tx(id, relay_category::legacy);
}
//-----------------------------------------------------------------------------------------------
bool core::get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data) const

@ -35,7 +35,9 @@
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#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"
@ -46,6 +48,7 @@
#include "cryptonote_basic/cryptonote_stat_info.h"
#include "warnings.h"
#include "crypto/hash.h"
#include "span.h"
PUSH_WARNINGS
DISABLE_VS_WARNINGS(4355)
@ -77,7 +80,7 @@ namespace cryptonote
* limited to, communication among the Blockchain, the transaction pool,
* any miners, and the network.
*/
class core: public i_miner_handler
class core final: public i_miner_handler, public i_core_events
{
public:
@ -115,14 +118,12 @@ namespace cryptonote
*
* @param tx_blob the tx to handle
* @param tvc metadata about the transaction's validity
* @param keeped_by_block if the transaction has been in a block
* @param tx_relay how the transaction was received
* @param relayed whether or not the transaction was relayed to us
* @param do_not_relay whether to prevent the transaction from being relayed
*
* @return true if the transaction was accepted, false otherwise
*/
bool handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
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
@ -130,15 +131,35 @@ namespace cryptonote
* 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 keeped_by_block if the transactions have been in a block
* @param tx_relay how the transaction was received.
* @param relayed whether or not the transactions were relayed to us
* @param do_not_relay whether to prevent the transactions from being relayed
*
* @return true if the transactions were accepted, false otherwise
*/
bool handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
bool handle_incoming_txs(epee::span<const tx_blob_entry> tx_blobs, epee::span<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
*
* @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_blob_entry>& tx_blobs, std::vector<tx_verification_context>& 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
@ -212,9 +233,10 @@ namespace cryptonote
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);
/**
* @brief called when a transaction is relayed
* @brief called when a transaction is relayed.
* @note Should only be invoked from `levin_notify`.
*/
virtual void on_transaction_relayed(const cryptonote::blobdata& tx);
virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, relay_method tx_relay) final;
/**
@ -440,11 +462,11 @@ namespace cryptonote
/**
* @copydoc tx_memory_pool::get_transactions
* @param include_unrelayed_txes include unrelayed txes in result
* @param include_sensitive_txes include private transactions
*
* @note see tx_memory_pool::get_transactions
*/
bool get_pool_transactions(std::vector<transaction>& txs, bool include_unrelayed_txes = true) const;
bool get_pool_transactions(std::vector<transaction>& txs, bool include_sensitive_txes = false) const;
/**
* @copydoc tx_memory_pool::get_txpool_backlog
@ -455,34 +477,34 @@ namespace cryptonote
/**
* @copydoc tx_memory_pool::get_transactions
* @param include_unrelayed_txes include unrelayed txes in result
* @param include_sensitive_txes include private transactions
*
* @note see tx_memory_pool::get_transactions
*/
bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const;
bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive_txes = false) const;
/**
* @copydoc tx_memory_pool::get_transactions
* @param include_unrelayed_txes include unrelayed txes in result
* @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_unrelayed_txes = true) const;
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) const;
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_unrelayed_txes include unrelayed txes in result
* @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_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_unrelayed_txes = true) const;
bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_txes = false) const;
/**
* @copydoc tx_memory_pool::get_pool_for_rpc
@ -852,11 +874,11 @@ namespace cryptonote
* @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
* @param do_not_relay whether to prevent the transaction from being relayed
*
*/
bool add_new_tx(transaction& tx, const crypto::hash& tx_hash, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
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
@ -865,15 +887,14 @@ namespace cryptonote
*
* @param tx the transaction to add
* @param tvc return-by-reference metadata about the transaction's verification process
* @param keeped_by_block whether or not the transaction has been in a block
* @param tx_relay how the transaction was received
* @param relayed whether or not the transaction was relayed to us
* @param do_not_relay whether to prevent the transaction from being relayed
*
* @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, bool keeped_by_block, bool relayed, bool do_not_relay);
bool add_new_tx(transaction& tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed);
/**
* @copydoc Blockchain::add_new_block
@ -929,8 +950,8 @@ namespace cryptonote
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 keeped_by_block, bool relayed, bool do_not_relay);
bool handle_incoming_tx_post(const tx_blob_entry &tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay);
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_verification_batch_info> &tx_info, bool keeped_by_block);

@ -0,0 +1,44 @@
// Copyright (c) 2019, 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 void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, relay_method tx_relay) = 0;
};
}

@ -115,8 +115,10 @@ namespace cryptonote
}
//---------------------------------------------------------------------------------
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, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version)
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);
@ -227,7 +229,7 @@ namespace cryptonote
crypto::hash max_used_block_id = null_hash;
uint64_t max_used_block_height = 0;
cryptonote::txpool_tx_meta_t meta;
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)
{
@ -241,11 +243,10 @@ namespace cryptonote
meta.max_used_block_height = 0;
meta.last_failed_height = 0;
meta.last_failed_id = null_hash;
meta.kept_by_block = kept_by_block;
meta.receive_time = receive_time;
meta.last_relayed_time = time(NULL);
meta.relayed = relayed;
meta.do_not_relay = do_not_relay;
meta.set_relay_method(tx_relay);
meta.double_spend_seen = have_tx_keyimges_as_spent(tx);
meta.pruned = tx.pruned;
meta.bf_padding = 0;
@ -256,9 +257,10 @@ namespace cryptonote
m_parsed_tx_cache.insert(std::make_pair(id, tx));
CRITICAL_REGION_LOCAL1(m_blockchain);
LockedTXN lock(m_blockchain);
m_blockchain.add_txpool_tx(id, blob, meta);
if (!insert_key_images(tx, id, kept_by_block))
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<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id);
lock.commit();
}
@ -280,7 +282,6 @@ namespace cryptonote
{
//update transactions container
meta.weight = tx_weight;
meta.kept_by_block = kept_by_block;
meta.fee = fee;
meta.max_used_block_id = max_used_block_id;
meta.max_used_block_height = max_used_block_height;
@ -289,7 +290,7 @@ namespace cryptonote
meta.receive_time = receive_time;
meta.last_relayed_time = time(NULL);
meta.relayed = relayed;
meta.do_not_relay = do_not_relay;
meta.set_relay_method(tx_relay);
meta.double_spend_seen = false;
meta.pruned = tx.pruned;
meta.bf_padding = 0;
@ -302,9 +303,10 @@ namespace cryptonote
CRITICAL_REGION_LOCAL1(m_blockchain);
LockedTXN lock(m_blockchain);
m_blockchain.remove_txpool_tx(id);
m_blockchain.add_txpool_tx(id, blob, meta);
if (!insert_key_images(tx, id, kept_by_block))
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<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id);
lock.commit();
}
@ -315,7 +317,7 @@ namespace cryptonote
}
tvc.m_added_to_pool = true;
if(meta.fee > 0 && !do_not_relay)
if(meta.fee > 0 && tx_relay != relay_method::none)
tvc.m_should_be_relayed = true;
}
@ -331,7 +333,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay, uint8_t version)
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;
size_t blob_size = 0;
@ -339,7 +341,7 @@ namespace cryptonote
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, keeped_by_block, relayed, do_not_relay, version);
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
@ -375,7 +377,7 @@ namespace cryptonote
txpool_tx_meta_t meta;
if (!m_blockchain.get_txpool_tx_meta(txid, meta))
{
MERROR("Failed to find tx in txpool");
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
@ -384,7 +386,7 @@ namespace cryptonote
--it;
continue;
}
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid);
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))
{
@ -413,17 +415,38 @@ namespace cryptonote
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, bool kept_by_block)
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<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image];
CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: kept_by_block=" << kept_by_block
<< ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL
<< "tx_id=" << id );
auto ins_res = kei_image_set.insert(id);
CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set");
/* If any existing key-image in the set is publicly visible AND this is
not forcibly "kept_by_block", then fail (duplicate key image). If all
existing key images are supposed to be hidden, we silently allow so
that the node doesn't leak knowledge of a local/stem tx. */
bool visible = false;
if (tx_relay != relay_method::block)
{
for (const crypto::hash& other_id : kei_image_set)
visible |= m_blockchain.txpool_tx_matches_category(other_id, relay_category::legacy);
}
CHECK_AND_ASSERT_MES(!visible, 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);
/* If adding a tx (hash) that already exists, fail only if the tx has
been publicly "broadcast" previously. This way, when a private tx is
received for the first time from a remote node, "this" node will
respond as-if it were seen for the first time. LMDB does the
"hard-check" on key-images, so the effect is overwriting the existing
tx_pool metadata and "first seen" time. */
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;
@ -475,10 +498,10 @@ namespace cryptonote
txpool_tx_meta_t meta;
if (!m_blockchain.get_txpool_tx_meta(id, meta))
{
MERROR("Failed to find tx in txpool");
MERROR("Failed to find tx_meta in txpool");
return false;
}
txblob = m_blockchain.get_txpool_tx_blob(id);
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())
{
@ -533,7 +556,7 @@ namespace cryptonote
MERROR("Failed to find tx in txpool");
return false;
}
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid);
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())
{
@ -611,7 +634,7 @@ namespace cryptonote
remove.push_back(std::make_pair(txid, meta.weight));
}
return true;
}, false);
}, false, relay_category::all);
if (!remove.empty())
{
@ -621,7 +644,7 @@ namespace cryptonote
const crypto::hash &txid = entry.first;
try
{
cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid);
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))
{
@ -649,7 +672,7 @@ namespace cryptonote
}
//---------------------------------------------------------------------------------
//TODO: investigate whether boolean return is appropriate
bool tx_memory_pool::get_relayable_transactions(std::vector<std::pair<crypto::hash, cryptonote::blobdata>> &txs) const
bool tx_memory_pool::get_relayable_transactions(std::vector<std::tuple<crypto::hash, cryptonote::blobdata, relay_method>> &txs) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
@ -667,8 +690,7 @@ namespace cryptonote
{
try
{
cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid);
txs.push_back(std::make_pair(txid, bd));
txs.emplace_back(txid, m_blockchain.get_txpool_tx_blob(txid, relay_category::all), meta.get_relay_method());
}
catch (const std::exception &e)
{
@ -678,26 +700,27 @@ namespace cryptonote
}
}
return true;
}, false);
}, false, relay_category::relayable);
return true;
}
//---------------------------------------------------------------------------------
void tx_memory_pool::set_relayed(const std::vector<std::pair<crypto::hash, cryptonote::blobdata>> &txs)
void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
const time_t now = time(NULL);
LockedTXN lock(m_blockchain);
for (auto it = txs.begin(); it != txs.end(); ++it)
for (const auto& hash : hashes)
{
try
{
txpool_tx_meta_t meta;
if (m_blockchain.get_txpool_tx_meta(it->first, meta))
if (m_blockchain.get_txpool_tx_meta(hash, meta))
{
meta.relayed = true;
meta.last_relayed_time = now;
m_blockchain.update_txpool_tx(it->first, meta);
meta.set_relay_method(method);
m_blockchain.update_txpool_tx(hash, meta);
}
}
catch (const std::exception &e)
@ -709,18 +732,19 @@ namespace cryptonote
lock.commit();
}
//---------------------------------------------------------------------------------
size_t tx_memory_pool::get_transactions_count(bool include_unrelayed_txes) const
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_unrelayed_txes);
return m_blockchain.get_txpool_tx_count(include_sensitive);
}
//---------------------------------------------------------------------------------
void tx_memory_pool::get_transactions(std::vector<transaction>& txs, bool include_unrelayed_txes) const
void tx_memory_pool::get_transactions(std::vector<transaction>& txs, bool include_sensitive) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
txs.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes));
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 *bd){
transaction tx;
if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx)))
@ -732,39 +756,42 @@ namespace cryptonote
tx.set_hash(txid);
txs.push_back(std::move(tx));
return true;
}, true, include_unrelayed_txes);
}, true, category);
}
//------------------------------------------------------------------
void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes) const
void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
txs.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes));
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 *bd){
txs.push_back(txid);
return true;
}, false, include_unrelayed_txes);
}, false, category);
}
//------------------------------------------------------------------
void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes) const
void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
const uint64_t now = time(NULL);
backlog.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes));
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 *bd){
backlog.push_back({meta.weight, meta.fee, meta.receive_time - now});
return true;
}, false, include_unrelayed_txes);
}, false, category);
}
//------------------------------------------------------------------
void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes) const
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<uint64_t, txpool_histo> agebytes;
stats.txs_total = m_blockchain.get_txpool_tx_count(include_unrelayed_txes);
stats.txs_total = m_blockchain.get_txpool_tx_count(include_sensitive);
std::vector<uint32_t> 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 *bd){
@ -789,7 +816,8 @@ namespace cryptonote
if (meta.double_spend_seen)
++stats.num_double_spends;
return true;
}, false, include_unrelayed_txes);
}, false, category);
stats.bytes_med = epee::misc_utils::median(weights);
if (stats.txs_total > 1)
{
@ -847,8 +875,10 @@ namespace cryptonote
{
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());
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 *bd){
tx_info txi;
txi.id_hash = epee::string_tools::pod_to_hex(txid);
@ -879,7 +909,7 @@ namespace cryptonote
txi.double_spend_seen = meta.double_spend_seen;
tx_infos.push_back(std::move(txi));
return true;
}, true, include_sensitive_data);
}, true, category);
txpool_tx_meta_t meta;
for (const key_images_container::value_type& kee : m_spent_key_images) {
@ -889,30 +919,13 @@ namespace cryptonote
ki.id_hash = epee::string_tools::pod_to_hex(k_image);
for (const crypto::hash& tx_id_hash : kei_image_set)
{
if (!include_sensitive_data)
{
try
{
if (!m_blockchain.get_txpool_tx_meta(tx_id_hash, meta))
{
MERROR("Failed to get tx meta from txpool");
return false;
}
if (!meta.relayed)
// Do not include that transaction if in restricted mode and it's not relayed
continue;
}
catch (const std::exception &e)
{
MERROR("Failed to get tx meta from txpool: " << e.what());
return false;
}
}
ki.txs_hashes.push_back(epee::string_tools::pod_to_hex(tx_id_hash));
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(ki);
key_image_infos.push_back(std::move(ki));
}
return true;
}
@ -948,18 +961,19 @@ namespace cryptonote
txi.double_spend_seen = meta.double_spend_seen;
tx_infos.push_back(txi);
return true;
}, true, false);
}, true, relay_category::broadcasted);
for (const key_images_container::value_type& kee : m_spent_key_images) {
std::vector<crypto::hash> tx_hashes;
const std::unordered_set<crypto::hash>& kei_image_set = kee.second;
for (const crypto::hash& tx_id_hash : kei_image_set)
{
tx_hashes.push_back(tx_id_hash);
if (m_blockchain.txpool_tx_matches_category(tx_id_hash, relay_category::broadcasted))
tx_hashes.push_back(tx_id_hash);
}
const crypto::key_image& k_image = kee.first;
key_image_infos[k_image] = std::move(tx_hashes);
if (!tx_hashes.empty())
key_image_infos[kee.first] = std::move(tx_hashes);
}
return true;
}
@ -973,19 +987,26 @@ namespace cryptonote
for (const auto& image : key_images)
{
spent.push_back(m_spent_key_images.find(image) == m_spent_key_images.end() ? false : true);
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) const
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);
return m_blockchain.get_txpool_tx_blob(id, txblob, tx_category);
}
catch (const std::exception &e)
{
@ -1009,11 +1030,11 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::have_tx(const crypto::hash &id) const
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);
return m_blockchain.get_db().txpool_has_tx(id, tx_category);
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const
@ -1032,7 +1053,14 @@ namespace cryptonote
bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
return m_spent_key_images.end() != m_spent_key_images.find(key_im);
bool spent = false;
const auto found = m_spent_key_images.find(key_im);
if (found != m_spent_key_images.end())
{
for (const crypto::hash& tx_hash : found->second)
spent |= m_blockchain.txpool_tx_matches_category(tx_hash, relay_category::broadcasted);
}
return spent;
}
//---------------------------------------------------------------------------------
void tx_memory_pool::lock() const
@ -1217,13 +1245,14 @@ namespace cryptonote
<< "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);
}, !short_format, relay_category::all);
return ss.str();
}
@ -1255,7 +1284,7 @@ namespace cryptonote
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))
if (!m_blockchain.get_txpool_tx_meta(sorted_it->second, meta) && !meta.matches(relay_category::legacy))
{
MERROR(" failed to find tx meta");
continue;
@ -1304,7 +1333,9 @@ namespace cryptonote
}
}
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(sorted_it->second);
// "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
@ -1379,7 +1410,7 @@ namespace cryptonote
remove.insert(txid);
}
return true;
}, false);
}, false, relay_category::all);
size_t n_removed = 0;
if (!remove.empty())
@ -1389,7 +1420,7 @@ namespace cryptonote
{
try
{
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid);
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
{
@ -1450,7 +1481,7 @@ namespace cryptonote
remove.push_back(txid);
return true;
}
if (!insert_key_images(tx, txid, meta.kept_by_block))
if (!insert_key_images(tx, txid, meta.get_relay_method()))
{
MFATAL("Failed to insert key images from txpool tx");
return false;
@ -1458,7 +1489,7 @@ namespace cryptonote
m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(meta.fee / (double)meta.weight, meta.receive_time), txid);
m_txpool_weight += meta.weight;
return true;
}, true);
}, true, relay_category::all);
if (!r)
return false;
}

@ -32,17 +32,20 @@
#include "include_base_utils.h"
#include <set>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <boost/serialization/version.hpp>
#include <boost/utility.hpp>
#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"
@ -105,9 +108,10 @@ namespace cryptonote
* @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, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version);
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
@ -119,14 +123,13 @@ namespace cryptonote
*
* @param tx the transaction to be added
* @param tvc return-by-reference status about the transaction verification
* @param kept_by_block has this transaction been in a block?
* @tx_relay how the transaction was received
* @param relayed was this transaction from the network or a local client?
* @param do_not_relay to avoid relaying the transaction to the network
* @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, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version);
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
@ -149,10 +152,11 @@ namespace cryptonote
* @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, otherwise false
* @return true if the transaction is in the pool and meets tx_category requirements
*/
bool have_tx(const crypto::hash &id) const;
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
@ -236,37 +240,37 @@ namespace cryptonote
* @brief get a list of all transactions in the pool
*
* @param txs return-by-reference the list of transactions
* @param include_unrelayed_txes include unrelayed txes in the result
* @param include_sensitive return stempool, anonymity-pool, and unrelayed txes
*
*/
void get_transactions(std::vector<transaction>& txs, bool include_unrelayed_txes = true) const;
void get_transactions(std::vector<transaction>& 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_unrelayed_txes include unrelayed txes in the result
* @param include_sensitive return stempool, anonymity-pool, and unrelayed txes
*
*/
void get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const;
void get_transaction_hashes(std::vector<crypto::hash>& 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_unrelayed_txes include unrelayed txes in the result
* @param include_sensitive return stempool, anonymity-pool, and unrelayed txes
*
*/
void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes = true) const;
void get_transaction_backlog(std::vector<tx_backlog_entry>& 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_unrelayed_txes include unrelayed txes in the result
* @param include_sensitive return stempool, anonymity-pool, and unrelayed txes
*
*/
void get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes = true) const;
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
@ -275,11 +279,12 @@ namespace cryptonote
*
* @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 include unrelayed txes and fields that are sensitive to the node privacy
* @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_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data = true) const;
bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data = false) const;
/**
* @brief get information about all transactions and key images in the pool
@ -308,10 +313,11 @@ namespace cryptonote
*
* @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) const;
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
@ -326,21 +332,22 @@ namespace cryptonote
*
* @return true
*/
bool get_relayable_transactions(std::vector<std::pair<crypto::hash, cryptonote::blobdata>>& txs) const;
bool get_relayable_transactions(std::vector<std::tuple<crypto::hash, cryptonote::blobdata, relay_method>>& txs) const;
/**
* @brief tell the pool that certain transactions were just relayed
*
* @param txs the list of transactions (and their hashes)
* @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(const std::vector<std::pair<crypto::hash, cryptonote::blobdata>>& txs);
void set_relayed(epee::span<const crypto::hash> 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_unrelayed_txes = true) const;
size_t get_transactions_count(bool include_sensitive = false) const;
/**
* @brief get a string containing human-readable pool information
@ -441,7 +448,7 @@ namespace cryptonote
*
* @return true on success, false on error
*/
bool insert_key_images(const transaction_prefix &tx, const crypto::hash &txid, bool kept_by_block);
bool insert_key_images(const transaction_prefix &tx, const crypto::hash &txid, relay_method tx_relay);
/**
* @brief remove old transactions from the pool
@ -544,7 +551,7 @@ namespace cryptonote
* transaction on the assumption that the original will not be in a
* block again.
*/
typedef std::unordered_map<crypto::key_image, std::unordered_set<crypto::hash> > key_images_container;
typedef std::unordered_map<crypto::key_image, std::unordered_set<crypto::hash>> key_images_container;
#if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
public:

@ -130,7 +130,7 @@ namespace cryptonote
//----------------- i_bc_protocol_layout ---------------------------------------
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context);
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context);
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone);
//----------------------------------------------------------------------------------
//bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context);
bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe);

@ -455,7 +455,7 @@ namespace cryptonote
for(auto tx_blob_it = arg.b.txs.begin(); tx_blob_it!=arg.b.txs.end();tx_blob_it++)
{
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
m_core.handle_incoming_tx(*tx_blob_it, tvc, true, true, false);
m_core.handle_incoming_tx(*tx_blob_it, tvc, relay_method::block, true);
if(tvc.m_verifivation_failed)
{
LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection");
@ -619,7 +619,7 @@ namespace cryptonote
{
MDEBUG("Incoming tx " << tx_hash << " not in pool, adding");
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
if(!m_core.handle_incoming_tx(tx_blob, tvc, true, true, false) || tvc.m_verifivation_failed)
if(!m_core.handle_incoming_tx(tx_blob, tvc, relay_method::block, true) || tvc.m_verifivation_failed)
{
LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection");
drop_connection(context, false, false);
@ -667,13 +667,13 @@ namespace cryptonote
drop_connection(context, false, false);
m_core.resume_mine();
return 1;
}
}
size_t tx_idx = 0;
for(auto& tx_hash: new_block.tx_hashes)
{
cryptonote::blobdata txblob;
if(m_core.get_pool_transaction(tx_hash, txblob))
if(m_core.get_pool_transaction(tx_hash, txblob, relay_category::broadcasted))
{
have_tx.push_back({txblob, crypto::null_hash});
}
@ -702,7 +702,7 @@ namespace cryptonote
need_tx_indices.push_back(tx_idx);
}
}
++tx_idx;
}
@ -909,8 +909,8 @@ namespace cryptonote
newtxs.reserve(arg.txs.size());
for (size_t i = 0; i < arg.txs.size(); ++i)
{
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, false, true, false);
cryptonote::tx_verification_context tvc{};
m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, relay_method::flood, true);
if(tvc.m_verifivation_failed)
{
LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection");
@ -925,7 +925,7 @@ namespace cryptonote
if(arg.txs.size())
{
//TODO: add announce usage here
relay_transactions(arg, context);
relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone());
}
return 1;
@ -1316,7 +1316,7 @@ namespace cryptonote
TIME_MEASURE_START(transactions_process_time);
num_txs += block_entry.txs.size();
std::vector<tx_verification_context> tvc;
m_core.handle_incoming_txs(block_entry.txs, tvc, true, true, false);
m_core.handle_incoming_txs(block_entry.txs, tvc, relay_method::block, true);
if (tvc.size() != block_entry.txs.size())
{
LOG_ERROR_CCONTEXT("Internal error: tvc.size() != block_entry.txs.size()");
@ -2344,14 +2344,14 @@ skip:
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)
bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)
{
for(auto& tx_blob : arg.txs)
m_core.on_transaction_relayed(tx_blob);
// no check for success, so tell core they're relayed unconditionally
m_p2p->send_txs(std::move(arg.txs), exclude_context.m_remote_address.get_zone(), exclude_context.m_connection_id, m_core.pad_transactions());
return true;
/* Push all outgoing transactions to this function. The behavior needs to
identify how the transaction is going to be relayed, and then update the
local mempool before doing the relay. The code was already updating the
DB twice on received transactions - it is difficult to workaround this
due to the internal design. */
return m_p2p->send_txs(std::move(arg.txs), zone, source, m_core, m_core.pad_transactions()) != epee::net_utils::zone::invalid;
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>

@ -41,7 +41,7 @@ namespace cryptonote
struct i_cryptonote_protocol
{
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)=0;
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)=0;
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)=0;
//virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0;
};
@ -54,7 +54,7 @@ namespace cryptonote
{
return false;
}
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)
{
return false;
}

@ -0,0 +1,43 @@
// Copyright (c) 2019, 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 <cstdint>
namespace cryptonote
{
//! Methods tracking how a tx was received and relayed
enum class relay_method : std::uint8_t
{
none = 0, //!< Received via RPC with `do_not_relay` set
local, //!< Received via RPC; trying to send over i2p/tor, etc.
block, //!< Received in block, takes precedence over others
flood //!< Received/sent over public networks
};
}

@ -0,0 +1,37 @@
// Copyright (c) 2019, 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
namespace cryptonote
{
class core;
struct cryptonote_connection_context;
struct i_core_events;
}

@ -35,6 +35,7 @@
#include "byte_slice.h"
#include "cryptonote_basic/blobdatatype.h"
#include "cryptonote_protocol/fwd.h"
#include "net/enums.h"
#include "span.h"
@ -51,11 +52,6 @@ namespace nodetool
template<typename> struct p2p_connection_context_t;
}
namespace cryptonote
{
struct cryptonote_connection_context;
}
namespace cryptonote
{
namespace levin

@ -43,6 +43,7 @@
#include <vector>
#include "cryptonote_config.h"
#include "cryptonote_protocol/fwd.h"
#include "cryptonote_protocol/levin_notify.h"
#include "warnings.h"
#include "net/abstract_tcp_server2.h"
@ -336,7 +337,7 @@ namespace nodetool
virtual void callback(p2p_connection_context& context);
//----------------- i_p2p_endpoint -------------------------------------------------------------
virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections);
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs);
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, bool pad_txs);
virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context);
virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context);
virtual bool drop_connection(const epee::net_utils::connection_context_base& context);

@ -2053,13 +2053,18 @@ namespace nodetool
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)
epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, const bool pad_txs)
{
namespace enet = epee::net_utils;
const auto send = [&txs, &source, pad_txs] (std::pair<const enet::zone, network_zone>& network)
const auto send = [&txs, &source, &core, pad_txs] (std::pair<const enet::zone, network_zone>& network)
{
if (network.second.m_notifier.send_txs(std::move(txs), source, (pad_txs || network.first != enet::zone::public_)))
const bool is_public = (network.first == enet::zone::public_);
const cryptonote::relay_method tx_relay = is_public ?
cryptonote::relay_method::flood : cryptonote::relay_method::local;
core.on_transactions_relayed(epee::to_span(txs), tx_relay);
if (network.second.m_notifier.send_txs(std::move(txs), source, (pad_txs || !is_public)))
return network.first;
return enet::zone::invalid;
};

@ -34,6 +34,8 @@
#include <utility>
#include <vector>
#include "cryptonote_basic/blobdatatype.h"
#include "cryptonote_protocol/enums.h"
#include "cryptonote_protocol/fwd.h"
#include "net/enums.h"
#include "net/net_utils_base.h"
#include "p2p_protocol_defs.h"
@ -48,7 +50,7 @@ namespace nodetool
struct i_p2p_endpoint
{
virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections)=0;
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)=0;
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, bool pad_txs)=0;
virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)=0;
virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context)=0;
virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0;
@ -73,7 +75,7 @@ namespace nodetool
{
return false;
}
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)
virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, const bool pad_txs)
{
return epee::net_utils::zone::invalid;
}

@ -29,6 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include <boost/preprocessor/stringize.hpp>
#include <boost/uuid/nil_generator.hpp>
#include "include_base_utils.h"
#include "string_tools.h"
using namespace epee;
@ -1095,9 +1096,8 @@ namespace cryptonote
}
res.sanity_check_failed = false;
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed)
tx_verification_context tvc{};
if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, (req.do_not_relay ? relay_method::none : relay_method::local), false) || tvc.m_verifivation_failed)
{
res.status = "Failed";
std::string reason = "";
@ -1142,7 +1142,7 @@ namespace cryptonote
NOTIFY_NEW_TRANSACTIONS::request r;
r.txs.push_back(tx_blob);
m_core.get_protocol()->relay_transactions(r, fake_context);
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
res.status = CORE_RPC_STATUS_OK;
return true;
@ -2370,7 +2370,7 @@ namespace cryptonote
if (req.txids.empty())
{
std::vector<transaction> pool_txs;
bool r = m_core.get_pool_transactions(pool_txs);
bool r = m_core.get_pool_transactions(pool_txs, true);
if (!r)
{
res.status = "Failed to get txpool contents";
@ -2747,13 +2747,11 @@ namespace cryptonote
crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
cryptonote::blobdata txblob;
bool r = m_core.get_pool_transaction(txid, txblob);
if (r)
if (!m_core.get_pool_transaction(txid, txblob, relay_category::legacy))
{
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
NOTIFY_NEW_TRANSACTIONS::request r;
r.txs.push_back(txblob);
m_core.get_protocol()->relay_transactions(r, fake_context);
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
}
else

@ -28,6 +28,7 @@
#include "daemon_handler.h"
#include <boost/uuid/nil_generator.hpp>
// likely included by daemon_handler.h's includes,
// but including here for clarity
#include "cryptonote_core/cryptonote_core.h"
@ -288,10 +289,9 @@ namespace rpc
return;
}
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, false, false, !relay) || tvc.m_verifivation_failed)
if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, (relay ? relay_method::local : relay_method::none), false) || tvc.m_verifivation_failed)
{
if (tvc.m_verifivation_failed)
{
@ -368,7 +368,7 @@ namespace rpc
NOTIFY_NEW_TRANSACTIONS::request r;
r.txs.push_back(tx_blob);
m_core.get_protocol()->relay_transactions(r, fake_context);
m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid);
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
res.status = Message::STATUS_OK;

@ -160,8 +160,8 @@ string tx2str(const cryptonote::transaction& tx, const cryptonote::hash256& tx_h
return ss.str();
}*/
bool tests::proxy_core::handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) {
if (!keeped_by_block)
bool tests::proxy_core::handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, cryptonote::relay_method tx_relay, bool relayed) {
if (tx_relay != cryptonote::relay_method::block)
return true;
crypto::hash tx_hash = null_hash;
@ -190,13 +190,13 @@ bool tests::proxy_core::handle_incoming_tx(const cryptonote::tx_blob_entry& tx_b
return true;
}
bool tests::proxy_core::handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
bool tests::proxy_core::handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, cryptonote::relay_method tx_relay, bool relayed)
{
tvc.resize(tx_blobs.size());
size_t i = 0;
for (const auto &tx_blob: tx_blobs)
{
if (!handle_incoming_tx(tx_blob, tvc[i], keeped_by_block, relayed, do_not_relay))
if (!handle_incoming_tx(tx_blob, tvc[i], tx_relay, relayed))
return false;
++i;
}

@ -34,6 +34,7 @@
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/verification_context.h"
#include "cryptonote_core/i_core_events.h"
#include <unordered_map>
namespace tests
@ -51,7 +52,7 @@ namespace tests
: height(_height), id(_id), longhash(_longhash), blk(_blk), blob(_blob), txes(_txes) { }
};
class proxy_core
class proxy_core : public cryptonote::i_core_events
{
cryptonote::block m_genesis;
std::list<crypto::hash> m_known_block_list;
@ -75,8 +76,8 @@ namespace tests
bool get_stat_info(cryptonote::core_stat_info& st_inf){return true;}
bool have_block(const crypto::hash& id);
void get_blockchain_top(uint64_t& height, crypto::hash& top_id);
bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blobs, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, cryptonote::relay_method tx_relay, bool relayed);
bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blobs, std::vector<cryptonote::tx_verification_context>& tvc, cryptonote::relay_method tx_relay, bool relayed);
bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true);
void pause_mine(){}
void resume_mine(){}
@ -90,9 +91,9 @@ namespace tests
bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; }
uint64_t get_target_blockchain_height() const { return 1; }
size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; }
virtual void on_transaction_relayed(const cryptonote::blobdata& tx) {}
virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, cryptonote::relay_method tx_relay) {}
cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; }
bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; }
bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob, cryptonote::relay_category tx_category) const { return false; }
bool pool_has_tx(const crypto::hash &txid) const { return false; }
bool get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata, cryptonote::block>>& blocks, std::vector<cryptonote::blobdata>& txs) const { return false; }
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::transaction>& txs, std::vector<crypto::hash>& missed_txs) const { return false; }

@ -40,6 +40,7 @@ set(core_tests_sources
ring_signature_1.cpp
transaction_tests.cpp
tx_validation.cpp
tx_pool.cpp
v2_tests.cpp
rct.cpp
bulletproofs.cpp
@ -57,6 +58,7 @@ set(core_tests_headers
integer_overflow.h
multisig.h
ring_signature_1.h
tx_pool.h
transaction_tests.h
tx_validation.h
v2_tests.h

@ -53,6 +53,7 @@
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_protocol/enums.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
#include "misc_language.h"
@ -108,17 +109,17 @@ typedef serialized_object<cryptonote::transaction> serialized_transaction;
struct event_visitor_settings
{
int valid_mask;
bool txs_keeped_by_block;
int mask;
enum settings
{
set_txs_keeped_by_block = 1 << 0
set_txs_keeped_by_block = 1 << 0,
set_txs_do_not_relay = 1 << 1,
set_local_relay = 1 << 2
};
event_visitor_settings(int a_valid_mask = 0, bool a_txs_keeped_by_block = false)
: valid_mask(a_valid_mask)
, txs_keeped_by_block(a_txs_keeped_by_block)
event_visitor_settings(int a_mask = 0)
: mask(a_mask)
{
}
@ -128,8 +129,7 @@ private:
template<class Archive>
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & valid_mask;
ar & txs_keeped_by_block;
ar & mask;
}
};
@ -503,7 +503,7 @@ private:
t_test_class& m_validator;
size_t m_ev_index;
bool m_txs_keeped_by_block;
cryptonote::relay_method m_tx_relay;
public:
push_core_event_visitor(cryptonote::core& c, const std::vector<test_event_entry>& events, t_test_class& validator)
@ -511,7 +511,7 @@ public:
, m_events(events)
, m_validator(validator)
, m_ev_index(0)
, m_txs_keeped_by_block(false)
, m_tx_relay(cryptonote::relay_method::flood)
{
}
@ -530,9 +530,21 @@ public:
{
log_event("event_visitor_settings");
if (settings.valid_mask & event_visitor_settings::set_txs_keeped_by_block)
if (settings.mask & event_visitor_settings::set_txs_keeped_by_block)
{
m_txs_keeped_by_block = settings.txs_keeped_by_block;
m_tx_relay = cryptonote::relay_method::block;
}
else if (settings.mask & event_visitor_settings::set_local_relay)
{
m_tx_relay = cryptonote::relay_method::local;
}
else if (settings.mask & event_visitor_settings::set_txs_do_not_relay)
{
m_tx_relay = cryptonote::relay_method::none;
}
else
{
m_tx_relay = cryptonote::relay_method::flood;
}
return true;
@ -544,7 +556,7 @@ public:
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
size_t pool_size = m_c.get_pool_transactions_count();
m_c.handle_incoming_tx({t_serializable_object_to_blob(tx), crypto::null_hash}, tvc, m_txs_keeped_by_block, false, false);
m_c.handle_incoming_tx({t_serializable_object_to_blob(tx), crypto::null_hash}, tvc, m_tx_relay, false);
bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count();
bool r = m_validator.check_tx_verification_context(tvc, tx_added, m_ev_index, tx);
CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed");
@ -564,7 +576,7 @@ public:
tvcs.push_back(tvc0);
}
size_t pool_size = m_c.get_pool_transactions_count();
m_c.handle_incoming_txs(tx_blobs, tvcs, m_txs_keeped_by_block, false, false);
m_c.handle_incoming_txs(tx_blobs, tvcs, m_tx_relay, false);
size_t tx_added = m_c.get_pool_transactions_count() - pool_size;
bool r = m_validator.check_tx_verification_context_array(tvcs, tx_added, m_ev_index, txs);
CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed");
@ -644,7 +656,7 @@ public:
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
size_t pool_size = m_c.get_pool_transactions_count();
m_c.handle_incoming_tx(sr_tx.data, tvc, m_txs_keeped_by_block, false, false);
m_c.handle_incoming_tx(sr_tx.data, tvc, m_tx_relay, false);
bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count();
cryptonote::transaction tx;
@ -955,7 +967,7 @@ inline bool do_replay_file(const std::string& filename)
#define MAKE_MINER_TX_MANUALLY(TX, BLK) MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, 0)
#define SET_EVENT_VISITOR_SETT(VEC_EVENTS, SETT, VAL) VEC_EVENTS.push_back(event_visitor_settings(SETT, VAL));
#define SET_EVENT_VISITOR_SETT(VEC_EVENTS, SETT) VEC_EVENTS.push_back(event_visitor_settings(SETT));
#define GENERATE(filename, genclass) \
{ \

@ -32,6 +32,7 @@
#include "chaingen_tests_list.h"
#include "common/util.h"
#include "common/command_line.h"
#include "tx_pool.h"
#include "transaction_tests.h"
namespace po = boost::program_options;
@ -155,6 +156,12 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_tx_output_is_not_txout_to_key);
GENERATE_AND_PLAY(gen_tx_signatures_are_invalid);
// Mempool
GENERATE_AND_PLAY(txpool_spend_key_public);
GENERATE_AND_PLAY(txpool_spend_key_all);
GENERATE_AND_PLAY(txpool_double_spend_norelay);
GENERATE_AND_PLAY(txpool_double_spend_local);
// Double spend
GENERATE_AND_PLAY(gen_double_spend_in_tx<false>);
GENERATE_AND_PLAY(gen_double_spend_in_tx<true>);

@ -46,7 +46,7 @@ bool gen_double_spend_in_different_chains::generate(std::vector<test_event_entry
{
INIT_DOUBLE_SPEND_TEST();
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, true);
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block);
MAKE_TX(events, tx_1, bob_account, alice_account, send_amount / 2 - TESTS_DEFAULT_FEE, blk_1);
events.pop_back();
MAKE_TX(events, tx_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1);
@ -96,3 +96,4 @@ bool gen_double_spend_in_different_chains::check_double_spend(cryptonote::core&
return true;
}

@ -147,7 +147,7 @@ bool gen_double_spend_in_tx<txs_keeped_by_block>::generate(std::vector<test_even
if (!construct_tx(bob_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_1, 0))
return false;
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block);
SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0);
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(tx_1);
DO_CALLBACK(events, "mark_invalid_block");
@ -163,7 +163,7 @@ bool gen_double_spend_in_the_same_block<txs_keeped_by_block>::generate(std::vect
INIT_DOUBLE_SPEND_TEST();
DO_CALLBACK(events, "mark_last_valid_block");
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block);
SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0);
MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1);
cryptonote::transaction tx_1 = txs_1.front();
@ -190,7 +190,7 @@ bool gen_double_spend_in_different_blocks<txs_keeped_by_block>::generate(std::ve
INIT_DOUBLE_SPEND_TEST();
DO_CALLBACK(events, "mark_last_valid_block");
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block);
SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0);
// Create two identical transactions, but don't push it to events list
MAKE_TX(events, tx_blk_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1);
@ -220,7 +220,7 @@ bool gen_double_spend_in_alt_chain_in_the_same_block<txs_keeped_by_block>::gener
{
INIT_DOUBLE_SPEND_TEST();
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block);
SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0);
// Main chain
MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_account);
@ -255,7 +255,7 @@ bool gen_double_spend_in_alt_chain_in_different_blocks<txs_keeped_by_block>::gen
{
INIT_DOUBLE_SPEND_TEST();
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block);
SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0);
// Main chain
MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_account);

@ -0,0 +1,561 @@
// Copyright (c) 2019, 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 "tx_pool.h"
#include <boost/chrono/chrono.hpp>
#include <boost/thread/thread_only.hpp>
#include <limits>
#include "string_tools.h"
#define INIT_MEMPOOL_TEST() \
uint64_t send_amount = 1000; \
uint64_t ts_start = 1338224400; \
GENERATE_ACCOUNT(miner_account); \
GENERATE_ACCOUNT(bob_account); \
MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); \
REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); \
txpool_base::txpool_base()
: test_chain_unit_base()
, m_broadcasted_tx_count(0)
, m_all_tx_count(0)
{
REGISTER_CALLBACK_METHOD(txpool_spend_key_public, increase_broadcasted_tx_count);
REGISTER_CALLBACK_METHOD(txpool_spend_key_public, increase_all_tx_count);
REGISTER_CALLBACK_METHOD(txpool_spend_key_public, check_txpool_spent_keys);
}
bool txpool_base::increase_broadcasted_tx_count(cryptonote::core& /*c*/, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/)
{
++m_broadcasted_tx_count;
return true;
}
bool txpool_base::increase_all_tx_count(cryptonote::core& /*c*/, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/)
{
++m_all_tx_count;
return true;
}
bool txpool_base::check_txpool_spent_keys(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events)
{
std::vector<cryptonote::tx_info> infos{};
std::vector<cryptonote::spent_key_image_info> key_images{};
if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images) || infos.size() != m_broadcasted_tx_count || key_images.size() != m_broadcasted_tx_count)
{
MERROR("Failed broadcasted spent keys retrieval - Expected Broadcasted Count: " << m_broadcasted_tx_count << " Actual Info Count: " << infos.size() << " Actual Key Image Count: " << key_images.size());
return false;
}
infos.clear();
key_images.clear();
if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images, false) || infos.size() != m_broadcasted_tx_count || key_images.size() != m_broadcasted_tx_count)
{
MERROR("Failed broadcasted spent keys retrieval - Expected Broadcasted Count: " << m_broadcasted_tx_count << " Actual Info Count: " << infos.size() << " Actual Key Image Count: " << key_images.size());
return false;
}
infos.clear();
key_images.clear();
if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images, true) || infos.size() != m_all_tx_count || key_images.size() != m_all_tx_count)
{
MERROR("Failed all spent keys retrieval - Expected All Count: " << m_all_tx_count << " Actual Info Count: " << infos.size() << " Actual Key Image Count: " << key_images.size());
return false;
}
return true;
}
bool txpool_spend_key_public::generate(std::vector<test_event_entry>& events) const
{
INIT_MEMPOOL_TEST();
DO_CALLBACK(events, "check_txpool_spent_keys");
MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0);
DO_CALLBACK(events, "increase_broadcasted_tx_count");
DO_CALLBACK(events, "increase_all_tx_count");
DO_CALLBACK(events, "check_txpool_spent_keys");
return true;
}
bool txpool_spend_key_all::generate(std::vector<test_event_entry>& events)
{
INIT_MEMPOOL_TEST();
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_do_not_relay);
DO_CALLBACK(events, "check_txpool_spent_keys");
MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0);
DO_CALLBACK(events, "increase_all_tx_count");
DO_CALLBACK(events, "check_txpool_spent_keys");
return true;
}
txpool_double_spend_base::txpool_double_spend_base()
: txpool_base()
, m_broadcasted_hashes()
, m_no_relay_hashes()
, m_all_hashes()
, m_no_new_index(0)
, m_new_timestamp_index(0)
, m_last_tx(crypto::hash{})
{
REGISTER_CALLBACK_METHOD(txpool_double_spend_base, mark_no_new);
REGISTER_CALLBACK_METHOD(txpool_double_spend_base, mark_timestamp_change);
REGISTER_CALLBACK_METHOD(txpool_double_spend_base, timestamp_change_pause);
REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_unchanged);
REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_new_broadcasted);
REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_new_hidden);
REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_new_no_relay);
}
bool txpool_double_spend_base::mark_no_new(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
m_no_new_index = ev_index + 1;
return true;
}
bool txpool_double_spend_base::mark_timestamp_change(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
m_new_timestamp_index = ev_index + 1;
return true;
}
bool txpool_double_spend_base::timestamp_change_pause(cryptonote::core& /*c*/, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/)
{
boost::this_thread::sleep_for(boost::chrono::seconds{1} + boost::chrono::milliseconds{100});
return true;
}
bool txpool_double_spend_base::check_changed(cryptonote::core& c, const size_t ev_index, relay_test condition)
{
const std::size_t public_hash_count = m_broadcasted_hashes.size();
const std::size_t all_hash_count = m_all_hashes.size();
const std::size_t new_broadcasted_hash_count = m_broadcasted_hashes.size() + unsigned(condition == relay_test::broadcasted);
const std::size_t new_all_hash_count = m_all_hashes.size() + unsigned(condition == relay_test::hidden) + unsigned(condition == relay_test::no_relay);
std::vector<crypto::hash> hashes{};
if (!c.get_pool_transaction_hashes(hashes))
{
MERROR("Failed to get broadcasted transaction pool hashes");
return false;
}
for (const crypto::hash& hash : hashes)
m_broadcasted_hashes.insert(hash);
if (new_broadcasted_hash_count != m_broadcasted_hashes.size())
{
MERROR("Expected " << new_broadcasted_hash_count << " broadcasted hashes but got " << m_broadcasted_hashes.size());
return false;
}
if (m_broadcasted_hashes.size() != c.get_pool_transactions_count())
{
MERROR("Expected " << m_broadcasted_hashes.size() << " broadcasted hashes but got " << c.get_pool_transactions_count());
return false;
}
hashes.clear();
if (!c.get_pool_transaction_hashes(hashes, false))
{
MERROR("Failed to get broadcasted transaction pool hashes");
return false;
}
for (const crypto::hash& hash : hashes)
m_all_hashes.insert(std::make_pair(hash, 0));
if (new_broadcasted_hash_count != m_broadcasted_hashes.size())
{
MERROR("Expected " << new_broadcasted_hash_count << " broadcasted hashes but got " << m_broadcasted_hashes.size());
return false;
}
hashes.clear();
if (!c.get_pool_transaction_hashes(hashes, true))
{
MERROR("Failed to get all transaction pool hashes");
return false;
}
for (const crypto::hash& hash : hashes)
m_all_hashes.insert(std::make_pair(hash, 0));
if (new_all_hash_count != m_all_hashes.size())
{
MERROR("Expected " << new_all_hash_count << " all hashes but got " << m_all_hashes.size());
return false;
}
if (condition == relay_test::no_relay)
{
if (!m_no_relay_hashes.insert(m_last_tx).second)
{
MERROR("Expected new no_relay tx but got a duplicate legacy tx");
return false;
}
for (const crypto::hash& hash : m_no_relay_hashes)
{
if (!c.pool_has_tx(hash))
{
MERROR("Expected public tx " << hash << " to be listed in pool");
return false;
}
}
}
// check receive time changes
{
std::vector<cryptonote::tx_info> infos{};
std::vector<cryptonote::spent_key_image_info> key_images{};
if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images, true) || infos.size() != m_all_hashes.size())
{
MERROR("Unable to retrieve all txpool metadata");
return false;
}
for (const cryptonote::tx_info& info : infos)
{
crypto::hash tx_hash;
if (!epee::string_tools::hex_to_pod(info.id_hash, tx_hash))
{
MERROR("Unable to convert tx_hash hex to binary");
return false;
}
const auto entry = m_all_hashes.find(tx_hash);
if (entry == m_all_hashes.end())
{
MERROR("Unable to find tx_hash in set of tracked hashes");
return false;
}
if (m_new_timestamp_index == ev_index && m_last_tx == tx_hash)
{
if (entry->second >= info.receive_time)
{
MERROR("Last relay time did not change as expected - last at " << entry->second << " and current at " << info.receive_time);
return false;
}
entry->second = info.receive_time;
}
else if (entry->second != info.receive_time)
{
MERROR("Last relayed time changed unexpectedly from " << entry->second << " to " << info.receive_time);
return false;
}
}
}
{
std::vector<cryptonote::transaction> txes{};
if (!c.get_pool_transactions(txes))
{
MERROR("Failed to get broadcasted transactions from pool");
return false;
}
hashes.clear();
for (const cryptonote::transaction& tx : txes)
hashes.push_back(cryptonote::get_transaction_hash(tx));
std::unordered_set<crypto::hash> public_hashes = m_broadcasted_hashes;
for (const crypto::hash& hash : hashes)
{
if (!c.pool_has_tx(hash))
{
MERROR("Expected broadcasted tx " << hash << " to be listed in pool");
return false;
}
if (!public_hashes.erase(hash))
{
MERROR("An unexected transaction was returned from the public pool");
return false;
}
}
if (!public_hashes.empty())
{
MERROR(public_hashes.size() << " transaction(s) were missing from the public pool");
return false;
}
}
{
std::vector<cryptonote::transaction> txes{};
if (!c.get_pool_transactions(txes, false))
{
MERROR("Failed to get broadcasted transactions from pool");
return false;
}
hashes.clear();
for (const cryptonote::transaction& tx : txes)
hashes.push_back(cryptonote::get_transaction_hash(tx));
std::unordered_set<crypto::hash> public_hashes = m_broadcasted_hashes;
for (const crypto::hash& hash : hashes)
{
if (!public_hashes.erase(hash))
{
MERROR("An unexected transaction was returned from the public pool");
return false;
}
}
if (!public_hashes.empty())
{
MERROR(public_hashes.size() << " transaction(s) were missing from the public pool");
return false;
}
}
{
std::vector<cryptonote::transaction> txes{};
if (!c.get_pool_transactions(txes, true))
{
MERROR("Failed to get all transactions from pool");
return false;
}
hashes.clear();
for (const cryptonote::transaction& tx : txes)
hashes.push_back(cryptonote::get_transaction_hash(tx));
std::unordered_map<crypto::hash, uint64_t> all_hashes = m_all_hashes;
for (const crypto::hash& hash : hashes)
{
if (!all_hashes.erase(hash))
{
MERROR("An unexected transaction was returned from the all pool");
return false;
}
}
if (!all_hashes.empty())
{
MERROR(m_broadcasted_hashes.size() << " transaction(s) were missing from the all pool");
return false;
}
}
{
std::vector<cryptonote::tx_backlog_entry> entries{};
if (!c.get_txpool_backlog(entries))
{
MERROR("Failed to get broadcasted txpool backlog");
return false;
}
if (m_broadcasted_hashes.size() != entries.size())
{
MERROR("Expected " << m_broadcasted_hashes.size() << " in the broadcasted txpool backlog but got " << entries.size());
return false;
}
}
for (const std::pair<crypto::hash, uint64_t>& hash : m_all_hashes)
{
cryptonote::blobdata tx_blob{};
if (!c.get_pool_transaction(hash.first, tx_blob, cryptonote::relay_category::all))
{
MERROR("Failed to retrieve tx expected to be in pool: " << hash.first);
return false;
}
}
{
std::unordered_map<crypto::hash, uint64_t> difference = m_all_hashes;
for (const crypto::hash& hash : m_broadcasted_hashes)
difference.erase(hash);
for (const crypto::hash& hash : m_no_relay_hashes)
difference.erase(hash);
for (const std::pair<crypto::hash, uint64_t>& hash : difference)
{
if (c.pool_has_tx(hash.first))
{
MERROR("Did not expect private/hidden tx " << hash.first << " to be listed in pool");
return false;
}
cryptonote::blobdata tx_blob{};
if (c.get_pool_transaction(hash.first, tx_blob, cryptonote::relay_category::broadcasted))
{
MERROR("Tx " << hash.first << " is not supposed to be in broadcasted pool");
return false;
}
if (!c.get_pool_transaction(hash.first, tx_blob, cryptonote::relay_category::all))
{
MERROR("Tx " << hash.first << " blob could not be retrieved from pool");
return false;
}
}
}
{
cryptonote::txpool_stats stats{};
if (!c.get_pool_transaction_stats(stats) || stats.txs_total != m_broadcasted_hashes.size())
{
MERROR("Expected broadcasted stats to list " << m_broadcasted_hashes.size() << " txes but got " << stats.txs_total);
return false;
}
if (!c.get_pool_transaction_stats(stats, false) || stats.txs_total != m_broadcasted_hashes.size())
{
MERROR("Expected broadcasted stats to list " << m_broadcasted_hashes.size() << " txes but got " << stats.txs_total);
return false;
}
if (!c.get_pool_transaction_stats(stats, true) || stats.txs_total != m_all_hashes.size())
{
MERROR("Expected all stats to list " << m_all_hashes.size() << " txes but got " << stats.txs_total);
return false;
}
}
{
std::vector<cryptonote::rpc::tx_in_pool> infos{};
cryptonote::rpc::key_images_with_tx_hashes key_images{};
if (!c.get_pool_for_rpc(infos, key_images) || infos.size() != m_broadcasted_hashes.size() || key_images.size() != m_broadcasted_hashes.size())
{
MERROR("Expected broadcasted rpc data to return " << m_broadcasted_hashes.size() << " but got " << infos.size() << " infos and " << key_images.size() << "key images");
return false;
}
}
return true;
}
bool txpool_double_spend_base::check_unchanged(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */)
{
return check_changed(c, ev_index, relay_test::no_change);
}
bool txpool_double_spend_base::check_new_broadcasted(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */)
{
return check_changed(c, ev_index, relay_test::broadcasted);
}
bool txpool_double_spend_base::check_new_hidden(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */)
{
return check_changed(c, ev_index, relay_test::hidden);
}
bool txpool_double_spend_base::check_new_no_relay(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */)
{
return check_changed(c, ev_index, relay_test::no_relay);
}
bool txpool_double_spend_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& tx)
{
m_last_tx = cryptonote::get_transaction_hash(tx);
if (m_no_new_index == event_idx)
return !tvc.m_verifivation_failed && !tx_added;
else
return !tvc.m_verifivation_failed && tx_added;
}
bool txpool_double_spend_norelay::generate(std::vector<test_event_entry>& events) const
{
INIT_MEMPOOL_TEST();
DO_CALLBACK(events, "check_txpool_spent_keys");
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_do_not_relay);
DO_CALLBACK(events, "mark_no_new");
MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0);
DO_CALLBACK(events, "increase_all_tx_count");
DO_CALLBACK(events, "check_txpool_spent_keys");
DO_CALLBACK(events, "mark_timestamp_change");
DO_CALLBACK(events, "check_new_no_relay");
DO_CALLBACK(events, "timestamp_change_pause");
DO_CALLBACK(events, "mark_no_new");
events.push_back(tx_0);
DO_CALLBACK(events, "check_txpool_spent_keys");
DO_CALLBACK(events, "check_unchanged");
SET_EVENT_VISITOR_SETT(events, 0);
DO_CALLBACK(events, "timestamp_change_pause");
DO_CALLBACK(events, "mark_no_new");
events.push_back(tx_0);
DO_CALLBACK(events, "check_txpool_spent_keys");
DO_CALLBACK(events, "check_unchanged");
// kepped by block currently does not change txpool status
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block);
DO_CALLBACK(events, "timestamp_change_pause");
DO_CALLBACK(events, "mark_no_new");
events.push_back(tx_0);
DO_CALLBACK(events, "check_txpool_spent_keys");
DO_CALLBACK(events, "check_unchanged");
return true;
}
bool txpool_double_spend_local::generate(std::vector<test_event_entry>& events) const
{
INIT_MEMPOOL_TEST();
DO_CALLBACK(events, "check_txpool_spent_keys");
SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_local_relay);
DO_CALLBACK(events, "mark_no_new");
MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0);
DO_CALLBACK(events, "increase_all_tx_count");
DO_CALLBACK(events, "check_txpool_spent_keys");
DO_CALLBACK(events, "mark_timestamp_change");
DO_CALLBACK(events, "check_new_hidden");
DO_CALLBACK(events, "timestamp_change_pause");
DO_CALLBACK(events, "mark_no_new");
events.push_back(tx_0);
DO_CALLBACK(events, "check_txpool_spent_keys");
DO_CALLBACK(events, "mark_timestamp_change");
DO_CALLBACK(events, "check_unchanged");
SET_EVENT_VISITOR_SETT(events, 0);
DO_CALLBACK(events, "timestamp_change_pause");
events.push_back(tx_0);
DO_CALLBACK(events, "increase_broadcasted_tx_count");
DO_CALLBACK(events, "check_txpool_spent_keys");
DO_CALLBACK(events, "mark_timestamp_change");
DO_CALLBACK(events, "check_new_broadcasted");
DO_CALLBACK(events, "timestamp_change_pause");
DO_CALLBACK(events, "mark_no_new");
events.push_back(tx_0);
DO_CALLBACK(events, "check_unchanged");
return true;
}

@ -0,0 +1,118 @@
// Copyright (c) 2019, 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 <unordered_map>
#include <unordered_set>
#include "chaingen.h"
#include "crypto/crypto.h"
enum class relay_test
{
no_change = 0, //!< No expected changes to the txpool
broadcasted, //!< A new block or fluff/flood tx is expected in txpool
hidden, //!< A new stem or local tx is expected in txpool
no_relay //!< A new no relay is expected in txpool
};
class txpool_base : public test_chain_unit_base
{
size_t m_broadcasted_tx_count;
size_t m_all_tx_count;
public:
txpool_base();
bool increase_broadcasted_tx_count(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events);
bool increase_all_tx_count(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events);
bool check_txpool_spent_keys(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events);
};
struct txpool_spend_key_public : txpool_base
{
txpool_spend_key_public() : txpool_base()
{}
bool generate(std::vector<test_event_entry>& events) const;
};
struct txpool_spend_key_all : txpool_base
{
txpool_spend_key_all() : txpool_base()
{}
bool generate(std::vector<test_event_entry>& events);
};
class txpool_double_spend_base : public txpool_base
{
std::unordered_set<crypto::hash> m_broadcasted_hashes;
std::unordered_set<crypto::hash> m_no_relay_hashes;
std::unordered_map<crypto::hash, uint64_t> m_all_hashes;
size_t m_no_new_index;
size_t m_new_timestamp_index;
crypto::hash m_last_tx;
bool check_changed(cryptonote::core& c, size_t ev_index, relay_test condition);
public:
txpool_double_spend_base();
bool mark_no_new(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
bool mark_timestamp_change(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
//! Pause for 1 second, so that `receive_time` for tx meta changes (tx hidden from public rpc being updated)
bool timestamp_change_pause(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
bool check_unchanged(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
bool check_new_broadcasted(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
bool check_new_hidden(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
bool check_new_no_relay(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/);
};
struct txpool_double_spend_norelay : txpool_double_spend_base
{
txpool_double_spend_norelay()
: txpool_double_spend_base()
{}
bool generate(std::vector<test_event_entry>& events) const;
};
struct txpool_double_spend_local : txpool_double_spend_base
{
txpool_double_spend_local()
: txpool_double_spend_base()
{}
bool generate(std::vector<test_event_entry>& events) const;
};

@ -32,6 +32,7 @@
#include "cryptonote_core/cryptonote_core.h"
#include "p2p/net_node.h"
#include "p2p/net_node.inl"
#include "cryptonote_core/i_core_events.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.inl"
@ -43,7 +44,7 @@ namespace cryptonote {
class blockchain_storage;
}
class test_core
class test_core : public cryptonote::i_core_events
{
public:
void on_synchronized(){}
@ -56,8 +57,8 @@ public:
bool get_stat_info(cryptonote::core_stat_info& st_inf) const {return true;}
bool have_block(const crypto::hash& id) const {return true;}
void get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=crypto::null_hash;}
bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; }
bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blob, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; }
bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, cryptonote::relay_method tx_relay, bool relayed) { return true; }
bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blob, std::vector<cryptonote::tx_verification_context>& tvc, cryptonote::relay_method tx_relay, bool relayed) { return true; }
bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true) { return true; }
void pause_mine(){}
void resume_mine(){}
@ -71,9 +72,9 @@ public:
bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; }
uint64_t get_target_blockchain_height() const { return 1; }
size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; }
virtual void on_transaction_relayed(const cryptonote::blobdata& tx) {}
virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, cryptonote::relay_method tx_relay) {}
cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; }
bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; }
bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob, cryptonote::relay_category tx_category) const { return false; }
bool pool_has_tx(const crypto::hash &txid) const { return false; }
bool get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata, cryptonote::block>>& blocks, std::vector<cryptonote::blobdata>& txs) const { return false; }
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::transaction>& txs, std::vector<crypto::hash>& missed_txs) const { return false; }

Loading…
Cancel
Save