From 488ed8e562f61eb7e2d88c60ccba1676a857922c Mon Sep 17 00:00:00 2001 From: SChernykh Date: Tue, 15 Nov 2022 22:20:54 +0100 Subject: [PATCH] Added more 128 bit calculations --- src/common.h | 32 +++- src/log.cpp | 8 +- src/side_chain.cpp | 31 ++-- src/side_chain.h | 6 +- src/util.cpp | 48 ++++++ tests/src/difficulty_type_tests.cpp | 243 +++++++++++++++++++++++++--- 6 files changed, 317 insertions(+), 51 deletions(-) diff --git a/src/common.h b/src/common.h index 25e4154..6cc351d 100644 --- a/src/common.h +++ b/src/common.h @@ -109,6 +109,8 @@ constexpr uint8_t TX_EXTRA_MERGE_MINING_TAG = 3; #ifdef _MSC_VER #define umul128 _umul128 #define udiv128 _udiv128 +FORCEINLINE uint64_t shiftleft128(uint64_t lo, uint64_t hi, uint64_t shift) { return __shiftleft128(lo, hi, static_cast(shift)); } +FORCEINLINE uint64_t shiftright128(uint64_t lo, uint64_t hi, uint64_t shift) { return __shiftright128(lo, hi, static_cast(shift)); } #else FORCEINLINE uint64_t umul128(uint64_t a, uint64_t b, uint64_t* hi) { @@ -126,6 +128,9 @@ FORCEINLINE uint64_t udiv128(uint64_t hi, uint64_t lo, uint64_t divisor, uint64_ return result; } + +FORCEINLINE uint64_t shiftleft128(uint64_t lo, uint64_t hi, uint64_t shift) { return (hi << shift) | (lo >> (64 - shift)); } +FORCEINLINE uint64_t shiftright128(uint64_t lo, uint64_t hi, uint64_t shift) { return (hi << (64 - shift)) | (lo >> shift); } #endif template constexpr FORCEINLINE T round_up(T a, size_t granularity) { return static_cast(((a + (granularity - static_cast(1))) / granularity) * granularity); } @@ -197,6 +202,8 @@ struct difficulty_type return *this; } + FORCEINLINE difficulty_type& operator+=(uint64_t b) { return operator+=(difficulty_type{ b, 0 }); } + FORCEINLINE difficulty_type& operator-=(const difficulty_type& b) { #ifdef _MSC_VER @@ -212,6 +219,8 @@ struct difficulty_type return *this; } + FORCEINLINE difficulty_type& operator-=(uint64_t b) { return operator-=(difficulty_type{ b, 0 }); } + FORCEINLINE difficulty_type& operator*=(const uint64_t b) { uint64_t t; @@ -232,6 +241,8 @@ struct difficulty_type return *this; } + difficulty_type& operator/=(difficulty_type b); + FORCEINLINE bool operator<(const difficulty_type& other) const { if (hi < other.hi) return true; @@ -279,20 +290,37 @@ struct difficulty_type static_assert(sizeof(difficulty_type) == sizeof(uint64_t) * 2, "struct difficulty_type has invalid size, check your compiler options"); static_assert(std::is_standard_layout::value, "struct difficulty_type is not a POD, check your compiler options"); -FORCEINLINE difficulty_type operator+(const difficulty_type& a, const difficulty_type& b) +template +FORCEINLINE difficulty_type operator+(const difficulty_type& a, const T& b) { difficulty_type result = a; result += b; return result; } -FORCEINLINE difficulty_type operator-(const difficulty_type& a, const difficulty_type& b) +template +FORCEINLINE difficulty_type operator-(const difficulty_type& a, const T& b) { difficulty_type result = a; result -= b; return result; } +FORCEINLINE difficulty_type operator*(const difficulty_type& a, uint64_t b) +{ + difficulty_type result = a; + result *= b; + return result; +} + +template +FORCEINLINE difficulty_type operator/(const difficulty_type& a, const T& b) +{ + difficulty_type result = a; + result /= b; + return result; +} + struct TxMempoolData { FORCEINLINE TxMempoolData() : id(), blob_size(0), weight(0), fee(0), time_received(0) {} diff --git a/src/log.cpp b/src/log.cpp index 9a5a6f3..9a4551b 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -63,7 +63,6 @@ public: // Create default loop here uv_default_loop(); - init_uv_threadpool(); uv_cond_init(&m_cond); uv_mutex_init(&m_mutex); @@ -92,6 +91,8 @@ public: if (!m_logFile.is_open()) { LOGERR(0, "failed to open " << log_file_name); } + + init_uv_threadpool(); } ~Worker() @@ -160,7 +161,6 @@ private: #endif const uint32_t N = std::max(std::min(std::thread::hardware_concurrency(), 4U), 8U); - LOGINFO(4, "running " << N << " threads in the UV thread pool"); char buf[40] = {}; log::Stream s(buf); @@ -169,13 +169,13 @@ private: int err = putenv(buf); if (err != 0) { err = errno; - LOGWARN(1, "Couldn't set UV thread pool size to " << N << " threads, putenv returned error " << err); + LOGWARN(0, "Couldn't set UV thread pool size to " << N << " threads, putenv returned error " << err); } static uv_work_t dummy; err = uv_queue_work(uv_default_loop_checked(), &dummy, [](uv_work_t*) {}, nullptr); if (err) { - LOGERR(1, "init_uv_threadpool: uv_queue_work failed, error " << uv_err_name(err)); + LOGERR(0, "init_uv_threadpool: uv_queue_work failed, error " << uv_err_name(err)); } } diff --git a/src/side_chain.cpp b/src/side_chain.cpp index 4acfc28..d51f04a 100644 --- a/src/side_chain.cpp +++ b/src/side_chain.cpp @@ -325,7 +325,7 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares uint64_t block_depth = 0; const PoolBlock* cur = tip; do { - MinerShare cur_share{ cur->m_difficulty.lo, &cur->m_minerWallet }; + MinerShare cur_share{ cur->m_difficulty, &cur->m_minerWallet }; for (const hash& uncle_id : cur->m_uncles) { auto it = m_blocksById.find(uncle_id); @@ -343,14 +343,9 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares } // Take some % of uncle's weight into this share - uint64_t product[2]; - product[0] = umul128(uncle->m_difficulty.lo, m_unclePenalty, &product[1]); - - uint64_t rem; - const uint64_t uncle_penalty = udiv128(product[1], product[0], 100, &rem); - + const difficulty_type uncle_penalty = uncle->m_difficulty * m_unclePenalty / 100; cur_share.m_weight += uncle_penalty; - shares.emplace_back(uncle->m_difficulty.lo - uncle_penalty, &uncle->m_minerWallet); + shares.emplace_back(uncle->m_difficulty - uncle_penalty, &uncle->m_minerWallet); } shares.push_back(cur_share); @@ -1092,9 +1087,9 @@ bool SideChain::split_reward(uint64_t reward, const std::vector& sha { const size_t num_shares = shares.size(); - const uint64_t total_weight = std::accumulate(shares.begin(), shares.end(), 0ULL, [](uint64_t a, const MinerShare& b) { return a + b.m_weight; }); + const difficulty_type total_weight = std::accumulate(shares.begin(), shares.end(), difficulty_type(), [](const difficulty_type& a, const MinerShare& b) { return a + b.m_weight; }); - if (total_weight == 0) { + if (total_weight.empty()) { LOGERR(1, "total_weight is 0. Check the code!"); return false; } @@ -1103,18 +1098,14 @@ bool SideChain::split_reward(uint64_t reward, const std::vector& sha rewards.reserve(num_shares); // Each miner gets a proportional fraction of the block reward - uint64_t w = 0; + difficulty_type w; uint64_t reward_given = 0; for (uint64_t i = 0; i < num_shares; ++i) { w += shares[i].m_weight; - uint64_t hi; - const uint64_t lo = umul128(w, reward, &hi); - - uint64_t rem; - const uint64_t next_value = udiv128(hi, lo, total_weight, &rem); - rewards.emplace_back(next_value - reward_given); - reward_given = next_value; + const difficulty_type next_value = w * reward / total_weight; + rewards.emplace_back(next_value.lo - reward_given); + reward_given = next_value.lo; } // Double check that we gave out the exact amount @@ -1209,9 +1200,7 @@ bool SideChain::get_difficulty(const PoolBlock* tip, std::vector } } - curDifficulty = diff2 - diff1; - curDifficulty *= m_targetBlockTime; - curDifficulty /= delta_t; + curDifficulty = (diff2 - diff1) * m_targetBlockTime / delta_t; if (curDifficulty < m_minDifficulty) { curDifficulty = m_minDifficulty; diff --git a/src/side_chain.h b/src/side_chain.h index 13f357f..a106f19 100644 --- a/src/side_chain.h +++ b/src/side_chain.h @@ -29,10 +29,10 @@ class P2PServer; struct MinerShare { - FORCEINLINE MinerShare() : m_weight(0), m_wallet(nullptr) {} - FORCEINLINE MinerShare(uint64_t w, const Wallet* x) : m_weight(w), m_wallet(x) {} + FORCEINLINE MinerShare() : m_weight(), m_wallet(nullptr) {} + FORCEINLINE MinerShare(const difficulty_type& w, const Wallet* x) : m_weight(w), m_wallet(x) {} - uint64_t m_weight; + difficulty_type m_weight; const Wallet* m_wallet; }; diff --git a/src/util.cpp b/src/util.cpp index 72f09e2..1f12b68 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -75,6 +75,54 @@ void make_thread_background() #endif } +NOINLINE difficulty_type& difficulty_type::operator/=(difficulty_type b) +{ + if (*this < b) { + lo = 0; + hi = 0; + return *this; + } + + if (*this - b < b) { + lo = 1; + hi = 0; + return *this; + } + + if (b.hi == 0) { + return operator/=(b.lo); + } + + const uint64_t shift = bsr(b.hi) + 1; + const uint64_t divisor = shiftleft128(b.lo, b.hi, 64 - shift); + + uint64_t t; + if (hi < divisor) { + uint64_t r; + t = udiv128(hi, lo, divisor, &r) >> shift; + } + else { + uint64_t r; + t = shiftright128(udiv128(hi - divisor, lo, divisor, &r), 1, shift); + } + + difficulty_type product; + product.lo = umul128(b.lo, t, &product.hi); + + uint64_t t1, t2; + t1 = umul128(b.hi, t, &t2); + product.hi += t1; + + if (t2 || (product.hi < t1) || (*this < product)) { + --t; + } + + lo = t; + hi = 0; + + return *this; +} + NOINLINE bool difficulty_type::check_pow(const hash& pow_hash) const { const uint64_t* a = reinterpret_cast(pow_hash.h); diff --git a/tests/src/difficulty_type_tests.cpp b/tests/src/difficulty_type_tests.cpp index 037ee7d..a74240c 100644 --- a/tests/src/difficulty_type_tests.cpp +++ b/tests/src/difficulty_type_tests.cpp @@ -24,6 +24,8 @@ namespace p2pool { +static const difficulty_type max_diff{ std::numeric_limits::max(), std::numeric_limits::max() }; + TEST(difficulty_type, constructors) { difficulty_type diff; @@ -56,10 +58,7 @@ TEST(difficulty_type, target) } // diff = max - { - difficulty_type d(std::numeric_limits::max(), std::numeric_limits::max()); - ASSERT_EQ(d.target(), 1); - } + ASSERT_EQ(max_diff.target(), 1); // diff = 2^32 { @@ -128,54 +127,60 @@ TEST(difficulty_type, mul_div) { auto check = [](const difficulty_type& a, uint64_t b, const difficulty_type& product) { - difficulty_type result = a; + ASSERT_EQ(a * b, product); + difficulty_type result = a; result *= b; ASSERT_EQ(result, product); if (b) { - result /= b; - ASSERT_EQ(result, a); + ASSERT_EQ(result / b, a); + + difficulty_type tmp = result; + tmp /= difficulty_type(b, 0); + ASSERT_EQ(tmp, a); + + difficulty_type tmp2 = result; + tmp2 /= b; + ASSERT_EQ(tmp2, a); } }; - const difficulty_type max_diff(std::numeric_limits::max(), std::numeric_limits::max()); - // (2^128 - 1) * 0 = 0 - check(max_diff, 0, difficulty_type(0, 0)); + check(max_diff, 0, { 0, 0 }); // (2^128 - 1) * 1 = 2^128 - 1 check(max_diff, 1, max_diff); // 5057672949897463733145855 * 67280421310721 = 2^128 - 1 - check(difficulty_type(18446744073709277439ull, 274176ull), 67280421310721ull, max_diff); + check({ 18446744073709277439ull, 274176ull }, 67280421310721ull, max_diff); // 10^19 * 10 = 10^20 - check(difficulty_type(10000000000000000000ull, 0), 10, difficulty_type(7766279631452241920ull, 5)); + check({ 10000000000000000000ull, 0 }, 10, { 7766279631452241920ull, 5 }); // 10^20 * 10 = 10^21 - check(difficulty_type(7766279631452241920ull, 5), 10, difficulty_type(3875820019684212736ull, 54)); + check({ 7766279631452241920ull, 5 }, 10, { 3875820019684212736ull, 54 }); // 0 * (2^64 - 1) = 0 - check(difficulty_type(0, 0), std::numeric_limits::max(), difficulty_type(0, 0)); + check({ 0, 0 }, std::numeric_limits::max(), { 0, 0 }); // 1 * (2^64 - 1) = 2^64 - 1 - check(difficulty_type(1, 0), std::numeric_limits::max(), difficulty_type(std::numeric_limits::max(), 0)); + check({ 1, 0 }, std::numeric_limits::max(), { std::numeric_limits::max(), 0 }); // 2^64 * (2^64 - 1) = 2^128 - 2^64 - check(difficulty_type(0, 1), std::numeric_limits::max(), difficulty_type(0, std::numeric_limits::max())); + check({ 0, 1 }, std::numeric_limits::max(), { 0, std::numeric_limits::max() }); // (2^64 + 1) * (2^64 - 1) = 2^128 - 1 - check(difficulty_type(1, 1), std::numeric_limits::max(), max_diff); + check({ 1, 1 }, std::numeric_limits::max(), max_diff); // 2753074036095 * 6700417 = 2^64 - 1 - check(difficulty_type(2753074036095ull, 0), 6700417, difficulty_type(std::numeric_limits::max(), 0)); + check({ 2753074036095ull, 0 }, 6700417, { std::numeric_limits::max(), 0 }); // 2^32 * 2^32 = 2^64 - check(difficulty_type(4294967296ull, 0), 4294967296ull, difficulty_type(0, 1)); + check({ 4294967296ull, 0 }, 4294967296ull, { 0, 1 }); // 274177 * 67280421310721 = 2^64 + 1 - check(difficulty_type(274177, 0), 67280421310721ull, difficulty_type(1, 1)); + check({ 274177, 0 }, 67280421310721ull, { 1, 1 }); // Powers of 2 { @@ -210,7 +215,203 @@ TEST(difficulty_type, mul_div) } // No carry - check(difficulty_type(123, 456), 789, difficulty_type(97047, 359784)); + check({ 123, 456 }, 789, { 97047, 359784 }); +} + +static NOINLINE difficulty_type div128_ref(difficulty_type a, difficulty_type b) +{ + difficulty_type result{}; + + while (a >= b) { + difficulty_type t = b; + difficulty_type q{ 1, 0 }; + while (a - t >= t) { + t += t; + q += q; + } + a -= t; + result += q; + } + + return result; +} + +TEST(difficulty_type, div128) +{ + auto check = [](difficulty_type a, difficulty_type b, difficulty_type result) + { + ASSERT_EQ(div128_ref(a, b), result); + ASSERT_EQ(a / b, result); + a /= b; + ASSERT_EQ(a, result); + }; + + // (2^128 - 1) / (2^128 - 1) = 1 + check(max_diff, max_diff, { 1, 0 }); + + // (2^128 - 1) / (2^64 - 1) = 2^64 + 1 + check(max_diff, { std::numeric_limits::max(), 0 }, { 1, 1 }); + + // (2^128 - 1) / 2^64 = 2^64 - 1 + check(max_diff, { 0, 1 }, { std::numeric_limits::max(), 0 }); + + // (2^128 - 1) / (2^64 + 1) = 2^64 - 1 + check(max_diff, { 1, 1 }, { std::numeric_limits::max(), 0 }); + + // (2^128 - 1) / 8100430714362380904069067128193 = 42007935 + check(max_diff, { 439125228929, 439125228929 }, { 42007935, 0 }); + + // (2^128 - 2^64) / (2^64 + 1) = 2^64 - 2 + check({ 0, std::numeric_limits::max() }, { 1, 1 }, { std::numeric_limits::max() - 1, 0 }); + + // (2^128 - 2^64) / 2^64 = 2^64 - 1 + check({ 0, std::numeric_limits::max() }, { 0, 1 }, { std::numeric_limits::max(), 0 }); + + // (2^128 - 2^64) / (2^64 - 1) = 2^64 + check({ 0, std::numeric_limits::max() }, { std::numeric_limits::max(), 0 }, { 0, 1 }); + + { + difficulty_type a = max_diff - 4; + + // (2^128 - 5) / 2002733033099709041094789607565039 = 169909 + check(a, { 7565587230673184495, 108568375269759 }, { 169909, 0 }); + + // (2^128 - 5) / 2002733033099709041094789607565040 = 169908 + check(a, { 7565587230673184496, 108568375269759 }, { 169908, 0 }); + + a -= 1; + + // (2^128 - 6) / 2002733033099709041094789607565039 = 169908 + check(a, { 7565587230673184495, 108568375269759 }, { 169908, 0 }); + + // (2^128 - 6) / 2002733033099709041094789607565038 = 169909 + check(a, { 7565587230673184494, 108568375269759 }, { 169909, 0 }); + } + + // Powers of 2 + for (difficulty_type i{ 1, 0 }, j = max_diff; !i.empty(); i += i, j /= 2) { + check(max_diff, i, j); + } + + // Trivial tests + check({ 0, 3 }, { 0, 1 }, { 3, 0 }); + check({ 0, 3 }, { 1, 1 }, { 2, 0 }); + check({ 123 * 4 - 1, 456 * 4 }, { 123, 456 }, { 3, 0 }); + check({ 123 * 4, 456 * 4 }, { 123, 456 }, { 4, 0 }); + + // Exhaustive tests (top 8 bits of each number) + for (uint64_t i = 1; i < 256; ++i) { + for (uint64_t j = 1; j < 256; ++j) { + const difficulty_type a{ 0, i << 56 }; + const difficulty_type b{ 0, j << 56 }; + { + difficulty_type t = a; + t /= b; + ASSERT_EQ(t.lo, i / j); + ASSERT_EQ(t.hi, 0); + } + } + } + + // Bit patterns + std::vector patterns; + + // 2^N-1, 2^N, 2^N+1 + for (uint64_t i = 0; i < 128; ++i) { + difficulty_type t; + reinterpret_cast(&t)[i / 64] |= 1ull << (i % 64); + patterns.emplace_back(t - 1); + patterns.emplace_back(t); + patterns.emplace_back(t + 1); + } + + // 2^N+2^M, 2^N-2^M + bool check_bits[128] = {}; + for (uint64_t i = 64 - 4; i < 64 + 4; ++i) { + check_bits[i] = true; + } + for (uint64_t i = 128 - 8; i < 128; ++i) { + check_bits[i] = true; + } + for (uint64_t i = 0; i < 128; ++i) { + if (!check_bits[i]) { + continue; + } + difficulty_type t1; + reinterpret_cast(&t1)[i / 64] = 1ull << (i % 64); + for (uint64_t j = i + 1; j < 128; ++j) { + if (!check_bits[j]) { + continue; + } + difficulty_type t2; + reinterpret_cast(&t2)[j / 64] = 1ull << (j % 64); + patterns.emplace_back(t2 + t1); + patterns.emplace_back(t2 - t1); + } + } + + // All previous patterns, but ~X + for (size_t i = 0, n = patterns.size(); i < n; ++i) { + patterns.emplace_back(~patterns[i].lo, ~patterns[i].hi); + } + + std::sort(patterns.begin(), patterns.end()); + patterns.erase(std::unique(patterns.begin(), patterns.end()), patterns.end()); + + // remove 0 + patterns.erase(patterns.begin()); + + for (size_t i = 0, n = patterns.size(); i < n; ++i) { + const difficulty_type& a = patterns[i]; + for (size_t j = i + 1; j < n; ++j) { + const difficulty_type& b = patterns[j]; + ASSERT_EQ(div128_ref(b, a), b / a); + } + } + + // Random tests with fixed seed + std::mt19937_64 r(0); + + for (uint64_t i = 0; i < 10000000; ++i) { + // Random number of bits [1, 63] + const uint64_t N = (r() % 63) + 1; + + // Random multiplier [1, 2^N - 1] + uint64_t k; + do { + k = r() & ((1ull << N) - 1); + } while (k == 0); + + uint64_t t; + const uint64_t max_a = udiv128(1, 0, k + 1, &t); + + // Random number [2^64, 2^128 / (k + 1)] + difficulty_type a{ r(), 0 }; + do { + a.hi = r() % max_a; + } while (a.hi == 0); + + difficulty_type b1 = a * k; + difficulty_type b2 = b1 - 1; + difficulty_type b3 = b1 + a; + difficulty_type b4 = b3 - 1; + + b1 /= a; + ASSERT_EQ(b1.lo, k); + ASSERT_EQ(b1.hi, 0); + + b2 /= a; + ASSERT_EQ(b2.lo, k - 1); + ASSERT_EQ(b2.hi, 0); + + b3 /= a; + ASSERT_EQ(b3.lo, k + 1); + ASSERT_EQ(b3.hi, 0); + + b4 /= a; + ASSERT_EQ(b4.lo, k); + ASSERT_EQ(b4.hi, 0); + } } TEST(difficulty_type, compare)