Add nodejs pool extra nonce support

This commit is contained in:
Codex Bot
2026-05-02 23:33:23 +02:00
parent a4134bde72
commit f29afbb2a5
7 changed files with 216 additions and 46 deletions
+11 -6
View File
@@ -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();
+3 -3
View File
@@ -24,15 +24,15 @@ typedef Callback<void, const char* /*msg*/, size_t /*msg_size*/, double /*tcp_pi
FORCEINLINE static constexpr void dummy_callback(const char* /*msg*/, size_t /*msg_size*/, double /*tcp_ping*/) {}
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 = 0);
template<typename T, typename U>
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<void, const char*, size_t, double>::Derived<T> CallbackT;
typedef Callback<void, const char*, size_t, double>::Derived<U> CallbackU;
Call(address, port, req, auth, proxy, ssl, ssl_fingerprint, new CallbackT(std::forward<T>(cb)), new CallbackU(std::forward<U>(close_cb)), loop);
Call(address, port, req, auth, proxy, ssl, ssl_fingerprint, new CallbackT(std::forward<T>(cb)), new CallbackU(std::forward<U>(close_cb)), loop, timeout_seconds);
}
} // namespace JSONRPCRequest
+7 -3
View File
@@ -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<uint8_t>& /*co
}
// Get new mining job
on_timer();
}, &m_loop);
}, &m_loop, MERGE_MINING_RPC_TIMEOUT_SECONDS);
}
void MergeMiningClientJSON_RPC::print_status() const
+111 -18
View File
@@ -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<uint32_t>(p[3]) << 24);
}
bool compare_ignoring_ranges(const uint8_t* a, const uint8_t* b, size_t size, const std::initializer_list<std::pair<size_t, size_t>>& 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<uint8_t>& 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<size_t>(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<uint8_t> 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<char> 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<AuxChainData> aux_chains = m_pool->block_template().get_aux_chains(work.template_id);
std::vector<p2pool::SubmitAuxBlockData> 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);
}
+20 -15
View File
@@ -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<uint8_t> 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<uint8_t> block_template_blob;
std::vector<uint8_t> hashing_blob;
std::vector<uint8_t> hashing_blobs;
};
struct RpcClient : public Client
+51 -1
View File
@@ -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<uint8_t>&& blob)
void p2pool::submit_aux_block_async(const std::vector<SubmitAuxBlockData>& aux_blocks)
{
std::vector<SubmitAuxBlockData> 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<SubmitAuxBlockData>& 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<SubmitAuxBlockData> submit_data;
+13
View File
@@ -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<bool> m_stopped;
@@ -254,6 +255,18 @@ private:
mutable uv_mutex_t m_submitAuxBlockDataLock;
mutable std::vector<SubmitAuxBlockData> 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<RecentAuxSubmit> m_recentAuxSubmits;
uv_async_t m_submitBlockAsync;
uv_async_t m_submitAuxBlockAsync;
uv_async_t m_blockTemplateAsync;