From 1c73dd0c9fa70b54eab7478952051d428e9747bb Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Sun, 8 Sep 2024 19:54:12 +0100 Subject: [PATCH 1/6] switch to LWMA difficulty algorithm for HF2+ --- src/cryptonote_basic/difficulty.cpp | 299 ++++++++++++++++++++++++++++ src/cryptonote_basic/difficulty.h | 3 +- src/cryptonote_core/blockchain.cpp | 48 ++++- 3 files changed, 346 insertions(+), 4 deletions(-) diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 165c1936e..2965afdef 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -239,6 +239,305 @@ namespace cryptonote { return res.convert_to(); } + // Copyright (c) 2014-2023, 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include +#include +#include +#include +#include +#include + +#include "int-util.h" +#include "crypto/hash.h" +#include "cryptonote_config.h" +#include "difficulty.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "difficulty" + +namespace cryptonote { + + using std::size_t; + using std::uint64_t; + using std::vector; + +#if defined(__x86_64__) + static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { + low = mul128(a, b, &high); + } + +#else + + static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { + // __int128 isn't part of the standard, so the previous function wasn't portable. mul128() in Windows is fine, + // but this portable function should be used elsewhere. Credit for this function goes to latexi95. + + uint64_t aLow = a & 0xFFFFFFFF; + uint64_t aHigh = a >> 32; + uint64_t bLow = b & 0xFFFFFFFF; + uint64_t bHigh = b >> 32; + + uint64_t res = aLow * bLow; + uint64_t lowRes1 = res & 0xFFFFFFFF; + uint64_t carry = res >> 32; + + res = aHigh * bLow + carry; + uint64_t highResHigh1 = res >> 32; + uint64_t highResLow1 = res & 0xFFFFFFFF; + + res = aLow * bHigh; + uint64_t lowRes2 = res & 0xFFFFFFFF; + carry = res >> 32; + + res = aHigh * bHigh + carry; + uint64_t highResHigh2 = res >> 32; + uint64_t highResLow2 = res & 0xFFFFFFFF; + + //Addition + + uint64_t r = highResLow1 + lowRes2; + carry = r >> 32; + low = (r << 32) | lowRes1; + r = highResHigh1 + highResLow2 + carry; + uint64_t d3 = r & 0xFFFFFFFF; + carry = r >> 32; + r = highResHigh2 + carry; + high = d3 | (r << 32); + } + +#endif + + static inline bool cadd(uint64_t a, uint64_t b) { + return a + b < a; + } + + static inline bool cadc(uint64_t a, uint64_t b, bool c) { + return a + b < a || (c && a + b == (uint64_t) -1); + } + + bool check_hash_64(const crypto::hash &hash, uint64_t difficulty) { + uint64_t low, high, top, cur; + // First check the highest word, this will most likely fail for a random hash. + mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high); + if (high != 0) { + return false; + } + mul(swap64le(((const uint64_t *) &hash)[0]), difficulty, low, cur); + mul(swap64le(((const uint64_t *) &hash)[1]), difficulty, low, high); + bool carry = cadd(cur, low); + cur = high; + mul(swap64le(((const uint64_t *) &hash)[2]), difficulty, low, high); + carry = cadc(cur, low, carry); + carry = cadc(high, top, carry); + return !carry; + } + + uint64_t next_difficulty_64(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { + + if(timestamps.size() > DIFFICULTY_WINDOW) + { + timestamps.resize(DIFFICULTY_WINDOW); + cumulative_difficulties.resize(DIFFICULTY_WINDOW); + } + + + size_t length = timestamps.size(); + assert(length == cumulative_difficulties.size()); + if (length <= 1) { + return 1; + } + static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small"); + assert(length <= DIFFICULTY_WINDOW); + sort(timestamps.begin(), timestamps.end()); + size_t cut_begin, cut_end; + static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large"); + if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) { + cut_begin = 0; + cut_end = length; + } else { + cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2; + cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT); + } + assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length); + uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin]; + if (time_span == 0) { + time_span = 1; + } + uint64_t total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; + assert(total_work > 0); + uint64_t low, high; + mul(total_work, target_seconds, low, high); + // blockchain errors "difficulty overhead" if this function returns zero. + // TODO: consider throwing an exception instead + if (high != 0 || low + time_span - 1 < low) { + return 0; + } + return (low + time_span - 1) / time_span; + } + +#if defined(_MSC_VER) +#ifdef max +#undef max +#endif +#endif + + const difficulty_type max64bit(std::numeric_limits::max()); + const boost::multiprecision::uint256_t max128bit(std::numeric_limits::max()); + const boost::multiprecision::uint512_t max256bit(std::numeric_limits::max()); + +#define FORCE_FULL_128_BITS + + bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty) { +#ifndef FORCE_FULL_128_BITS + // fast check + if (difficulty >= max64bit && ((const uint64_t *) &hash)[3] > 0) + return false; +#endif + // usual slow check + boost::multiprecision::uint512_t hashVal = 0; +#ifdef FORCE_FULL_128_BITS + for(int i = 0; i < 4; i++) { // highest word is zero +#else + for(int i = 1; i < 4; i++) { // highest word is zero +#endif + hashVal <<= 64; + hashVal |= swap64le(((const uint64_t *) &hash)[3 - i]); + } + return hashVal * difficulty <= max256bit; + } + + bool check_hash(const crypto::hash &hash, difficulty_type difficulty) { + if (difficulty <= max64bit) // if can convert to small difficulty - do it + return check_hash_64(hash, difficulty.convert_to()); + else + return check_hash_128(hash, difficulty); + } + + difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { + //cutoff DIFFICULTY_LAG + if(timestamps.size() > DIFFICULTY_WINDOW) + { + timestamps.resize(DIFFICULTY_WINDOW); + cumulative_difficulties.resize(DIFFICULTY_WINDOW); + } + + + size_t length = timestamps.size(); + assert(length == cumulative_difficulties.size()); + if (length <= 1) { + return 1; + } + static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small"); + assert(length <= DIFFICULTY_WINDOW); + sort(timestamps.begin(), timestamps.end()); + size_t cut_begin, cut_end; + static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large"); + if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) { + cut_begin = 0; + cut_end = length; + } else { + cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2; + cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT); + } + assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length); + uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin]; + if (time_span == 0) { + time_span = 1; + } + difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; + assert(total_work > 0); + boost::multiprecision::uint256_t res = (boost::multiprecision::uint256_t(total_work) * target_seconds + time_span - 1) / time_span; + if(res > max128bit) + return 0; // to behave like previous implementation, may be better return max128bit? + return res.convert_to(); + } + + difficulty_type next_difficulty_v2(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { + + // LWMA difficulty algorithm + // Copyright (c) 2017-2018 Zawy + // MIT license http://www.opensource.org/licenses/mit-license.php. + // This is an improved version of Tom Harding's (Deger8) "WT-144" + // Karbowanec, Masari, Bitcoin Gold, and Bitcoin Cash have contributed. + // See https://github.com/zawy12/difficulty-algorithms/issues/3 for other algos. + // Do not use "if solvetime < 0 then solvetime = 1" which allows a catastrophic exploit. + // T= target_solvetime; + // N=45, 55, 70, 90, 120 for T=600, 240, 120, 90, and 60 + + const int64_t T = static_cast(target_seconds); + size_t N = DIFFICULTY_WINDOW_V2; + + if (timestamps.size() > N) { + timestamps.resize(N + 1); + cumulative_difficulties.resize(N + 1); + } + size_t n = timestamps.size(); + assert(n == cumulative_difficulties.size()); + assert(n <= DIFFICULTY_WINDOW_V2); + // If new coin, just "give away" first 5 blocks at low difficulty + if ( n < 6 ) { return 1; } + // If height "n" is from 6 to N, then reset N to n-1. + else if (n < N+1) { N=n-1; } + + // To get an average solvetime to within +/- ~0.1%, use an adjustment factor. + // adjust=0.99 for 90 < N < 130 + const long double adjust = 0.998; + // The divisor k normalizes LWMA. + const long double k = N * (N + 1) / 2; + + long double LWMA(0), sum_inverse_D(0), harmonic_mean_D(0), nextDifficulty(0); + int64_t solveTime(0); + uint64_t difficulty(0), next_difficulty(0); + + // Loop through N most recent blocks. + for (size_t i = 1; i <= N; i++) { + solveTime = static_cast(timestamps[i]) - static_cast(timestamps[i - 1]); + solveTime = std::min((T * 7), std::max(solveTime, (-7 * T))); + difficulty = (cumulative_difficulties[i] - cumulative_difficulties[i - 1]).convert_to(); + LWMA += (int64_t)(solveTime * i) / k; + sum_inverse_D += 1 / static_cast(difficulty); + } + + // Keep LWMA sane in case something unforeseen occurs. + if (static_cast(boost::math::round(LWMA)) < T / 20) + LWMA = static_cast(T / 20); + + harmonic_mean_D = N / sum_inverse_D * adjust; + nextDifficulty = harmonic_mean_D * T / LWMA; + next_difficulty = static_cast(nextDifficulty); + + return next_difficulty; + } + std::string hex(difficulty_type v) { static const char chars[] = "0123456789abcdef"; diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h index ee9378eb9..0430544db 100644 --- a/src/cryptonote_basic/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -58,6 +58,7 @@ namespace cryptonote bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty); bool check_hash(const crypto::hash &hash, difficulty_type difficulty); difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); - + difficulty_type next_difficulty_v2(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); + std::string hex(difficulty_type v); } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 0e3fdacf7..e99716593 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -967,7 +967,13 @@ start: } size_t target = get_difficulty_target(); - difficulty_type diff = next_difficulty(timestamps, difficulties, target); + difficulty_type diff; + uint8_t version = get_current_hard_fork_version(); + if (version == 1) { + diff = next_difficulty(timestamps, difficulties, target); + } else { + diff = next_difficulty_v2(timestamps, difficulties, target); + } CRITICAL_REGION_LOCAL1(m_difficulty_lock); m_difficulty_for_next_block_top_hash = top_hash; @@ -1026,6 +1032,7 @@ size_t Blockchain::recalculate_difficulties(boost::optional start_heig std::vector timestamps; std::vector difficulties; + uint8_t version = get_current_hard_fork_version(); timestamps.reserve(DIFFICULTY_BLOCKS_COUNT + 1); difficulties.reserve(DIFFICULTY_BLOCKS_COUNT + 1); if (start_height > 1) @@ -1045,7 +1052,9 @@ size_t Blockchain::recalculate_difficulties(boost::optional start_heig for (uint64_t height = start_height; height <= top_height; ++height) { size_t target = DIFFICULTY_TARGET_V2; - difficulty_type recalculated_diff = next_difficulty(timestamps, difficulties, target); + difficulty_type recalculated_diff = (version == 1) + ? next_difficulty(timestamps, difficulties, target) + : next_difficulty_v2(timestamps, difficulties, target); boost::multiprecision::uint256_t recalculated_cum_diff_256 = boost::multiprecision::uint256_t(recalculated_diff) + last_cum_diff; CHECK_AND_ASSERT_THROW_MES(recalculated_cum_diff_256 <= std::numeric_limits::max(), "Difficulty overflow!"); @@ -1299,6 +1308,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: LOG_PRINT_L3("Blockchain::" << __func__); std::vector timestamps; std::vector cumulative_difficulties; + uint8_t version = get_current_hard_fork_version(); // if the alt chain isn't long enough to calculate the difficulty target // based on its blocks alone, need to get more blocks from the main chain @@ -1354,7 +1364,11 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: size_t target = DIFFICULTY_TARGET_V2; // calculate the difficulty target for the block and return it - return next_difficulty(timestamps, cumulative_difficulties, target); + if (version == 1) { + return next_difficulty(timestamps, cumulative_difficulties, target); + } else { + return next_difficulty_v2(timestamps, cumulative_difficulties, target); + } } //------------------------------------------------------------------ // This function does a sanity check on basic things that all miner @@ -3576,6 +3590,34 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context return true; } //------------------------------------------------------------------ +bool Blockchain::check_tx_type_and_version(const transaction& tx, tx_verification_context &tvc) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + const uint8_t hf_version = m_hardfork->get_current_version(); + + // Prior to v2, only allow TX v1/v2 + if (hf_version < HF_VERSION_ENABLE_N_OUTS) { + if (tx.version >= TRANSACTION_VERSION_N_OUTS) { + MERROR_VER("N-out TXs are not permitted prior to v" + std::to_string(HF_VERSION_ENABLE_N_OUTS)); + tvc.m_version_mismatch = true; + return false; + } + } + + // After v2 allow N-out TXs for TRANSFER ONLY + if (hf_version >= HF_VERSION_ENABLE_N_OUTS) { + if (tx.version >= TRANSACTION_VERSION_N_OUTS && tx.type != cryptonote::transaction_type::TRANSFER) { + MERROR("N-out TXs are only permitted for TRANSFER TX type"); + tvc.m_version_mismatch = true; + return false; + } + } + + return true; +} +//------------------------------------------------------------------ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const { LOG_PRINT_L3("Blockchain::" << __func__); From 30a293106772b88de81dae731a0dd5dc12e376b1 Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Mon, 9 Sep 2024 12:01:15 +0100 Subject: [PATCH 2/6] fixed copy/paste glitch on previous --- src/cryptonote_basic/difficulty.cpp | 251 +--------------------------- 1 file changed, 5 insertions(+), 246 deletions(-) diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 2965afdef..440c2fec7 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -28,247 +28,6 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include -#include -#include -#include -#include - -#include "int-util.h" -#include "crypto/hash.h" -#include "cryptonote_config.h" -#include "difficulty.h" - -#undef MONERO_DEFAULT_LOG_CATEGORY -#define MONERO_DEFAULT_LOG_CATEGORY "difficulty" - -namespace cryptonote { - - using std::size_t; - using std::uint64_t; - using std::vector; - -#if defined(__x86_64__) - static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { - low = mul128(a, b, &high); - } - -#else - - static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { - // __int128 isn't part of the standard, so the previous function wasn't portable. mul128() in Windows is fine, - // but this portable function should be used elsewhere. Credit for this function goes to latexi95. - - uint64_t aLow = a & 0xFFFFFFFF; - uint64_t aHigh = a >> 32; - uint64_t bLow = b & 0xFFFFFFFF; - uint64_t bHigh = b >> 32; - - uint64_t res = aLow * bLow; - uint64_t lowRes1 = res & 0xFFFFFFFF; - uint64_t carry = res >> 32; - - res = aHigh * bLow + carry; - uint64_t highResHigh1 = res >> 32; - uint64_t highResLow1 = res & 0xFFFFFFFF; - - res = aLow * bHigh; - uint64_t lowRes2 = res & 0xFFFFFFFF; - carry = res >> 32; - - res = aHigh * bHigh + carry; - uint64_t highResHigh2 = res >> 32; - uint64_t highResLow2 = res & 0xFFFFFFFF; - - //Addition - - uint64_t r = highResLow1 + lowRes2; - carry = r >> 32; - low = (r << 32) | lowRes1; - r = highResHigh1 + highResLow2 + carry; - uint64_t d3 = r & 0xFFFFFFFF; - carry = r >> 32; - r = highResHigh2 + carry; - high = d3 | (r << 32); - } - -#endif - - static inline bool cadd(uint64_t a, uint64_t b) { - return a + b < a; - } - - static inline bool cadc(uint64_t a, uint64_t b, bool c) { - return a + b < a || (c && a + b == (uint64_t) -1); - } - - bool check_hash_64(const crypto::hash &hash, uint64_t difficulty) { - uint64_t low, high, top, cur; - // First check the highest word, this will most likely fail for a random hash. - mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high); - if (high != 0) { - return false; - } - mul(swap64le(((const uint64_t *) &hash)[0]), difficulty, low, cur); - mul(swap64le(((const uint64_t *) &hash)[1]), difficulty, low, high); - bool carry = cadd(cur, low); - cur = high; - mul(swap64le(((const uint64_t *) &hash)[2]), difficulty, low, high); - carry = cadc(cur, low, carry); - carry = cadc(high, top, carry); - return !carry; - } - - uint64_t next_difficulty_64(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { - - if(timestamps.size() > DIFFICULTY_WINDOW) - { - timestamps.resize(DIFFICULTY_WINDOW); - cumulative_difficulties.resize(DIFFICULTY_WINDOW); - } - - - size_t length = timestamps.size(); - assert(length == cumulative_difficulties.size()); - if (length <= 1) { - return 1; - } - static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small"); - assert(length <= DIFFICULTY_WINDOW); - sort(timestamps.begin(), timestamps.end()); - size_t cut_begin, cut_end; - static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large"); - if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) { - cut_begin = 0; - cut_end = length; - } else { - cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2; - cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT); - } - assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length); - uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin]; - if (time_span == 0) { - time_span = 1; - } - uint64_t total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; - assert(total_work > 0); - uint64_t low, high; - mul(total_work, target_seconds, low, high); - // blockchain errors "difficulty overhead" if this function returns zero. - // TODO: consider throwing an exception instead - if (high != 0 || low + time_span - 1 < low) { - return 0; - } - return (low + time_span - 1) / time_span; - } - -#if defined(_MSC_VER) -#ifdef max -#undef max -#endif -#endif - - const difficulty_type max64bit(std::numeric_limits::max()); - const boost::multiprecision::uint256_t max128bit(std::numeric_limits::max()); - const boost::multiprecision::uint512_t max256bit(std::numeric_limits::max()); - -#define FORCE_FULL_128_BITS - - bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty) { -#ifndef FORCE_FULL_128_BITS - // fast check - if (difficulty >= max64bit && ((const uint64_t *) &hash)[3] > 0) - return false; -#endif - // usual slow check - boost::multiprecision::uint512_t hashVal = 0; -#ifdef FORCE_FULL_128_BITS - for(int i = 0; i < 4; i++) { // highest word is zero -#else - for(int i = 1; i < 4; i++) { // highest word is zero -#endif - hashVal <<= 64; - hashVal |= swap64le(((const uint64_t *) &hash)[3 - i]); - } - return hashVal * difficulty <= max256bit; - } - - bool check_hash(const crypto::hash &hash, difficulty_type difficulty) { - if (difficulty <= max64bit) // if can convert to small difficulty - do it - return check_hash_64(hash, difficulty.convert_to()); - else - return check_hash_128(hash, difficulty); - } - - difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { - //cutoff DIFFICULTY_LAG - if(timestamps.size() > DIFFICULTY_WINDOW) - { - timestamps.resize(DIFFICULTY_WINDOW); - cumulative_difficulties.resize(DIFFICULTY_WINDOW); - } - - - size_t length = timestamps.size(); - assert(length == cumulative_difficulties.size()); - if (length <= 1) { - return 1; - } - static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small"); - assert(length <= DIFFICULTY_WINDOW); - sort(timestamps.begin(), timestamps.end()); - size_t cut_begin, cut_end; - static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large"); - if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) { - cut_begin = 0; - cut_end = length; - } else { - cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2; - cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT); - } - assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length); - uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin]; - if (time_span == 0) { - time_span = 1; - } - difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; - assert(total_work > 0); - boost::multiprecision::uint256_t res = (boost::multiprecision::uint256_t(total_work) * target_seconds + time_span - 1) / time_span; - if(res > max128bit) - return 0; // to behave like previous implementation, may be better return max128bit? - return res.convert_to(); - } - - // Copyright (c) 2014-2023, 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. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - #include #include #include @@ -494,7 +253,7 @@ namespace cryptonote { // N=45, 55, 70, 90, 120 for T=600, 240, 120, 90, and 60 const int64_t T = static_cast(target_seconds); - size_t N = DIFFICULTY_WINDOW_V2; + size_t N = DIFFICULTY_WINDOW; if (timestamps.size() > N) { timestamps.resize(N + 1); @@ -502,18 +261,18 @@ namespace cryptonote { } size_t n = timestamps.size(); assert(n == cumulative_difficulties.size()); - assert(n <= DIFFICULTY_WINDOW_V2); + assert(n <= DIFFICULTY_WINDOW); // If new coin, just "give away" first 5 blocks at low difficulty if ( n < 6 ) { return 1; } // If height "n" is from 6 to N, then reset N to n-1. else if (n < N+1) { N=n-1; } - + // To get an average solvetime to within +/- ~0.1%, use an adjustment factor. // adjust=0.99 for 90 < N < 130 const long double adjust = 0.998; // The divisor k normalizes LWMA. const long double k = N * (N + 1) / 2; - + long double LWMA(0), sum_inverse_D(0), harmonic_mean_D(0), nextDifficulty(0); int64_t solveTime(0); uint64_t difficulty(0), next_difficulty(0); @@ -537,7 +296,7 @@ namespace cryptonote { return next_difficulty; } - + std::string hex(difficulty_type v) { static const char chars[] = "0123456789abcdef"; From d1eed6e9ff599a627ba058996e3731c9c4d7687a Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Sat, 14 Sep 2024 11:06:23 +0100 Subject: [PATCH 3/6] interim commit - NOT TESTED --- src/blockchain_db/lmdb/db_lmdb.cpp | 12 ++- src/blockchain_utilities/CMakeLists.txt | 31 +++++++ src/blocks/checkpoints.dat | Bin 3716 -> 6212 bytes src/cryptonote_basic/cryptonote_basic.h | 16 +++- .../cryptonote_boost_serialization.h | 54 +++++++----- .../cryptonote_format_utils.cpp | 2 +- src/cryptonote_basic/verification_context.h | 1 + src/cryptonote_config.h | 12 ++- src/cryptonote_core/blockchain.cpp | 18 +++- src/cryptonote_core/blockchain.h | 13 +++ src/cryptonote_core/cryptonote_core.cpp | 4 +- src/cryptonote_core/cryptonote_tx_utils.cpp | 63 ++++++++++++-- src/cryptonote_core/tx_pool.cpp | 6 +- src/cryptonote_core/tx_verification_utils.cpp | 2 +- src/hardforks/hardforks.cpp | 4 +- src/simplewallet/simplewallet.cpp | 17 ++-- src/wallet/wallet2.cpp | 82 +++++++++++++++--- 17 files changed, 273 insertions(+), 64 deletions(-) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 4d8f827c4..9b7ce773a 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1261,7 +1261,17 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons yield_tx_info yield_data; yield_data.block_height = m_height; yield_data.tx_hash = tx_hash; - yield_data.return_address = tx.return_address; + if (tx.version == TRANSACTION_VERSION_2_OUTS) { + if (tx.return_address == crypto::null_pkey) + throw0(DB_ERROR("missing return_address entry (needed to create yield data for PROTOCOL_TX) - v2 STAKE")); + yield_data.return_address = tx.return_address; + } else if (tx.version >= TRANSACTION_VERSION_N_OUTS) { + if (tx.return_address_list.empty()) + throw0(DB_ERROR("no return_address_list entry (needed to create yield data for the PROTOCOL_TX)")); + else if (tx.return_address_list.size() > 1) + throw0(DB_ERROR("too many return_address_list entries provided (only one needed to create yield data for the PROTOCOL_TX)")); + yield_data.return_address = tx.return_address_list[0]; + } yield_data.locked_coins = tx.amount_burnt; if (tx.vin.empty()) throw0(DB_ERROR("tx.vin is empty (needed to create yield data for the PROTOCOL_TX)")); diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index a9163072b..6fefd9a4d 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -133,6 +133,16 @@ monero_private_headers(blockchain_stats ${blockchain_stats_private_headers}) +set(blockchain_scanner_sources + blockchain_scanner.cpp + ) + +set(blockchain_scanner_private_headers) + +monero_private_headers(blockchain_scanner + ${blockchain_scanner_private_headers}) + + monero_add_executable(blockchain_import ${blockchain_import_sources} ${blockchain_import_private_headers}) @@ -281,6 +291,27 @@ set_property(TARGET blockchain_depth OUTPUT_NAME "salvium-blockchain-depth") install(TARGETS blockchain_depth DESTINATION bin) +monero_add_executable(blockchain_scanner + ${blockchain_scanner_sources} + ${blockchain_scanner_private_headers}) + +target_link_libraries(blockchain_scanner + PRIVATE + cryptonote_core + blockchain_db + version + epee + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) + +set_property(TARGET blockchain_scanner + PROPERTY + OUTPUT_NAME "salvium-blockchain-scanner") +install(TARGETS blockchain_scanner DESTINATION bin) + monero_add_executable(blockchain_stats ${blockchain_stats_sources} ${blockchain_stats_private_headers}) diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat index 247c00a784ce16b85b661b6adcd7590b1ff99ec0..784218d3a06edadb2ec5b963b8c55820bd8e3759 100644 GIT binary patch delta 2526 zcmV<42_g1`9mFsJVX*;&4u3G6<|d-Uf_-ht(cV|(_d5r5LuAljF{pt+iYJzg0Jiu` z|2N?h;u?lD%Wd$?b7yJwXX^0tSU?ExTNehW%sEYh%pFOa<|Y~m z{_aRQe=USOHxoNizN+jx)l{`S8@Ky>JmDbC^?oTbG)a>pO3_ZrLu=bPe9RF~1+L#Y z%|`IprzB#kKKyxVhRzcmc^GidOrl6CdStWgNPc(XWU>PnuAtxbtE~D_yV()Gbi(o& z`2&p(<)wlfj|}_YV}IZ}$m;O(32!^oFKIE>2zrcR&R8@30YBDK2)5wEK{?aq!#@YlA7=z~GIuilb34ty?Z9e>cKcyc1_OalKN`FOTL zjj7&>ljYn&DvqQS1>@8K1SA$RA!NBvI^XQOXA$b{a!Y1}MN60`0^eZ{Vbx?oaYDHe zA#0z*$DpBCk5ApI({p=}!aq)*=+mHtWdPCD(04%1uz6zaM&hk#+tyyTtpUX1FbEeeG&0i*L>N* z0G|x0lPJtO&&7*%!Ev@MWdEJ^X}D8((iMvbGYG~9Qh!M3&rP3`N3a*owR_Mi1VwQ2 zhN#Vpj|7~WTj6_;0z=f8+rDY`@q{QSP_5&Qc&mipMr;2@swLxR;z#XLB!5ZA8_RvC z42m)oh@j6~oPPnLH~kna6-Rkyt{;%O@LZ8}tsS$uu_k098=O+&EJq9Q?F0{9b&G_>(|o&;^0| z?wUtJX`8}s*UCoLU2Oe37xCUAH=fd(c@l%LV{G&760mjwFb<|B0DvlVNRG)8mKM-i z*2>gn_5nPm*2%69K1Te$W&15Vdch~Z3J^RDMt>=RB@4Kz-gl`>+;f;SK$1X!*A0!i z#_~{sm||_D?MOdjRj4H5PaT&?Y{MR5To%499qGH4de`#ViE1K`h+lHw9yP&HJuX2g_79T^KD)3&irn6=# z;P9)wBaVA!DG0glxA-Er`W z;ZR`Zny~vgn!LwVEaXyIL2Q1dDf;Z5v_N}P2|nkDPvEHKhn1>XuLx|)dHXm&sek1} z^;KP$4nYwV&!C(ia7^7tbU)qVX9H9_@hhYfgDxfB6D~d_bko_jV}Mq#`fp*OZ4G+j z>VG($GuifSh`d2id09?*HCvf``F07~b44skfRS*wl=Z0yUB9cx4~UyCb(m*R5x~fn zr$*jyYyh4~;0krv8$r#8;TmIUSbsB7@9ovatZrm=0Nai$5MP=CJge_T!n|?N;W`W5 zGpWPJgZMi?#%m6W$SeE8*a9DQLEeC(vV$e~tuds}^n=I!)(a-|V6M8lHGg4=X-nAE z`{ZUYrPDY&+UmXw!qO%BX=Xo`%Wt1u4#)3IB}7R=ULHnD=qKF|=g4Mw0mS&IDE_p8 zVu51gQ!(P$MQe@(Gy5cSSV}dld0V|*0z9JFUIk`!rv`KaE>rl{HH(@lRrb8kh;V8k zUuw^YSlrfsY-lD4Va88cJAXFEea`U9nQPns82gEAxD|R@;FeRb{v~e-UD|cZ&3gBH ziuxZzUdx#4>;k5ORB2_Th<<`&Vm7H*jgWzjXv67H!`nkXbWKR>o!S~QxHzLK4HVWN za5O*8qoSWpa~H%N+tHcbHVUsDZpy~0#syJfOPjK;M;WWD6J0PmAwYic(!aRL2VWwlMtDV-)ofM{_DxPs`Y5P`QiP945Vt^~OFErR`Z zIoOS@q(sK(zQTl09RXnB6F(&*wFSJ1 zSfo1ZEuYZPmrc8(du!MuuT1%K@1^!&`467Oqwp--OjYVqOn)udvzEGMoODbg`o38@ zxyaf>Ja`Yas8LRSGt}X^16hPe{8i1)8I4sn+Po%!!cmdl)+|%MER7EuVJ=(cl)PiY zdXn}cCFvo^!tusTbYfBpLW^yR&{bIqKXf#tdRBFr$_f`Ut16oX0ytZx|2J6lkhyvWRN9O1bSMLk01(ZP&VR98%B-pP ze^Rw(hcm7^8+fjbE>hT;xpKP^tN*@PJtuEyp?(i{@xHEIyk+}PwQr`R extra; // TX type cryptonote::transaction_type type; - // Return address crypto::public_key return_address; + // Return address list (must be at least 1 and at most BULLETPROOF_MAX_OUTPUTS-1 - the "-1" is for the change output) + std::vector return_address_list; + //return_address_change_mask + std::vector return_address_change_mask; // Return TX public key crypto::public_key return_pubkey; // Source asset type @@ -224,8 +227,13 @@ namespace cryptonote if (type != cryptonote::transaction_type::PROTOCOL) { VARINT_FIELD(amount_burnt) if (type != cryptonote::transaction_type::MINER) { - FIELD(return_address) - FIELD(return_pubkey) + if (type == cryptonote::transaction_type::TRANSFER && version >= TRANSACTION_VERSION_N_OUTS) { + FIELD(return_address_list) + FIELD(return_address_change_mask) + } else { + FIELD(return_address) + FIELD(return_pubkey) + } FIELD(source_asset_type) FIELD(destination_asset_type) VARINT_FIELD(amount_slippage_limit) @@ -244,6 +252,8 @@ namespace cryptonote extra.clear(); type = cryptonote::transaction_type::UNSET; return_address = crypto::null_pkey; + return_address_list.clear(); + return_address_change_mask.clear(); return_pubkey = crypto::null_pkey; source_asset_type.clear(); destination_asset_type.clear(); diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 759e51290..a3c7a16ce 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -166,24 +166,7 @@ namespace boost inline void serialize(Archive &a, cryptonote::transaction_prefix &x, const boost::serialization::version_type ver) { a & x.version; - a & x.vin; - a & x.vout; - a & x.extra; - a & x.type; - if (x.type != cryptonote::transaction_type::MINER && x.type != cryptonote::transaction_type::PROTOCOL) { - a & x.return_address; - a & x.return_pubkey; - a & x.source_asset_type; - a & x.destination_asset_type; - a & x.amount_burnt; - a & x.amount_slippage_limit; - } - } - - template - inline void serialize(Archive &a, cryptonote::transaction &x, const boost::serialization::version_type ver) - { - a & x.version; + a & x.unlock_time; a & x.vin; a & x.vout; a & x.extra; @@ -191,8 +174,39 @@ namespace boost if (x.type != cryptonote::transaction_type::PROTOCOL) { a & x.amount_burnt; if (x.type != cryptonote::transaction_type::MINER) { - a & x.return_address; - a & x.return_pubkey; + if (x.type == cryptonote::transaction_type::TRANSFER && x.version >= TRANSACTION_VERSION_N_OUTS) { + a & x.return_address_list; + a & x.return_address_change_mask; + } else { + a & x.return_address; + a & x.return_pubkey; + } + a & x.source_asset_type; + a & x.destination_asset_type; + a & x.amount_slippage_limit; + } + } + } + + template + inline void serialize(Archive &a, cryptonote::transaction &x, const boost::serialization::version_type ver) + { + a & x.version; + a & x.unlock_time; + a & x.vin; + a & x.vout; + a & x.extra; + a & x.type; + if (x.type != cryptonote::transaction_type::PROTOCOL) { + a & x.amount_burnt; + if (x.type != cryptonote::transaction_type::MINER) { + if (x.type == cryptonote::transaction_type::TRANSFER && x.version >= TRANSACTION_VERSION_N_OUTS) { + a & x.return_address_list; + a & x.return_address_change_mask; + } else { + a & x.return_address; + a & x.return_pubkey; + } a & x.source_asset_type; a & x.destination_asset_type; a & x.amount_slippage_limit; diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 9293157d4..0c12680cb 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1724,7 +1724,7 @@ namespace cryptonote blobdata blob = t_serializable_object_to_blob(static_cast(b)); crypto::hash tree_root_hash = get_tx_tree_hash(b); blob.append(reinterpret_cast(&tree_root_hash), sizeof(tree_root_hash)); - blob.append(tools::get_varint_data(b.tx_hashes.size()+1)); + blob.append(tools::get_varint_data(b.tx_hashes.size() + (b.major_version >= HF_VERSION_ENABLE_N_OUTS ? 2 : 1))); return blob; } //--------------------------------------------------------------- diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index 10a16a8c1..27f6a6c03 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -59,6 +59,7 @@ namespace cryptonote bool m_fee_too_low; bool m_too_few_outputs; bool m_tx_extra_too_big; + bool m_version_mismatch; // TX version wrong for the currently-active HF version }; struct block_verification_context diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 59797a6e1..928e2ce37 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -42,7 +42,9 @@ #define CRYPTONOTE_MAX_TX_PER_BLOCK 0x10000000 #define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 #define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 -#define CURRENT_TRANSACTION_VERSION 2 +#define CURRENT_TRANSACTION_VERSION 3 +#define TRANSACTION_VERSION_2_OUTS 2 +#define TRANSACTION_VERSION_N_OUTS 3 #define CURRENT_BLOCK_MAJOR_VERSION 1 #define CURRENT_BLOCK_MINOR_VERSION 1 #define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 @@ -212,9 +214,11 @@ #define HF_VERSION_LONG_TERM_BLOCK_WEIGHT 2 #define HF_VERSION_2021_SCALING 2 -#define HF_VERSION_ENABLE_CONVERT 2 -#define HF_VERSION_ENABLE_ORACLE 2 -#define HF_VERSION_SLIPPAGE_YIELD 2 +#define HF_VERSION_ENABLE_N_OUTS 2 + +#define HF_VERSION_ENABLE_CONVERT 255 +#define HF_VERSION_ENABLE_ORACLE 255 +#define HF_VERSION_SLIPPAGE_YIELD 255 #define TESTNET_VERSION 11 #define STAGENET_VERSION 1 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index e99716593..43de69729 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1462,6 +1462,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl switch (version) { case HF_VERSION_BULLETPROOF_PLUS: + case HF_VERSION_ENABLE_N_OUTS: if (b.miner_tx.amount_burnt > 0) { CHECK_AND_ASSERT_MES(money_in_use + b.miner_tx.amount_burnt > money_in_use, false, "miner transaction is overflowed by amount_burnt"); money_in_use += b.miner_tx.amount_burnt; @@ -3597,13 +3598,22 @@ bool Blockchain::check_tx_type_and_version(const transaction& tx, tx_verificatio const uint8_t hf_version = m_hardfork->get_current_version(); - // Prior to v2, only allow TX v1/v2 + // Prior to v2, only allow TX v2 if (hf_version < HF_VERSION_ENABLE_N_OUTS) { + + // Check for N-out TXs if (tx.version >= TRANSACTION_VERSION_N_OUTS) { MERROR_VER("N-out TXs are not permitted prior to v" + std::to_string(HF_VERSION_ENABLE_N_OUTS)); tvc.m_version_mismatch = true; return false; } + + // Check for v1 TXs - genesis block protocol_tx exception required! + if (tx.version == 1 && epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(tx)) == "4f78ff511e860acd03138737a71505eb62eb78b620e180e58c8e13ed0e1e3e19") { + MERROR("v1 TXs are not permitted"); + tvc.m_version_mismatch = true; + return false; + } } // After v2 allow N-out TXs for TRANSFER ONLY @@ -3632,7 +3642,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys, const uint8_t &hf_version) { PERF_TIMER(expand_transaction_2); - CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2"); + CHECK_AND_ASSERT_MES(tx.version == 2 || tx.version == 3, false, "Transaction version is not 2/3"); rct::rctSig &rv = tx.rct_signatures; @@ -3821,7 +3831,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } // min/max tx version based on HF, and we accept v1 txes if having a non mixable - const size_t max_tx_version = (hf_version < HF_VERSION_SLIPPAGE_YIELD) ? 2 : CURRENT_TRANSACTION_VERSION; + const size_t max_tx_version = (hf_version >= HF_VERSION_ENABLE_N_OUTS) ? TRANSACTION_VERSION_N_OUTS : TRANSACTION_VERSION_2_OUTS; if (tx.version > max_tx_version) { MERROR_VER("transaction version " << (unsigned)tx.version << " is higher than max accepted version " << max_tx_version); @@ -6098,7 +6108,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "7f60b4980ea16b32e3f9fc1959d9d4116ba91f2b067bd70b3e21c44520096d14"; +static const char expected_block_hashes_hash[] = "b8639efef99951207b401131ed9f88e37c856a693d237fc6fad349b929aa1059"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index d0aa7c6c5..9afbbfefc 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -725,6 +725,19 @@ namespace cryptonote */ bool check_tx_outputs(const transaction& tx, tx_verification_context &tvc) const; + /** + * @brief check that a transaction's version & type conforms to current standards + * + * This function checks, for example at the time of this writing, that + * the TX version and type is supported by the current HF version on-chain. + * + * @param tx the transaction to check the version and type of + * @param tvc returned info about tx verification + * + * @return false if the TX version and/or type is unsupported, otherwise true + */ + bool check_tx_type_and_version(const transaction& tx, tx_verification_context &tvc) const; + /** * @brief gets the block weight limit based on recent blocks * diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 7f5620e75..252cddfd2 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -842,8 +842,8 @@ namespace cryptonote } bad_semantics_txes_lock.unlock(); - uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); - const size_t max_tx_version = (version < HF_VERSION_SLIPPAGE_YIELD) ? 2 : CURRENT_TRANSACTION_VERSION; + uint8_t hf_version = m_blockchain_storage.get_current_hard_fork_version(); + const size_t max_tx_version = (hf_version >= HF_VERSION_ENABLE_N_OUTS) ? TRANSACTION_VERSION_N_OUTS : TRANSACTION_VERSION_2_OUTS; if (tx.version == 0 || tx.version > max_tx_version) { // v2 is the latest one we know diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index dc9250b1f..ab0bde122 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -46,6 +46,11 @@ using namespace epee; #include "ringct/rctSigs.h" #include "oracle/asset_types.h" +extern "C" +{ +#include "crypto/keccak.h" +#include "crypto/crypto-ops.h" +} using namespace crypto; namespace cryptonote @@ -583,6 +588,7 @@ namespace cryptonote // Different forks take a different proportion of the block_reward for stakers switch (hard_fork_version) { case HF_VERSION_BULLETPROOF_PLUS: + case HF_VERSION_ENABLE_N_OUTS: // SRCG: subtract 20% that will be rewarded to staking users CHECK_AND_ASSERT_MES(tx.amount_burnt == 0, false, "while creating outs: amount_burnt is nonzero"); tx.amount_burnt = amount / 5; @@ -653,8 +659,11 @@ namespace cryptonote tx.set_null(); amount_keys.clear(); - if (hf_version >= HF_VERSION_SLIPPAGE_YIELD) { - tx.version = 3; + tx.type = (tx_type == cryptonote::transaction_type::RETURN) ? cryptonote::TRANSFER : tx_type; + + // Configure the correct TX version for the current HF + TX type + if (hf_version >= HF_VERSION_ENABLE_N_OUTS && tx.type == cryptonote::transaction_type::TRANSFER) { + tx.version = TRANSACTION_VERSION_N_OUTS; } else { tx.version = 2; } @@ -662,8 +671,6 @@ namespace cryptonote tx.extra = extra; crypto::public_key txkey_pub; - tx.type = (tx_type == cryptonote::transaction_type::RETURN) ? cryptonote::TRANSFER : tx_type; - tx.source_asset_type = source_asset; tx.destination_asset_type = dest_asset; @@ -891,7 +898,7 @@ namespace cryptonote need_additional_txkeys, additional_tx_keys, additional_tx_public_keys, amount_keys, out_eph_public_key, use_view_tags, view_tag); - + tx_out out; cryptonote::set_tx_out(dst_entr.amount, dst_entr.asset_type, dst_entr.is_change ? 0 : unlock_time, out_eph_public_key, use_view_tags, view_tag, out); tx.vout.push_back(out); @@ -902,7 +909,51 @@ namespace cryptonote remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys)); - if (tx.type == cryptonote::transaction_type::TRANSFER || tx.type == cryptonote::transaction_type::STAKE) { + if (hf_version >= HF_VERSION_ENABLE_N_OUTS && tx.type == cryptonote::transaction_type::TRANSFER) { + + // Get the output public key for the change output + crypto::public_key P_change = crypto::null_pkey; + CHECK_AND_ASSERT_MES(tx.vout.size() >= 2, false, "Internal error - too few outputs for TRANSFER tx"); + CHECK_AND_ASSERT_MES(cryptonote::get_output_public_key(tx.vout[change_index], P_change), false, "Internal error - failed to get TX change output public key"); + CHECK_AND_ASSERT_MES(P_change != crypto::null_pkey, false, "Internal error - not found TX change output for TRANSFER tx"); + + // Calculate the F points and change mask for every destination + for (size_t op_index=0; op_index cryptonote::transaction_type::MAX) { - LOG_PRINT_L1("Transaction with id= "<< id << " has invalid type " << (uint8_t)tx.type); + if (!m_blockchain.check_tx_type_and_version(tx, tvc)) { + LOG_PRINT_L1("Transaction with id= "<< id << " has invalid type " << (uint8_t)tx.type << " and/or version " << tx.version); tvc.m_verifivation_failed = true; return false; } diff --git a/src/cryptonote_core/tx_verification_utils.cpp b/src/cryptonote_core/tx_verification_utils.cpp index ffa2c514d..e4bc74b4f 100644 --- a/src/cryptonote_core/tx_verification_utils.cpp +++ b/src/cryptonote_core/tx_verification_utils.cpp @@ -130,7 +130,7 @@ bool ver_rct_non_semantics_simple_cached // mixring. Future versions of the protocol may differ in this regard, but if this assumptions // holds true in the future, enable the verification hash by modifying the `untested_tx` // condition below. - const bool untested_tx = tx.version > 2 || tx.rct_signatures.type > rct::RCTTypeBulletproofPlus; + const bool untested_tx = tx.version > 3 || tx.rct_signatures.type > rct::RCTTypeBulletproofPlus; VER_ASSERT(!untested_tx, "Unknown TX type. Make sure RCT cache works correctly with this type and then enable it in the code here."); // Don't cache older (or newer) rctSig types diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 5c7498454..ceb7cac31 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -45,8 +45,8 @@ const hardfork_t testnet_hard_forks[] = { // version 1 from the start of the blockchain { 1, 1, 0, 1341378000 }, - // version 2 starts from block 1000, which is on or around the 23rd of November, 2015. Fork time finalised on 2015-11-20. No fork voting occurs for the v2 fork. - //{ 2, 1000, 0, 1445355000 }, + // version 2 starts from block 250 + { 2, 250, 0, 1445355000 }, }; const size_t num_testnet_hard_forks = sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]); const uint64_t testnet_hard_fork_version_1_till = ((uint64_t)-1); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 77f4ad981..253d6635e 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -7249,7 +7249,7 @@ bool simple_wallet::locked_transfer(const std::vector &args_) // TODO: add locked versions if (args_.size() < 2) { - PRINT_USAGE(USAGE_TRANSFER); + PRINT_USAGE(USAGE_LOCKED_TRANSFER); return true; } @@ -8043,10 +8043,17 @@ bool simple_wallet::return_payment(const std::vector &args_) return true; } - // Verify we have a valid return_address and tx_pubkey - if (td.m_tx.return_address == crypto::null_pkey || td.m_tx.return_pubkey != crypto::null_pkey) { - fail_msg_writer() << tr("invalid return_address/return_pubkey for txid ") << args_[0]; - return true; + if (td.m_tx.version >= HF_VERSION_ENABLE_N_OUTS) { + if (td.m_tx.return_address_list.empty() || td.m_tx.return_address_change_mask.empty()) { + fail_msg_writer() << tr("invalid return_address_list for txid ") << args_[0]; + return true; + } + } else { + // Verify we have a valid return_address and tx_pubkey + if (td.m_tx.return_address == crypto::null_pkey || td.m_tx.return_pubkey != crypto::null_pkey) { + fail_msg_writer() << tr("invalid return_address/return_pubkey for txid ") << args_[0]; + return true; + } } // Check that we have the key image information, and that it is usable diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e8fe2b4f8..803aa79a6 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -11383,26 +11383,84 @@ std::vector wallet2::create_transactions_return(std::vector uint32_t priority = adjust_priority(0); std::vector extra; // No need for a TX extra beyond that which will be calculated herein + // To return a payment, we need to know the y value to process the F value + // ...but the y value is calculated differently depending on the original TX + ec_scalar y; + crypto::public_key return_address = null_pkey; + // Get P_change from the TX crypto::public_key P_change = crypto::null_pkey; - size_t change_index = (td_origin.m_internal_output_index == 0) ? 1 : 0; + size_t change_index; + uint32_t hf_version = get_current_hard_fork(); + if (hf_version >= HF_VERSION_ENABLE_N_OUTS) { + + // Calculate z_i (the shared secret between sender and ourselves for the original TX) + crypto::public_key txkey_pub = null_pkey; + const std::vector in_additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td_origin.m_tx); + if (td_origin.m_tx.vout.size()>2) { + THROW_WALLET_EXCEPTION_IF(td_origin.m_internal_output_index >= in_additional_tx_pub_keys.size(), + error::wallet_internal_error, + "at create_transactions_return(): incorrect number of additional TX pubkeys in origin for return_payment"); + txkey_pub = in_additional_tx_pub_keys[td_origin.m_internal_output_index]; + } else { + txkey_pub = get_tx_pub_key_from_extra(td_origin.m_tx); + } + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!generate_key_derivation(txkey_pub, m_account.get_keys().m_view_secret_key, derivation), error::wallet_internal_error, "at create_transactions_return(): failed to generate_key_derivation"); + crypto::secret_key z_i; + derivation_to_scalar(derivation, td_origin.m_internal_output_index, z_i); + + // Calculate the y value for return_payment support + struct { + char domain_separator[8]; + rct::key amount_key; + } buf; + std::memset(buf.domain_separator, 0x0, sizeof(buf.domain_separator)); + std::strncpy(buf.domain_separator, "RETURN", 7); + buf.amount_key = rct::sk2rct(z_i); + crypto::hash_to_scalar(&buf, sizeof(buf), y); + + // The change_index needs decoding too + uint8_t eci_data = td_origin.m_tx.return_address_change_mask[td_origin.m_internal_output_index]; + + // Calculate the encrypted_change_index data for this output + std::memset(buf.domain_separator, 0x0, sizeof(buf.domain_separator)); + std::strncpy(buf.domain_separator, "CHG_IDX", 8); + crypto::secret_key eci_out; + keccak((uint8_t *)&buf, sizeof(buf), (uint8_t*)&eci_out, sizeof(eci_out)); + assert(false); + change_index = eci_data ^ eci_out.data[0]; + + return_address = td_origin.m_tx.return_address_list[td_origin.m_internal_output_index]; + + } else { + + // Calculate y + struct { + char domain_separator[8]; + crypto::public_key pubkey; + } buf; + std::memset(buf.domain_separator, 0x0, sizeof(buf.domain_separator)); + std::strncpy(buf.domain_separator, "RETURN", 6); + buf.pubkey = P_change; + crypto::hash_to_scalar(&buf, sizeof(buf), y); + + // Change index is the one we didn't receive + change_index = (td_origin.m_internal_output_index == 0) ? 1 : 0; + + return_address = td_origin.m_tx.return_address; + } THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_public_key(td_origin.m_tx.vout[change_index], P_change), error::wallet_internal_error, "Failed to identify change output"); - + // Calculate yF - ec_scalar y; - struct { - char domain_separator[8]; - crypto::public_key pubkey; - } buf; - std::memset(buf.domain_separator, 0x0, sizeof(buf.domain_separator)); - std::strncpy(buf.domain_separator, "RETURN", 6); - buf.pubkey = P_change; - crypto::hash_to_scalar(&buf, sizeof(buf), y); - rct::key key_y = (rct::key&)(y); rct::key key_F = (rct::key&)(td_origin.m_tx.return_address); rct::key key_yF = rct::scalarmultKey(key_F, key_y); + // Sanity check that we aren't attempting to return our own TX output to ourselves + rct::key key_yF_check = rct::scalarmultKey(rct::sk2rct(m_account.get_keys().m_view_secret_key), rct::pk2rct(P_change)); + THROW_WALLET_EXCEPTION_IF(key_yF_check == key_yF, error::wallet_internal_error, "Attempting to return a payment to ourself"); + // Build the subaddress to send the return to cryptonote::account_public_address address; address.m_spend_public_key = P_change; From 8b2b039036620327c7b6d6cbc3cba3cae3d47401 Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Tue, 8 Oct 2024 12:03:20 +0100 Subject: [PATCH 4/6] N-out-TX support working for simple cases - needs edge case testing still --- src/wallet/wallet2.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 803aa79a6..8b03f8369 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -11298,8 +11298,9 @@ std::vector wallet2::create_transactions_all(uint64_t below // gather all dust and non-dust outputs of specified subaddress (if any) and below specified threshold (if any) bool fund_found = false; - for (size_t i = 0; i < m_transfers.size(); ++i) + for (size_t idx = 0; idx < m_transfers_indices[asset_type].size(); idx++) { + size_t i = m_transfers_indices[asset_type][idx]; const transfer_details& td = m_transfers[i]; if (m_ignore_fractional_outputs && td.amount() < fractional_threshold) { @@ -11392,21 +11393,23 @@ std::vector wallet2::create_transactions_return(std::vector crypto::public_key P_change = crypto::null_pkey; size_t change_index; uint32_t hf_version = get_current_hard_fork(); - if (hf_version >= HF_VERSION_ENABLE_N_OUTS) { + if (hf_version >= HF_VERSION_ENABLE_N_OUTS && td_origin.m_tx.version >= TRANSACTION_VERSION_N_OUTS) { // Calculate z_i (the shared secret between sender and ourselves for the original TX) - crypto::public_key txkey_pub = null_pkey; + crypto::public_key txkey_pub = null_pkey; // R const std::vector in_additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td_origin.m_tx); - if (td_origin.m_tx.vout.size()>2) { - THROW_WALLET_EXCEPTION_IF(td_origin.m_internal_output_index >= in_additional_tx_pub_keys.size(), + if (in_additional_tx_pub_keys.size() != 0) { + THROW_WALLET_EXCEPTION_IF(in_additional_tx_pub_keys.size() != td_origin.m_tx.vout.size(), error::wallet_internal_error, - "at create_transactions_return(): incorrect number of additional TX pubkeys in origin for return_payment"); + tr("at create_transactions_return(): incorrect number of additional TX pubkeys in origin TX for return_payment")); txkey_pub = in_additional_tx_pub_keys[td_origin.m_internal_output_index]; } else { txkey_pub = get_tx_pub_key_from_extra(td_origin.m_tx); } - crypto::key_derivation derivation; - THROW_WALLET_EXCEPTION_IF(!generate_key_derivation(txkey_pub, m_account.get_keys().m_view_secret_key, derivation), error::wallet_internal_error, "at create_transactions_return(): failed to generate_key_derivation"); + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + THROW_WALLET_EXCEPTION_IF(!generate_key_derivation(txkey_pub, m_account.get_keys().m_view_secret_key, derivation), + error::wallet_internal_error, + tr("at create_transactions_return(): failed to generate_key_derivation")); crypto::secret_key z_i; derivation_to_scalar(derivation, td_origin.m_internal_output_index, z_i); @@ -11428,7 +11431,6 @@ std::vector wallet2::create_transactions_return(std::vector std::strncpy(buf.domain_separator, "CHG_IDX", 8); crypto::secret_key eci_out; keccak((uint8_t *)&buf, sizeof(buf), (uint8_t*)&eci_out, sizeof(eci_out)); - assert(false); change_index = eci_data ^ eci_out.data[0]; return_address = td_origin.m_tx.return_address_list[td_origin.m_internal_output_index]; @@ -11450,16 +11452,18 @@ std::vector wallet2::create_transactions_return(std::vector return_address = td_origin.m_tx.return_address; } - THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_public_key(td_origin.m_tx.vout[change_index], P_change), error::wallet_internal_error, "Failed to identify change output"); + THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_public_key(td_origin.m_tx.vout[change_index], P_change), + error::wallet_internal_error, + tr("Failed to identify change output")); // Calculate yF rct::key key_y = (rct::key&)(y); - rct::key key_F = (rct::key&)(td_origin.m_tx.return_address); + rct::key key_F = (rct::key&)(return_address); rct::key key_yF = rct::scalarmultKey(key_F, key_y); // Sanity check that we aren't attempting to return our own TX output to ourselves rct::key key_yF_check = rct::scalarmultKey(rct::sk2rct(m_account.get_keys().m_view_secret_key), rct::pk2rct(P_change)); - THROW_WALLET_EXCEPTION_IF(key_yF_check == key_yF, error::wallet_internal_error, "Attempting to return a payment to ourself"); + THROW_WALLET_EXCEPTION_IF(key_yF_check == key_yF, error::wallet_internal_error, tr("Attempting to return a payment to ourself")); // Build the subaddress to send the return to cryptonote::account_public_address address; From da3ef2511d8e4ffd4a56ce771476badced3366b7 Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Wed, 9 Oct 2024 12:21:50 +0100 Subject: [PATCH 5/6] fixed return_payment issues for N-out-TXs; fixed change_index being incorrect datatype; partial fix to asset_type RPC propagation issue --- src/cryptonote_core/cryptonote_tx_utils.cpp | 2 +- src/cryptonote_core/cryptonote_tx_utils.h | 1 + src/wallet/wallet2.cpp | 11 ++++++----- src/wallet/wallet_rpc_server.cpp | 2 ++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index ab0bde122..32547e335 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -856,7 +856,7 @@ namespace cryptonote uint64_t summary_outs_money = 0; //fill outputs size_t output_index = 0; - size_t change_index = 0; + uint8_t change_index = 0; for(const tx_destination_entry& dst_entr: destinations) { CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index a34262753..f7bf7dac4 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -150,6 +150,7 @@ namespace cryptonote FIELD(original) VARINT_FIELD(amount) FIELD(addr) + FIELD(asset_type) FIELD(is_subaddress) FIELD(is_integrated) FIELD(is_change) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 8b03f8369..cd41aa763 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -11391,7 +11391,7 @@ std::vector wallet2::create_transactions_return(std::vector // Get P_change from the TX crypto::public_key P_change = crypto::null_pkey; - size_t change_index; + uint8_t change_index; uint32_t hf_version = get_current_hard_fork(); if (hf_version >= HF_VERSION_ENABLE_N_OUTS && td_origin.m_tx.version >= TRANSACTION_VERSION_N_OUTS) { @@ -11452,6 +11452,11 @@ std::vector wallet2::create_transactions_return(std::vector return_address = td_origin.m_tx.return_address; } + + // Sanity check that we aren't attempting to return our own TX change output to ourselves + THROW_WALLET_EXCEPTION_IF(change_index == td_origin.m_internal_output_index, error::wallet_internal_error, tr("Attempting to return change to ourself")); + + // Sanity check that we can obtain the change output from the origin TX THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_public_key(td_origin.m_tx.vout[change_index], P_change), error::wallet_internal_error, tr("Failed to identify change output")); @@ -11461,10 +11466,6 @@ std::vector wallet2::create_transactions_return(std::vector rct::key key_F = (rct::key&)(return_address); rct::key key_yF = rct::scalarmultKey(key_F, key_y); - // Sanity check that we aren't attempting to return our own TX output to ourselves - rct::key key_yF_check = rct::scalarmultKey(rct::sk2rct(m_account.get_keys().m_view_secret_key), rct::pk2rct(P_change)); - THROW_WALLET_EXCEPTION_IF(key_yF_check == key_yF, error::wallet_internal_error, tr("Attempting to return a payment to ourself")); - // Build the subaddress to send the return to cryptonote::account_public_address address; address.m_spend_public_key = P_change; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 44f1415fc..22df641c1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -392,6 +392,7 @@ namespace tools td.address = d.address(m_wallet->nettype(), pd.m_payment_id); } + entry.asset_type = pd.m_tx.source_asset_type; entry.type = "out"; entry.subaddr_index = { pd.m_subaddr_account, 0 }; for (uint32_t i: pd.m_subaddr_indices) @@ -424,6 +425,7 @@ namespace tools } entry.type = is_failed ? "failed" : "pending"; + entry.asset_type = pd.m_tx.source_asset_type; entry.subaddr_index = { pd.m_subaddr_account, 0 }; for (uint32_t i: pd.m_subaddr_indices) entry.subaddr_indices.push_back({pd.m_subaddr_account, i}); From 218911d9fc9a10a47b51fa2e986b5a6485b1ac9a Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Wed, 9 Oct 2024 12:24:29 +0100 Subject: [PATCH 6/6] bumped RC version --- src/version.cpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp.in b/src/version.cpp.in index 6dcddb944..317e35d50 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_SALVIUM_VERSION_TAG "@VERSIONTAG@" -#define DEF_SALVIUM_VERSION "0.5.2" +#define DEF_SALVIUM_VERSION "0.5.3-rc2" #define DEF_MONERO_VERSION_TAG "release" #define DEF_MONERO_VERSION "0.18.3.3" #define DEF_MONERO_RELEASE_NAME "Zero"