From f29afbb2a579b2303b72189019489aa99bf8d408 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sat, 2 May 2026 23:33:23 +0200 Subject: [PATCH] Add nodejs pool extra nonce support --- src/json_rpc_request.cpp | 17 ++-- src/json_rpc_request.h | 6 +- src/merge_mining_client_json_rpc.cpp | 10 ++- src/nodejs_pool_rpc.cpp | 129 +++++++++++++++++++++++---- src/nodejs_pool_rpc.h | 35 ++++---- src/p2pool.cpp | 52 ++++++++++- src/p2pool.h | 13 +++ 7 files changed, 216 insertions(+), 46 deletions(-) diff --git a/src/json_rpc_request.cpp b/src/json_rpc_request.cpp index 47bf2c7..ac8b998 100644 --- a/src/json_rpc_request.cpp +++ b/src/json_rpc_request.cpp @@ -27,7 +27,7 @@ namespace JSONRPCRequest { struct CurlContext : public nocopy_nomove { - CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop); + CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop, int timeout_seconds); ~CurlContext(); static int socket_func(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp) @@ -81,9 +81,10 @@ struct CurlContext : public nocopy_nomove uint64_t m_connectedTime; std::string m_proxy; + int m_timeoutSeconds; }; -CurlContext::CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop) +CurlContext::CurlContext(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop, int timeout_seconds) : m_callback(cb) , m_closeCallback(close_cb) , m_loop(loop) @@ -95,6 +96,7 @@ CurlContext::CurlContext(const std::string& address, int port, const std::string , m_startTime(0) , m_connectedTime(0) , m_proxy(proxy) + , m_timeoutSeconds(timeout_seconds) { m_pollHandles.reserve(2); @@ -179,11 +181,14 @@ CurlContext::CurlContext(const std::string& address, int port, const std::string curl_easy_setopt_checked(m_handle, CURLOPT_WRITEFUNCTION, write_func); curl_easy_setopt_checked(m_handle, CURLOPT_WRITEDATA, this); + int timeout = m_timeoutSeconds; + if (timeout <= 0) { #ifdef DEV_TEST_SYNC - const int timeout = 10; + timeout = 10; #else - const int timeout = proxy.empty() ? 1 : 5; + timeout = proxy.empty() ? 1 : 5; #endif + } curl_easy_setopt_checked(m_handle, CURLOPT_URL, m_url.c_str()); @@ -518,7 +523,7 @@ void CurlContext::shutdown() } } -void Call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop) +void Call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, CallbackBase* cb, CallbackBase* close_cb, uv_loop_t* loop, int timeout_seconds) { if (!loop) { loop = uv_default_loop_checked(); @@ -528,7 +533,7 @@ void Call(const std::string& address, int port, const std::string& req, const st [=]() { try { - new CurlContext(address, port, req, auth, proxy, ssl, ssl_fingerprint, cb, close_cb, loop); + new CurlContext(address, port, req, auth, proxy, ssl, ssl_fingerprint, cb, close_cb, loop, timeout_seconds); } catch (const std::exception& e) { const char* msg = e.what(); diff --git a/src/json_rpc_request.h b/src/json_rpc_request.h index c5a1441..19a13bf 100644 --- a/src/json_rpc_request.h +++ b/src/json_rpc_request.h @@ -24,15 +24,15 @@ typedef Callback -FORCEINLINE void call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, T&& cb, U&& close_cb, uv_loop_t* loop = nullptr) +FORCEINLINE void call(const std::string& address, int port, const std::string& req, const std::string& auth, const std::string& proxy, bool ssl, const std::string& ssl_fingerprint, T&& cb, U&& close_cb, uv_loop_t* loop = nullptr, int timeout_seconds = 0) { typedef Callback::Derived CallbackT; typedef Callback::Derived CallbackU; - Call(address, port, req, auth, proxy, ssl, ssl_fingerprint, new CallbackT(std::forward(cb)), new CallbackU(std::forward(close_cb)), loop); + Call(address, port, req, auth, proxy, ssl, ssl_fingerprint, new CallbackT(std::forward(cb)), new CallbackU(std::forward(close_cb)), loop, timeout_seconds); } } // namespace JSONRPCRequest diff --git a/src/merge_mining_client_json_rpc.cpp b/src/merge_mining_client_json_rpc.cpp index a9fdfe7..be7ee82 100644 --- a/src/merge_mining_client_json_rpc.cpp +++ b/src/merge_mining_client_json_rpc.cpp @@ -28,6 +28,10 @@ LOG_CATEGORY(MergeMiningClientJSON_RPC) namespace p2pool { +namespace { +static constexpr int MERGE_MINING_RPC_TIMEOUT_SECONDS = 30; +} + MergeMiningClientJSON_RPC::MergeMiningClientJSON_RPC(p2pool* pool, const std::string& host, const std::string& wallet) : MergeMiningClientShared(pool, wallet) , m_host(host) @@ -135,7 +139,7 @@ void MergeMiningClientJSON_RPC::merge_mining_get_chain_id() if (size > 0) { LOGERR(1, "couldn't get merge mining id from " << m_host << ':' << m_port << ", error " << log::const_buf(data, size)); } - }, &m_loop); + }, &m_loop, MERGE_MINING_RPC_TIMEOUT_SECONDS); } bool MergeMiningClientJSON_RPC::parse_merge_mining_get_chain_id(const char* data, size_t size) @@ -237,7 +241,7 @@ void MergeMiningClientJSON_RPC::merge_mining_get_aux_block(uint64_t height, cons LOGERR(3, "couldn't get merge mining job from " << m_host << ':' << m_port << ", error " << log::const_buf(data, size)); } m_getJobRunning = false; - }, &m_loop); + }, &m_loop, MERGE_MINING_RPC_TIMEOUT_SECONDS); } bool MergeMiningClientJSON_RPC::parse_merge_mining_get_aux_block(const char* data, size_t size, bool& changed) @@ -343,7 +347,7 @@ void MergeMiningClientJSON_RPC::submit_solution(const std::vector& /*co } // Get new mining job on_timer(); - }, &m_loop); + }, &m_loop, MERGE_MINING_RPC_TIMEOUT_SECONDS); } void MergeMiningClientJSON_RPC::print_status() const diff --git a/src/nodejs_pool_rpc.cpp b/src/nodejs_pool_rpc.cpp index 10bfba1..a687ed0 100644 --- a/src/nodejs_pool_rpc.cpp +++ b/src/nodejs_pool_rpc.cpp @@ -27,9 +27,10 @@ LOG_CATEGORY(NodejsPoolRpc) namespace p2pool { static constexpr int DEFAULT_BACKLOG = 64; -static constexpr uint32_t NODEJS_POOL_NONCE_SIZE = 17; static constexpr size_t MAX_CACHED_WORK_ITEMS = 8; static constexpr size_t NODEJS_POOL_RPC_CALLBACK_BUF_SIZE = 65536; +static constexpr size_t EXTERNAL_EXTRA_NONCE_SIZE = 4; +static constexpr uint32_t HASHING_BLOB_BATCH_COUNT = 128; static thread_local const char* log_category_prefix = "NodejsPoolRpc "; namespace { @@ -56,6 +57,28 @@ FORCEINLINE uint32_t read_u32_le(const uint8_t* p) | (static_cast(p[3]) << 24); } +bool compare_ignoring_ranges(const uint8_t* a, const uint8_t* b, size_t size, const std::initializer_list>& ignored_ranges) +{ + for (size_t i = 0; i < size; ++i) { + bool ignored = false; + + for (const auto& range : ignored_ranges) { + const size_t offset = range.first; + const size_t range_size = range.second; + if ((range_size > 0) && (offset <= i) && (i < offset + range_size)) { + ignored = true; + break; + } + } + + if (!ignored && (a[i] != b[i])) { + return false; + } + } + + return true; +} + std::string http_response(const std::string& body, const char* status = "200 OK", const char* content_type = "application/json") { std::string s; @@ -200,19 +223,19 @@ bool NodejsPoolRpc::find_cached_work_by_block_blob(const std::vector& b continue; } - if (work.nonce_offset + NONCE_SIZE > blob.size()) { + if ((work.nonce_offset + NONCE_SIZE > blob.size()) || (work.extra_nonce_offset + work.extra_nonce_size > blob.size())) { continue; } - const size_t prefix_size = work.nonce_offset; - const size_t suffix_offset = work.nonce_offset + NONCE_SIZE; - const size_t suffix_size = blob.size() - suffix_offset; - - if (prefix_size && (memcmp(blob.data(), work.block_template_blob.data(), prefix_size) != 0)) { - continue; - } - - if (suffix_size && (memcmp(blob.data() + suffix_offset, work.block_template_blob.data() + suffix_offset, suffix_size) != 0)) { + if (!compare_ignoring_ranges( + blob.data(), + work.block_template_blob.data(), + blob.size(), + { + { work.nonce_offset, NONCE_SIZE }, + { work.extra_nonce_offset, work.extra_nonce_size } + })) + { continue; } @@ -269,6 +292,8 @@ bool NodejsPoolRpc::make_work_snapshot(WorkSnapshot& out) const out.nonce_offset = full_nonce_offset; out.hashing_nonce_offset = nonce_offset; out.extra_nonce_offset = extra_nonce_offset; + out.extra_nonce_size = EXTERNAL_EXTRA_NONCE_SIZE; + out.hashing_blob_extra_nonce_start = 1; out.mainchain_difficulty = mainchain_diff; out.sidechain_difficulty = sidechain_diff; out.aux_difficulty = aux_diff; @@ -276,6 +301,51 @@ bool NodejsPoolRpc::make_work_snapshot(WorkSnapshot& out) const out.block_template_blob = std::move(block_template_blob); out.hashing_blob.assign(hashing_blob, hashing_blob + hashing_blob_size); + uint64_t batch_height = 0; + difficulty_type batch_mainchain_diff, batch_aux_diff, batch_sidechain_diff; + hash batch_seed_hash; + size_t batch_nonce_offset = 0; + uint8_t batch_blob[128] = {}; + out.hashing_blob_size = 0; + out.hashing_blobs.clear(); + out.hashing_blobs.reserve(static_cast(HASHING_BLOB_BATCH_COUNT) * sizeof(batch_blob)); + + for (uint32_t i = 0; i < HASHING_BLOB_BATCH_COUNT; ++i) { + const uint32_t current_extra_nonce = out.hashing_blob_extra_nonce_start + i; + const uint32_t current_blob_size = m_pool->block_template().get_hashing_blob( + template_id, + current_extra_nonce, + batch_blob, + batch_height, + batch_mainchain_diff, + batch_aux_diff, + batch_sidechain_diff, + batch_seed_hash, + batch_nonce_offset); + + if (!current_blob_size) { + break; + } + + if (i == 0) { + out.hashing_blob_size = current_blob_size; + } + else if (current_blob_size != out.hashing_blob_size) { + return false; + } + + if ((batch_nonce_offset != nonce_offset) || (batch_seed_hash != seed_hash)) { + return false; + } + + out.hashing_blobs.insert(out.hashing_blobs.end(), batch_blob, batch_blob + current_blob_size); + ++out.hashing_blob_count; + } + + if (!out.hashing_blob_size || !out.hashing_blob_count) { + return false; + } + std::vector work_key; work_key.reserve(HASH_SIZE * 2 + sizeof(uint64_t) * 4 + sizeof(uint32_t) + out.hashing_blob.size()); work_key.insert(work_key.end(), miner_data.prev_id.h, miner_data.prev_id.h + HASH_SIZE); @@ -444,6 +514,7 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons 2048 + (work.block_template_blob.size() * 2) + (work.hashing_blob.size() * 2) + + (work.hashing_blobs.size() * 2) + 256; std::vector response(response_capacity, '\0'); @@ -451,6 +522,10 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons s << "{\"id\":\"" << log::EscapedString(id) << "\",\"jsonrpc\":\"2.0\",\"result\":{" << "\"blocktemplate_blob\":\"" << log::hex_buf(work.block_template_blob.data(), work.block_template_blob.size()) << '"' << ",\"blockhashing_blob\":\"" << log::hex_buf(work.hashing_blob.data(), work.hashing_blob.size()) << '"' + << ",\"p2pool_hashing_blob_batch_start\":" << work.hashing_blob_extra_nonce_start + << ",\"p2pool_hashing_blob_batch_size\":" << work.hashing_blob_size + << ",\"p2pool_hashing_blob_batch_count\":" << work.hashing_blob_count + << ",\"p2pool_hashing_blob_batch\":\"" << log::hex_buf(work.hashing_blobs.data(), work.hashing_blobs.size()) << '"' << ",\"difficulty\":" << work.submit_difficulty.lo << ",\"height\":" << work.mining_height << ",\"nonce_offset\":" << work.nonce_offset @@ -465,9 +540,9 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons << ",\"mainchain_difficulty\":\"" << work.mainchain_difficulty << '"' << ",\"backend_submit_difficulty\":\"" << work.submit_difficulty << '"' << ",\"backend_mode\":\"aux_only\"" - << ",\"p2pool_fixed_extra_nonce\":true" - << ",\"p2pool_fixed_extra_nonce_value\":0" - << ",\"p2pool_nodejs_pool_nonce_size\":" << NODEJS_POOL_NONCE_SIZE + << ",\"p2pool_external_extra_nonce\":true" + << ",\"p2pool_external_extra_nonce_size\":" << work.extra_nonce_size + << ",\"p2pool_external_extra_nonce_endianness\":\"LE\"" << "}}"; return std::string(response.data(), s.m_pos); } @@ -493,7 +568,10 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons } const uint32_t nonce = read_u32_le(blob.data() + work.nonce_offset); - const uint32_t extra_nonce = 0; + if (work.extra_nonce_offset + work.extra_nonce_size > blob.size()) { + return json_error_response(id, 0, "invalid_extra_nonce_offset"); + } + const uint32_t extra_nonce = read_u32_le(blob.data() + work.extra_nonce_offset); uint8_t hashing_blob[128] = {}; uint64_t height = 0; @@ -517,16 +595,31 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons return json_error_response(id, 0, "low_diff"); } + const bool meets_mainchain = mainchain_diff.check_pow(result_hash); + const bool meets_aux = aux_diff.check_pow(result_hash); + const bool meets_sidechain = sidechain_diff.check_pow(result_hash); + + LOGINFO(4, "NodejsPoolRpc submitblock: template id = " << work.template_id + << ", nonce = " << nonce + << ", extra_nonce = " << extra_nonce + << ", backend_submit_diff = " << work.submit_difficulty + << ", aux_diff = " << aux_diff + << ", sidechain_diff = " << sidechain_diff + << ", mainchain_diff = " << mainchain_diff + << ", meets_aux = " << (meets_aux ? "true" : "false") + << ", meets_sidechain = " << (meets_sidechain ? "true" : "false") + << ", meets_mainchain = " << (meets_mainchain ? "true" : "false")); + bool parent_submitted = false; bool sidechain_submitted = false; size_t aux_submitted = 0; - if (mainchain_diff.check_pow(result_hash)) { + if (meets_mainchain) { m_pool->submit_block_async(std::move(blob)); parent_submitted = true; } - if (aux_diff.check_pow(result_hash)) { + if (meets_aux) { const std::vector aux_chains = m_pool->block_template().get_aux_chains(work.template_id); std::vector aux_blocks; @@ -544,7 +637,7 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons } } - if (sidechain_diff.check_pow(result_hash)) { + if (meets_sidechain) { sidechain_submitted = m_pool->submit_sidechain_block(work.template_id, nonce, extra_nonce); } diff --git a/src/nodejs_pool_rpc.h b/src/nodejs_pool_rpc.h index f930be8..8e4e4ad 100644 --- a/src/nodejs_pool_rpc.h +++ b/src/nodejs_pool_rpc.h @@ -32,25 +32,30 @@ public: void on_shutdown() override; private: - struct WorkSnapshot - { - bool ready = false; - uint64_t mainchain_height = 0; - uint64_t mining_height = 0; - uint64_t sidechain_height = 0; + struct WorkSnapshot + { + bool ready = false; + uint64_t mainchain_height = 0; + uint64_t mining_height = 0; + uint64_t sidechain_height = 0; hash parent_prev_id; hash seed_hash; hash work_hash; - uint32_t template_id = 0; - size_t nonce_offset = 0; - size_t hashing_nonce_offset = 0; - size_t extra_nonce_offset = 0; - difficulty_type mainchain_difficulty; - difficulty_type sidechain_difficulty; - difficulty_type aux_difficulty; - difficulty_type submit_difficulty; - std::vector block_template_blob; + uint32_t template_id = 0; + size_t nonce_offset = 0; + size_t hashing_nonce_offset = 0; + size_t extra_nonce_offset = 0; + size_t extra_nonce_size = 4; + uint32_t hashing_blob_extra_nonce_start = 0; + uint32_t hashing_blob_count = 0; + uint32_t hashing_blob_size = 0; + difficulty_type mainchain_difficulty; + difficulty_type sidechain_difficulty; + difficulty_type aux_difficulty; + difficulty_type submit_difficulty; + std::vector block_template_blob; std::vector hashing_blob; + std::vector hashing_blobs; }; struct RpcClient : public Client diff --git a/src/p2pool.cpp b/src/p2pool.cpp index 8c07465..5a466c1 100644 --- a/src/p2pool.cpp +++ b/src/p2pool.cpp @@ -172,6 +172,7 @@ p2pool::p2pool(const Params& params) uv_mutex_init_checked(&m_submitBlockDataLock); uv_mutex_init_checked(&m_submitAuxBlockDataLock); + uv_mutex_init_checked(&m_recentAuxSubmitsLock); uv_mutex_init_checked(&m_missingHeightsLock); @@ -256,6 +257,7 @@ p2pool::~p2pool() uv_mutex_destroy(&m_submitBlockDataLock); uv_mutex_destroy(&m_submitAuxBlockDataLock); + uv_mutex_destroy(&m_recentAuxSubmitsLock); uv_mutex_destroy(&m_missingHeightsLock); @@ -906,9 +908,28 @@ void p2pool::submit_block_async(std::vector&& blob) void p2pool::submit_aux_block_async(const std::vector& aux_blocks) { + std::vector filtered_aux_blocks; + filtered_aux_blocks.reserve(aux_blocks.size()); + + for (const SubmitAuxBlockData& aux_block : aux_blocks) { + if (remember_recent_aux_submit(aux_block)) { + filtered_aux_blocks.push_back(aux_block); + } + else { + LOGINFO(4, "submit_aux_block_async: skipping duplicate aux submit for template id = " << aux_block.template_id + << ", chain_id = " << aux_block.chain_id + << ", nonce = " << aux_block.nonce + << ", extra_nonce = " << aux_block.extra_nonce); + } + } + + if (filtered_aux_blocks.empty()) { + return; + } + { MutexLock lock(m_submitAuxBlockDataLock); - m_submitAuxBlockData.insert(m_submitAuxBlockData.end(), aux_blocks.begin(), aux_blocks.end()); + m_submitAuxBlockData.insert(m_submitAuxBlockData.end(), filtered_aux_blocks.begin(), filtered_aux_blocks.end()); } // If p2pool is stopped, m_submitAuxBlockAsync is most likely already closed @@ -924,6 +945,35 @@ void p2pool::submit_aux_block_async(const std::vector& aux_b } } +bool p2pool::remember_recent_aux_submit(const SubmitAuxBlockData& data) const +{ + // Collapse burst duplicates from overlapping workers, but keep the window short + // so a later retry after a genuine transport failure can still go through. + constexpr uint64_t DUPLICATE_WINDOW = 5; + const uint64_t now = seconds_since_epoch(); + + MutexLock lock(m_recentAuxSubmitsLock); + + m_recentAuxSubmits.erase( + std::remove_if(m_recentAuxSubmits.begin(), m_recentAuxSubmits.end(), + [now](const RecentAuxSubmit& item) { + return (now > item.timestamp) && (now - item.timestamp > DUPLICATE_WINDOW); + }), + m_recentAuxSubmits.end()); + + for (const RecentAuxSubmit& item : m_recentAuxSubmits) { + if ((item.chain_id == data.chain_id) + && (item.template_id == data.template_id) + && (item.nonce == data.nonce) + && (item.extra_nonce == data.extra_nonce)) { + return false; + } + } + + m_recentAuxSubmits.push_back(RecentAuxSubmit{ data.chain_id, data.template_id, data.nonce, data.extra_nonce, now }); + return true; +} + void p2pool::submit_aux_block() const { std::vector submit_data; diff --git a/src/p2pool.h b/src/p2pool.h index 7349620..23c3492 100644 --- a/src/p2pool.h +++ b/src/p2pool.h @@ -157,6 +157,7 @@ private: void submit_block() const; void submit_aux_block() const; + bool remember_recent_aux_submit(const SubmitAuxBlockData& data) const; std::atomic m_stopped; @@ -254,6 +255,18 @@ private: mutable uv_mutex_t m_submitAuxBlockDataLock; mutable std::vector m_submitAuxBlockData; + struct RecentAuxSubmit + { + hash chain_id; + uint32_t template_id = 0; + uint32_t nonce = 0; + uint32_t extra_nonce = 0; + uint64_t timestamp = 0; + }; + + mutable uv_mutex_t m_recentAuxSubmitsLock; + mutable std::vector m_recentAuxSubmits; + uv_async_t m_submitBlockAsync; uv_async_t m_submitAuxBlockAsync; uv_async_t m_blockTemplateAsync;