aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt37
-rw-r--r--tests/benchmark.cpp437
-rw-r--r--tests/benchmark.h.in5
-rwxr-xr-xtests/functional_tests/proofs.py39
-rw-r--r--tests/unit_tests/CMakeLists.txt2
-rw-r--r--tests/unit_tests/json_serialization.cpp19
-rw-r--r--tests/unit_tests/json_serialization.h42
-rw-r--r--tests/unit_tests/levin.cpp254
-rw-r--r--tests/unit_tests/tx_proof.cpp130
-rw-r--r--tests/unit_tests/zmq_rpc.cpp722
10 files changed, 1666 insertions, 21 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ed5a3b9e3..c601b93ed 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -28,6 +28,8 @@
#
# Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+set(MONERO_WALLET_CRYPTO_BENCH "auto" CACHE STRING "Select wallet crypto libraries for benchmarking")
+
# The docs say this only affects grouping in IDEs
set(folder "tests")
set(TEST_DATA_DIR "${CMAKE_CURRENT_LIST_DIR}/data")
@@ -118,6 +120,41 @@ add_test(
NAME hash-target
COMMAND hash-target-tests)
+#
+# Configure wallet crypto benchmark
+#
+if (${MONERO_WALLET_CRYPTO_BENCH} STREQUAL "auto")
+ set(MONERO_WALLET_CRYPTO_BENCH "cn")
+ monero_crypto_autodetect(AVAILABLE BEST)
+ if (DEFINED AVAILABLE)
+ list(APPEND MONERO_WALLET_CRYPTO_BENCH ${AVAILABLE})
+ endif ()
+ message("Wallet crypto bench is using ${MONERO_WALLET_CRYPTO_BENCH}")
+endif ()
+
+list(REMOVE_DUPLICATES MONERO_WALLET_CRYPTO_BENCH)
+list(REMOVE_ITEM MONERO_WALLET_CRYPTO_BENCH "cn") # always used for comparison
+set(MONERO_WALLET_CRYPTO_BENCH_NAMES "(cn)")
+foreach(BENCH IN LISTS MONERO_WALLET_CRYPTO_BENCH)
+ monero_crypto_valid(${BENCH} VALID)
+ if (NOT VALID)
+ message(FATAL_ERROR "Invalid MONERO_WALLET_CRYPTO_BENCH option ${BENCH}")
+ endif ()
+
+ monero_crypto_get_target(${BENCH} BENCH_LIBRARY)
+ list(APPEND BENCH_OBJECTS $<TARGET_OBJECTS:${BENCH_LIBRARY}>)
+
+ monero_crypto_get_namespace(${BENCH} BENCH_NAMESPACE)
+ set(MONERO_WALLET_CRYPTO_BENCH_NAMES "${MONERO_WALLET_CRYPTO_BENCH_NAMES}(${BENCH_NAMESPACE})")
+endforeach ()
+
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/benchmark.h.in" "${MONERO_GENERATED_HEADERS_DIR}/tests/benchmark.h")
+add_executable(monero-wallet-crypto-bench benchmark.cpp ${BENCH_OBJECTS})
+target_link_libraries(monero-wallet-crypto-bench cncrypto)
+
+add_test(NAME wallet-crypto-bench COMMAND monero-wallet-crypto-bench)
+
+
set(enabled_tests
core_tests
difficulty
diff --git a/tests/benchmark.cpp b/tests/benchmark.cpp
new file mode 100644
index 000000000..0461f4c11
--- /dev/null
+++ b/tests/benchmark.cpp
@@ -0,0 +1,437 @@
+// 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.
+
+#include "tests/benchmark.h"
+
+#include <boost/fusion/adapted/std_tuple.hpp>
+#include <boost/fusion/algorithm/iteration/fold.hpp>
+#include <boost/preprocessor/seq/enum.hpp>
+#include <boost/preprocessor/seq/for_each.hpp>
+#include <boost/preprocessor/seq/seq.hpp>
+#include <boost/preprocessor/seq.hpp>
+#include <boost/preprocessor/stringize.hpp>
+#include <boost/spirit/include/karma_char.hpp>
+#include <boost/spirit/include/karma_format.hpp>
+#include <boost/spirit/include/karma_repeat.hpp>
+#include <boost/spirit/include/karma_right_alignment.hpp>
+#include <boost/spirit/include/karma_sequence.hpp>
+#include <boost/spirit/include/karma_string.hpp>
+#include <boost/spirit/include/karma_uint.hpp>
+#include <boost/spirit/include/qi_char.hpp>
+#include <boost/spirit/include/qi_list.hpp>
+#include <boost/spirit/include/qi_parse.hpp>
+#include <boost/spirit/include/qi_uint.hpp>
+#include <chrono>
+#include <cstring>
+#include <functional>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "crypto/crypto.h"
+#include "cryptonote_basic/cryptonote_basic.h"
+#include "monero/crypto/amd64-64-24k.h"
+#include "monero/crypto/amd64-51-30k.h"
+
+#define CHECK(...) \
+ if(!( __VA_ARGS__ )) \
+ throw std::runtime_error{ \
+ "TEST FAILED (line " \
+ BOOST_PP_STRINGIZE( __LINE__ ) \
+ "): " \
+ BOOST_PP_STRINGIZE( __VA_ARGS__ ) \
+ }
+
+//! Define function that forwards arguments to `crypto::func`.
+#define FORWARD_FUNCTION(func) \
+ template<typename... T> \
+ static bool func (T&&... args) \
+ { \
+ return ::crypto:: func (std::forward<T>(args)...); \
+ }
+
+#define CRYPTO_FUNCTION(library, func) \
+ BOOST_PP_CAT(BOOST_PP_CAT(monero_crypto_, library), func)
+
+#define CRYPTO_BENCHMARK(r, _, library) \
+ struct library \
+ { \
+ static constexpr const char* name() noexcept { return BOOST_PP_STRINGIZE(library); } \
+ static bool generate_key_derivation(const ::crypto::public_key &tx_pub, const ::crypto::secret_key &view_sec, ::crypto::key_derivation &out) \
+ { \
+ return CRYPTO_FUNCTION(library, _generate_key_derivation) (out.data, tx_pub.data, view_sec.data) == 0; \
+ } \
+ static bool derive_subaddress_public_key(const ::crypto::public_key &spend_pub, const ::crypto::key_derivation &d, std::size_t index, ::crypto::public_key &out) \
+ { \
+ ::crypto::ec_scalar scalar; \
+ ::crypto::derivation_to_scalar(d, index, scalar); \
+ return CRYPTO_FUNCTION(library, _generate_subaddress_public_key) (out.data, spend_pub.data, scalar.data) == 0; \
+ } \
+ };
+
+
+namespace
+{
+ //! Default number of iterations for benchmark timing.
+ constexpr const unsigned default_iterations = 1000;
+
+ //! \return Byte compare two objects of `T`.
+ template<typename T>
+ bool compare(const T& lhs, const T& rhs) noexcept
+ {
+ static_assert(!epee::has_padding<T>(), "type might have padding");
+ return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(T)) == 0;
+ }
+
+ //! Benchmark default monero crypto library - a re-arranged ref10 implementation.
+ struct cn
+ {
+ static constexpr const char* name() noexcept { return "cn"; }
+ FORWARD_FUNCTION( generate_key_derivation );
+ FORWARD_FUNCTION( derive_subaddress_public_key );
+ };
+
+ // Define functions for every library except for `cn` which is the head library.
+ BOOST_PP_SEQ_FOR_EACH(CRYPTO_BENCHMARK, _, BOOST_PP_SEQ_TAIL(BENCHMARK_LIBRARIES));
+
+ // All enabled benchmark libraries
+ using enabled_libraries = std::tuple<BOOST_PP_SEQ_ENUM(BENCHMARK_LIBRARIES)>;
+
+
+ //! Callable that runs a benchmark against all enabled libraries
+ template<typename R>
+ struct run_benchmark
+ {
+ using result = R;
+
+ template<typename B>
+ result operator()(result out, const B benchmark) const
+ {
+ using inner_result = typename B::result;
+ out.push_back({boost::fusion::fold(enabled_libraries{}, inner_result{}, benchmark), benchmark.name()});
+ std::sort(out.back().first.begin(), out.back().first.end());
+ return out;
+ }
+ };
+
+ //! Run 0+ benchmarks against all enabled libraries
+ template<typename R, typename... B>
+ R run_benchmarks(B&&... benchmarks)
+ {
+ auto out = boost::fusion::fold(std::make_tuple(std::forward<B>(benchmarks)...), R{}, run_benchmark<R>{});
+ std::sort(out.begin(), out.end());
+ return out;
+ }
+
+ //! Run a suite of benchmarks - allows for comparison against a subset of benchmarks
+ template<typename S>
+ std::pair<typename S::result, std::string> run_suite(const S& suite)
+ {
+ return {suite(), suite.name()};
+ }
+
+ //! Arguments given to every crypto library being benchmarked.
+ struct bench_args
+ {
+ explicit bench_args(unsigned iterations)
+ : iterations(iterations), one(), two()
+ {
+ crypto::generate_keys(one.pub, one.sec, one.sec, false);
+ crypto::generate_keys(two.pub, two.sec, two.sec, false);
+ }
+
+ const unsigned iterations;
+ cryptonote::keypair one;
+ cryptonote::keypair two;
+ };
+
+ /*! Tests the ECDH step used for monero txes where the tx-pub is always
+ de-compressed into a table every time. */
+ struct tx_pub_standard
+ {
+ using result = std::vector<std::pair<std::chrono::steady_clock::duration, std::string>>;
+ static constexpr const char* name() noexcept { return "standard"; }
+
+ const bench_args args;
+
+ template<typename L>
+ result operator()(result out, const L library) const
+ {
+ crypto::key_derivation us;
+ crypto::key_derivation them;
+ CHECK(crypto::generate_key_derivation(args.one.pub, args.two.sec, them));
+ CHECK(library.generate_key_derivation(args.one.pub, args.two.sec, us));
+ CHECK(compare(us, them));
+
+ unsigned i = 0;
+ for (unsigned j = 0; j < 100; ++j)
+ i += library.generate_key_derivation(args.one.pub, args.two.sec, us);
+ CHECK(i == 100);
+
+ i = 0;
+ const auto start = std::chrono::steady_clock::now();
+ for (unsigned j = 0; j < args.iterations; ++j)
+ i += library.generate_key_derivation(args.one.pub, args.two.sec, us);
+ const auto end = std::chrono::steady_clock::now();
+ CHECK(i == args.iterations);
+ CHECK(compare(us, them));
+
+ out.push_back({end - start, library.name()});
+ return out;
+ }
+ };
+
+ //! Tests various possible optimizations for tx ECDH-step.
+ struct tx_pub_suite
+ {
+ using result = std::vector<std::pair<tx_pub_standard::result, std::string>>;
+ static constexpr const char* name() noexcept { return "generate_key_derivation step"; }
+
+ const bench_args args;
+
+ result operator()() const
+ {
+ return run_benchmarks<result>(tx_pub_standard{args});
+ }
+ };
+
+ /*! Tests the shared-secret to output-key step used for monero txes where
+ the users spend-public is always de-compressed. */
+ struct output_pub_standard
+ {
+ using result = std::vector<std::pair<std::chrono::steady_clock::duration, std::string>>;
+ static constexpr const char* name() noexcept { return "standard"; }
+
+ const bench_args args;
+
+ template<typename L>
+ result operator()(result out, const L library) const
+ {
+ crypto::key_derivation derived;
+ crypto::public_key us;
+ crypto::public_key them;
+ CHECK(crypto::generate_key_derivation(args.one.pub, args.two.sec, derived));
+ CHECK(library.derive_subaddress_public_key(args.two.pub, derived, 0, us));
+ CHECK(crypto::derive_subaddress_public_key(args.two.pub, derived, 0, them));
+ CHECK(compare(us, them));
+
+ unsigned i = 0;
+ for (unsigned j = 0; j < 100; ++j)
+ i += library.derive_subaddress_public_key(args.two.pub, derived, j, us);
+ CHECK(i == 100);
+
+ i = 0;
+ const auto start = std::chrono::steady_clock::now();
+ for (unsigned j = 0; j < args.iterations; ++j)
+ i += library.derive_subaddress_public_key(args.two.pub, derived, j, us);
+ const auto end = std::chrono::steady_clock::now();
+ CHECK(i == args.iterations);
+
+ out.push_back({end - start, library.name()});
+ return out;
+ }
+ };
+
+ //! Tests various possible optimizations for shared-secret to output-key step.
+ struct output_pub_suite
+ {
+ using result = std::vector<std::pair<output_pub_standard::result, std::string>>;
+ static constexpr const char* name() noexcept { return "derive_subaddress_public_key step"; }
+
+ const bench_args args;
+
+ result operator()() const
+ {
+ return run_benchmarks<result>(output_pub_standard{args});
+ }
+ };
+
+ struct tx_bench_args
+ {
+ const bench_args main;
+ unsigned outputs;
+ };
+
+ /*! Simulates "standard" tx scanning where a tx-pubkey is de-compressed into
+ a table and user spend-public is de-compressed, every time. */
+ struct tx_standard
+ {
+ using result = std::vector<std::pair<std::chrono::steady_clock::duration, std::string>>;
+ static constexpr const char* name() noexcept { return "standard"; }
+
+ const tx_bench_args args;
+
+ template<typename L>
+ result operator()(result out, const L library) const
+ {
+ crypto::key_derivation derived_us;
+ crypto::key_derivation derived_them;
+ crypto::public_key us;
+ crypto::public_key them;
+ CHECK(library.generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_us));
+ CHECK(crypto::generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_them));
+ CHECK(library.derive_subaddress_public_key(args.main.two.pub, derived_us, 0, us));
+ CHECK(crypto::derive_subaddress_public_key(args.main.two.pub, derived_them, 0, them));
+ CHECK(compare(us, them));
+
+ unsigned i = 0;
+ for (unsigned j = 0; j < 100; ++j)
+ {
+ i += library.generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_us);
+ i += library.derive_subaddress_public_key(args.main.two.pub, derived_us, j, us);
+ }
+ CHECK(i == 200);
+
+ i = 0;
+ const auto start = std::chrono::steady_clock::now();
+ for (unsigned j = 0; j < args.main.iterations; ++j)
+ {
+ i += library.generate_key_derivation(args.main.one.pub, args.main.two.sec, derived_us);
+ for (unsigned k = 0; k < args.outputs; ++k)
+ i += library.derive_subaddress_public_key(args.main.two.pub, derived_us, k, us);
+ }
+ const auto end = std::chrono::steady_clock::now();
+ CHECK(i == args.main.iterations + args.main.iterations * args.outputs);
+
+ out.push_back({end - start, library.name()});
+ return out;
+ }
+ };
+
+ //! Tests various possible optimizations for tx scanning.
+ struct tx_suite
+ {
+ using result = std::vector<std::pair<output_pub_standard::result, std::string>>;
+ std::string name() const { return "Transactions with " + std::to_string(args.outputs) + " outputs"; }
+
+ const tx_bench_args args;
+
+ result operator()() const
+ {
+ return run_benchmarks<result>(tx_standard{args});
+
+ }
+ };
+
+ std::chrono::steady_clock::duration print(const tx_pub_standard::result& leaf, std::ostream& out, unsigned depth)
+ {
+ namespace karma = boost::spirit::karma;
+ const std::size_t align = leaf.empty() ?
+ 0 : std::to_string(leaf.back().first.count()).size();
+ const auto best = leaf.empty() ?
+ std::chrono::steady_clock::duration::max() : leaf.front().first;
+ for (auto const& entry : leaf)
+ {
+ out << karma::format(karma::repeat(depth ? depth - 1 : 0)["| "]) << '|';
+ out << karma::format((karma::right_align(std::min(20u - depth, 20u), '-')["> " << karma::string]), entry.second);
+ out << " => " << karma::format((karma::right_align(align)[karma::uint_]), entry.first.count());
+ out << " ns (+";
+ out << (double((entry.first - best).count()) / best.count()) * 100 << "%)" << std::endl;
+ }
+ out << karma::format(karma::repeat(depth ? depth - 1 : 0)["| "]) << std::endl;
+ return best;
+ }
+
+ template<typename T>
+ std::chrono::steady_clock::duration
+ print(const std::vector<std::pair<T, std::string>>& node, std::ostream& out, unsigned depth)
+ {
+ auto best = std::chrono::steady_clock::duration::max();
+ for (auto const& entry : node)
+ {
+ std::stringstream buffer{};
+ auto last = print(entry.first, buffer, depth + 1);
+ if (last != std::chrono::steady_clock::duration::max())
+ {
+ namespace karma = boost::spirit::karma;
+ best = std::min(best, last);
+ out << karma::format(karma::repeat(depth)["|-"]);
+ out << "+ " << entry.second << ' ';
+ out << last.count() << " ns (+";
+ out << (double((last - best).count()) / best.count()) * 100 << "%)" << std::endl;
+ out << buffer.str();
+ }
+ }
+ return best;
+ }
+} // anonymous namespace
+
+int main(int argc, char** argv)
+{
+ using results = std::vector<std::pair<tx_pub_suite::result, std::string>>;
+ try
+ {
+ unsigned iterations = default_iterations;
+ std::vector<unsigned> nums{};
+ if (2 <= argc) iterations = std::stoul(argv[1]);
+ if (3 <= argc)
+ {
+ namespace qi = boost::spirit::qi;
+ if (!qi::parse(argv[2], argv[2] + strlen(argv[2]), (qi::uint_ % ','), nums))
+ throw std::runtime_error{"bad tx outputs string"};
+ }
+ else
+ {
+ nums = {2, 4};
+ }
+ std::sort(nums.begin(), nums.end());
+ nums.erase(std::unique(nums.begin(), nums.end()), nums.end());
+
+ std::cout << "Running benchmark using " << iterations << " iterations" << std::endl;
+
+ const bench_args args{iterations};
+
+ results val{};
+
+ std::cout << "Transaction Component Benchmarks" << std::endl;
+ std::cout << "--------------------------------" << std::endl;
+ val.push_back(run_suite(tx_pub_suite{args}));
+ val.push_back(run_suite(output_pub_suite{args}));
+ std::sort(val.begin(), val.end());
+ print(val, std::cout, 0);
+
+ val.clear();
+ std::cout << "Transaction Benchmarks" << std::endl;
+ std::cout << "----------------------" << std::endl;
+ for (const unsigned num : nums)
+ val.push_back(run_suite(tx_suite{{args, num}}));
+ std::sort(val.begin(), val.end());
+ print(val, std::cout, 0);
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Error: " << e.what() << std::endl;
+ return 1;
+ }
+ return 0;
+}
+
diff --git a/tests/benchmark.h.in b/tests/benchmark.h.in
new file mode 100644
index 000000000..b13ea30b7
--- /dev/null
+++ b/tests/benchmark.h.in
@@ -0,0 +1,5 @@
+#pragma once
+
+// A Boost PP sequence
+#define BENCHMARK_LIBRARIES @MONERO_WALLET_CRYPTO_BENCH_NAMES@
+
diff --git a/tests/functional_tests/proofs.py b/tests/functional_tests/proofs.py
index 5f23f7ea4..e58d29f94 100755
--- a/tests/functional_tests/proofs.py
+++ b/tests/functional_tests/proofs.py
@@ -130,13 +130,13 @@ class ProofsTest():
sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
res = self.wallet[0].get_tx_proof(txid, sending_address, 'foo');
- assert res.signature.startswith('InProof');
+ assert res.signature.startswith('InProofV2');
signature0i = res.signature
res = self.wallet[0].get_tx_proof(txid, receiving_address, 'bar');
- assert res.signature.startswith('OutProof');
+ assert res.signature.startswith('OutProofV2');
signature0o = res.signature
res = self.wallet[1].get_tx_proof(txid, receiving_address, 'baz');
- assert res.signature.startswith('InProof');
+ assert res.signature.startswith('InProofV2');
signature1 = res.signature
res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i);
@@ -219,6 +219,23 @@ class ProofsTest():
except: ok = True
assert ok or not res.good
+
+ # Test bad cross-version verification
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i.replace('ProofV2','ProofV1'));
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0o.replace('ProofV2','ProofV1'));
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature1.replace('ProofV2','ProofV1'));
+ except: ok = True
+ assert ok or not res.good
+
def check_spend_proof(self, txid):
daemon = Daemon()
@@ -270,7 +287,7 @@ class ProofsTest():
balance1 = res.balance
res = self.wallet[0].get_reserve_proof(all_ = True, message = 'foo')
- assert res.signature.startswith('ReserveProof')
+ assert res.signature.startswith('ReserveProofV2')
signature = res.signature
for i in range(2):
res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
@@ -287,9 +304,15 @@ class ProofsTest():
except: ok = True
assert ok or not res.good
+ # Test bad cross-version verification
+ ok = False
+ try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature.replace('ProofV2','ProofV1'))
+ except: ok = True
+ assert ok or not res.good
+
amount = int(balance0 / 10)
res = self.wallet[0].get_reserve_proof(all_ = False, amount = amount, message = 'foo')
- assert res.signature.startswith('ReserveProof')
+ assert res.signature.startswith('ReserveProofV2')
signature = res.signature
for i in range(2):
res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
@@ -306,6 +329,12 @@ class ProofsTest():
except: ok = True
assert ok or not res.good
+ # Test bad cross-version verification
+ ok = False
+ try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature.replace('ProofV2','ProofV1'))
+ except: ok = True
+ assert ok or not res.good
+
ok = False
try: self.wallet[0].get_reserve_proof(all_ = False, amount = balance0 + 1, message = 'foo')
except: ok = True
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/json_serialization.cpp b/tests/unit_tests/json_serialization.cpp
index 5873d0ab6..1db923f7b 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)
{
@@ -109,7 +112,7 @@ 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 +140,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 +171,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 +202,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/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..1d065fc45 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(const 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()));
+}