From df11160e7a6e97111ebb051988876c54975afe89 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 29 Sep 2022 21:54:15 +0200 Subject: [PATCH] decompose_amount_randomly + unit test --- src/currency_core/currency_config.h | 1 + src/currency_core/currency_format_utils.cpp | 11 +-- src/currency_core/currency_format_utils.h | 43 +++++++++++ tests/unit_tests/amounts_tests.cpp | 79 +++++++++++++++++++++ 4 files changed, 124 insertions(+), 10 deletions(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index c6246d79..eb2b5186 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -62,6 +62,7 @@ #define WALLET_MAX_ALLOWED_OUTPUT_AMOUNT ((uint64_t)0xffffffffffffffffLL) #define CURRENCY_MINER_TX_MAX_OUTS CURRENCY_TX_MAX_ALLOWED_OUTS +#define CURRENCY_TX_OUTS_RND_SPLIT_DIGITS_TO_KEEP 3 #define DIFFICULTY_STARTER 1 #define DIFFICULTY_POS_TARGET 120 // seconds diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 3851dd92..bd047a89 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -207,16 +207,7 @@ namespace currency if (tx_version > TRANSACTION_VERSION_PRE_HF4) { // randomly split into CURRENCY_TX_MIN_ALLOWED_OUTS outputs - // TODO: consider refactoring - uint64_t amount_remaining = block_reward; - for(size_t i = 1; i < CURRENCY_TX_MIN_ALLOWED_OUTS; ++i) // starting from 1 for one less iteration - { - uint64_t amount = crypto::rand() % amount_remaining; - amount_remaining -= amount; - out_amounts.push_back(amount); - } - out_amounts.push_back(amount_remaining); - // std::shuffle(out_amounts.begin(), out_amounts.end(), crypto::uniform_random_bit_generator()); + decompose_amount_randomly(block_reward, [&](uint64_t a){ out_amounts.push_back(a); }, CURRENCY_TX_MIN_ALLOWED_OUTS); } else { diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 566efc5f..4910a72c 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -699,6 +699,49 @@ namespace currency { decompose_amount_into_digits(amount, dust_threshold, chunk_handler, dust_handler, max_output_allowed, CURRENCY_TX_MAX_ALLOWED_OUTS, 0); } + + // num_digits_to_keep -- how many digits to keep in chunks, 0 means all digits + // Ex.: num_digits_to_keep == 3, number_of_outputs == 2 then 1.0 may be decomposed into 0.183 + 0.817 + // num_digits_to_keep == 0, number_of_outputs == 2 then 1.0 may be decomposed into 0.183374827362 + 0.816625172638 + template + void decompose_amount_randomly(uint64_t amount, chunk_handler_t chunk_cb, size_t number_of_outputs = CURRENCY_TX_MIN_ALLOWED_OUTS, size_t num_digits_to_keep = CURRENCY_TX_OUTS_RND_SPLIT_DIGITS_TO_KEEP) + { + if (amount < number_of_outputs) + return; + + uint64_t boundary = 1000; + if (num_digits_to_keep != CURRENCY_TX_OUTS_RND_SPLIT_DIGITS_TO_KEEP) + { + boundary = 1; + for(size_t i = 0; i < num_digits_to_keep; ++i) + boundary *= 10; + } + + auto trim_digits_and_add_variance = [boundary, num_digits_to_keep](uint64_t& v){ + if (num_digits_to_keep != 0 && v > 1) + { + uint64_t multiplier = 1; + while(v >= boundary) + { + v /= 10; + multiplier *= 10; + } + v = v / 2 + crypto::rand() % (v + 1); + v *= multiplier; + } + }; + + uint64_t amount_remaining = amount; + for(size_t i = 1; i < number_of_outputs && amount_remaining > 1; ++i) // starting from 1 for one less iteration + { + uint64_t chunk_amount = amount_remaining / (number_of_outputs - i + 1); + trim_digits_and_add_variance(chunk_amount); + amount_remaining -= chunk_amount; + chunk_cb(chunk_amount); + } + chunk_cb(amount_remaining); + } + //--------------------------------------------------------------- inline size_t get_input_expected_signatures_count(const txin_v& tx_in) { diff --git a/tests/unit_tests/amounts_tests.cpp b/tests/unit_tests/amounts_tests.cpp index d1133bb0..300b92a7 100644 --- a/tests/unit_tests/amounts_tests.cpp +++ b/tests/unit_tests/amounts_tests.cpp @@ -122,3 +122,82 @@ TEST_neg(0__0); TEST_neg(0_0_); TEST_neg(_0_0); TEST_neg(0_0_0); + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +size_t get_nonzero_digits_count(uint64_t x) +{ + size_t result = 0; + while(x != 0) + { + if (x % 10 != 0) + ++result; + x /= 10; + } + return result; +} + +void foo(uint64_t amount, size_t outputs_count, size_t num_digits_to_keep) +{ + std::vector vec; + decompose_amount_randomly(amount, [&](uint64_t a){ vec.push_back(a); }, outputs_count, num_digits_to_keep); + //std::cout << amount << " -> (" << vec.size() << ") "; + ASSERT_EQ(vec.size(), outputs_count); + for(size_t i = 0; i + 1 < outputs_count; ++i) + { + //std::cout << vec[i] << ","; + ASSERT_LE(get_nonzero_digits_count(vec[i]), num_digits_to_keep); + } + //std::cout << vec.back() << ENDL; + ASSERT_LE(get_nonzero_digits_count(vec.back()), num_digits_to_keep); +} + +TEST(decompose_amount_randomly, 1) +{ + std::vector vec; + for(size_t i = 0; i < 1000; ++i) + { + vec.clear(); + decompose_amount_randomly(0, [&](uint64_t a){ vec.push_back(a); }, 2, 3); + ASSERT_EQ(vec.size(), 0); + + vec.clear(); + decompose_amount_randomly(1, [&](uint64_t a){ vec.push_back(a); }, 2, 3); + ASSERT_EQ(vec.size(), 0); + + vec.clear(); + decompose_amount_randomly(2, [&](uint64_t a){ vec.push_back(a); }, 2, 3); + ASSERT_EQ(vec.size(), 2); + ASSERT_EQ(vec[0], 1); + ASSERT_EQ(vec[1], 1); + + vec.clear(); + decompose_amount_randomly(4, [&](uint64_t a){ vec.push_back(a); }, 2, 1); + ASSERT_EQ(vec.size(), 2); + ASSERT_LE(vec[0], 3); + ASSERT_GE(vec[0], 1); + ASSERT_LE(vec[1], 3); + ASSERT_GE(vec[1], 1); + + vec.clear(); + decompose_amount_randomly(3, [&](uint64_t a){ vec.push_back(a); }, 3, 1); + ASSERT_EQ(vec.size(), 3); + ASSERT_EQ(vec[0], 1); + ASSERT_EQ(vec[1], 1); + ASSERT_EQ(vec[2], 1); + + foo(1000, 2, 3); + foo(1000, 2, 2); + foo(1000, 2, 1); + + foo(10000, 4, 2); + foo(10010, 4, 3); + foo(17283, 4, 4); + + foo(100000, 4, 5); + foo(100000, 4, 5); + + foo(1000000000000, 2, 3); + } +}