Added merge mining RPC client

Basic code and one API call implemented
This commit is contained in:
SChernykh 2023-11-01 19:49:44 +01:00
parent fd20313d65
commit 75bb046f22
5 changed files with 235 additions and 8 deletions

View file

@ -77,6 +77,7 @@ set(HEADERS
src/keccak.h src/keccak.h
src/log.h src/log.h
src/mempool.h src/mempool.h
src/merge_mining_client.h
src/merkle.h src/merkle.h
src/p2p_server.h src/p2p_server.h
src/p2pool.h src/p2pool.h
@ -109,6 +110,7 @@ set(SOURCES
src/main.cpp src/main.cpp
src/memory_leak_debug.cpp src/memory_leak_debug.cpp
src/mempool.cpp src/mempool.cpp
src/merge_mining_client.cpp
src/merkle.cpp src/merkle.cpp
src/p2p_server.cpp src/p2p_server.cpp
src/p2pool.cpp src/p2pool.cpp

View file

@ -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. 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: Response: a JSON containing these fields:
Field|Description Field|Description
-|- -|-
`result`|`OK` or an error message `chain_id`|A unique 32-byte hex-encoded value that identifies this merge mined 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 ### merge_mining_get_job

164
src/merge_mining_client.cpp Normal file
View file

@ -0,0 +1,164 @@
/*
* This file is part of the Monero P2Pool <https://github.com/SChernykh/p2pool>
* Copyright (c) 2021-2023 SChernykh <https://github.com/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 <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#include "merge_mining_client.h"
#include "p2pool.h"
#include "params.h"
#include "json_rpc_request.h"
#include "json_parsers.h"
#include <rapidjson/document.h>
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<MergeMiningClient*>(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

59
src/merge_mining_client.h Normal file
View file

@ -0,0 +1,59 @@
/*
* This file is part of the Monero P2Pool <https://github.com/SChernykh/p2pool>
* Copyright (c) 2021-2023 SChernykh <https://github.com/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 <http://www.gnu.org/licenses/>.
*/
#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<MergeMiningClient*>(async->data);
uv_close(reinterpret_cast<uv_handle_t*>(&client->m_shutdownAsync), nullptr);
delete GetLoopUserData(&client->m_loop, false);
}
};
} // namespace p2pool

View file

@ -335,7 +335,7 @@ void p2pool::handle_miner_data(MinerData& data)
} }
// TODO: remove after testing // TODO: remove after testing
#if 1 #if 0
{ {
data.aux_chains.clear(); data.aux_chains.clear();
data.aux_chains.resize(10); data.aux_chains.resize(10);
@ -994,7 +994,7 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
rapidjson::Document doc; rapidjson::Document doc;
doc.Parse(data, size); 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"); 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)); std::this_thread::sleep_for(std::chrono::milliseconds(1000));
get_info(); get_info();
@ -1071,7 +1071,7 @@ void p2pool::parse_get_version_rpc(const char* data, size_t size)
rapidjson::Document doc; rapidjson::Document doc;
doc.Parse(data, size); 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"); 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)); std::this_thread::sleep_for(std::chrono::milliseconds(1000));
get_version(); get_version();
@ -1161,7 +1161,7 @@ void p2pool::parse_get_miner_data_rpc(const char* data, size_t size)
rapidjson::Document doc; rapidjson::Document doc;
doc.Parse(data, size); 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"); LOGWARN(1, "get_miner_data RPC response is invalid, skipping it");
return; return;
} }