From 28262a30febc199c809b3688623a7b7a7f34a550 Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Wed, 19 Mar 2025 09:30:02 +0000 Subject: [PATCH] added unit tests for protocol_tx and check_output_types; bumped version to v0.9.6-rc1 --- README.md | 10 +- src/cryptonote_core/blockchain.cpp | 33 +- src/version.cpp.in | 4 +- tests/unit_tests/CMakeLists.txt | 3 +- tests/unit_tests/check_output_types.cpp | 130 +++++ tests/unit_tests/get_tx_asset_types.cpp | 665 ------------------------ tests/unit_tests/protocol_tx.cpp | 492 ++++++++++++++++++ 7 files changed, 637 insertions(+), 700 deletions(-) create mode 100644 tests/unit_tests/check_output_types.cpp delete mode 100644 tests/unit_tests/get_tx_asset_types.cpp create mode 100644 tests/unit_tests/protocol_tx.cpp diff --git a/README.md b/README.md index 9d9f389..8b7868d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Salvium Zero v0.9.5 +# Salvium Zero v0.9.6 Copyright (c) 2023-2024, Salvium Portions Copyright (c) 2014-2023, The Monero Project @@ -172,7 +172,7 @@ invokes cmake commands as needed. ```bash cd salvium - git checkout v0.9.5 + git checkout v0.9.6 make ``` @@ -251,7 +251,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( ```bash git clone https://github.com/salvium/salvium cd salvium - git checkout v0.9.5 + git checkout v0.9.6 ``` * Build: @@ -370,10 +370,10 @@ application. cd salvium ``` -* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v0.9.5'. If you don't care about the version and just want binaries from master, skip this step: +* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v0.9.6'. If you don't care about the version and just want binaries from master, skip this step: ```bash - git checkout v0.9.5 + git checkout v0.9.6 ``` * If you are on a 64-bit system, run: diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 334a727..6f019c5 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1482,16 +1482,12 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, // if nothing is created by this TX - check no money is included CHECK_AND_ASSERT_MES(b.protocol_tx.vin.size() == 1, false, "coinbase protocol transaction in the block has no inputs"); - size_t vout_size = b.protocol_tx.vout.size(); - if (vout_size == 0) { - LOG_PRINT_L2("coinbase protocol transaction in the block has no outputs"); - return true; - } // Can we have matured STAKE transactions yet? uint64_t stake_lock_period = get_config(m_nettype).STAKE_LOCK_PERIOD; if (height <= stake_lock_period) { - return false; + CHECK_AND_ASSERT_MES(b.protocol_tx.vout.size() == 0, false, "protocol transaction in the block has outputs"); + return true; } // Get the staking data for the block that matured this time @@ -1543,28 +1539,9 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, // Check we have the correct number of entries CHECK_AND_ASSERT_MES(b.protocol_tx.vout.size() == yield_payouts.size() + audit_payouts.size(), false, "Invalid number of outputs in protocol_tx - aborting"); - // go through each vout and validate - //std::set used_keys; - // Merge the yield and audit payouts into an iterable vector std::vector> payouts{yield_payouts}; payouts.insert(payouts.end(), audit_payouts.begin(), audit_payouts.end()); - /* - if (hf_version >= HF_VERSION_AUDIT2) { - std::sort(payouts.begin(), payouts.end(), [](const auto& lhs, const auto& rhs) { - // If block heights are different (only possible with mixed AUDIT+STAKE) sort by them first - if (lhs.first.block_height < rhs.first.block_height) return true; - if (lhs.first.block_height > rhs.first.block_height) return false; - - // If output keys are different, sort by them second - if (lhs.first.return_address < rhs.first.return_address) return true; - if (lhs.first.return_address > rhs.first.return_address) return false; - - // If block heights _and_ output keys are same, sort by amount third - return lhs.second < rhs.second; - }); - } - */ size_t output_idx = 0; for (auto it = payouts.begin(); it != payouts.end(); it++, output_idx++) { @@ -1575,8 +1552,9 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, CHECK_AND_ASSERT_MES(out_key == it->first.return_address, false, "Incorrect output key detected in protocol_tx"); // Verify the output amount - CHECK_AND_ASSERT_MES(b.protocol_tx.vout[output_idx].amount == it->second, false, "Incorrect output amount detected in protocol_tx"); - + uint64_t expected_amount = it->second; + CHECK_AND_ASSERT_MES(b.protocol_tx.vout[output_idx].amount == expected_amount, false, "Incorrect output amount detected in protocol_tx. expected_amount: " << expected_amount); + // Verify the output asset type std::string out_asset_type; cryptonote::get_output_asset_type(b.protocol_tx.vout[output_idx], out_asset_type); @@ -4526,6 +4504,7 @@ bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vecto } yield_block_info ybi = m_yield_block_info_cache[idx]; if (ybi.slippage_total_this_block == 0) continue; + if (ybi.locked_coins_tally == 0) continue; boost::multiprecision::int128_t slippage_128 = ybi.slippage_total_this_block; diff --git a/src/version.cpp.in b/src/version.cpp.in index f328b00..5612207 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,7 +1,7 @@ #define DEF_SALVIUM_VERSION_TAG "@VERSIONTAG@" -#define DEF_SALVIUM_VERSION "0.9.5a" +#define DEF_SALVIUM_VERSION "0.9.6-rc1" #define DEF_MONERO_VERSION_TAG "release" -#define DEF_MONERO_VERSION "0.18.3.3" +#define DEF_MONERO_VERSION "0.18.3.4" #define DEF_MONERO_RELEASE_NAME "Zero" #define DEF_MONERO_VERSION_FULL DEF_SALVIUM_VERSION "-" DEF_SALVIUM_VERSION_TAG ", based on Monero " DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@ diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 7b831cb..4859062 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -53,7 +53,8 @@ set(unit_tests_sources expect.cpp fee.cpp json_serialization.cpp - get_tx_asset_types.cpp + check_output_types.cpp + protocol_tx.cpp get_xtype_from_string.cpp hashchain.cpp hmac_keccak.cpp diff --git a/tests/unit_tests/check_output_types.cpp b/tests/unit_tests/check_output_types.cpp new file mode 100644 index 0000000..077d053 --- /dev/null +++ b/tests/unit_tests/check_output_types.cpp @@ -0,0 +1,130 @@ +// Copyright (c) 2025, Salvium (author: akil) +// Portions copyright (c) 2019-2021, Haven Protocol +// Portions copyright (c) 2016-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_basic.h" + +using namespace cryptonote; + +TEST(check_output_types, check_all) +{ + transaction tx; + tx.version = 2; + uint8_t hf_version = 1; + + // should only allow for single output for audit and stake txs + tx.type = transaction_type::AUDIT; + txout_to_key out; out.asset_type = "SAL"; + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.push_back(tx_out {0, out}); + tx.vout.push_back(tx_out {0, out}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.pop_back(); + EXPECT_TRUE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + tx.type = transaction_type::STAKE; + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.push_back(tx_out {0, out}); + tx.vout.push_back(tx_out {0, out}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.pop_back(); + EXPECT_TRUE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + // should only allow txout_to_key or txout_to_tagged_key output types + txout_to_script tx_out_script; + tx.vout.push_back(tx_out {0, tx_out_script}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + txout_to_scripthash tx_out_scripthash; + tx.vout.push_back(tx_out {0, tx_out_scripthash}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + // should force all outputs to be of the same type + out.asset_type = "SAL"; + txout_to_tagged_key out2; out2.asset_type = "SAL"; + tx.vout.push_back(tx_out {0, out}); + tx.vout.push_back(tx_out {0, out2}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + // should only allow SAL outputs before version 6 + out.asset_type = "SAL1"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + out.asset_type = "SAL"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_TRUE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + // after version 6, AUDIT txs still should only have SAL outputs + hf_version = 6; + tx.type = transaction_type::AUDIT; + + out.asset_type = "SAL1"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + out.asset_type = "SAL"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_TRUE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + // after version 6, non-AUDIT txs should only allow SAL1 outputs + tx.type = transaction_type::PROTOCOL; + + out.asset_type = "SAL"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + out.asset_type = "SAL1"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_TRUE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + tx.type = transaction_type::TRANSFER; + out.asset_type = "SAL"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_FALSE(check_output_types(tx, hf_version)); + tx.vout.clear(); + + out.asset_type = "SAL1"; + tx.vout.push_back(tx_out {0, out}); + EXPECT_TRUE(check_output_types(tx, hf_version)); + tx.vout.clear(); +} diff --git a/tests/unit_tests/get_tx_asset_types.cpp b/tests/unit_tests/get_tx_asset_types.cpp deleted file mode 100644 index 6596000..0000000 --- a/tests/unit_tests/get_tx_asset_types.cpp +++ /dev/null @@ -1,665 +0,0 @@ -// Copyright (c) 2019-2021, Haven Protocol -// Portions copyright (c) 2016-2019, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "gtest/gtest.h" -#include "cryptonote_core/cryptonote_tx_utils.cpp" -#include "cryptonote_basic/cryptonote_basic.h" -#include "vector" - -/* -// Regular transfers tests. Same asset type in input and output. Should be successful. -TEST(get_tx_asset_types, successful_on_1_input_type_1_output_type_FULM) -{ - cryptonote::transaction tx; - tx.version = 7; - - cryptonote::txin_to_key fulm_key; - tx.vin.push_back(fulm_key); - - cryptonote::tx_out out; - cryptonote::tx_out out1; - cryptonote::txout_to_key out_fulm; - out.target = out_fulm; - out1.target = out_fulm; - - tx.vout.push_back(out); - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_TRUE(get_tx_asset_types(tx, tx.hash, source, dest, false)); - - EXPECT_EQ(source, "FULM"); - EXPECT_EQ(dest, "FULM"); -} -TEST(get_tx_asset_types, successful_on_1_input_type_1_output_type_FUSD) -{ - cryptonote::transaction tx; - tx.version = 7; - - cryptonote::txin_to_key offshore_key; - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out; - cryptonote::tx_out out1; - cryptonote::txout_offshore out_fusd; - out.target = out_fusd; - out1.target = out_fusd; - - tx.vout.push_back(out); - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_TRUE(get_tx_asset_types(tx, tx.hash, source, dest, false)); - - EXPECT_EQ(source, "FUSD"); - EXPECT_EQ(dest, "FUSD"); -} - -// pass on correct conversions -TEST(get_tx_asset_types, successful_offshore) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_to_key fulm_key; - - cryptonote::txout_to_key out_fulm; - cryptonote::txout_offshore out_fusd; - - tx.vin.push_back(fulm_key); - tx.vin.push_back(fulm_key); - - cryptonote::tx_out out; - out.target = out_fulm; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_fusd; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_TRUE(get_tx_asset_types(tx, tx.hash, source, dest, false)); - - EXPECT_EQ(source, "FULM"); - EXPECT_EQ(dest, "FUSD"); -} -TEST(get_tx_asset_types, successful_onshore) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_onshore fusd_key; - - cryptonote::txout_to_key out_fulm; - cryptonote::txout_offshore out_fusd; - - tx.vin.push_back(fusd_key); - - cryptonote::tx_out out; - out.target = out_fulm; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_fusd; - tx.vout.push_back(out1); - cryptonote::tx_out out2; - out2.target = out_fulm; - tx.vout.push_back(out2); - - std::string source; - std::string dest; - EXPECT_TRUE(get_tx_asset_types(tx, tx.hash, source, dest, false)); - - EXPECT_EQ(source, "FUSD"); - EXPECT_EQ(dest, "FULM"); -} - -// fail on multiple input types - 3 or more -TEST(get_tx_asset_types, fail_on_multiple_input_types_3_or_more) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xasset_key); - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_offshore; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on multiple input types - 1. XHV, 2. not XUSD -TEST(get_tx_asset_types, fail_on_multiple_input_types_XHV_not_XUSD) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - xasset_key.asset_type = "XBTC"; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xasset_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_offshore; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on multiple input types - 1. XUSD, 2. not XHV -TEST(get_tx_asset_types, fail_on_multiple_input_types_XUSD_not_XHV) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - xasset_key.asset_type = "XBTC"; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - - tx.vin.push_back(onshore_key); - tx.vin.push_back(xasset_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_offshore; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on multiple input types - ins) 1. XHV 2. XUSD outs) only XHV -TEST(get_tx_asset_types, fail_on_multiple_input_types_only_XHV_out) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on multiple input types - ins) 1. XHV 2. XUSD outs) only XUSD -TEST(get_tx_asset_types, fail_on_multiple_input_types_only_XUSD_out) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out1; - out1.target = out_offshore; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on multiple input types - ins) 1. XHV 2. XUSD outs) 1. not XHV 2. not XUSD -TEST(get_tx_asset_types, fail_on_multiple_input_types_out_not_XHV_not_XUSD) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - out_xasset.asset_type = "XEUR"; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out; - out.target = out_xasset; - tx.vout.push_back(out); - tx.vout.push_back(out); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on multiple input types - ins) 1. XHV 2. XUSD outs) 1. XHV 2. not XUSD -TEST(get_tx_asset_types, fail_on_multiple_input_types_out_XHV_not_XUSD) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - out_xasset.asset_type = "XEUR"; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xhv_key); - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_xasset; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on multiple input types - ins) 1. XHV 2. XUSD outs) 1. not XHV 2. XUSD -TEST(get_tx_asset_types, fail_on_multiple_input_types_out_XUSD_not_XHV) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - out_xasset.asset_type = "XEUR"; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xhv_key); - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out; - out.target = out_xasset; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_offshore; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// pass on multiple input types - ins) 1. XHV 2. XUSD outs) 1. XHV 2. XUSD -TEST(get_tx_asset_types, pass_on_multiple_input_types_onshore) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_offshore offshore_key; - cryptonote::txin_onshore onshore_key; - cryptonote::txin_xasset xasset_key; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xhv_key); - tx.vin.push_back(offshore_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_offshore; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_TRUE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on single input types with more than 2 output types -TEST(get_tx_asset_types, fail_single_input_and_more_than_2output_types) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_offshore out_offshore; - cryptonote::txout_xasset out_xasset; - out_xasset.asset_type = "XAU"; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xhv_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_offshore; - tx.vout.push_back(out1); - cryptonote::tx_out out2; - out2.target = out_xasset; - tx.vout.push_back(out2); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on single input types & single output types & they are not equal -TEST(get_tx_asset_types, fail_single_input_single_output_types_are_not_equal) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_xasset out_xasset; - cryptonote::txout_xasset out_xasset1; - out_xasset.asset_type = "XAG"; - out_xasset1.asset_type = "XAG"; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xhv_key); - - cryptonote::tx_out out; - out.target = out_xasset; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_xasset1; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on single input types & 2 output types & none of the outputs matches inputs -TEST(get_tx_asset_types, none_of_output_mathces_input) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_xasset out_xasset; - cryptonote::txout_xasset out_xasset1; - out_xasset.asset_type = "XAG"; - out_xasset1.asset_type = "XBTC"; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xhv_key); - - cryptonote::tx_out out; - out.target = out_xasset; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_xasset1; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// pass on single input types & 2 output types & 1 of the outputs matches inputs, other ddesn't but not allowed. xhv -> xasset -// This case will expected to be caught by get_tx_type() -TEST(get_tx_asset_types, succesfuul_on_logical_input_output_but_not_allowed) -{ - cryptonote::transaction tx; - tx.version = 7; - cryptonote::txin_to_key xhv_key; - - cryptonote::txout_to_key out_xhv; - cryptonote::txout_xasset out_xasset1; - out_xasset1.asset_type = "XBTC"; - - tx.vin.push_back(xhv_key); - tx.vin.push_back(xhv_key); - - cryptonote::tx_out out; - out.target = out_xhv; - tx.vout.push_back(out); - cryptonote::tx_out out1; - out1.target = out_xasset1; - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_TRUE(get_tx_asset_types(tx, tx.hash, source, dest, false)); - - EXPECT_EQ(source, "XHV"); - EXPECT_EQ(dest, "XBTC"); -} - -// pass on 2 different xasset but source and dest are different. This case will expectedd to be catch by get_tx_type() -TEST(get_tx_asset_types, successful_on_logical_input_output_but_not_allowed_xassets) -{ - cryptonote::transaction tx; - tx.version = 7; - - cryptonote::txin_xasset xasset_key; - xasset_key.asset_type = "XBTC"; - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - - cryptonote::tx_out out; - cryptonote::tx_out out1; - cryptonote::txout_xasset out_xasset; - cryptonote::txout_xasset out_xasset1; - out_xasset.asset_type = "XBTC"; - out_xasset1.asset_type = "XJPY"; - out.target = out_xasset; - out1.target = out_xasset1; - - tx.vout.push_back(out); - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_TRUE(get_tx_asset_types(tx, tx.hash, source, dest, false)); - - EXPECT_EQ(source, "XBTC"); - EXPECT_EQ(dest, "XJPY"); -} - -// fail on 2 different xasset -TEST(get_tx_asset_types, fail_on_2_different_xasset) -{ - cryptonote::transaction tx; - tx.version = 7; - - cryptonote::txin_xasset xasset_key; - xasset_key.asset_type = "XBTC"; - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - - cryptonote::tx_out out; - cryptonote::tx_out out1; - cryptonote::txout_xasset out_xasset; - cryptonote::txout_xasset out_xasset1; - out_xasset.asset_type = "XJPY"; - out_xasset1.asset_type = "XJPY"; - out.target = out_xasset; - out1.target = out_xasset1; - - tx.vout.push_back(out); - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on unknown asset types -TEST(get_tx_asset_types, fail_on_2_unknown_asset_types) -{ - cryptonote::transaction tx; - tx.version = 7; - - cryptonote::txin_xasset xasset_key; - xasset_key.asset_type = "xabc"; - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - - cryptonote::tx_out out; - cryptonote::tx_out out1; - cryptonote::txout_xasset out_xasset; - cryptonote::txout_xasset out_xasset1; - out_xasset.asset_type = "xabc"; - out_xasset1.asset_type = "xabc"; - out.target = out_xasset; - out1.target = out_xasset1; - - tx.vout.push_back(out); - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on unknown asset types -TEST(get_tx_asset_types, fail_on_2_unknown_asset_types_and_multiple_outs) -{ - cryptonote::transaction tx; - tx.version = 7; - - cryptonote::txin_xasset xasset_key; - xasset_key.asset_type = "xabc"; - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - - cryptonote::tx_out out; - cryptonote::tx_out out1; - cryptonote::txout_xasset out_xasset; - cryptonote::txout_xasset out_xasset1; - out_xasset.asset_type = "xabc"; - out_xasset1.asset_type = "xbdc"; - out.target = out_xasset; - out1.target = out_xasset1; - - tx.vout.push_back(out); - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} - -// fail on unknown asset types -TEST(get_tx_asset_types, fail_on_1_unknown_asset_type) -{ - cryptonote::transaction tx; - tx.version = 7; - - cryptonote::txin_xasset xasset_key; - xasset_key.asset_type = "XBTC"; - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - tx.vin.push_back(xasset_key); - - cryptonote::tx_out out; - cryptonote::tx_out out1; - cryptonote::txout_xasset out_xasset; - cryptonote::txout_xasset out_xasset1; - out_xasset.asset_type = "XBTC"; - out_xasset1.asset_type = "xbdc"; - out.target = out_xasset; - out1.target = out_xasset1; - - tx.vout.push_back(out); - tx.vout.push_back(out1); - - std::string source; - std::string dest; - EXPECT_FALSE(get_tx_asset_types(tx, tx.hash, source, dest, false)); -} -*/ diff --git a/tests/unit_tests/protocol_tx.cpp b/tests/unit_tests/protocol_tx.cpp new file mode 100644 index 0000000..59587a1 --- /dev/null +++ b/tests/unit_tests/protocol_tx.cpp @@ -0,0 +1,492 @@ +// Copyright (c) 2025, Salvium (author: akil) + +// +// 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. + +#define IN_UNIT_TESTS + +#include "gtest/gtest.h" + +#include "cryptonote_core/blockchain.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_core/cryptonote_core.h" +#include "blockchain_db/testdb.h" + +using namespace cryptonote; + +const uint64_t AMOUNT_BURNT = 1000000000000; // 1 SAL +const uint64_t STAKE_REWARD = 10000000000000; // 10 SAL +const uint64_t STAKE_PAYOUT = 216001000000000000; // 216k SAL +const uint64_t STAKE_LOCK_PERIOD = get_config(network_type::FAKECHAIN).STAKE_LOCK_PERIOD; +const auto AUDIT_HARD_FORKS = get_config(network_type::FAKECHAIN).AUDIT_HARD_FORKS; + +class TestDB: public BaseTestDB { +public: + TestDB() { m_open = true; } + + virtual uint64_t height() const override { return blocks.size(); } + virtual void add_block(const block& blk + , size_t block_weight + , uint64_t long_term_block_weight + , const difficulty_type& cumulative_difficulty + , const uint64_t& coins_generated + , uint64_t num_rct_outs + , oracle::asset_type_counts& cum_rct_by_asset_type + , const crypto::hash& blk_hash + , uint64_t slippage_total + , uint64_t yield_total + , uint64_t audit_total + , const network_type nettype + , yield_block_info& ybi + , audit_block_info& abi + ) override { + uint64_t height = this->height(); + yield_block_info ybi_prev = { 0, 0, 0, 0, 100 }; + yield_block_info ybi_matured = { 0, 0, 0, 0, 100 }; + if (height >= 1) { + ybi_prev = yield_info.at(height - 1); + } + if (height > STAKE_LOCK_PERIOD) { + ybi_matured = yield_info.at(height - STAKE_LOCK_PERIOD - 1); + } + + ybi.block_height = height; + ybi.locked_coins_this_block = yield_total; + ybi.slippage_total_this_block = slippage_total; + ybi.locked_coins_tally = yield_total + ybi_prev.locked_coins_tally - ybi_matured.locked_coins_this_block; + + abi.locked_coins_this_block = audit_total; + abi.locked_coins_tally = audit_total; + abi.block_height = ybi.block_height; + + yield_info[ybi.block_height] = ybi; + audit_info[abi.block_height] = abi; + blocks.push_back(blk); + } + virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const std::pair& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash, const bool miner_tx) override { + if (tx.first.type == transaction_type::STAKE || tx.first.type == transaction_type::AUDIT) { + yield_tx_info yti; + yti.tx_hash = tx_hash; + yti.block_height = (uint64_t)this->height(); + yti.locked_coins = tx.first.amount_burnt; + yti.return_address = tx.first.return_address_list[0]; + if (tx.first.type == transaction_type::STAKE) { + yield_txs[yti.block_height].push_back(yti); + } else if (tx.first.type == transaction_type::AUDIT) { + audit_txs[yti.block_height].push_back(yti); + } + } + + return 1; + } + virtual int get_yield_block_info(const uint64_t height, yield_block_info& ybi) const override { + if (yield_info.count(height) == 0) + return 1; + + ybi = yield_info.at(height); + return 0; + } + virtual int get_audit_block_info(const uint64_t height, audit_block_info& abi) const override { + if (audit_info.count(height) == 0) + return 1; + + abi = audit_info.at(height); + return 0; + } + virtual int get_yield_tx_info(const uint64_t height, std::vector& yti_container) const override { + if (yield_txs.count(height) == 0) + return 1; + + yti_container = yield_txs.at(height); + return 0; + } + virtual int get_audit_tx_info(const uint64_t height, std::vector& ati_container) const override { + if (audit_txs.count(height) == 0) + return 1; + + ati_container = audit_txs.at(height); + return 0; + } + virtual crypto::hash get_block_hash_from_height(const uint64_t &height) const override { + crypto::hash hash = crypto::null_hash; + *(uint64_t*)&hash = height; + return hash; + } + virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override { + uint64_t h = height(); + crypto::hash top = crypto::null_hash; + if (h) + *(uint64_t*)&top = h - 1; + if (block_height) + *block_height = h - 1; + return top; + } + +private: + std::vector blocks; + std::map yield_info; + std::map audit_info; + std::map> yield_txs; + std::map> audit_txs; +}; + + +transaction get_audit_tx() { + transaction audit_tx; + audit_tx.type = transaction_type::AUDIT; + + std::string str = "audit-return-address-32-bytes32"; + rct::key audit_return_address; + memcpy(audit_return_address.bytes, str.data(), sizeof(audit_return_address.bytes)); + + audit_tx.return_address_list.push_back(rct::rct2pk(audit_return_address)); + audit_tx.amount_burnt = AMOUNT_BURNT; + audit_tx.version = 2; + + return audit_tx; +} + +transaction get_stake_tx() { + transaction stake_tx; + stake_tx.type = transaction_type::STAKE; + + std::string str = "stake-return-address-32-bytes32"; + rct::key stake_return_address; + memcpy(stake_return_address.bytes, str.data(), sizeof(stake_return_address.bytes)); + + stake_tx.return_address_list.push_back(rct::rct2pk(stake_return_address)); + stake_tx.amount_burnt = AMOUNT_BURNT; + stake_tx.version = 2; + + return stake_tx; +} + +void add_block(Blockchain *bc, uint8_t num_audit_txs, uint8_t num_stake_txs) { + block b; + b.miner_tx.amount_burnt = STAKE_REWARD; + + // add txs + std::vector> audit_txs; + std::vector> stake_txs; + for (int i = 0; i < num_audit_txs; i++) { + audit_txs.push_back(std::make_pair(get_audit_tx(), "")); + } + for (int i = 0; i < num_stake_txs; i++) { + stake_txs.push_back(std::make_pair(get_stake_tx(), "")); + } + + // merge & add txs to the block + std::vector> txs; + txs.insert(txs.end(), audit_txs.begin(), audit_txs.end()); + txs.insert(txs.end(), stake_txs.begin(), stake_txs.end()); + for (auto tx : txs) { + b.tx_hashes.push_back(tx.first.hash); + } + + // add the block + yield_block_info ybi; + audit_block_info abi; + bc->get_db().add_block( + std::make_pair(b, ""), + 0, 0, 1, 0, + txs, + network_type::FAKECHAIN, ybi, abi + ); +} + +uint64_t progress_chain(Blockchain *bc, uint64_t num_blocks) { + for (int i = 0; i < num_blocks; i++) { + add_block(bc, 0, 0); + } + return bc->get_db().height(); +} + +TEST(protocol_tx, validate) +{ + const std::pair hard_forks[3] = { + std::make_pair(1, 1), + std::make_pair(5, 1 * STAKE_LOCK_PERIOD), + std::make_pair(6, 2 * STAKE_LOCK_PERIOD), + }; + const test_options test_options = { + hard_forks, + 1000, + }; + + BlockchainAndPool bap; + Blockchain *bc = &bap.blockchain; + bool r = bc->init(new TestDB(), network_type::FAKECHAIN, true, &test_options, 0, NULL); + ASSERT_TRUE(r); + block b; + b.miner_tx.amount_burnt = STAKE_REWARD; + + // ******** Special cases for genesis block(height is 0) ******** + + uint64_t height = bc->get_db().height() - 1; + uint8_t hf_version = bc->get_ideal_hard_fork_version(height); + + // should have version 1 + b.protocol_tx.version = 2; + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.version = 1; + + // shouldn't have any inputs + b.protocol_tx.vin.push_back(txin_gen{height}); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vin.clear(); + + // shouldn't have any outputs + b.protocol_tx.vout.push_back(tx_out {0, txout_target_v{txout_to_key{}}}); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + + // should now pass + EXPECT_TRUE(bc->validate_protocol_transaction(b, height, hf_version)); + + // ******** Normal cases ******** + height = progress_chain(bc, 1); + hf_version = bc->get_current_hard_fork_version(); + + // should only have 1 input + b.protocol_tx.vin.clear(); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vin.push_back(txin_gen{height}); + b.protocol_tx.vin.push_back(txin_gen{height}); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vin.pop_back(); + + // should now pass with no output + b.protocol_tx.vout.clear(); + EXPECT_TRUE(bc->validate_protocol_transaction(b, height, hf_version)); + + // height should be at least STAKE_LOCK_PERIOD if there is at least 1 output + b.protocol_tx.vout.push_back(tx_out {10000, txout_target_v{txout_to_key{}}}); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + + // reach to stake period height + height = progress_chain(bc, STAKE_LOCK_PERIOD); + hf_version = bc->get_current_hard_fork_version(); + + // should fail if can't find a ybi & abi entry for the block + // (this will always pass since you can't add a block without ybi & abi entry) + + // should fail if protocol tx outputs(1) > audit txs(0) + yield txs(0) + b.protocol_tx.vout.push_back(tx_out {10000, txout_target_v{txout_to_key{}}}); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + + // there shouldn't be outputs for audit txs in hf_version 5 + EXPECT_EQ((int)bc->get_ideal_hard_fork_version(height), 5); + add_block(bc, 1, 1); + + // progress chain until the audit payout + height = progress_chain(bc, AUDIT_HARD_FORKS.at(6).first); + hf_version = bc->get_current_hard_fork_version(); + + // make outputs for audit tx + b.protocol_tx.vout.push_back(tx_out {AMOUNT_BURNT, txout_target_v{txout_to_key{ + get_audit_tx().return_address_list[0], + "SAL", + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW + }}}); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + + // progress chain until the stake payout + height = progress_chain(bc, STAKE_LOCK_PERIOD - AUDIT_HARD_FORKS.at(6).first); + hf_version = bc->get_current_hard_fork_version(); + + // make sure its paid out with correct asset type & amount + b.protocol_tx.vout.push_back( + tx_out { + STAKE_PAYOUT - 1, // wrong amount + txout_target_v{txout_to_key{ + get_stake_tx().return_address_list[0], + "SAL1", // wrong asset type + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - 1 // wrong unlock window + } + } + } + ); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // fix amount + b.protocol_tx.vout[0].amount = STAKE_PAYOUT; + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // fix asset type + boost::get(b.protocol_tx.vout[0].target).asset_type = "SAL"; + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // fix unlock window + boost::get(b.protocol_tx.vout[0].target).unlock_time = CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + EXPECT_TRUE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + + // audit txs should now be paid out in hf_version 6 + EXPECT_EQ((int)bc->get_ideal_hard_fork_version(height), 6); + add_block(bc, 1, 0); + + // progress chain until the payout + height = progress_chain(bc, AUDIT_HARD_FORKS.at(6).first); + hf_version = bc->get_current_hard_fork_version(); + + // validate we get paid out with correct asset type & amount + b.protocol_tx.vout.push_back( + tx_out { + AMOUNT_BURNT - 1, // wrong amount + txout_target_v{txout_to_key{ + get_audit_tx().return_address_list[0], + "SAL", // wrong asset type + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - 1 // wrong unlock window + } + } + } + ); + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // fix amount + b.protocol_tx.vout[0].amount = AMOUNT_BURNT; + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // fix asset type + boost::get(b.protocol_tx.vout[0].target).asset_type = "SAL1"; + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // fix unlock window + boost::get(b.protocol_tx.vout[0].target).unlock_time = CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + EXPECT_TRUE(bc->validate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + + // add a stake tx + add_block(bc, 0, 1); + + // progress chain to add audit tx so that they both mature at the same time + height = progress_chain(bc, STAKE_LOCK_PERIOD - AUDIT_HARD_FORKS.at(6).first - 1); + hf_version = bc->get_current_hard_fork_version(); + + // add an audit tx + add_block(bc, 1, 0); + + // progress chain to mature height + height = progress_chain(bc, AUDIT_HARD_FORKS.at(6).first); + hf_version = bc->get_current_hard_fork_version(); + + // fail since protocol tx outputs(0) < audit txs(1) + yield txs(1) + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // audit tx output + b.protocol_tx.vout.push_back(tx_out {AMOUNT_BURNT, txout_target_v{txout_to_key{ + get_audit_tx().return_address_list[0], + "SAL1", + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW + }}}); + // fail since protocol tx outputs(1) < audit txs(1) + yield txs(1) + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // stake tx output + b.protocol_tx.vout.push_back(tx_out {STAKE_PAYOUT, txout_target_v{txout_to_key{ + get_stake_tx().return_address_list[0], + "SAL1", + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW + }}}); + // fail since protocol tx outputs[0] != stake_tx + EXPECT_FALSE(bc->validate_protocol_transaction(b, height, hf_version)); + + // swap the outputs + std::swap(b.protocol_tx.vout[0], b.protocol_tx.vout[1]); + EXPECT_TRUE(bc->validate_protocol_transaction(b, height, hf_version)); +} + + +TEST(protocol_tx, prevalidate) +{ + tx_memory_pool* tx_pool = nullptr; + Blockchain blockchain(*tx_pool); + block b; + + // ******** Special cases for genesis block(height is 0) ******** + uint64_t height = 0; + uint8_t hf_version = 1; + + // should have version 1 + b.protocol_tx.version = 2; + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.version = 1; + + // shouldn't have any inputs + b.protocol_tx.vin.push_back(txin_gen{height}); + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vin.clear(); + + // shouldn't have any outputs + b.protocol_tx.vout.push_back(tx_out {0, txout_target_v{txout_to_key{}}}); + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + + // should now pass + EXPECT_TRUE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + + // ******** Normal cases ******** + height = 1; + hf_version = 1; + + // should have version > 1 + b.protocol_tx.version = 1; + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.version = 2; + + // should have only 1 input + b.protocol_tx.vin.push_back(txin_gen{height}); + b.protocol_tx.vin.push_back(txin_gen{height}); + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vin.clear(); + + // should have only input type txin_gen + b.protocol_tx.vin.push_back(txin_to_key {0, "SAL", {}, crypto::key_image{}}); + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vin.clear(); + b.protocol_tx.vin.push_back(txin_gen{height}); + + // should have rct type NULL + b.protocol_tx.rct_signatures.type = rct::RCTTypeSalviumOne; + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.rct_signatures.type = rct::RCTTypeNull; + + // txin_gen height must match block height + boost::get(b.protocol_tx.vin[0]).height = height + 1; + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + boost::get(b.protocol_tx.vin[0]).height = height; + + // output amounts can't overflow + b.protocol_tx.vout.push_back(tx_out {UINT64_MAX, txout_target_v{txout_to_key{}}}); + b.protocol_tx.vout.push_back(tx_out {1, txout_target_v{txout_to_key{}}}); + EXPECT_FALSE(blockchain.prevalidate_protocol_transaction(b, height, hf_version)); + b.protocol_tx.vout.clear(); + b.protocol_tx.vout.push_back(tx_out {UINT64_MAX, txout_target_v{txout_to_key{}}}); +}