Dynamic PPLNS window

This commit is contained in:
SChernykh 2023-01-08 00:34:38 +01:00
parent ccc5117172
commit 4f34c4466a
6 changed files with 79 additions and 66 deletions

View file

@ -185,8 +185,8 @@ struct
#endif #endif
difficulty_type difficulty_type
{ {
FORCEINLINE difficulty_type() : lo(0), hi(0) {} FORCEINLINE constexpr difficulty_type() : lo(0), hi(0) {}
FORCEINLINE difficulty_type(uint64_t a, uint64_t b) : lo(a), hi(b) {} FORCEINLINE constexpr difficulty_type(uint64_t a, uint64_t b) : lo(a), hi(b) {}
uint64_t lo; uint64_t lo;
uint64_t hi; uint64_t hi;
@ -254,7 +254,10 @@ struct
return (lo < other.lo); 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 !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); }
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(sizeof(difficulty_type) == sizeof(uint64_t) * 2, "struct difficulty_type has invalid size, check your compiler options");
static_assert(std::is_standard_layout<difficulty_type>::value, "struct difficulty_type is not a POD, check your compiler options"); static_assert(std::is_standard_layout<difficulty_type>::value, "struct difficulty_type is not a POD, check your compiler options");
static constexpr difficulty_type diff_max = { std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max() };
template<typename T> template<typename T>
FORCEINLINE difficulty_type operator+(const difficulty_type& a, const T& b) FORCEINLINE difficulty_type operator+(const difficulty_type& a, const T& b)
{ {

View file

@ -492,7 +492,7 @@ struct DummyStream
MSVC_PRAGMA(warning(suppress:26444)) \ MSVC_PRAGMA(warning(suppress:26444)) \
[=]() { \ [=]() { \
log::DummyStream x; \ log::DummyStream x; \
x << level << __VA_ARGS__; \ x << (level) << __VA_ARGS__; \
}; \ }; \
} \ } \
} while (0) } while (0)
@ -508,7 +508,7 @@ struct DummyStream
#define LOG(level, severity, ...) \ #define LOG(level, severity, ...) \
do { \ do { \
SIDE_EFFECT_CHECK(level, __VA_ARGS__); \ 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); \ log::Writer CONCAT(log_wrapper_, __LINE__)(severity); \
CONCAT(log_wrapper_, __LINE__) << log::Gray() << log_category_prefix; \ CONCAT(log_wrapper_, __LINE__) << log::Gray() << log_category_prefix; \
log::apply_severity<severity>(CONCAT(log_wrapper_, __LINE__)); \ log::apply_severity<severity>(CONCAT(log_wrapper_, __LINE__)); \

View file

@ -146,6 +146,14 @@ struct PoolBlock
// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15 // 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_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<uint8_t, HASH_SIZE + NONCE_SIZE + EXTRA_NONCE_SIZE> full_id; typedef std::array<uint8_t, HASH_SIZE + NONCE_SIZE + EXTRA_NONCE_SIZE> full_id;
FORCEINLINE full_id get_full_id() const FORCEINLINE full_id get_full_id() const

View file

@ -316,8 +316,10 @@ P2PServer* SideChain::p2pServer() const
return m_pool ? m_pool->p2p_server() : nullptr; return m_pool ? m_pool->p2p_server() : nullptr;
} }
bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares) const bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares, bool quiet) const
{ {
const int L = quiet ? 6 : 3;
shares.clear(); shares.clear();
shares.reserve(m_chainWindowSize * 2); shares.reserve(m_chainWindowSize * 2);
@ -325,14 +327,34 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
uint64_t block_depth = 0; uint64_t block_depth = 0;
const PoolBlock* cur = tip; 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 { do {
MinerShare cur_share{ cur->m_difficulty, &cur->m_minerWallet }; MinerShare cur_share{ cur->m_difficulty, &cur->m_minerWallet };
for (const hash& uncle_id : cur->m_uncles) { for (const hash& uncle_id : cur->m_uncles) {
auto it = m_blocksById.find(uncle_id); auto it = m_blocksById.find(uncle_id);
if (it == m_blocksById.end()) { if (it == m_blocksById.end()) {
LOGWARN(3, "get_shares: can't find uncle block at height = " << cur->m_sidechainHeight << ", id = " << uncle_id); LOGWARN(L, "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 calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight);
return false; return false;
} }
@ -345,11 +367,28 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
// Take some % of uncle's weight into this share // Take some % of uncle's weight into this share
const difficulty_type uncle_penalty = uncle->m_difficulty * m_unclePenalty / 100; 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; 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); 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; ++block_depth;
if (block_depth >= m_chainWindowSize) { if (block_depth >= m_chainWindowSize) {
@ -363,8 +402,8 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
auto it = m_blocksById.find(cur->m_parent); auto it = m_blocksById.find(cur->m_parent);
if (it == m_blocksById.end()) { 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(L, "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 calculate shares for block at height = " << tip->m_sidechainHeight << ", id = " << tip->m_sidechainId << ", mainchain height = " << tip->m_txinGenHeight);
return false; return false;
} }
@ -393,50 +432,6 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
return true; return true;
} }
bool SideChain::get_wallets(const PoolBlock* tip, std::vector<const Wallet*>& 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) bool SideChain::block_seen(const PoolBlock& block)
{ {
// Check if it's some old block // Check if it's some old block
@ -2090,10 +2085,10 @@ void SideChain::launch_precalc(const PoolBlock* block)
if (b->m_precalculated) { if (b->m_precalculated) {
continue; continue;
} }
std::vector<const Wallet*> wallets; std::vector<MinerShare> shares;
if (get_wallets(b, wallets)) { if (get_shares(b, shares, true)) {
b->m_precalculated = 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); MutexLock lock2(m_precalcJobsMutex);
m_precalcJobs.push_back(job); m_precalcJobs.push_back(job);
@ -2130,20 +2125,20 @@ void SideChain::precalc_worker()
uint8_t t[HASH_SIZE * 2 + sizeof(size_t)]; uint8_t t[HASH_SIZE * 2 + sizeof(size_t)];
memcpy(t, job->b->m_txkeySec.h, HASH_SIZE); memcpy(t, job->b->m_txkeySec.h, HASH_SIZE);
for (size_t i = 0, n = job->wallets.size(); i < n; ++i) { for (size_t i = 0, n = job->shares.size(); i < n; ++i) {
memcpy(t + HASH_SIZE, job->wallets[i]->view_public_key().h, HASH_SIZE); memcpy(t + HASH_SIZE, job->shares[i].m_wallet->view_public_key().h, HASH_SIZE);
memcpy(t + HASH_SIZE * 2, &i, sizeof(i)); memcpy(t + HASH_SIZE * 2, &i, sizeof(i));
if (!m_uniquePrecalcInputs->insert(robin_hood::hash_bytes(t, array_size(t))).second) { 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) { for (size_t i = 0, n = job->shares.size(); i < n; ++i) {
if (job->wallets[i]) { if (job->shares[i].m_wallet) {
hash eph_public_key; hash eph_public_key;
uint8_t view_tag; 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; delete job;

View file

@ -77,6 +77,10 @@ public:
const PoolBlock* chainTip() const { return m_chainTip; } const PoolBlock* chainTip() const { return m_chainTip; }
bool precalcFinished() const { return m_precalcFinished.load(); } 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<MinerShare>& shares, std::vector<uint64_t>& rewards); static bool split_reward(uint64_t reward, const std::vector<MinerShare>& shares, std::vector<uint64_t>& rewards);
private: private:
@ -85,9 +89,8 @@ private:
NetworkType m_networkType; NetworkType m_networkType;
private: private:
bool get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares) const; bool get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares, bool quiet = false) const;
bool get_difficulty(const PoolBlock* tip, std::vector<DifficultyData>& difficultyData, difficulty_type& curDifficulty) const; bool get_difficulty(const PoolBlock* tip, std::vector<DifficultyData>& difficultyData, difficulty_type& curDifficulty) const;
bool get_wallets(const PoolBlock* tip, std::vector<const Wallet*>& wallets) const;
void verify_loop(PoolBlock* block); void verify_loop(PoolBlock* block);
void verify(PoolBlock* block); void verify(PoolBlock* block);
void update_chain_tip(const PoolBlock* block); void update_chain_tip(const PoolBlock* block);
@ -134,7 +137,7 @@ private:
struct PrecalcJob struct PrecalcJob
{ {
const PoolBlock* b; const PoolBlock* b;
std::vector<const Wallet*> wallets; std::vector<MinerShare> shares;
}; };
uv_cond_t m_precalcJobsCond; uv_cond_t m_precalcJobsCond;

View file

@ -16,6 +16,8 @@ add_subdirectory(../external/src/RandomX RandomX)
set(LIBS ${LIBS} randomx) set(LIBS ${LIBS} randomx)
add_definitions(-DWITH_RANDOMX) add_definitions(-DWITH_RANDOMX)
add_definitions(-DP2POOL_UNIT_TESTS)
include(cmake/flags.cmake) include(cmake/flags.cmake)
set(HEADERS set(HEADERS