From 75bb046f22d10377ee1c55af98c7d161dd8df417 Mon Sep 17 00:00:00 2001 From: SChernykh Date: Wed, 1 Nov 2023 19:49:44 +0100 Subject: [PATCH] Added merge mining RPC client Basic code and one API call implemented --- CMakeLists.txt | 2 + docs/MERGE_MINING.MD | 10 ++- src/merge_mining_client.cpp | 164 ++++++++++++++++++++++++++++++++++++ src/merge_mining_client.h | 59 +++++++++++++ src/p2pool.cpp | 8 +- 5 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 src/merge_mining_client.cpp create mode 100644 src/merge_mining_client.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d2db36f..b2d646d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ set(HEADERS src/keccak.h src/log.h src/mempool.h + src/merge_mining_client.h src/merkle.h src/p2p_server.h src/p2pool.h @@ -109,6 +110,7 @@ set(SOURCES src/main.cpp src/memory_leak_debug.cpp src/mempool.cpp + src/merge_mining_client.cpp src/merkle.cpp src/p2p_server.cpp src/p2pool.cpp diff --git a/docs/MERGE_MINING.MD b/docs/MERGE_MINING.MD index 6b935c4..abd6a48 100644 --- a/docs/MERGE_MINING.MD +++ b/docs/MERGE_MINING.MD @@ -46,15 +46,17 @@ Reference code: `get_aux_slot` and `find_aux_nonce` in `merkle.cpp` P2Pool must be able to get mining jobs from merge mined chains and submit PoW solutions to them. -### merge_mining_get_id +### merge_mining_get_chain_id -Request: an empty JSON +Example request: `{"jsonrpc":"2.0","id":"0","method":"merge_mining_get_chain_id"}` Response: a JSON containing these fields: Field|Description -|- -`result`|`OK` or an error message -`id`|A unique 32-byte hex-encoded value that identifies this merge mined chain. +`chain_id`|A unique 32-byte hex-encoded value that identifies this merge mined chain. + +Example response 1: `{"jsonrpc":"2.0","id":"0","result":{"chain_id":"f89175d2ce8ce92eaa062eea5c433d0d70f89f5e1554c066dc27943e8cfc37b0"}}` +Example response 2: `{"jsonrpc":"2.0","id":"0","error":"something went wrong"}` ### merge_mining_get_job diff --git a/src/merge_mining_client.cpp b/src/merge_mining_client.cpp new file mode 100644 index 0000000..242d9ee --- /dev/null +++ b/src/merge_mining_client.cpp @@ -0,0 +1,164 @@ +/* + * This file is part of the Monero P2Pool + * Copyright (c) 2021-2023 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 "merge_mining_client.h" +#include "p2pool.h" +#include "params.h" +#include "json_rpc_request.h" +#include "json_parsers.h" +#include + +LOG_CATEGORY(MergeMiningClient) + +namespace p2pool { + +MergeMiningClient::MergeMiningClient(p2pool* pool, const std::string& host) + : m_host(host) + , m_port(80) + , m_pool(pool) + , m_loop{} + , m_loopThread{} + , m_shutdownAsync{} +{ + const size_t k = host.find_last_of(':'); + if (k != std::string::npos) { + m_host = host.substr(0, k); + m_port = strtoul(host.substr(k + 1).c_str(), nullptr, 10); + } + + if (m_host.empty() || (m_port == 0) || (m_port >= 65536)) { + LOGERR(1, "Invalid host " << host); + throw std::exception(); + } + + int err = uv_loop_init(&m_loop); + if (err) { + LOGERR(1, "failed to create event loop, error " << uv_err_name(err)); + throw std::exception(); + } + + // Init loop user data before running it + GetLoopUserData(&m_loop); + + err = uv_async_init(&m_loop, &m_shutdownAsync, on_shutdown); + if (err) { + LOGERR(1, "uv_async_init failed, error " << uv_err_name(err)); + uv_loop_close(&m_loop); + throw std::exception(); + } + m_shutdownAsync.data = this; + + err = uv_thread_create(&m_loopThread, loop, this); + if (err) { + LOGERR(1, "failed to start event loop thread, error " << uv_err_name(err)); + uv_loop_close(&m_loop); + throw std::exception(); + } + + merge_mining_get_chain_id(); +} + +MergeMiningClient::~MergeMiningClient() +{ + uv_async_send(&m_shutdownAsync); + uv_thread_join(&m_loopThread); + + LOGINFO(1, "stopped"); +} + +void MergeMiningClient::merge_mining_get_chain_id() +{ + constexpr char req[] = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"merge_mining_get_chain_id\"}"; + + JSONRPCRequest::call(m_host, m_port, req, std::string(), m_pool->params().m_socks5Proxy, + [this](const char* data, size_t size, double) { + parse_merge_mining_get_chain_id(data, size); + }, + [](const char* data, size_t size, double) { + if (size > 0) { + LOGERR(1, "couldn't get merge mining id, error " << log::const_buf(data, size)); + } + }); +} + +bool MergeMiningClient::parse_merge_mining_get_chain_id(const char* data, size_t size) +{ + auto err = [this](const char* msg) { + LOGWARN(1, "merge_mining_get_chain_id RPC call failed: " << msg << ". Trying again in 1 second."); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + merge_mining_get_chain_id(); + return false; + }; + + rapidjson::Document doc; + + if (doc.Parse(data, size).HasParseError() || !doc.IsObject()) { + return err("parsing failed"); + } + + if (doc.HasMember("error")) { + return err(doc["error"].IsString() ? doc["error"].GetString() : "an unknown error occurred"); + } + + const auto& result = doc["result"]; + + if (!result.IsObject() || !result.HasMember("chain_id")) { + return err("couldn't parse result"); + } + + const auto& chain_id = result["chain_id"]; + + if (!chain_id.IsString() || (chain_id.GetStringLength() != HASH_SIZE * 2)) { + return err("invalid chain_id"); + } + + const char* s = chain_id.GetString(); + hash id; + + for (uint32_t i = 0; i < HASH_SIZE; ++i) { + uint8_t d[2]; + if (!from_hex(s[i * 2], d[0]) || !from_hex(s[i * 2 + 1], d[1])) { + return err("chain_id is not hex-encoded"); + } + id.h[i] = (d[0] << 4) | d[1]; + } + + m_chainID = id; + return true; +} + +void MergeMiningClient::loop(void* data) +{ + LOGINFO(1, "event loop started"); + + MergeMiningClient* client = static_cast(data); + + int err = uv_run(&client->m_loop, UV_RUN_DEFAULT); + if (err) { + LOGWARN(1, "uv_run returned " << err); + } + + err = uv_loop_close(&client->m_loop); + if (err) { + LOGWARN(1, "uv_loop_close returned error " << uv_err_name(err)); + } + + LOGINFO(1, "event loop stopped"); +} + +} // namespace p2pool diff --git a/src/merge_mining_client.h b/src/merge_mining_client.h new file mode 100644 index 0000000..8a35ade --- /dev/null +++ b/src/merge_mining_client.h @@ -0,0 +1,59 @@ +/* + * This file is part of the Monero P2Pool + * Copyright (c) 2021-2023 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 "uv_util.h" + +namespace p2pool { + +class p2pool; + +class MergeMiningClient +{ +public: + MergeMiningClient(p2pool* pool, const std::string& host); + ~MergeMiningClient(); + +private: + static void loop(void* data); + + void merge_mining_get_chain_id(); + bool parse_merge_mining_get_chain_id(const char* data, size_t size); + + std::string m_host; + uint32_t m_port; + + hash m_chainID; + + p2pool* m_pool; + + uv_loop_t m_loop; + uv_thread_t m_loopThread; + + uv_async_t m_shutdownAsync; + + static void on_shutdown(uv_async_t* async) + { + MergeMiningClient* client = reinterpret_cast(async->data); + uv_close(reinterpret_cast(&client->m_shutdownAsync), nullptr); + + delete GetLoopUserData(&client->m_loop, false); + } +}; + +} // namespace p2pool diff --git a/src/p2pool.cpp b/src/p2pool.cpp index 460d89c..cb52eae 100644 --- a/src/p2pool.cpp +++ b/src/p2pool.cpp @@ -335,7 +335,7 @@ void p2pool::handle_miner_data(MinerData& data) } // TODO: remove after testing -#if 1 +#if 0 { data.aux_chains.clear(); data.aux_chains.resize(10); @@ -994,7 +994,7 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size) rapidjson::Document doc; doc.Parse(data, size); - if (!doc.IsObject() || !doc.HasMember("result")) { + if (doc.HasParseError() || !doc.IsObject() || !doc.HasMember("result")) { LOGWARN(1, "get_info RPC response is invalid (\"result\" not found), trying again in 1 second"); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); get_info(); @@ -1071,7 +1071,7 @@ void p2pool::parse_get_version_rpc(const char* data, size_t size) rapidjson::Document doc; doc.Parse(data, size); - if (!doc.IsObject() || !doc.HasMember("result")) { + if (doc.HasParseError() || !doc.IsObject() || !doc.HasMember("result")) { LOGWARN(1, "get_version RPC response is invalid (\"result\" not found), trying again in 1 second"); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); get_version(); @@ -1161,7 +1161,7 @@ void p2pool::parse_get_miner_data_rpc(const char* data, size_t size) rapidjson::Document doc; doc.Parse(data, size); - if (!doc.IsObject() || !doc.HasMember("result")) { + if (doc.HasParseError() || !doc.IsObject() || !doc.HasMember("result")) { LOGWARN(1, "get_miner_data RPC response is invalid, skipping it"); return; }