diff --git a/src/block_template.h b/src/block_template.h index f841b3f..9c7b2aa 100644 --- a/src/block_template.h +++ b/src/block_template.h @@ -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 m_finalReward; diff --git a/src/merge_mining_client.h b/src/merge_mining_client.h index 0fb27c2..93d5a00 100644 --- a/src/merge_mining_client.h +++ b/src/merge_mining_client.h @@ -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& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector& blob, const std::vector& merkle_proof, uint32_t merkle_proof_path) = 0; + virtual void submit_solution(const ChainParameters& chain_params, const std::vector& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector& blob, const std::vector& 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; diff --git a/src/merge_mining_client_json_rpc.cpp b/src/merge_mining_client_json_rpc.cpp index be7ee82..bb00c44 100644 --- a/src/merge_mining_client_json_rpc.cpp +++ b/src/merge_mining_client_json_rpc.cpp @@ -317,7 +317,18 @@ void MergeMiningClientJSON_RPC::submit_solution(const std::vector& /*co const std::vector& 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& /*coinbase_merkle_proof*/, const uint8_t (&/*hashing_blob*/)[128], size_t /*nonce_offset*/, const hash& seed_hash, const std::vector& blob, const std::vector& 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& aux_blob, const hash& seed_hash, const std::vector& blob, const std::vector& merkle_proof, uint32_t merkle_proof_path) +{ std::vector buf((aux_blob.size() + HASH_SIZE + blob.size()) * 2 + merkle_proof.size() * (HASH_SIZE * 2 + 3) + 256); log::Stream s(buf.data(), buf.size()); diff --git a/src/merge_mining_client_json_rpc.h b/src/merge_mining_client_json_rpc.h index 3e1634e..4f99a4f 100644 --- a/src/merge_mining_client_json_rpc.h +++ b/src/merge_mining_client_json_rpc.h @@ -32,6 +32,7 @@ public: bool get_params(ChainParameters& out_params) const override; void submit_solution(const std::vector& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector& blob, const std::vector& merkle_proof, uint32_t merkle_proof_path) override; + void submit_solution(const ChainParameters& chain_params, const std::vector& coinbase_merkle_proof, const uint8_t (&hashing_blob)[128], size_t nonce_offset, const hash& seed_hash, const std::vector& blob, const std::vector& 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& aux_blob, const hash& seed_hash, const std::vector& blob, const std::vector& merkle_proof, uint32_t merkle_proof_path); std::vector m_previousAuxBlobs[NUM_PREVIOUS_HASHES]; diff --git a/src/miner.cpp b/src/miner.cpp index b9d31a5..fd0c909 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -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, {} }); } } diff --git a/src/nodejs_pool_rpc.cpp b/src/nodejs_pool_rpc.cpp index 306b083..548db1c 100644 --- a/src/nodejs_pool_rpc.cpp +++ b/src/nodejs_pool_rpc.cpp @@ -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& blob) +bool extract_submitblock_request(const rapidjson::Value& params, std::vector& 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::max())) { + template_id = static_cast(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::max())) { + template_id = static_cast(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& 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(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(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 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 aux_chains = m_pool->block_template().get_aux_chains(work.template_id); + if (meets_aux) { + std::vector aux_blocks; + aux_blocks.reserve(work.aux_chains.size()); - std::vector 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()) { diff --git a/src/nodejs_pool_rpc.h b/src/nodejs_pool_rpc.h index a66b529..96063ba 100644 --- a/src/nodejs_pool_rpc.h +++ b/src/nodejs_pool_rpc.h @@ -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 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; + std::vector aux_chains; + std::vector 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& 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; diff --git a/src/p2pool.cpp b/src/p2pool.cpp index 5a466c1..7d0e73b 100644 --- a/src/p2pool.cpp +++ b/src/p2pool.cpp @@ -658,6 +658,21 @@ void p2pool::set_aux_job_donation(const std::vectorget_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 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(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); diff --git a/src/p2pool.h b/src/p2pool.h index 23c3492..e771c2c 100644 --- a/src/p2pool.h +++ b/src/p2pool.h @@ -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&& 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& 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; diff --git a/src/stratum_server.cpp b/src/stratum_server.cpp index 081c14f..f3a24f4 100644 --- a/src/stratum_server.cpp +++ b/src/stratum_server.cpp @@ -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(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, {} }); } }