From a4134bde72eb1ba2d966d3c5c17036f979f84ced Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sat, 2 May 2026 09:53:27 +0200 Subject: [PATCH] Add nodejs pool facade for Monero P2Pool --- CMakeLists.txt | 2 + src/main.cpp | 1 + src/nodejs_pool_rpc.cpp | 570 ++++++++++++++++++++++++++++++++++++++++ src/nodejs_pool_rpc.h | 92 +++++++ src/p2pool.cpp | 8 + src/p2pool.h | 3 + src/params.cpp | 5 + src/params.h | 1 + 8 files changed, 682 insertions(+) create mode 100644 src/nodejs_pool_rpc.cpp create mode 100644 src/nodejs_pool_rpc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 34ec3b3..39efe61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,7 @@ set(HEADERS src/merge_mining_client.h src/merge_mining_client_json_rpc.h src/merkle.h + src/nodejs_pool_rpc.h src/p2p_server.h src/p2pool.h src/p2pool_api.h @@ -153,6 +154,7 @@ set(SOURCES src/merge_mining_client.cpp src/merge_mining_client_json_rpc.cpp src/merkle.cpp + src/nodejs_pool_rpc.cpp src/p2p_server.cpp src/p2pool.cpp src/p2pool_api.cpp diff --git a/src/main.cpp b/src/main.cpp index 7411704..4db60aa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -65,6 +65,7 @@ void p2pool_usage() "--rpc-port monerod RPC API port number, default is 18081\n" "--zmq-port monerod ZMQ pub port number, default is 18083 (same port as in monerod's \"--zmq-pub\" command line parameter)\n" "--stratum Comma-separated list of IP:port for stratum server to listen on\n" + "--nodejs-pool-rpc Comma-separated list of IP:port for daemon-compatible RPC facade for nodejs pools\n" "--p2p Comma-separated list of IP:port for p2p server to listen on\n" "--addpeers Comma-separated list of IP:port of other p2pool nodes to connect to\n" "--stratum-ban-time N Number of seconds to ban misbehaving stratum client, default is %u\n" diff --git a/src/nodejs_pool_rpc.cpp b/src/nodejs_pool_rpc.cpp new file mode 100644 index 0000000..10bfba1 --- /dev/null +++ b/src/nodejs_pool_rpc.cpp @@ -0,0 +1,570 @@ +/* + * This file is part of the Monero P2Pool + * Copyright (c) 2021-2026 SChernykh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.h" +#include "nodejs_pool_rpc.h" +#include "block_template.h" +#include "keccak.h" +#include "p2pool.h" +#include "rapidjson_wrapper.h" + +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 thread_local const char* log_category_prefix = "NodejsPoolRpc "; + +namespace { + +FORCEINLINE void append_u64(std::vector& buf, uint64_t value) +{ + for (size_t i = 0; i < sizeof(value); ++i) { + buf.push_back(static_cast(value >> (i * 8))); + } +} + +FORCEINLINE void append_u32(std::vector& buf, uint32_t value) +{ + for (size_t i = 0; i < sizeof(value); ++i) { + buf.push_back(static_cast(value >> (i * 8))); + } +} + +FORCEINLINE uint32_t read_u32_le(const uint8_t* p) +{ + return static_cast(p[0]) + | (static_cast(p[1]) << 8) + | (static_cast(p[2]) << 16) + | (static_cast(p[3]) << 24); +} + +std::string http_response(const std::string& body, const char* status = "200 OK", const char* content_type = "application/json") +{ + std::string s; + s.reserve(body.size() + 128); + s += "HTTP/1.1 "; + s += status; + s += "\r\nContent-Type: "; + s += content_type; + s += "\r\nContent-Length: "; + s += std::to_string(body.size()); + s += "\r\nConnection: close\r\n\r\n"; + s += body; + return s; +} + +bool parse_content_length(const std::string& headers, size_t& out) +{ + const std::string lower = tolower(headers); + size_t k = lower.find("\r\ncontent-length:"); + if (k == std::string::npos) { + if (lower.rfind("content-length:", 0) == 0) { + k = 0; + } + else { + out = 0; + return true; + } + } + + k = lower.find(':', k); + if (k == std::string::npos) { + return false; + } + ++k; + + while ((k < headers.size()) && ((headers[k] == ' ') || (headers[k] == '\t'))) { + ++k; + } + + char* end = nullptr; + const unsigned long long value = strtoull(headers.c_str() + k, &end, 10); + if ((end == headers.c_str() + k) || (value > std::numeric_limits::max())) { + return false; + } + + out = static_cast(value); + return true; +} + +const char* jsonrpc_id_to_cstr(const rapidjson::Value& id, std::string& tmp) +{ + if (id.IsString()) { + return id.GetString(); + } + + if (id.IsInt64()) { + tmp = std::to_string(id.GetInt64()); + return tmp.c_str(); + } + + if (id.IsUint64()) { + tmp = std::to_string(id.GetUint64()); + return tmp.c_str(); + } + + return "0"; +} + +bool extract_submitblock_blob(const rapidjson::Value& params, std::vector& blob) +{ + if (params.IsArray() && !params.Empty() && params[0].IsString()) { + return from_hex(params[0].GetString(), params[0].GetStringLength(), blob); + } + + return false; +} + +} // namespace + +NodejsPoolRpc::NodejsPoolRpc(p2pool* pool, const std::string& listen_addresses) + : TCPServer(DEFAULT_BACKLOG, RpcClient::allocate, std::string(), Params::ProxyType::INVALID) + , m_pool(pool) +{ + m_callbackBuf.resize(NODEJS_POOL_RPC_CALLBACK_BUF_SIZE); + uv_mutex_init_checked(&m_recentWorkLock); + start_listening(listen_addresses, false); + + if (m_listenPort < 0) { + LOGERR(1, "failed to listen on nodejs pool rpc facade"); + throw std::exception(); + } +} + +NodejsPoolRpc::~NodejsPoolRpc() +{ + shutdown_tcp(); + uv_mutex_destroy(&m_recentWorkLock); +} + +void NodejsPoolRpc::on_shutdown() +{ +} + +const char* NodejsPoolRpc::get_log_category() const +{ + return log_category_prefix; +} + +void NodejsPoolRpc::remember_work_snapshot(const WorkSnapshot& work) const +{ + if (!work.ready) { + return; + } + + 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); + }; + + for (size_t i = 0; i < m_recentWork.size(); ++i) { + if (same_template(m_recentWork[i])) { + m_recentWork.erase(m_recentWork.begin() + i); + break; + } + } + + m_recentWork.insert(m_recentWork.begin(), work); + if (m_recentWork.size() > MAX_CACHED_WORK_ITEMS) { + m_recentWork.resize(MAX_CACHED_WORK_ITEMS); + } +} + +bool NodejsPoolRpc::find_cached_work_by_block_blob(const std::vector& blob, WorkSnapshot& out) const +{ + MutexLock lock(m_recentWorkLock); + + for (const WorkSnapshot& work : m_recentWork) { + if (!work.ready || (work.block_template_blob.size() != blob.size())) { + continue; + } + + if (work.nonce_offset + 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)) { + continue; + } + + out = work; + return true; + } + + return false; +} + +bool NodejsPoolRpc::make_work_snapshot(WorkSnapshot& out) const +{ + const MinerData miner_data = m_pool->miner_data(); + if (!miner_data.height || miner_data.prev_id.empty()) { + return false; + } + + uint8_t hashing_blob[128] = {}; + uint64_t mining_height = 0; + uint64_t sidechain_height = 0; + difficulty_type mainchain_diff, aux_diff, sidechain_diff; + hash seed_hash; + size_t nonce_offset = 0; + uint32_t template_id = 0; + + const uint32_t hashing_blob_size = m_pool->block_template().get_hashing_blob(0, hashing_blob, mining_height, sidechain_height, mainchain_diff, aux_diff, sidechain_diff, seed_hash, nonce_offset, template_id); + if (!hashing_blob_size) { + return false; + } + + size_t full_nonce_offset = 0; + size_t extra_nonce_offset = 0; + size_t merkle_root_offset = 0; + hash merge_mining_root; + const BlockTemplate* block_tpl = nullptr; + + std::vector block_template_blob = m_pool->block_template().get_block_template_blob(template_id, 0, full_nonce_offset, extra_nonce_offset, merkle_root_offset, merge_mining_root, &block_tpl); + if (block_template_blob.empty()) { + return false; + } + + if (merkle_root_offset && (merkle_root_offset + HASH_SIZE <= block_template_blob.size())) { + memcpy(block_template_blob.data() + merkle_root_offset, merge_mining_root.h, HASH_SIZE); + } + + out.ready = true; + out.mainchain_height = miner_data.height - 1; + out.mining_height = mining_height; + out.sidechain_height = sidechain_height; + out.parent_prev_id = miner_data.prev_id; + out.seed_hash = seed_hash; + out.work_hash = {}; + out.template_id = template_id; + out.nonce_offset = full_nonce_offset; + out.hashing_nonce_offset = nonce_offset; + out.extra_nonce_offset = extra_nonce_offset; + out.mainchain_difficulty = mainchain_diff; + out.sidechain_difficulty = sidechain_diff; + out.aux_difficulty = aux_diff; + 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); + + 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); + work_key.insert(work_key.end(), seed_hash.h, seed_hash.h + HASH_SIZE); + append_u64(work_key, mining_height); + append_u64(work_key, sidechain_height); + append_u64(work_key, out.submit_difficulty.lo); + append_u64(work_key, out.submit_difficulty.hi); + append_u32(work_key, template_id); + work_key.insert(work_key.end(), out.hashing_blob.begin(), out.hashing_blob.end()); + keccak(work_key.data(), static_cast(work_key.size()), out.work_hash.h); + + remember_work_snapshot(out); + return true; +} + +bool NodejsPoolRpc::RpcClient::on_read(const char* data, uint32_t size) +{ + m_request.append(data, size); + return static_cast(m_owner)->handle_http_request(this); +} + +bool NodejsPoolRpc::handle_http_request(RpcClient* client) +{ + std::string& request = client->m_request; + const size_t headers_end = request.find("\r\n\r\n"); + if (headers_end == std::string::npos) { + return true; + } + + size_t content_length = 0; + if (!parse_content_length(request.substr(0, headers_end), content_length)) { + const std::string response = http_response("{\"error\":\"invalid content-length\"}", "400 Bad Request"); + const bool sent = send(client, [&response](uint8_t* buf, size_t buf_size) { + const size_t n = std::min(buf_size, response.size()); + memcpy(buf, response.data(), n); + return n; + }, true); + (void)sent; + client->close(); + return true; + } + + const size_t request_size = headers_end + 4 + content_length; + if (request.size() < request_size) { + return true; + } + + const size_t line_end = request.find("\r\n"); + if (line_end == std::string::npos) { + return true; + } + + const std::string first_line = request.substr(0, line_end); + const size_t s1 = first_line.find(' '); + const size_t s2 = (s1 == std::string::npos) ? std::string::npos : first_line.find(' ', s1 + 1); + if ((s1 == std::string::npos) || (s2 == std::string::npos)) { + const std::string response = http_response("{\"error\":\"invalid request line\"}", "400 Bad Request"); + const bool sent = send(client, [&response](uint8_t* buf, size_t buf_size) { + const size_t n = std::min(buf_size, response.size()); + memcpy(buf, response.data(), n); + return n; + }, true); + (void)sent; + client->close(); + return true; + } + + const std::string method = first_line.substr(0, s1); + const std::string path = first_line.substr(s1 + 1, s2 - s1 - 1); + const std::string body = request.substr(headers_end + 4, content_length); + request.clear(); + LOGINFO(4, "http request: method=" << method << ", path=" << path << ", content_length=" << content_length); + + std::string response; + if ((method == "GET") && (path == "/get_height")) { + response = http_response(build_get_height_response()); + } + else if ((method == "POST") && (path == "/json_rpc")) { + response = http_response(build_json_rpc_response(body)); + } + else { + response = http_response("{\"error\":\"not found\"}", "404 Not Found"); + } + + const bool sent = send(client, [&response](uint8_t* buf, size_t buf_size) { + const size_t n = std::min(buf_size, response.size()); + memcpy(buf, response.data(), n); + return n; + }, true); + (void)sent; + client->close(); + return true; +} + +std::string NodejsPoolRpc::build_get_height_response() const +{ + WorkSnapshot work; + if (!make_work_snapshot(work)) { + return "{\"status\":\"BUSY\"}"; + } + + char body[512] = {}; + log::Stream s(body); + s << "{\"height\":" << work.mainchain_height + << ",\"hash\":\"" << work.work_hash << '"' + << ",\"status\":\"OK\"" + << ",\"untrusted\":false}"; + return std::string(body, s.m_pos); +} + +std::string NodejsPoolRpc::json_error_response(const char* id, int code, const std::string& message) const +{ + char body[1024] = {}; + log::Stream s(body); + s << "{\"id\":\"" << log::EscapedString(id) + << "\",\"jsonrpc\":\"2.0\",\"error\":{\"code\":" << code + << ",\"message\":\"" << log::EscapedString(message) << "\"}}"; + return std::string(body, s.m_pos); +} + +std::string NodejsPoolRpc::build_json_rpc_response(const std::string& body) const +{ + rapidjson::Document doc; + doc.Parse(body.c_str(), body.size()); + + if (doc.HasParseError() || !doc.IsObject()) { + return json_error_response("0", -32700, "parse error"); + } + + std::string id_buf; + const char* id = "0"; + if (doc.HasMember("id")) { + id = jsonrpc_id_to_cstr(doc["id"], id_buf); + } + + auto method_it = doc.FindMember("method"); + if ((method_it == doc.MemberEnd()) || !method_it->value.IsString()) { + return json_error_response(id, -32600, "invalid request"); + } + + const std::string method = method_it->value.GetString(); + LOGINFO(4, "json_rpc method=" << method); + + if (method == "getlastblockheader") { + WorkSnapshot work; + if (!make_work_snapshot(work)) { + return json_error_response(id, 0, "not_ready"); + } + + char response[1024] = {}; + log::Stream s(response); + s << "{\"id\":\"" << log::EscapedString(id) + << "\",\"jsonrpc\":\"2.0\",\"result\":{\"status\":\"OK\",\"block_header\":{\"hash\":\"" + << work.work_hash << "\",\"height\":" << work.mainchain_height << "}}}"; + return std::string(response, s.m_pos); + } + + if (method == "getblocktemplate") { + WorkSnapshot work; + if (!make_work_snapshot(work)) { + return json_error_response(id, 0, "not_ready"); + } + + const size_t response_capacity = + 2048 + + (work.block_template_blob.size() * 2) + + (work.hashing_blob.size() * 2) + + 256; + + std::vector response(response_capacity, '\0'); + log::Stream s(response.data(), response.size()); + 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()) << '"' + << ",\"difficulty\":" << work.submit_difficulty.lo + << ",\"height\":" << work.mining_height + << ",\"nonce_offset\":" << work.nonce_offset + << ",\"hashing_nonce_offset\":" << work.hashing_nonce_offset + << ",\"reserved_offset\":" << work.extra_nonce_offset + << ",\"seed_hash\":\"" << work.seed_hash << '"' + << ",\"status\":\"OK\"" + << ",\"template_id\":" << work.template_id + << ",\"sidechain_height\":" << work.sidechain_height + << ",\"aux_difficulty\":\"" << work.aux_difficulty << '"' + << ",\"sidechain_difficulty\":\"" << work.sidechain_difficulty << '"' + << ",\"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 + << "}}"; + return std::string(response.data(), s.m_pos); + } + + if ((method == "submitblock") || (method == "submit_block")) { + auto params_it = doc.FindMember("params"); + if (params_it == doc.MemberEnd()) { + return json_error_response(id, -32602, "missing params"); + } + + std::vector blob; + if (!extract_submitblock_blob(params_it->value, blob) || blob.empty()) { + return json_error_response(id, -32602, "invalid blob"); + } + + WorkSnapshot work; + if (!find_cached_work_by_block_blob(blob, work)) { + return json_error_response(id, 0, "unknown_or_stale_template"); + } + + if (work.nonce_offset + NONCE_SIZE > blob.size()) { + return json_error_response(id, 0, "invalid_nonce_offset"); + } + + const uint32_t nonce = read_u32_le(blob.data() + work.nonce_offset); + const uint32_t extra_nonce = 0; + + 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; + + 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"); + } + + memcpy(hashing_blob + nonce_offset, blob.data() + work.nonce_offset, NONCE_SIZE); + + hash result_hash; + if (!m_pool->calculate_hash(hashing_blob, hashing_blob_size, height, seed_hash, result_hash, false)) { + return json_error_response(id, 0, "pow_check_failed"); + } + + if (!work.submit_difficulty.check_pow(result_hash)) { + return json_error_response(id, 0, "low_diff"); + } + + bool parent_submitted = false; + bool sidechain_submitted = false; + size_t aux_submitted = 0; + + if (mainchain_diff.check_pow(result_hash)) { + m_pool->submit_block_async(std::move(blob)); + parent_submitted = true; + } + + if (aux_diff.check_pow(result_hash)) { + const std::vector aux_chains = m_pool->block_template().get_aux_chains(work.template_id); + + 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 }); + } + } + + aux_submitted = aux_blocks.size(); + if (!aux_blocks.empty()) { + m_pool->submit_aux_block_async(aux_blocks); + } + } + + if (sidechain_diff.check_pow(result_hash)) { + sidechain_submitted = m_pool->submit_sidechain_block(work.template_id, nonce, extra_nonce); + } + + char response[2048] = {}; + log::Stream s(response, sizeof(response)); + s << "{\"id\":\"" << log::EscapedString(id) << "\",\"jsonrpc\":\"2.0\",\"result\":{" + << "\"status\":\"OK\"" + << ",\"accepted\":true" + << ",\"backend_mode\":\"aux_only\"" + << ",\"aux_accepted\":" << ((aux_submitted > 0) ? "true" : "false") + << ",\"aux_submitted\":" << aux_submitted + << ",\"sidechain_accepted\":" << (sidechain_submitted ? "true" : "false") + << ",\"parent_submitted\":" << (parent_submitted ? "true" : "false") + << ",\"template_id\":" << work.template_id + << ",\"submit_difficulty\":\"" << work.submit_difficulty << '"' + << "}}"; + return std::string(response, s.m_pos); + } + + return json_error_response(id, -32601, "method not found"); +} + +} // namespace p2pool diff --git a/src/nodejs_pool_rpc.h b/src/nodejs_pool_rpc.h new file mode 100644 index 0000000..f930be8 --- /dev/null +++ b/src/nodejs_pool_rpc.h @@ -0,0 +1,92 @@ +/* + * This file is part of the Monero P2Pool + * Copyright (c) 2021-2026 SChernykh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "tcp_server.h" + +namespace p2pool { + +class p2pool; + +class NodejsPoolRpc : public TCPServer +{ +public: + NodejsPoolRpc(p2pool* pool, const std::string& listen_addresses); + ~NodejsPoolRpc() override; + + 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; + 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; + std::vector hashing_blob; + }; + + struct RpcClient : public Client + { + RpcClient() : Client(m_readBuf, sizeof(m_readBuf)) {} + + static Client* allocate() { return new RpcClient(); } + + size_t size() const override { return sizeof(RpcClient); } + void reset() override + { + Client::reset(); + m_request.clear(); + m_request.shrink_to_fit(); + } + + bool on_connect() override { return true; } + bool on_read(const char* data, uint32_t size) override; + + alignas(8) char m_readBuf[16384] = {}; + std::string m_request; + }; + + p2pool* m_pool; + mutable uv_mutex_t m_recentWorkLock; + mutable std::vector m_recentWork; + + const char* get_log_category() const override; + + bool make_work_snapshot(WorkSnapshot& out) const; + void remember_work_snapshot(const WorkSnapshot& work) const; + bool find_cached_work_by_block_blob(const std::vector& blob, WorkSnapshot& out) 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; + std::string json_error_response(const char* id, int code, const std::string& message) const; +}; + +} // namespace p2pool diff --git a/src/p2pool.cpp b/src/p2pool.cpp index 1dd8075..8c07465 100644 --- a/src/p2pool.cpp +++ b/src/p2pool.cpp @@ -29,6 +29,7 @@ #include "side_chain.h" #include "stratum_server.h" #include "p2p_server.h" +#include "nodejs_pool_rpc.h" #if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS) #include "miner.h" #endif @@ -274,6 +275,8 @@ p2pool::~p2pool() PoolBlock::s_precalculatedSharesLock = nullptr; delete p; } + + delete m_nodejsPoolRpc; } void p2pool::update_host_ping(const std::string& display_name, double ping) @@ -1358,6 +1361,9 @@ void p2pool::download_block_headers4(uint64_t start_height, uint64_t current_hei if (m_serversStarted.exchange(1) == 0) { m_p2pServer = new P2PServer(this); m_stratumServer = new StratumServer(this); + if (!m_params.m_nodejsPoolRpc.empty()) { + m_nodejsPoolRpc = new NodejsPoolRpc(this, m_params.m_nodejsPoolRpc); + } #if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS) if (m_params.m_minerThreads) { start_mining(m_params.m_minerThreads); @@ -2355,6 +2361,8 @@ int p2pool::run() #endif delete m_stratumServer; delete m_p2pServer; + delete m_nodejsPoolRpc; + m_nodejsPoolRpc = nullptr; LOGINFO(1, "stopped"); return 0; diff --git a/src/p2pool.h b/src/p2pool.h index 58cb128..7349620 100644 --- a/src/p2pool.h +++ b/src/p2pool.h @@ -30,6 +30,7 @@ class Mempool; class SideChain; class StratumServer; class P2PServer; +class NodejsPoolRpc; class Miner; class ConsoleCommands; class p2pool_api; @@ -76,6 +77,7 @@ public: StratumServer* stratum_server() const { return m_stratumServer; } P2PServer* p2p_server() const { return m_p2pServer; } + NodejsPoolRpc* nodejs_pool_rpc() const { return m_nodejsPoolRpc; } #if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS) void print_miner_status(); @@ -225,6 +227,7 @@ private: std::atomic m_serversStarted{ 0 }; StratumServer* m_stratumServer = nullptr; P2PServer* m_p2pServer = nullptr; + NodejsPoolRpc* m_nodejsPoolRpc = nullptr; std::atomic m_startupFinished{ false }; diff --git a/src/params.cpp b/src/params.cpp index 2200c69..629cab8 100644 --- a/src/params.cpp +++ b/src/params.cpp @@ -273,6 +273,11 @@ bool Params::process_arg(const std::vector& arg) return true; } + if ((arg[0] == "nodejs-pool-rpc") && has1(arg)) { + m_nodejsPoolRpc = arg[1]; + return true; + } + if ((arg[0] == "stratum-ban-time") && has1(arg)) { m_stratumBanTime = strtoull(arg[1].c_str(), nullptr, 10); return true; diff --git a/src/params.h b/src/params.h index a49caf1..c992941 100644 --- a/src/params.h +++ b/src/params.h @@ -89,6 +89,7 @@ struct Params std::string m_stratumAddresses; std::string m_p2pAddresses; + std::string m_nodejsPoolRpc; std::string m_p2pPeerList; std::string m_dataDir; std::string m_logFilePath;