Stabilize nodejs facade submissions

This commit is contained in:
Codex Bot
2026-05-07 01:15:47 +02:00
parent ba570f2de9
commit 3759b59e6b
10 changed files with 208 additions and 55 deletions
+4 -1
View File
@@ -111,7 +111,10 @@ private:
#ifdef P2POOL_UNIT_TESTS
BlockTemplate* m_oldTemplates[1] = {};
#else
BlockTemplate* m_oldTemplates[4] = {};
// Nodejs-pool submits can arrive noticeably later than native stratum
// submits. Keep enough old templates to rebuild aux proofs for delayed
// shares instead of failing with stale_template after a few rotations.
BlockTemplate* m_oldTemplates[512] = {};
#endif
std::atomic<uint64_t> m_finalReward;
+5
View File
@@ -50,6 +50,11 @@ public:
[[nodiscard]] virtual bool get_params(ChainParameters& out_params) const = 0;
virtual void on_external_block(const PoolBlock& block) = 0;
virtual void submit_solution(const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path) = 0;
virtual void submit_solution(const ChainParameters& chain_params, const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path)
{
(void)chain_params;
submit_solution(coinbase_merkle_proof, hashing_blob, nonce_offset, seed_hash, blob, merkle_proof, merkle_proof_path);
}
virtual void print_status() const = 0;
virtual void api_status(log::Stream&) const = 0;
+11
View File
@@ -317,7 +317,18 @@ void MergeMiningClientJSON_RPC::submit_solution(const std::vector<uint8_t>& /*co
const std::vector<uint8_t>& aux_blob = (index < NUM_PREVIOUS_HASHES) ? m_previousAuxBlobs[index] : m_chainParams.aux_blob;
const hash& aux_hash = (index < NUM_PREVIOUS_HASHES) ? m_previousAuxHashes[index] : m_chainParams.aux_hash;
const ChainParameters chain_params = m_chainParams;
submit_solution_with_params(chain_params, aux_hash, aux_blob, seed_hash, blob, merkle_proof, merkle_proof_path);
}
void MergeMiningClientJSON_RPC::submit_solution(const ChainParameters& chain_params, const std::vector<uint8_t>& /*coinbase_merkle_proof*/, const uint8_t (&/*hashing_blob*/)[128], size_t /*nonce_offset*/, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path)
{
submit_solution_with_params(chain_params, chain_params.aux_hash, chain_params.aux_blob, seed_hash, blob, merkle_proof, merkle_proof_path);
}
void MergeMiningClientJSON_RPC::submit_solution_with_params(const ChainParameters& /*chain_params*/, const hash& aux_hash, const std::vector<uint8_t>& aux_blob, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path)
{
std::vector<char> buf((aux_blob.size() + HASH_SIZE + blob.size()) * 2 + merkle_proof.size() * (HASH_SIZE * 2 + 3) + 256);
log::Stream s(buf.data(), buf.size());
+2
View File
@@ -32,6 +32,7 @@ public:
bool get_params(ChainParameters& out_params) const override;
void submit_solution(const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path) override;
void submit_solution(const ChainParameters& chain_params, const std::vector<uint8_t>& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path) override;
void print_status() const override;
void api_status(log::Stream&) const override;
@@ -51,6 +52,7 @@ private:
bool parse_merge_mining_get_aux_block(const char* data, size_t size, bool& changed);
bool parse_merge_mining_submit_solution(const char* data, size_t size) const;
void submit_solution_with_params(const ChainParameters& chain_params, const hash& aux_hash, const std::vector<uint8_t>& aux_blob, const hash& seed_hash, const std::vector<uint8_t>& blob, const std::vector<hash>& merkle_proof, uint32_t merkle_proof_path);
std::vector<uint8_t> m_previousAuxBlobs[NUM_PREVIOUS_HASHES];
+1 -1
View File
@@ -256,7 +256,7 @@ void Miner::run(WorkerData* data)
for (const AuxChainData& aux_data : j.m_auxChains) {
if (aux_data.difficulty.check_pow(h)) {
LOGINFO(0, log::Green() << "AUX BLOCK FOUND: chain_id " << aux_data.unique_id << ", diff " << aux_data.difficulty << ", worker thread " << data->m_index << '/' << data->m_count);
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, j.m_templateId, j.m_nonce, j.m_extraNonce });
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, j.m_templateId, j.m_nonce, j.m_extraNonce, {} });
}
}
+130 -27
View File
@@ -27,7 +27,7 @@ LOG_CATEGORY(NodejsPoolRpc)
namespace p2pool {
static constexpr int DEFAULT_BACKLOG = 64;
static constexpr size_t MAX_CACHED_WORK_ITEMS = 8;
static constexpr size_t MAX_CACHED_WORK_ITEMS = 512;
static constexpr size_t NODEJS_POOL_RPC_CALLBACK_BUF_SIZE = 65536;
static constexpr size_t EXTERNAL_EXTRA_NONCE_SIZE = 4;
static constexpr uint32_t MIN_HASHING_BLOB_BATCH_COUNT = 128;
@@ -147,12 +147,43 @@ const char* jsonrpc_id_to_cstr(const rapidjson::Value& id, std::string& tmp)
return "0";
}
bool extract_submitblock_blob(const rapidjson::Value& params, std::vector<uint8_t>& blob)
bool extract_submitblock_request(const rapidjson::Value& params, std::vector<uint8_t>& blob, uint32_t& template_id, bool& has_template_id)
{
if (params.IsArray() && !params.Empty() && params[0].IsString()) {
has_template_id = false;
return from_hex(params[0].GetString(), params[0].GetStringLength(), blob);
}
if (params.IsArray() && !params.Empty() && params[0].IsObject()) {
const rapidjson::Value& req = params[0];
auto blob_it = req.FindMember("blob");
if ((blob_it == req.MemberEnd()) || !blob_it->value.IsString()) {
return false;
}
has_template_id = false;
auto template_it = req.FindMember("template_id");
if (template_it != req.MemberEnd()) {
if (template_it->value.IsUint()) {
template_id = template_it->value.GetUint();
has_template_id = true;
}
else if (template_it->value.IsUint64() && (template_it->value.GetUint64() <= std::numeric_limits<uint32_t>::max())) {
template_id = static_cast<uint32_t>(template_it->value.GetUint64());
has_template_id = true;
}
else if (template_it->value.IsInt64() && (template_it->value.GetInt64() >= 0) && (template_it->value.GetInt64() <= std::numeric_limits<uint32_t>::max())) {
template_id = static_cast<uint32_t>(template_it->value.GetInt64());
has_template_id = true;
}
else {
return false;
}
}
return from_hex(blob_it->value.GetString(), blob_it->value.GetStringLength(), blob);
}
return false;
}
@@ -220,12 +251,14 @@ void NodejsPoolRpc::remember_work_snapshot(const WorkSnapshot& work) const
return;
}
WorkSnapshot cached = work;
MutexLock lock(m_recentWorkLock);
auto same_template = [&work](const WorkSnapshot& item) {
return (item.template_id == work.template_id)
&& (item.work_hash == work.work_hash)
&& (item.sidechain_height == work.sidechain_height);
auto same_template = [&cached](const WorkSnapshot& item) {
return (item.template_id == cached.template_id)
&& (item.work_hash == cached.work_hash)
&& (item.sidechain_height == cached.sidechain_height);
};
for (size_t i = 0; i < m_recentWork.size(); ++i) {
@@ -235,7 +268,7 @@ void NodejsPoolRpc::remember_work_snapshot(const WorkSnapshot& work) const
}
}
m_recentWork.insert(m_recentWork.begin(), work);
m_recentWork.insert(m_recentWork.begin(), std::move(cached));
if (m_recentWork.size() > MAX_CACHED_WORK_ITEMS) {
m_recentWork.resize(MAX_CACHED_WORK_ITEMS);
}
@@ -273,6 +306,50 @@ bool NodejsPoolRpc::find_cached_work_by_block_blob(const std::vector<uint8_t>& b
return false;
}
bool NodejsPoolRpc::find_cached_work_by_template_id(uint32_t template_id, WorkSnapshot& out) const
{
MutexLock lock(m_recentWorkLock);
for (const WorkSnapshot& work : m_recentWork) {
if (!work.ready) {
continue;
}
if (work.template_id == template_id) {
out = work;
return true;
}
}
return false;
}
uint32_t NodejsPoolRpc::copy_cached_hashing_blob(const WorkSnapshot& work, uint32_t extra_nonce, uint8_t (&blob)[128], size_t& nonce_offset) const
{
if (!work.hashing_blob_size || !work.hashing_blob_count || work.hashing_blobs.empty()) {
return 0;
}
if (extra_nonce < work.hashing_blob_extra_nonce_start) {
return 0;
}
const uint32_t index = extra_nonce - work.hashing_blob_extra_nonce_start;
if (index >= work.hashing_blob_count) {
return 0;
}
const size_t blob_size = work.hashing_blob_size;
const size_t offset = static_cast<size_t>(index) * blob_size;
if ((blob_size > sizeof(blob)) || (offset + blob_size > work.hashing_blobs.size())) {
return 0;
}
memcpy(blob, work.hashing_blobs.data() + offset, blob_size);
nonce_offset = work.hashing_nonce_offset;
return static_cast<uint32_t>(blob_size);
}
bool NodejsPoolRpc::make_work_snapshot(WorkSnapshot& out, uint32_t hashing_blob_batch_count) const
{
const MinerData miner_data = m_pool->miner_data();
@@ -327,6 +404,15 @@ bool NodejsPoolRpc::make_work_snapshot(WorkSnapshot& out, uint32_t hashing_blob_
out.submit_difficulty = aux_diff.empty() ? sidechain_diff : aux_diff;
out.block_template_blob = std::move(block_template_blob);
out.hashing_blob.assign(hashing_blob, hashing_blob + hashing_blob_size);
out.aux_chains = m_pool->block_template().get_aux_chains(template_id);
out.aux_chain_params.clear();
out.aux_chain_params.reserve(out.aux_chains.size());
for (const AuxChainData& aux_data : out.aux_chains) {
IMergeMiningClient::ChainParameters chain_params;
if (m_pool->get_merge_mining_chain_params(aux_data.unique_id, chain_params) && (chain_params.aux_hash == aux_data.data) && !chain_params.aux_blob.empty()) {
out.aux_chain_params.push_back(std::move(chain_params));
}
}
uint64_t batch_height = 0;
difficulty_type batch_mainchain_diff, batch_aux_diff, batch_sidechain_diff;
@@ -582,12 +668,17 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons
}
std::vector<uint8_t> blob;
if (!extract_submitblock_blob(params_it->value, blob) || blob.empty()) {
uint32_t requested_template_id = 0;
bool has_requested_template_id = false;
if (!extract_submitblock_request(params_it->value, blob, requested_template_id, has_requested_template_id) || blob.empty()) {
return json_error_response(id, -32602, "invalid blob");
}
WorkSnapshot work;
if (!find_cached_work_by_block_blob(blob, work)) {
const bool found_work = has_requested_template_id
? find_cached_work_by_template_id(requested_template_id, work)
: find_cached_work_by_block_blob(blob, work);
if (!found_work) {
return json_error_response(id, 0, "unknown_or_stale_template");
}
@@ -601,16 +692,21 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons
}
const uint32_t extra_nonce = read_u32_le(blob.data() + work.extra_nonce_offset);
uint8_t hashing_blob[128] = {};
uint64_t height = 0;
difficulty_type mainchain_diff, aux_diff, sidechain_diff;
hash seed_hash;
size_t nonce_offset = 0;
uint8_t hashing_blob[128] = {};
uint64_t height = work.mining_height;
difficulty_type mainchain_diff = work.mainchain_difficulty;
difficulty_type aux_diff = work.aux_difficulty;
difficulty_type sidechain_diff = work.sidechain_difficulty;
hash seed_hash = work.seed_hash;
size_t nonce_offset = 0;
const uint32_t hashing_blob_size = m_pool->block_template().get_hashing_blob(work.template_id, extra_nonce, hashing_blob, height, mainchain_diff, aux_diff, sidechain_diff, seed_hash, nonce_offset);
if (!hashing_blob_size || (nonce_offset + NONCE_SIZE > hashing_blob_size)) {
return json_error_response(id, 0, "stale_template");
}
uint32_t hashing_blob_size = copy_cached_hashing_blob(work, extra_nonce, hashing_blob, nonce_offset);
if (!hashing_blob_size) {
hashing_blob_size = m_pool->block_template().get_hashing_blob(work.template_id, extra_nonce, hashing_blob, height, mainchain_diff, aux_diff, sidechain_diff, seed_hash, nonce_offset);
}
if (!hashing_blob_size || (nonce_offset + NONCE_SIZE > hashing_blob_size)) {
return json_error_response(id, 0, "stale_template");
}
memcpy(hashing_blob + nonce_offset, blob.data() + work.nonce_offset, NONCE_SIZE);
@@ -647,17 +743,24 @@ std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) cons
parent_submitted = true;
}
if (meets_aux) {
const std::vector<AuxChainData> aux_chains = m_pool->block_template().get_aux_chains(work.template_id);
if (meets_aux) {
std::vector<p2pool::SubmitAuxBlockData> aux_blocks;
aux_blocks.reserve(work.aux_chains.size());
std::vector<p2pool::SubmitAuxBlockData> aux_blocks;
aux_blocks.reserve(aux_chains.size());
for (const AuxChainData& aux_data : aux_chains) {
if (aux_data.difficulty.check_pow(result_hash)) {
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, work.template_id, nonce, extra_nonce });
for (const AuxChainData& aux_data : work.aux_chains) {
if (aux_data.difficulty.check_pow(result_hash)) {
auto params_it = std::find_if(work.aux_chain_params.begin(), work.aux_chain_params.end(),
[&aux_data](const IMergeMiningClient::ChainParameters& params) {
return (params.aux_id == aux_data.unique_id) && (params.aux_hash == aux_data.data);
});
if (params_it == work.aux_chain_params.end()) {
LOGWARN(3, "NodejsPoolRpc submitblock: missing cached aux params for chain_id = " << aux_data.unique_id
<< ", aux_hash = " << aux_data.data);
continue;
}
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, work.template_id, nonce, extra_nonce, *params_it });
}
}
}
aux_submitted = aux_blocks.size();
if (!aux_blocks.empty()) {
+24 -19
View File
@@ -18,6 +18,7 @@
#pragma once
#include "tcp_server.h"
#include "merge_mining_client.h"
namespace p2pool {
@@ -32,30 +33,32 @@ 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;
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;
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;
std::vector<AuxChainData> aux_chains;
std::vector<IMergeMiningClient::ChainParameters> aux_chain_params;
};
struct RpcClient : public Client
@@ -87,7 +90,9 @@ private:
bool make_work_snapshot(WorkSnapshot& out, uint32_t hashing_blob_batch_count) const;
void remember_work_snapshot(const WorkSnapshot& work) const;
bool find_cached_work_by_template_id(uint32_t template_id, WorkSnapshot& out) const;
bool find_cached_work_by_block_blob(const std::vector<uint8_t>& blob, WorkSnapshot& out) const;
uint32_t copy_cached_hashing_blob(const WorkSnapshot& work, uint32_t extra_nonce, uint8_t (&blob)[128], size_t& nonce_offset) const;
bool handle_http_request(RpcClient* client);
std::string build_get_height_response() const;
std::string build_json_rpc_response(const std::string& body) const;
+27 -6
View File
@@ -658,6 +658,21 @@ void p2pool::set_aux_job_donation(const std::vector<IMergeMiningClient::ChainPar
}
#endif
bool p2pool::get_merge_mining_chain_params(const hash& chain_id, IMergeMiningClient::ChainParameters& out_params) const
{
ReadLock lock(m_mergeMiningClientsLock);
IMergeMiningClient::ChainParameters chain_params;
for (const IMergeMiningClient* c : m_mergeMiningClients) {
if (c->get_params(chain_params) && (chain_params.aux_id == chain_id)) {
out_params = chain_params;
return true;
}
}
return false;
}
void p2pool::update_aux_data(const hash& chain_id)
{
if (m_stopped) {
@@ -965,12 +980,13 @@ bool p2pool::remember_recent_aux_submit(const SubmitAuxBlockData& data) const
if ((item.chain_id == data.chain_id)
&& (item.template_id == data.template_id)
&& (item.nonce == data.nonce)
&& (item.extra_nonce == data.extra_nonce)) {
&& (item.extra_nonce == data.extra_nonce)
&& (data.chain_params.aux_hash.empty() || item.aux_hash.empty() || (item.aux_hash == data.chain_params.aux_hash))) {
return false;
}
}
m_recentAuxSubmits.push_back(RecentAuxSubmit{ data.chain_id, data.template_id, data.nonce, data.extra_nonce, now });
m_recentAuxSubmits.push_back(RecentAuxSubmit{ data.chain_id, data.chain_params.aux_hash, data.template_id, data.nonce, data.extra_nonce, now });
return true;
}
@@ -1025,24 +1041,29 @@ void p2pool::submit_aux_block() const
}
if (chain_id == chain_params.aux_id) {
IMergeMiningClient::ChainParameters submit_chain_params = submit_data[i].chain_params;
if (submit_chain_params.aux_id.empty() || submit_chain_params.aux_hash.empty() || submit_chain_params.aux_blob.empty()) {
submit_chain_params = chain_params;
}
std::vector<hash> proof;
uint32_t path;
if (m_blockTemplate->get_aux_proof(template_id, extra_nonce, chain_params.aux_hash, proof, path)) {
if (m_blockTemplate->get_aux_proof(template_id, extra_nonce, submit_chain_params.aux_hash, proof, path)) {
if (pool_block_debug()) {
const MinerData data = miner_data();
const uint32_t n_aux_chains = static_cast<uint32_t>(data.aux_chains.size() + 1);
const uint32_t index = get_aux_slot(chain_params.aux_id, data.aux_nonce, n_aux_chains);
if (!verify_merkle_proof(chain_params.aux_hash, proof, index, n_aux_chains, merge_mining_root)) {
if (!verify_merkle_proof(submit_chain_params.aux_hash, proof, index, n_aux_chains, merge_mining_root)) {
LOGERR(0, "submit_aux_block: verify_merkle_proof (1) failed for chain_id " << chain_id);
}
if (!verify_merkle_proof(chain_params.aux_hash, proof, path, merge_mining_root)) {
if (!verify_merkle_proof(submit_chain_params.aux_hash, proof, path, merge_mining_root)) {
LOGERR(0, "submit_aux_block: verify_merkle_proof (2) failed for chain_id " << chain_id);
}
}
c->submit_solution(block_tpl->get_coinbase_merkle_proof(), hashing_blob, nonce_offset, seed_hash, blob, proof, path);
c->submit_solution(submit_chain_params, block_tpl->get_coinbase_merkle_proof(), hashing_blob, nonce_offset, seed_hash, blob, proof, path);
}
else {
LOGWARN(3, "submit_aux_block: failed to get merkle proof for chain_id " << chain_id);
+3
View File
@@ -96,6 +96,7 @@ public:
#endif
void update_aux_data(const hash& chain_id);
bool get_merge_mining_chain_params(const hash& chain_id, IMergeMiningClient::ChainParameters& out_params) const;
void submit_block_async(uint32_t template_id, uint32_t nonce, uint32_t extra_nonce);
void submit_block_async(std::vector<uint8_t>&& blob);
@@ -106,6 +107,7 @@ public:
uint32_t template_id = 0;
uint32_t nonce = 0;
uint32_t extra_nonce = 0;
IMergeMiningClient::ChainParameters chain_params;
};
void submit_aux_block_async(const std::vector<SubmitAuxBlockData>& aux_blocks);
@@ -258,6 +260,7 @@ private:
struct RecentAuxSubmit
{
hash chain_id;
hash aux_hash;
uint32_t template_id = 0;
uint32_t nonce = 0;
uint32_t extra_nonce = 0;
+1 -1
View File
@@ -445,7 +445,7 @@ bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* jo
if (aux_data.difficulty.check_pow(resultHash)) {
const char* s = client->m_customUser;
LOGINFO(0, log::Green() << "client " << static_cast<char*>(client->m_addrString) << (*s ? " user " : "") << s << " found an aux block for chain_id " << aux_data.unique_id << ", diff " << aux_data.difficulty << ", submitting it");
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, template_id, nonce, extra_nonce });
aux_blocks.emplace_back(p2pool::SubmitAuxBlockData{ aux_data.unique_id, template_id, nonce, extra_nonce, {} });
}
}