diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/core_proxy/core_proxy.cpp | 8 | ||||
-rw-r--r-- | tests/core_proxy/core_proxy.h | 11 | ||||
-rw-r--r-- | tests/core_tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/core_tests/chaingen.cpp | 188 | ||||
-rw-r--r-- | tests/core_tests/chaingen.h | 55 | ||||
-rw-r--r-- | tests/core_tests/chaingen_main.cpp | 7 | ||||
-rw-r--r-- | tests/core_tests/double_spend.cpp | 3 | ||||
-rw-r--r-- | tests/core_tests/double_spend.inl | 10 | ||||
-rw-r--r-- | tests/core_tests/tx_pool.cpp | 561 | ||||
-rw-r--r-- | tests/core_tests/tx_pool.h | 118 | ||||
-rw-r--r-- | tests/core_tests/wallet_tools.cpp | 3 | ||||
-rwxr-xr-x | tests/functional_tests/mining.py | 12 | ||||
-rw-r--r-- | tests/unit_tests/CMakeLists.txt | 3 | ||||
-rw-r--r-- | tests/unit_tests/node_server.cpp | 11 | ||||
-rw-r--r-- | tests/unit_tests/rpc_version_str.cpp | 49 |
15 files changed, 981 insertions, 60 deletions
diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index db5b7797a..9463d14ce 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -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; } diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 0e41a2be0..8732c85cc 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -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; } diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index f93cbf3ad..42e76e613 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -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 diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 35e449e10..c38ea614c 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -47,6 +47,12 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/miner.h" +#include "blockchain_db/blockchain_db.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_core/tx_pool.h" +#include "cryptonote_core/blockchain.h" +#include "blockchain_db/testdb.h" + #include "chaingen.h" #include "device/device.hpp" using namespace std; @@ -55,6 +61,126 @@ using namespace epee; using namespace crypto; using namespace cryptonote; +namespace +{ + /** + * Dummy TestDB to store height -> (block, hash) information + * for the use only in the test_generator::fill_nonce() function, + * which requires blockchain object to correctly compute PoW on HF12+ blocks + * as the mining function requires it to obtain a valid seedhash. + */ + class TestDB: public cryptonote::BaseTestDB + { + private: + struct block_t + { + cryptonote::block bl; + crypto::hash hash; + }; + + public: + TestDB() { m_open = true; } + + virtual void add_block( const cryptonote::block& blk + , size_t block_weight + , uint64_t long_term_block_weight + , const cryptonote::difficulty_type& cumulative_difficulty + , const uint64_t& coins_generated + , uint64_t num_rct_outs + , const crypto::hash& blk_hash + ) override + { + blocks.push_back({blk, blk_hash}); + } + + virtual uint64_t height() const override { return blocks.empty() ? 0 : blocks.size() - 1; } + + // Required for randomx + virtual crypto::hash get_block_hash_from_height(const uint64_t &height) const override + { + if (height < blocks.size()) + { + MDEBUG("Get hash for block height: " << height << " hash: " << blocks[height].hash); + return blocks[height].hash; + } + + MDEBUG("Get hash for block height: " << height << " zero-hash"); + crypto::hash hash = crypto::null_hash; + *(uint64_t*)&hash = height; + return hash; + } + + virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override + { + const uint64_t h = height(); + if (block_height != nullptr) + { + *block_height = h; + } + + return get_block_hash_from_height(h); + } + + virtual cryptonote::block get_top_block() const override + { + if (blocks.empty()) + { + cryptonote::block b; + return b; + } + + return blocks[blocks.size()-1].bl; + } + + virtual void pop_block(cryptonote::block &blk, std::vector<cryptonote::transaction> &txs) override { if (!blocks.empty()) blocks.pop_back(); } + virtual void set_hard_fork_version(uint64_t height, uint8_t version) override { if (height >= hf.size()) hf.resize(height + 1); hf[height] = version; } + virtual uint8_t get_hard_fork_version(uint64_t height) const override { if (height >= hf.size()) return 255; return hf[height]; } + + private: + std::vector<block_t> blocks; + std::vector<uint8_t> hf; + }; + +} + +static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector<test_event_entry> & events, cryptonote::network_type nettype) +{ + std::unique_ptr<cryptonote::Blockchain> bc; + v_hardforks_t hardforks; + cryptonote::test_options test_options_tmp{nullptr, 0}; + const cryptonote::test_options * test_options = &test_options_tmp; + if (!extract_hard_forks(events, hardforks)) + { + MDEBUG("Extracting hard-forks from blocks"); + extract_hard_forks_from_blocks(events, hardforks); + } + + hardforks.push_back(std::make_pair((uint8_t)0, (uint64_t)0)); // terminator + test_options_tmp.hard_forks = hardforks.data(); + test_options = &test_options_tmp; + + cryptonote::tx_memory_pool txpool(*bc); + bc.reset(new cryptonote::Blockchain(txpool)); + + cryptonote::Blockchain *blockchain = bc.get(); + auto bdb = new TestDB(); + + BOOST_FOREACH(const test_event_entry &ev, events) + { + if (typeid(block) != ev.type()) + { + continue; + } + + const block *blk = &boost::get<block>(ev); + auto blk_hash = get_block_hash(*blk); + bdb->add_block(*blk, 1, 1, 1, 0, 0, blk_hash); + } + + bool r = blockchain->init(bdb, nettype, true, test_options, 2, nullptr); + CHECK_AND_ASSERT_THROW_MES(r, "could not init blockchain from events"); + return bc; +} void test_generator::get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const { @@ -184,13 +310,7 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co //blk.tree_root_hash = get_tx_tree_hash(blk); - // Nonce search... - blk.nonce = 0; - while (!miner::find_nonce_for_given_block([](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash){ - return cryptonote::get_block_longhash(NULL, b, hash, height, threads); - }, blk, get_test_difficulty(hf_ver), height)) - blk.timestamp++; - + fill_nonce(blk, get_test_difficulty(hf_ver), height); add_block(blk, txs_weight, block_weights, already_generated_coins, hf_ver ? hf_ver.get() : 1); return true; @@ -268,6 +388,26 @@ bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const c return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, transaction(), tx_hashes, txs_weight); } +void test_generator::fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) +{ + const cryptonote::Blockchain *blockchain = nullptr; + std::unique_ptr<cryptonote::Blockchain> bc; + + if (blk.major_version >= RX_BLOCK_VERSION) + { + CHECK_AND_ASSERT_THROW_MES(m_events != nullptr, "events not set, cannot compute valid RandomX PoW"); + bc = init_blockchain(*m_events, m_nettype); + blockchain = bc.get(); + } + + blk.nonce = 0; + while (!miner::find_nonce_for_given_block([blockchain](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash){ + return cryptonote::get_block_longhash(blockchain, b, hash, height, threads); + }, blk, diffic, height)) { + blk.timestamp++; + } +} + namespace { uint64_t get_inputs_amount(const vector<tx_source_entry> &s) @@ -796,15 +936,6 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event fill_tx_sources_and_destinations(events, blk_head, from, to.get_keys().m_account_address, amount, fee, nmix, sources, destinations); } -void fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) -{ - blk.nonce = 0; - while (!miner::find_nonce_for_given_block([](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash){ - return cryptonote::get_block_longhash(NULL, b, hash, height, threads); - }, blk, diffic, height)) - blk.timestamp++; -} - cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr, uint64_t amount) { tx_destination_entry de; @@ -983,6 +1114,31 @@ bool extract_hard_forks(const std::vector<test_event_entry>& events, v_hardforks return !hard_forks.empty(); } +bool extract_hard_forks_from_blocks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks) +{ + int hf = -1; + int64_t height = 0; + + for(auto & ev : events) + { + if (typeid(block) != ev.type()) + { + continue; + } + + const block *blk = &boost::get<block>(ev); + if (blk->major_version != hf) + { + hf = blk->major_version; + hard_forks.push_back(std::make_pair(blk->major_version, (uint64_t)height)); + } + + height += 1; + } + + return !hard_forks.empty(); +} + void get_confirmed_txs(const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) { std::unordered_set<crypto::hash> confirmed_hashes; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index fa27deee4..12effffcf 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -54,6 +54,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" @@ -109,17 +110,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) { } @@ -129,8 +130,7 @@ private: template<class Archive> void serialize(Archive & ar, const unsigned int /*version*/) { - ar & valid_mask; - ar & txs_keeped_by_block; + ar & mask; } }; @@ -228,8 +228,8 @@ public: bf_hf_version= 1 << 8 }; - test_generator() {} - test_generator(const test_generator &other): m_blocks_info(other.m_blocks_info) {} + test_generator(): m_events(nullptr) {} + test_generator(const test_generator &other): m_blocks_info(other.m_blocks_info), m_events(other.m_events), m_nettype(other.m_nettype) {} void get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const; void get_last_n_block_weights(std::vector<size_t>& block_weights, const crypto::hash& head, size_t n) const; uint64_t get_already_generated_coins(const crypto::hash& blk_id) const; @@ -254,9 +254,14 @@ public: uint8_t hf_version = 1); bool construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, const cryptonote::account_base& miner_acc, const std::vector<crypto::hash>& tx_hashes, size_t txs_size); + void fill_nonce(cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); + void set_events(const std::vector<test_event_entry> * events) { m_events = events; } + void set_network_type(const cryptonote::network_type nettype) { m_nettype = nettype; } private: std::unordered_map<crypto::hash, block_info> m_blocks_info; + const std::vector<test_event_entry> * m_events; + cryptonote::network_type m_nettype; friend class boost::serialization::access; @@ -408,7 +413,6 @@ cryptonote::account_public_address get_address(const cryptonote::tx_destination_ inline cryptonote::difficulty_type get_test_difficulty(const boost::optional<uint8_t>& hf_ver=boost::none) {return !hf_ver || hf_ver.get() <= 1 ? 1 : 2;} inline uint64_t current_difficulty_window(const boost::optional<uint8_t>& hf_ver=boost::none){ return !hf_ver || hf_ver.get() <= 1 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; } -void fill_nonce(cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr=false, uint64_t amount=0); std::vector<cryptonote::tx_destination_entry> build_dsts(const var_addr_t& to1, bool sub1=false, uint64_t am1=0); @@ -491,6 +495,7 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx); bool extract_hard_forks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks); +bool extract_hard_forks_from_blocks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks); /************************************************************************/ /* */ @@ -504,7 +509,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) @@ -512,7 +517,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) { } @@ -531,9 +536,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; @@ -545,7 +562,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"); @@ -565,7 +582,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"); @@ -645,7 +662,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; @@ -957,7 +974,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) \ { \ diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 8406f416f..23f3170b8 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -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>); diff --git a/tests/core_tests/double_spend.cpp b/tests/core_tests/double_spend.cpp index afd212b27..4aa12c8e0 100644 --- a/tests/core_tests/double_spend.cpp +++ b/tests/core_tests/double_spend.cpp @@ -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; } + diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl index 600899b6f..684c9c4de 100644 --- a/tests/core_tests/double_spend.inl +++ b/tests/core_tests/double_spend.inl @@ -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); diff --git a/tests/core_tests/tx_pool.cpp b/tests/core_tests/tx_pool.cpp new file mode 100644 index 000000000..537015dca --- /dev/null +++ b/tests/core_tests/tx_pool.cpp @@ -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; +} + diff --git a/tests/core_tests/tx_pool.h b/tests/core_tests/tx_pool.h new file mode 100644 index 000000000..996c76698 --- /dev/null +++ b/tests/core_tests/tx_pool.h @@ -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; +}; diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index 21a9455c0..fdc4753f9 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -10,9 +10,6 @@ using namespace epee; using namespace crypto; using namespace cryptonote; -// Shared random generator -static std::default_random_engine RND(crypto::rand<unsigned>()); - void wallet_accessor_test::set_account(tools::wallet2 * wallet, cryptonote::account_base& account) { wallet->clear(); diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index ad646417e..d067c25e1 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -170,5 +170,15 @@ class MiningTest(): assert res.hash == block_hash +class Guard: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + daemon = Daemon() + try: daemon.stop_mining() + except: pass + if __name__ == '__main__': - MiningTest().run_test() + with Guard() as guard: + MiningTest().run_test() diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 96825f54f..cda25dfc9 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -92,7 +92,8 @@ set(unit_tests_sources ringdb.cpp wipeable_string.cpp is_hdd.cpp - aligned.cpp) + aligned.cpp + rpc_version_str.cpp) set(unit_tests_headers unit_tests_utils.h) diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index 5f91fc6d4..c92f70b97 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -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; } diff --git a/tests/unit_tests/rpc_version_str.cpp b/tests/unit_tests/rpc_version_str.cpp new file mode 100644 index 000000000..5dce60465 --- /dev/null +++ b/tests/unit_tests/rpc_version_str.cpp @@ -0,0 +1,49 @@ +// 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 "gtest/gtest.h" + +#include "rpc/rpc_version_str.h" +#include "version.h" + +TEST(rpc, is_version_string_valid) +{ + using namespace cryptonote::rpc; + ASSERT_TRUE(is_version_string_valid(MONERO_VERSION)); + ASSERT_TRUE(is_version_string_valid("0.14.1.2")); + ASSERT_TRUE(is_version_string_valid("0.15.0.0-release")); + ASSERT_TRUE(is_version_string_valid("0.15.0.0-fe3f6a3e6")); + + ASSERT_FALSE(is_version_string_valid("")); + ASSERT_FALSE(is_version_string_valid("invalid")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-invalid")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-release0")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-release ")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-fe3f6a3e60")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-fe3f6a3e6 ")); +} |