diff options
Diffstat (limited to 'tests/unit_tests')
-rw-r--r-- | tests/unit_tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/unit_tests/account.cpp | 34 | ||||
-rw-r--r-- | tests/unit_tests/json_serialization.cpp | 36 | ||||
-rw-r--r-- | tests/unit_tests/json_serialization.h | 42 | ||||
-rw-r--r-- | tests/unit_tests/levin.cpp | 254 | ||||
-rw-r--r-- | tests/unit_tests/serialization.cpp | 40 | ||||
-rw-r--r-- | tests/unit_tests/tx_proof.cpp | 130 | ||||
-rw-r--r-- | tests/unit_tests/zmq_rpc.cpp | 722 |
8 files changed, 1243 insertions, 17 deletions
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index ef0477888..a5984b2c9 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -83,6 +83,7 @@ set(unit_tests_sources test_peerlist.cpp test_protocol_pack.cpp threadpool.cpp + tx_proof.cpp hardfork.cpp unbound.cpp uri.cpp @@ -109,6 +110,7 @@ target_link_libraries(unit_tests cryptonote_protocol cryptonote_core daemon_messages + daemon_rpc_server blockchain_db lmdb_lib rpc diff --git a/tests/unit_tests/account.cpp b/tests/unit_tests/account.cpp index 2ab2f893a..68bf4dce7 100644 --- a/tests/unit_tests/account.cpp +++ b/tests/unit_tests/account.cpp @@ -29,14 +29,30 @@ #include "gtest/gtest.h" #include "cryptonote_basic/account.h" +#include "ringct/rctOps.h" +// Tests in-memory encryption of account secret keys TEST(account, encrypt_keys) { + // Generate account keys and random multisig keys cryptonote::keypair recovery_key = cryptonote::keypair::generate(hw::get_device("default")); cryptonote::account_base account; crypto::secret_key key = account.generate(recovery_key.sec); + + const size_t n_multisig = 4; + std::vector<crypto::secret_key> multisig_keys; + multisig_keys.reserve(n_multisig); + multisig_keys.resize(0); + for (size_t i = 0; i < n_multisig; ++i) + { + multisig_keys.push_back(rct::rct2sk(rct::skGen())); + } + ASSERT_TRUE(account.make_multisig(account.get_keys().m_view_secret_key, account.get_keys().m_spend_secret_key, account.get_keys().m_account_address.m_spend_public_key, multisig_keys)); + const cryptonote::account_keys keys = account.get_keys(); + ASSERT_EQ(keys.m_multisig_keys.size(),n_multisig); + // Encrypt and decrypt keys ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); @@ -50,22 +66,40 @@ TEST(account, encrypt_keys) ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); account.decrypt_viewkey(chacha_key); ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); account.encrypt_viewkey(chacha_key); ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); + + account.decrypt_viewkey(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); + + account.encrypt_viewkey(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_NE(account.get_keys().m_multisig_keys, keys.m_multisig_keys); account.decrypt_keys(chacha_key); ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_EQ(account.get_keys().m_multisig_keys, keys.m_multisig_keys); } diff --git a/tests/unit_tests/json_serialization.cpp b/tests/unit_tests/json_serialization.cpp index 5873d0ab6..f76199e57 100644 --- a/tests/unit_tests/json_serialization.cpp +++ b/tests/unit_tests/json_serialization.cpp @@ -15,7 +15,7 @@ #include "serialization/json_object.h" -namespace +namespace test { cryptonote::transaction make_miner_transaction(cryptonote::account_public_address const& to) @@ -82,7 +82,10 @@ namespace return tx; } +} +namespace +{ template<typename T> T test_json(const T& value) { @@ -94,7 +97,7 @@ namespace rapidjson::Document doc; doc.Parse(reinterpret_cast<const char*>(buffer.data()), buffer.size()); - if (doc.HasParseError() || !doc.IsObject()) + if (doc.HasParseError()) { throw cryptonote::json::PARSE_FAIL(); } @@ -105,11 +108,26 @@ namespace } } // anonymous +TEST(JsonSerialization, VectorBytes) +{ + EXPECT_EQ(std::vector<std::uint8_t>{}, test_json(std::vector<std::uint8_t>{})); + EXPECT_EQ(std::vector<std::uint8_t>{0x00}, test_json(std::vector<std::uint8_t>{0x00})); +} + +TEST(JsonSerialization, InvalidVectorBytes) +{ + rapidjson::Document doc; + doc.SetString("1"); + + std::vector<std::uint8_t> out; + EXPECT_THROW(cryptonote::json::fromJsonValue(doc, out), cryptonote::json::BAD_INPUT); +} + TEST(JsonSerialization, MinerTransaction) { cryptonote::account_base acct; acct.generate(); - const auto miner_tx = make_miner_transaction(acct.get_keys().m_account_address); + const auto miner_tx = test::make_miner_transaction(acct.get_keys().m_account_address); crypto::hash tx_hash{}; ASSERT_TRUE(cryptonote::get_transaction_hash(miner_tx, tx_hash)); @@ -137,8 +155,8 @@ TEST(JsonSerialization, RegularTransaction) cryptonote::account_base acct2; acct2.generate(); - const auto miner_tx = make_miner_transaction(acct1.get_keys().m_account_address); - const auto tx = make_transaction( + const auto miner_tx = test::make_miner_transaction(acct1.get_keys().m_account_address); + const auto tx = test::make_transaction( acct1.get_keys(), {miner_tx}, {acct2.get_keys().m_account_address}, false, false ); @@ -168,8 +186,8 @@ TEST(JsonSerialization, RingctTransaction) cryptonote::account_base acct2; acct2.generate(); - const auto miner_tx = make_miner_transaction(acct1.get_keys().m_account_address); - const auto tx = make_transaction( + const auto miner_tx = test::make_miner_transaction(acct1.get_keys().m_account_address); + const auto tx = test::make_transaction( acct1.get_keys(), {miner_tx}, {acct2.get_keys().m_account_address}, true, false ); @@ -199,8 +217,8 @@ TEST(JsonSerialization, BulletproofTransaction) cryptonote::account_base acct2; acct2.generate(); - const auto miner_tx = make_miner_transaction(acct1.get_keys().m_account_address); - const auto tx = make_transaction( + const auto miner_tx = test::make_miner_transaction(acct1.get_keys().m_account_address); + const auto tx = test::make_transaction( acct1.get_keys(), {miner_tx}, {acct2.get_keys().m_account_address}, true, true ); diff --git a/tests/unit_tests/json_serialization.h b/tests/unit_tests/json_serialization.h new file mode 100644 index 000000000..2d8267261 --- /dev/null +++ b/tests/unit_tests/json_serialization.h @@ -0,0 +1,42 @@ +// Copyright (c) 2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +namespace test +{ + cryptonote::transaction make_miner_transaction(cryptonote::account_public_address const& to); + + cryptonote::transaction + make_transaction( + cryptonote::account_keys const& from, + std::vector<cryptonote::transaction> const& sources, + std::vector<cryptonote::account_public_address> const& destinations, + bool rct, + bool bulletproof); +} diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp index d9d273837..15563e764 100644 --- a/tests/unit_tests/levin.cpp +++ b/tests/unit_tests/levin.cpp @@ -680,6 +680,76 @@ TEST_F(levin_notify, local_without_padding) } } +TEST_F(levin_notify, forward_without_padding) +{ + cryptonote::levin::notify notifier = make_notifier(0, true, false); + + for (unsigned count = 0; count < 10; ++count) + add_connection(count % 2 == 0); + + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); + } + notifier.new_out_connection(); + io_service_.poll(); + + std::vector<cryptonote::blobdata> txs(2); + txs[0].resize(100, 'f'); + txs[1].resize(200, 'e'); + + std::vector<cryptonote::blobdata> sorted_txs = txs; + std::sort(sorted_txs.begin(), sorted_txs.end()); + + ASSERT_EQ(10u, contexts_.size()); + bool has_stemmed = false; + bool has_fluffed = false; + while (!has_stemmed || !has_fluffed) + { + auto context = contexts_.begin(); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), events_, cryptonote::relay_method::forward)); + + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + const bool is_stem = events_.has_stem_txes(); + EXPECT_EQ(txs, events_.take_relayed(is_stem ? cryptonote::relay_method::stem : cryptonote::relay_method::fluff)); + + if (!is_stem) + { + notifier.run_fluff(); + ASSERT_LT(0u, io_service_.poll()); + } + + std::size_t send_count = 0; + EXPECT_EQ(0u, context->process_send_queue()); + for (++context; context != contexts_.end(); ++context) + { + const std::size_t sent = context->process_send_queue(); + if (sent && is_stem) + EXPECT_EQ(1u, (context - contexts_.begin()) % 2); + send_count += sent; + } + + EXPECT_EQ(is_stem ? 1u : 9u, send_count); + ASSERT_EQ(is_stem ? 1u : 9u, receiver_.notified_size()); + for (unsigned count = 0; count < (is_stem ? 1u : 9u); ++count) + { + auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; + if (is_stem) + EXPECT_EQ(txs, notification.txs); + else + EXPECT_EQ(sorted_txs, notification.txs); + EXPECT_TRUE(notification._.empty()); + EXPECT_EQ(!is_stem, notification.dandelionpp_fluff); + } + + has_stemmed |= is_stem; + has_fluffed |= !is_stem; + notifier.run_epoch(); + } +} + TEST_F(levin_notify, block_without_padding) { cryptonote::levin::notify notifier = make_notifier(0, true, false); @@ -918,6 +988,73 @@ TEST_F(levin_notify, local_with_padding) } } +TEST_F(levin_notify, forward_with_padding) +{ + cryptonote::levin::notify notifier = make_notifier(0, true, true); + + for (unsigned count = 0; count < 10; ++count) + add_connection(count % 2 == 0); + + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); + } + notifier.new_out_connection(); + io_service_.poll(); + + std::vector<cryptonote::blobdata> txs(2); + txs[0].resize(100, 'e'); + txs[1].resize(200, 'f'); + + ASSERT_EQ(10u, contexts_.size()); + bool has_stemmed = false; + bool has_fluffed = false; + while (!has_stemmed || !has_fluffed) + { + auto context = contexts_.begin(); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), events_, cryptonote::relay_method::forward)); + + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + const bool is_stem = events_.has_stem_txes(); + EXPECT_EQ(txs, events_.take_relayed(is_stem ? cryptonote::relay_method::stem : cryptonote::relay_method::fluff)); + + if (!is_stem) + { + notifier.run_fluff(); + ASSERT_LT(0u, io_service_.poll()); + } + + std::size_t send_count = 0; + EXPECT_EQ(0u, context->process_send_queue()); + for (++context; context != contexts_.end(); ++context) + { + const std::size_t sent = context->process_send_queue(); + if (sent && is_stem) + { + EXPECT_EQ(1u, (context - contexts_.begin()) % 2); + EXPECT_FALSE(context->is_incoming()); + } + send_count += sent; + } + + EXPECT_EQ(is_stem ? 1u : 9u, send_count); + ASSERT_EQ(is_stem ? 1u : 9u, receiver_.notified_size()); + for (unsigned count = 0; count < (is_stem ? 1u : 9u); ++count) + { + auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; + EXPECT_EQ(txs, notification.txs); + EXPECT_FALSE(notification._.empty()); + EXPECT_EQ(!is_stem, notification.dandelionpp_fluff); + } + + has_stemmed |= is_stem; + has_fluffed |= !is_stem; + notifier.run_epoch(); + } +} + TEST_F(levin_notify, block_with_padding) { cryptonote::levin::notify notifier = make_notifier(0, true, true); @@ -1021,7 +1158,7 @@ TEST_F(levin_notify, private_fluff_without_padding) auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; EXPECT_EQ(txs, notification.txs); EXPECT_TRUE(notification._.empty()); - EXPECT_FALSE(notification.dandelionpp_fluff); + EXPECT_TRUE(notification.dandelionpp_fluff); } } } @@ -1057,7 +1194,7 @@ TEST_F(levin_notify, private_stem_without_padding) io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); - EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff)); + EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem)); EXPECT_EQ(0u, context->process_send_queue()); for (++context; context != contexts_.end(); ++context) @@ -1072,7 +1209,7 @@ TEST_F(levin_notify, private_stem_without_padding) auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; EXPECT_EQ(txs, notification.txs); EXPECT_TRUE(notification._.empty()); - EXPECT_FALSE(notification.dandelionpp_fluff); + EXPECT_TRUE(notification.dandelionpp_fluff); } } } @@ -1123,7 +1260,58 @@ TEST_F(levin_notify, private_local_without_padding) auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; EXPECT_EQ(txs, notification.txs); EXPECT_TRUE(notification._.empty()); - EXPECT_FALSE(notification.dandelionpp_fluff); + EXPECT_TRUE(notification.dandelionpp_fluff); + } + } +} + +TEST_F(levin_notify, private_forward_without_padding) +{ + // private mode always uses fluff but marked as stem + cryptonote::levin::notify notifier = make_notifier(0, false, false); + + for (unsigned count = 0; count < 10; ++count) + add_connection(count % 2 == 0); + + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); + } + notifier.new_out_connection(); + io_service_.poll(); + + std::vector<cryptonote::blobdata> txs(2); + txs[0].resize(100, 'e'); + txs[1].resize(200, 'f'); + + ASSERT_EQ(10u, contexts_.size()); + { + auto context = contexts_.begin(); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), events_, cryptonote::relay_method::forward)); + + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + notifier.run_fluff(); + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + + EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::forward)); + + EXPECT_EQ(0u, context->process_send_queue()); + for (++context; context != contexts_.end(); ++context) + { + const bool is_incoming = ((context - contexts_.begin()) % 2 == 0); + EXPECT_EQ(is_incoming ? 0u : 1u, context->process_send_queue()); + } + + ASSERT_EQ(5u, receiver_.notified_size()); + for (unsigned count = 0; count < 5; ++count) + { + auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; + EXPECT_EQ(txs, notification.txs); + EXPECT_TRUE(notification._.empty()); + EXPECT_TRUE(notification.dandelionpp_fluff); } } } @@ -1233,7 +1421,7 @@ TEST_F(levin_notify, private_fluff_with_padding) auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; EXPECT_EQ(txs, notification.txs); EXPECT_FALSE(notification._.empty()); - EXPECT_FALSE(notification.dandelionpp_fluff); + EXPECT_TRUE(notification.dandelionpp_fluff); } } } @@ -1268,7 +1456,7 @@ TEST_F(levin_notify, private_stem_with_padding) io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); - EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff)); + EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem)); EXPECT_EQ(0u, context->process_send_queue()); for (++context; context != contexts_.end(); ++context) @@ -1283,7 +1471,7 @@ TEST_F(levin_notify, private_stem_with_padding) auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; EXPECT_EQ(txs, notification.txs); EXPECT_FALSE(notification._.empty()); - EXPECT_FALSE(notification.dandelionpp_fluff); + EXPECT_TRUE(notification.dandelionpp_fluff); } } } @@ -1333,7 +1521,57 @@ TEST_F(levin_notify, private_local_with_padding) auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; EXPECT_EQ(txs, notification.txs); EXPECT_FALSE(notification._.empty()); - EXPECT_FALSE(notification.dandelionpp_fluff); + EXPECT_TRUE(notification.dandelionpp_fluff); + } + } +} + +TEST_F(levin_notify, private_forward_with_padding) +{ + cryptonote::levin::notify notifier = make_notifier(0, false, true); + + for (unsigned count = 0; count < 10; ++count) + add_connection(count % 2 == 0); + + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); + } + notifier.new_out_connection(); + io_service_.poll(); + + std::vector<cryptonote::blobdata> txs(2); + txs[0].resize(100, 'e'); + txs[1].resize(200, 'f'); + + ASSERT_EQ(10u, contexts_.size()); + { + auto context = contexts_.begin(); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), events_, cryptonote::relay_method::forward)); + + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + notifier.run_fluff(); + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + + EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::forward)); + + EXPECT_EQ(0u, context->process_send_queue()); + for (++context; context != contexts_.end(); ++context) + { + const bool is_incoming = ((context - contexts_.begin()) % 2 == 0); + EXPECT_EQ(is_incoming ? 0u : 1u, context->process_send_queue()); + } + + ASSERT_EQ(5u, receiver_.notified_size()); + for (unsigned count = 0; count < 5; ++count) + { + auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; + EXPECT_EQ(txs, notification.txs); + EXPECT_FALSE(notification._.empty()); + EXPECT_TRUE(notification.dandelionpp_fluff); } } } diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index ee205e666..b460559ff 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -616,6 +616,46 @@ TEST(Serialization, serializes_ringct_types) ASSERT_EQ(bp0, bp1); } +TEST(Serialization, key_encryption_transition) +{ + const cryptonote::network_type nettype = cryptonote::TESTNET; + tools::wallet2 w(nettype); + const boost::filesystem::path wallet_file = unit_test::data_dir / "wallet_9svHk1"; + const boost::filesystem::path key_file = unit_test::data_dir / "wallet_9svHk1.keys"; + const boost::filesystem::path temp_wallet_file = unit_test::data_dir / "wallet_9svHk1_temp"; + const boost::filesystem::path temp_key_file = unit_test::data_dir / "wallet_9svHk1_temp.keys"; + string password = "test"; + bool r = false; + + // Copy the original files for this test + boost::filesystem::copy(wallet_file,temp_wallet_file); + boost::filesystem::copy(key_file,temp_key_file); + + try + { + // Key transition + w.load(temp_wallet_file.string(), password); // legacy decryption method + ASSERT_TRUE(w.get_load_info().is_legacy_key_encryption); + const crypto::secret_key view_secret_key = w.get_account().get_keys().m_view_secret_key; + + w.rewrite(temp_wallet_file.string(), password); // transition to new key format + + w.load(temp_wallet_file.string(), password); // new decryption method + ASSERT_FALSE(w.get_load_info().is_legacy_key_encryption); + ASSERT_EQ(w.get_account().get_keys().m_view_secret_key,view_secret_key); + + r = true; + } + catch (const exception& e) + {} + + // Remove the temporary files + boost::filesystem::remove(temp_wallet_file); + boost::filesystem::remove(temp_key_file); + + ASSERT_TRUE(r); +} + TEST(Serialization, portability_wallet) { const cryptonote::network_type nettype = cryptonote::TESTNET; diff --git a/tests/unit_tests/tx_proof.cpp b/tests/unit_tests/tx_proof.cpp new file mode 100644 index 000000000..c5d06bc68 --- /dev/null +++ b/tests/unit_tests/tx_proof.cpp @@ -0,0 +1,130 @@ +// Copyright (c) 2018, 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 "crypto/crypto.h" +extern "C" { +#include "crypto/crypto-ops.h" +} +#include "crypto/hash.h" +#include <boost/algorithm/string.hpp> + +static inline unsigned char *operator &(crypto::ec_point &point) { + return &reinterpret_cast<unsigned char &>(point); + } + +static inline unsigned char *operator &(crypto::ec_scalar &scalar) { + return &reinterpret_cast<unsigned char &>(scalar); + } + +TEST(tx_proof, prove_verify_v2) +{ + crypto::secret_key r; + crypto::random32_unbiased(&r); + + // A = aG + // B = bG + crypto::secret_key a,b; + crypto::public_key A,B; + crypto::generate_keys(A, a, a, false); + crypto::generate_keys(B, b, b, false); + + // R_B = rB + crypto::public_key R_B; + ge_p3 B_p3; + ge_frombytes_vartime(&B_p3,&B); + ge_p2 R_B_p2; + ge_scalarmult(&R_B_p2, &unwrap(r), &B_p3); + ge_tobytes(&R_B, &R_B_p2); + + // R_G = rG + crypto::public_key R_G; + ge_frombytes_vartime(&B_p3,&B); + ge_p3 R_G_p3; + ge_scalarmult_base(&R_G_p3, &unwrap(r)); + ge_p3_tobytes(&R_G, &R_G_p3); + + // D = rA + crypto::public_key D; + ge_p3 A_p3; + ge_frombytes_vartime(&A_p3,&A); + ge_p2 D_p2; + ge_scalarmult(&D_p2, &unwrap(r), &A_p3); + ge_tobytes(&D, &D_p2); + + crypto::signature sig; + + // Message data + crypto::hash prefix_hash; + char data[] = "hash input"; + crypto::cn_fast_hash(data,sizeof(data)-1,prefix_hash); + + // Generate/verify valid v1 proof with standard address + crypto::generate_tx_proof_v1(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 1)); + + // Generate/verify valid v1 proof with subaddress + crypto::generate_tx_proof_v1(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 1)); + + // Generate/verify valid v2 proof with standard address + crypto::generate_tx_proof(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 2)); + + // Generate/verify valid v2 proof with subaddress + crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 2)); + + // Try to verify valid v2 proofs as v1 proof (bad) + crypto::generate_tx_proof(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 1)); + crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 1)); + + // Randomly-distributed test points + crypto::secret_key evil_a, evil_b, evil_d, evil_r; + crypto::public_key evil_A, evil_B, evil_D, evil_R; + crypto::generate_keys(evil_A, evil_a, evil_a, false); + crypto::generate_keys(evil_B, evil_b, evil_b, false); + crypto::generate_keys(evil_D, evil_d, evil_d, false); + crypto::generate_keys(evil_R, evil_r, evil_r, false); + + // Selectively choose bad point in v2 proof (bad) + crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, evil_R, A, B, D, sig, 2)); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, evil_A, B, D, sig, 2)); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, evil_B, D, sig, 2)); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, evil_D, sig, 2)); + + // Try to verify valid v1 proofs as v2 proof (bad) + crypto::generate_tx_proof_v1(prefix_hash, R_G, A, boost::none, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 2)); + crypto::generate_tx_proof_v1(prefix_hash, R_B, A, B, D, r, sig); + ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 2)); +} diff --git a/tests/unit_tests/zmq_rpc.cpp b/tests/unit_tests/zmq_rpc.cpp index af1f1608b..59759bed8 100644 --- a/tests/unit_tests/zmq_rpc.cpp +++ b/tests/unit_tests/zmq_rpc.cpp @@ -26,11 +26,25 @@ // 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 <boost/preprocessor/stringize.hpp> #include <gtest/gtest.h> +#include <rapidjson/document.h> +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/events.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "json_serialization.h" +#include "net/zmq.h" #include "rpc/message.h" +#include "rpc/zmq_pub.h" +#include "rpc/zmq_server.h" #include "serialization/json_object.h" +#define MASSERT(...) \ + if (!(__VA_ARGS__)) \ + return testing::AssertionFailure() << BOOST_PP_STRINGIZE(__VA_ARGS__) + TEST(ZmqFullMessage, InvalidRequest) { EXPECT_THROW( @@ -53,3 +67,711 @@ TEST(ZmqFullMessage, Request) cryptonote::rpc::FullMessage parsed{request, true}; EXPECT_STREQ("foo", parsed.getRequestType().c_str()); } + +namespace +{ + using published_json = std::pair<std::string, rapidjson::Document>; + + constexpr const char inproc_pub[] = "inproc://dummy_pub"; + + net::zmq::socket create_socket(void* ctx, const char* address) + { + net::zmq::socket sock{zmq_socket(ctx, ZMQ_PAIR)}; + if (!sock) + MONERO_ZMQ_THROW("failed to create socket"); + if (zmq_bind(sock.get(), address) != 0) + MONERO_ZMQ_THROW("socket bind failure"); + return sock; + } + + std::vector<std::string> get_messages(void* socket, int count = -1) + { + std::vector<std::string> out; + for ( ; count || count < 0; --count) + { + expect<std::string> next = net::zmq::receive(socket, (count < 0 ? ZMQ_DONTWAIT : 0)); + if (next == net::zmq::make_error_code(EAGAIN)) + return out; + out.push_back(std::move(*next)); + } + return out; + } + + std::vector<published_json> get_published(void* socket, int count = -1) + { + std::vector<published_json> out; + + const auto messages = get_messages(socket, count); + out.reserve(messages.size()); + + for (const std::string& message : messages) + { + const char* split = std::strchr(message.c_str(), ':'); + if (!split) + throw std::runtime_error{"Invalid ZMQ/Pub message"}; + + out.emplace_back(); + out.back().first = {message.c_str(), split}; + if (out.back().second.Parse(split + 1).HasParseError()) + throw std::runtime_error{"Failed to parse ZMQ/Pub message"}; + } + + return out; + } + + testing::AssertionResult compare_full_txpool(epee::span<const cryptonote::txpool_event> events, const published_json& pub) + { + MASSERT(pub.first == "json-full-txpool_add"); + MASSERT(pub.second.IsArray()); + MASSERT(pub.second.Size() <= events.size()); + + std::size_t i = 0; + for (const cryptonote::txpool_event& event : events) + { + MASSERT(i <= pub.second.Size()); + if (!event.res) + continue; + + cryptonote::transaction tx{}; + cryptonote::json::fromJsonValue(pub.second[i], tx); + + crypto::hash id{}; + MASSERT(cryptonote::get_transaction_hash(event.tx, id)); + MASSERT(cryptonote::get_transaction_hash(tx, id)); + MASSERT(event.tx.hash == tx.hash); + ++i; + } + return testing::AssertionSuccess(); + } + + testing::AssertionResult compare_minimal_txpool(epee::span<const cryptonote::txpool_event> events, const published_json& pub) + { + MASSERT(pub.first == "json-minimal-txpool_add"); + MASSERT(pub.second.IsArray()); + MASSERT(pub.second.Size() <= events.size()); + + std::size_t i = 0; + for (const cryptonote::txpool_event& event : events) + { + MASSERT(i <= pub.second.Size()); + if (!event.res) + continue; + + std::size_t actual_size = 0; + crypto::hash actual_id{}; + + MASSERT(pub.second[i].IsObject()); + GET_FROM_JSON_OBJECT(pub.second[i], actual_id, id); + GET_FROM_JSON_OBJECT(pub.second[i], actual_size, blob_size); + + std::size_t expected_size = 0; + crypto::hash expected_id{}; + MASSERT(cryptonote::get_transaction_hash(event.tx, expected_id, expected_size)); + MASSERT(expected_size == actual_size); + MASSERT(expected_id == actual_id); + ++i; + } + return testing::AssertionSuccess(); + } + + testing::AssertionResult compare_full_block(const epee::span<const cryptonote::block> expected, const published_json& pub) + { + MASSERT(pub.first == "json-full-chain_main"); + MASSERT(pub.second.IsArray()); + + std::vector<cryptonote::block> actual; + cryptonote::json::fromJsonValue(pub.second, actual); + + MASSERT(expected.size() == actual.size()); + + for (std::size_t i = 0; i < expected.size(); ++i) + { + crypto::hash id; + MASSERT(cryptonote::get_block_hash(expected[i], id)); + MASSERT(cryptonote::get_block_hash(actual[i], id)); + MASSERT(expected[i].hash == actual[i].hash); + } + + return testing::AssertionSuccess(); + } + + testing::AssertionResult compare_minimal_block(std::size_t height, const epee::span<const cryptonote::block> expected, const published_json& pub) + { + MASSERT(pub.first == "json-minimal-chain_main"); + MASSERT(pub.second.IsObject()); + MASSERT(!expected.empty()); + + std::size_t actual_height = 0; + crypto::hash actual_id{}; + crypto::hash actual_prev_id{}; + std::vector<crypto::hash> actual_ids{}; + GET_FROM_JSON_OBJECT(pub.second, actual_height, first_height); + GET_FROM_JSON_OBJECT(pub.second, actual_prev_id, first_prev_id); + GET_FROM_JSON_OBJECT(pub.second, actual_ids, ids); + + MASSERT(height == actual_height); + MASSERT(expected[0].prev_id == actual_prev_id); + MASSERT(expected.size() == actual_ids.size()); + + for (std::size_t i = 0; i < expected.size(); ++i) + { + crypto::hash id; + MASSERT(cryptonote::get_block_hash(expected[i], id)); + MASSERT(id == actual_ids[i]); + } + + return testing::AssertionSuccess(); + } + + struct zmq_base : public testing::Test + { + cryptonote::account_base acct; + + zmq_base() + : testing::Test(), acct() + { + acct.generate(); + } + + cryptonote::transaction make_miner_transaction() + { + return test::make_miner_transaction(acct.get_keys().m_account_address); + } + + cryptonote::transaction make_transaction(const std::vector<cryptonote::account_public_address>& destinations) + { + return test::make_transaction(acct.get_keys(), {make_miner_transaction()}, destinations, true, true); + } + + cryptonote::transaction make_transaction() + { + cryptonote::account_base temp_account; + temp_account.generate(); + return make_transaction({temp_account.get_keys().m_account_address}); + } + + cryptonote::block make_block() + { + cryptonote::block block{}; + block.major_version = 1; + block.minor_version = 3; + block.timestamp = 100; + block.prev_id = crypto::rand<crypto::hash>(); + block.nonce = 100; + block.miner_tx = make_miner_transaction(); + return block; + } + }; + + struct zmq_pub : public zmq_base + { + net::zmq::context ctx; + net::zmq::socket relay; + net::zmq::socket dummy_pub; + net::zmq::socket dummy_client; + std::shared_ptr<cryptonote::listener::zmq_pub> pub; + + zmq_pub() + : zmq_base(), + ctx(zmq_init(1)), + relay(create_socket(ctx.get(), cryptonote::listener::zmq_pub::relay_endpoint())), + dummy_pub(create_socket(ctx.get(), inproc_pub)), + dummy_client(zmq_socket(ctx.get(), ZMQ_PAIR)), + pub(std::make_shared<cryptonote::listener::zmq_pub>(ctx.get())) + { + if (!dummy_client) + MONERO_ZMQ_THROW("failed to create socket"); + if (zmq_connect(dummy_client.get(), inproc_pub) != 0) + MONERO_ZMQ_THROW("failed to connect to dummy pub"); + } + + virtual void TearDown() override final + { + EXPECT_EQ(0u, get_messages(relay.get()).size()); + EXPECT_EQ(0u, get_messages(dummy_client.get()).size()); + } + + template<std::size_t N> + bool sub_request(const char (&topic)[N]) + { + return pub->sub_request({topic, N - 1}); + } + }; + + struct dummy_handler final : cryptonote::rpc::RpcHandler + { + dummy_handler() + : cryptonote::rpc::RpcHandler() + {} + + virtual epee::byte_slice handle(std::string&& request) override final + { + throw std::logic_error{"not implemented"}; + } + }; + + struct zmq_server : public zmq_base + { + dummy_handler handler; + cryptonote::rpc::ZmqServer server; + std::shared_ptr<cryptonote::listener::zmq_pub> pub; + net::zmq::socket sub; + + zmq_server() + : zmq_base(), + handler(), + server(handler), + pub(), + sub() + { + void* ctx = server.init_rpc({}, {}); + if (!ctx) + throw std::runtime_error{"init_rpc failure"}; + + const std::string endpoint = inproc_pub; + pub = server.init_pub({std::addressof(endpoint), 1}); + if (!pub) + throw std::runtime_error{"failed to initiaze zmq/pub"}; + + sub.reset(zmq_socket(ctx, ZMQ_SUB)); + if (!sub) + MONERO_ZMQ_THROW("failed to create socket"); + if (zmq_connect(sub.get(), inproc_pub) != 0) + MONERO_ZMQ_THROW("failed to connect to dummy pub"); + + server.run(); + } + + virtual void TearDown() override final + { + EXPECT_EQ(0u, get_messages(sub.get()).size()); + sub.reset(); + pub.reset(); + server.stop(); + } + + template<std::size_t N> + void subscribe(const char (&topic)[N]) + { + if (zmq_setsockopt(sub.get(), ZMQ_SUBSCRIBE, topic, N - 1) != 0) + MONERO_ZMQ_THROW("failed to subscribe"); + } + }; +} + +TEST_F(zmq_pub, InvalidContext) +{ + EXPECT_THROW(cryptonote::listener::zmq_pub{nullptr}, std::logic_error); +} + +TEST_F(zmq_pub, NoBlocking) +{ + EXPECT_FALSE(pub->relay_to_pub(relay.get(), dummy_pub.get())); +} + +TEST_F(zmq_pub, DefaultDrop) +{ + EXPECT_EQ(0u, pub->send_txpool_add({{make_transaction(), {}, true}})); + + const cryptonote::block bl = make_block(); + EXPECT_EQ(0u,pub->send_chain_main(5, {std::addressof(bl), 1})); + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::chain_main{pub}(5, {std::addressof(bl), 1})); +} + +TEST_F(zmq_pub, JsonFullTxpool) +{ + static constexpr const char topic[] = "\1json-full-txpool_add"; + + ASSERT_TRUE(sub_request(topic)); + + std::vector<cryptonote::txpool_event> events + { + {make_transaction(), {}, true}, {make_transaction(), {}, true} + }; + + EXPECT_NO_THROW(pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_EQ(1u, pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); +} + +TEST_F(zmq_pub, JsonMinimalTxpool) +{ + static constexpr const char topic[] = "\1json-minimal-txpool_add"; + + ASSERT_TRUE(sub_request(topic)); + + std::vector<cryptonote::txpool_event> events + { + {make_transaction(), {}, true}, {make_transaction(), {}, true} + }; + + EXPECT_NO_THROW(pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_EQ(1u, pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); +} + +TEST_F(zmq_pub, JsonFullChain) +{ + static constexpr const char topic[] = "\1json-full-chain_main"; + + ASSERT_TRUE(sub_request(topic)); + + const std::array<cryptonote::block, 2> blocks{{make_block(), make_block()}}; + + EXPECT_EQ(1u, pub->send_chain_main(100, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_block(epee::to_span(blocks), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::chain_main{pub}(533, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_block(epee::to_span(blocks), pubs.front())); +} + +TEST_F(zmq_pub, JsonMinimalChain) +{ + static constexpr const char topic[] = "\1json-minimal-chain_main"; + + ASSERT_TRUE(sub_request(topic)); + + const std::array<cryptonote::block, 2> blocks{{make_block(), make_block()}}; + + EXPECT_EQ(1u, pub->send_chain_main(100, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_block(100, epee::to_span(blocks), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::chain_main{pub}(533, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_block(533, epee::to_span(blocks), pubs.front())); +} + +TEST_F(zmq_pub, JsonFullAll) +{ + static constexpr const char topic[] = "\1json-full"; + + ASSERT_TRUE(sub_request(topic)); + { + std::vector<cryptonote::txpool_event> events + { + {make_transaction(), {}, true}, {make_transaction(), {}, true} + }; + + EXPECT_EQ(1u, pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_NO_THROW(pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + } + { + const std::array<cryptonote::block, 2> blocks{{make_block(), make_block()}}; + + EXPECT_EQ(1u, pub->send_chain_main(100, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_block(epee::to_span(blocks), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::chain_main{pub}(533, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_full_block(epee::to_span(blocks), pubs.front())); + } +} + +TEST_F(zmq_pub, JsonMinimalAll) +{ + static constexpr const char topic[] = "\1json-minimal"; + + ASSERT_TRUE(sub_request(topic)); + + { + std::vector<cryptonote::txpool_event> events + { + {make_transaction(), {}, true}, {make_transaction(), {}, true} + }; + + EXPECT_EQ(1u, pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_NO_THROW(pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + + events.at(0).res = false; + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + } + { + const std::array<cryptonote::block, 2> blocks{{make_block(), make_block()}}; + + EXPECT_EQ(1u, pub->send_chain_main(100, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_block(100, epee::to_span(blocks), pubs.front())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::chain_main{pub}(533, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(1u, pubs.size()); + ASSERT_LE(1u, pubs.size()); + EXPECT_TRUE(compare_minimal_block(533, epee::to_span(blocks), pubs.front())); + } +} + +TEST_F(zmq_pub, JsonAll) +{ + static constexpr const char topic[] = "\1json"; + + ASSERT_TRUE(sub_request(topic)); + + { + std::vector<cryptonote::txpool_event> events + { + {make_transaction(), {}, true}, {make_transaction(), {}, true} + }; + + EXPECT_EQ(1u, pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(2u, pubs.size()); + ASSERT_LE(2u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.back())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(2u, pubs.size()); + ASSERT_LE(2u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.back())); + + events.at(0).res = false; + EXPECT_EQ(1u, pub->send_txpool_add(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(2u, pubs.size()); + ASSERT_LE(2u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.back())); + + events.at(0).res = false; + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(events)); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(2u, pubs.size()); + ASSERT_LE(2u, pubs.size()); + EXPECT_TRUE(compare_full_txpool(epee::to_span(events), pubs.front())); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.back())); + } + { + const std::array<cryptonote::block, 1> blocks{{make_block()}}; + + EXPECT_EQ(2u, pub->send_chain_main(100, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + auto pubs = get_published(dummy_client.get()); + EXPECT_EQ(2u, pubs.size()); + ASSERT_LE(2u, pubs.size()); + EXPECT_TRUE(compare_full_block(epee::to_span(blocks), pubs.front())); + EXPECT_TRUE(compare_minimal_block(100, epee::to_span(blocks), pubs.back())); + + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::chain_main{pub}(533, epee::to_span(blocks))); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get())); + + pubs = get_published(dummy_client.get()); + EXPECT_EQ(2u, pubs.size()); + ASSERT_LE(2u, pubs.size()); + EXPECT_TRUE(compare_full_block(epee::to_span(blocks), pubs.front())); + EXPECT_TRUE(compare_minimal_block(533, epee::to_span(blocks), pubs.back())); + } +} + +TEST_F(zmq_pub, JsonChainWeakPtrSkip) +{ + static constexpr const char topic[] = "\1json"; + + ASSERT_TRUE(sub_request(topic)); + + const std::array<cryptonote::block, 1> blocks{{make_block()}}; + + pub.reset(); + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::chain_main{pub}(533, epee::to_span(blocks))); +} + +TEST_F(zmq_pub, JsonTxpoolWeakPtrSkip) +{ + static constexpr const char topic[] = "\1json"; + + ASSERT_TRUE(sub_request(topic)); + + std::vector<cryptonote::txpool_event> events + { + {make_transaction(), {}, true}, {make_transaction(), {}, true} + }; + + pub.reset(); + EXPECT_NO_THROW(cryptonote::listener::zmq_pub::txpool_add{pub}(std::move(events))); +} + +TEST_F(zmq_server, pub) +{ + subscribe("json-minimal"); + + std::vector<cryptonote::txpool_event> events + { + {make_transaction(), {}, true}, {make_transaction(), {}, true} + }; + + const std::array<cryptonote::block, 1> blocks{{make_block()}}; + + ASSERT_EQ(1u, pub->send_txpool_add(events)); + ASSERT_EQ(1u, pub->send_chain_main(200, epee::to_span(blocks))); + + auto pubs = get_published(sub.get(), 2); + EXPECT_EQ(2u, pubs.size()); + ASSERT_LE(2u, pubs.size()); + EXPECT_TRUE(compare_minimal_txpool(epee::to_span(events), pubs.front())); + EXPECT_TRUE(compare_minimal_block(200, epee::to_span(blocks), pubs.back())); +} |