From 4f34c4466a0fd71b1117517278b754ad3bf54a7f Mon Sep 17 00:00:00 2001 From: SChernykh Date: Sun, 8 Jan 2023 00:34:38 +0100 Subject: [PATCH] Dynamic PPLNS window --- src/common.h | 9 +++- src/log.h | 4 +- src/pool_block.h | 8 +++ src/side_chain.cpp | 113 +++++++++++++++++++++---------------------- src/side_chain.h | 9 ++-- tests/CMakeLists.txt | 2 + 6 files changed, 79 insertions(+), 66 deletions(-) diff --git a/src/common.h b/src/common.h index 5f5598e..31e0fd6 100644 --- a/src/common.h +++ b/src/common.h @@ -185,8 +185,8 @@ struct #endif difficulty_type { - FORCEINLINE difficulty_type() : lo(0), hi(0) {} - FORCEINLINE difficulty_type(uint64_t a, uint64_t b) : lo(a), hi(b) {} + FORCEINLINE constexpr difficulty_type() : lo(0), hi(0) {} + FORCEINLINE constexpr difficulty_type(uint64_t a, uint64_t b) : lo(a), hi(b) {} uint64_t lo; uint64_t hi; @@ -254,7 +254,10 @@ struct return (lo < other.lo); } + FORCEINLINE bool operator>(const difficulty_type& other) const { return other.operator<(*this); } + FORCEINLINE bool operator>=(const difficulty_type& other) const { return !operator<(other); } + FORCEINLINE bool operator<=(const difficulty_type& other) const { return !operator>(other); } FORCEINLINE bool operator==(const difficulty_type& other) const { return (lo == other.lo) && (hi == other.hi); } FORCEINLINE bool operator!=(const difficulty_type& other) const { return (lo != other.lo) || (hi != other.hi); } @@ -294,6 +297,8 @@ struct 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"); +static constexpr difficulty_type diff_max = { std::numeric_limits::max(), std::numeric_limits::max() }; + template FORCEINLINE difficulty_type operator+(const difficulty_type& a, const T& b) { diff --git a/src/log.h b/src/log.h index a99ff8a..fc371de 100644 --- a/src/log.h +++ b/src/log.h @@ -492,7 +492,7 @@ struct DummyStream MSVC_PRAGMA(warning(suppress:26444)) \ [=]() { \ log::DummyStream x; \ - x << level << __VA_ARGS__; \ + x << (level) << __VA_ARGS__; \ }; \ } \ } while (0) @@ -508,7 +508,7 @@ struct DummyStream #define LOG(level, severity, ...) \ do { \ SIDE_EFFECT_CHECK(level, __VA_ARGS__); \ - if (level <= log::GLOBAL_LOG_LEVEL) { \ + if ((level) <= log::GLOBAL_LOG_LEVEL) { \ log::Writer CONCAT(log_wrapper_, __LINE__)(severity); \ CONCAT(log_wrapper_, __LINE__) << log::Gray() << log_category_prefix; \ log::apply_severity(CONCAT(log_wrapper_, __LINE__)); \ diff --git a/src/pool_block.h b/src/pool_block.h index 0f95aee..d0cae76 100644 --- a/src/pool_block.h +++ b/src/pool_block.h @@ -146,6 +146,14 @@ struct PoolBlock // but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15 FORCEINLINE uint8_t get_tx_type() const { return (m_majorVersion < HARDFORK_VIEW_TAGS_VERSION) ? TXOUT_TO_KEY : TXOUT_TO_TAGGED_KEY; } + FORCEINLINE uint8_t get_sidechain_version() const + { + // P2Pool forks to v2 at 2023-03-18 21:00 UTC + // Different miners can have different timestamps, + // so a temporary mix of v1 and v2 blocks is allowed + return (m_timestamp >= 1679173200) ? 2 : 1; + } + typedef std::array full_id; FORCEINLINE full_id get_full_id() const diff --git a/src/side_chain.cpp b/src/side_chain.cpp index 687ac50..8ed4d38 100644 --- a/src/side_chain.cpp +++ b/src/side_chain.cpp @@ -316,8 +316,10 @@ P2PServer* SideChain::p2pServer() const return m_pool ? m_pool->p2p_server() : nullptr; } -bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares) const +bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares, bool quiet) const { + const int L = quiet ? 6 : 3; + shares.clear(); shares.reserve(m_chainWindowSize * 2); @@ -325,14 +327,34 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares uint64_t block_depth = 0; const PoolBlock* cur = tip; + + difficulty_type mainchain_diff +#ifdef P2POOL_UNIT_TESTS + = m_testMainChainDiff +#endif + ; + + if (m_pool && !tip->m_parent.empty()) { + const uint64_t h = p2pool::get_seed_height(tip->m_txinGenHeight); + if (!m_pool->get_difficulty_at_height(h, mainchain_diff)) { + LOGWARN(L, "get_shares: couldn't get mainchain difficulty for height = " << h); + return false; + } + } + + // Dynamic PPLNS window starting from v2 + // Limit PPLNS weight to 2x of the Monero difficulty (max 2 blocks per PPLNS window on average) + const difficulty_type max_pplns_weight = (tip->get_sidechain_version() > 1) ? (mainchain_diff * 2) : diff_max; + difficulty_type pplns_weight; + do { MinerShare cur_share{ cur->m_difficulty, &cur->m_minerWallet }; for (const hash& uncle_id : cur->m_uncles) { auto it = m_blocksById.find(uncle_id); if (it == m_blocksById.end()) { - LOGWARN(3, "get_shares: can't find uncle block at height = " << cur->m_sidechainHeight << ", id = " << uncle_id); - LOGWARN(3, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight); + LOGWARN(L, "get_shares: can't find uncle block at height = " << cur->m_sidechainHeight << ", id = " << uncle_id); + LOGWARN(L, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight); return false; } @@ -345,11 +367,28 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares // Take some % of uncle's weight into this share const difficulty_type uncle_penalty = uncle->m_difficulty * m_unclePenalty / 100; + const difficulty_type uncle_weight = uncle->m_difficulty - uncle_penalty; + const difficulty_type new_pplns_weight = pplns_weight + uncle_weight; + + // Skip uncles that push PPLNS weight above the limit + if (new_pplns_weight > max_pplns_weight) { + continue; + } + cur_share.m_weight += uncle_penalty; - shares.emplace_back(uncle->m_difficulty - uncle_penalty, &uncle->m_minerWallet); + + shares.emplace_back(uncle_weight, &uncle->m_minerWallet); + pplns_weight = new_pplns_weight; } + // Always add non-uncle shares even if PPLNS weight goes above the limit shares.push_back(cur_share); + pplns_weight += cur_share.m_weight; + + // One non-uncle share can go above the limit, but it will also guarantee that "shares" is never empty + if (pplns_weight > max_pplns_weight) { + break; + } ++block_depth; if (block_depth >= m_chainWindowSize) { @@ -363,8 +402,8 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares auto it = m_blocksById.find(cur->m_parent); if (it == m_blocksById.end()) { - LOGWARN(3, "get_shares: can't find parent block at height = " << cur->m_sidechainHeight - 1 << ", id = " << cur->m_parent); - LOGWARN(3, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight); + LOGWARN(L, "get_shares: can't find parent block at height = " << cur->m_sidechainHeight - 1 << ", id = " << cur->m_parent); + LOGWARN(L, "get_shares: can't calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight); return false; } @@ -393,50 +432,6 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector& shares return true; } -bool SideChain::get_wallets(const PoolBlock* tip, std::vector& wallets) const -{ - // Collect wallets from each block in the PPLNS window, starting from the "tip" - wallets.clear(); - wallets.reserve(m_chainWindowSize * 2); - - uint64_t block_depth = 0; - const PoolBlock* cur = tip; - - do { - wallets.push_back(&cur->m_minerWallet); - - for (const hash& uncle_id : cur->m_uncles) { - auto it = m_blocksById.find(uncle_id); - if (it == m_blocksById.end()) { - return false; - } - - // Skip uncles which are already out of PPLNS window - if (tip->m_sidechainHeight - it->second->m_sidechainHeight < m_chainWindowSize) { - wallets.push_back(&it->second->m_minerWallet); - } - } - - ++block_depth; - if ((block_depth >= m_chainWindowSize) || (cur->m_sidechainHeight == 0)) { - break; - } - - auto it = m_blocksById.find(cur->m_parent); - if (it == m_blocksById.end()) { - return false; - } - - cur = it->second; - } while (true); - - // Remove duplicates - std::sort(wallets.begin(), wallets.end(), [](const Wallet* a, const Wallet* b) { return *a < *b; }); - wallets.erase(std::unique(wallets.begin(), wallets.end(), [](const Wallet* a, const Wallet* b) { return *a == *b; }), wallets.end()); - - return true; -} - bool SideChain::block_seen(const PoolBlock& block) { // Check if it's some old block @@ -2090,10 +2085,10 @@ void SideChain::launch_precalc(const PoolBlock* block) if (b->m_precalculated) { continue; } - std::vector wallets; - if (get_wallets(b, wallets)) { + std::vector shares; + if (get_shares(b, shares, true)) { b->m_precalculated = true; - PrecalcJob* job = new PrecalcJob{ b, std::move(wallets) }; + PrecalcJob* job = new PrecalcJob{ b, std::move(shares) }; { MutexLock lock2(m_precalcJobsMutex); m_precalcJobs.push_back(job); @@ -2130,20 +2125,20 @@ void SideChain::precalc_worker() uint8_t t[HASH_SIZE * 2 + sizeof(size_t)]; memcpy(t, job->b->m_txkeySec.h, HASH_SIZE); - for (size_t i = 0, n = job->wallets.size(); i < n; ++i) { - memcpy(t + HASH_SIZE, job->wallets[i]->view_public_key().h, HASH_SIZE); + for (size_t i = 0, n = job->shares.size(); i < n; ++i) { + memcpy(t + HASH_SIZE, job->shares[i].m_wallet->view_public_key().h, HASH_SIZE); memcpy(t + HASH_SIZE * 2, &i, sizeof(i)); if (!m_uniquePrecalcInputs->insert(robin_hood::hash_bytes(t, array_size(t))).second) { - job->wallets[i] = nullptr; + job->shares[i].m_wallet = nullptr; } } } - for (size_t i = 0, n = job->wallets.size(); i < n; ++i) { - if (job->wallets[i]) { + for (size_t i = 0, n = job->shares.size(); i < n; ++i) { + if (job->shares[i].m_wallet) { hash eph_public_key; uint8_t view_tag; - job->wallets[i]->get_eph_public_key(job->b->m_txkeySec, i, eph_public_key, view_tag); + job->shares[i].m_wallet->get_eph_public_key(job->b->m_txkeySec, i, eph_public_key, view_tag); } } delete job; diff --git a/src/side_chain.h b/src/side_chain.h index d09f184..a92d10e 100644 --- a/src/side_chain.h +++ b/src/side_chain.h @@ -77,6 +77,10 @@ public: const PoolBlock* chainTip() const { return m_chainTip; } bool precalcFinished() const { return m_precalcFinished.load(); } +#ifdef P2POOL_UNIT_TESTS + difficulty_type m_testMainChainDiff; +#endif + static bool split_reward(uint64_t reward, const std::vector& shares, std::vector& rewards); private: @@ -85,9 +89,8 @@ private: NetworkType m_networkType; private: - bool get_shares(const PoolBlock* tip, std::vector& shares) const; + bool get_shares(const PoolBlock* tip, std::vector& shares, bool quiet = false) const; bool get_difficulty(const PoolBlock* tip, std::vector& difficultyData, difficulty_type& curDifficulty) const; - bool get_wallets(const PoolBlock* tip, std::vector& wallets) const; void verify_loop(PoolBlock* block); void verify(PoolBlock* block); void update_chain_tip(const PoolBlock* block); @@ -134,7 +137,7 @@ private: struct PrecalcJob { const PoolBlock* b; - std::vector wallets; + std::vector shares; }; uv_cond_t m_precalcJobsCond; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1fba126..b08ce58 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,8 @@ add_subdirectory(../external/src/RandomX RandomX) set(LIBS ${LIBS} randomx) add_definitions(-DWITH_RANDOMX) +add_definitions(-DP2POOL_UNIT_TESTS) + include(cmake/flags.cmake) set(HEADERS