diff --git a/docs/MERGE_MINING.MD b/docs/MERGE_MINING.MD index 28492a1..223c721 100644 --- a/docs/MERGE_MINING.MD +++ b/docs/MERGE_MINING.MD @@ -100,3 +100,12 @@ Note that `merkle_proof` only contains a vector of 32-byte hashes for `aux_hash` `aux_nonce` and `n_aux_chains` can be extracted from the Merkle tree parameters (see above). `merkle_root_hash` can be extracted from the merge mining tag (see above). + +Response: a JSON containing these fields: +Field|Description +-|- +`status`|Block submit status + +Example response 1: `{"jsonrpc":"2.0","id":"0","result":{"status":"accepted"}}` + +Example response 2: `{"jsonrpc":"2.0","id":"0","error":"something went wrong"}` diff --git a/src/json_rpc_request.cpp b/src/json_rpc_request.cpp index a6a4152..77fa120 100644 --- a/src/json_rpc_request.cpp +++ b/src/json_rpc_request.cpp @@ -499,7 +499,9 @@ void Call(const std::string& address, int port, const std::string& req, const st }); if (!result) { - LOGERR(1, "JSON RPC \"" << req << "\" failed"); + static constexpr char err[] = "CallOnLoop failed"; + LOGERR(1, err); + (*close_cb)(err, sizeof(err) - 1, 0.0); } } diff --git a/src/merge_mining_client.cpp b/src/merge_mining_client.cpp index dbef2c9..b078909 100644 --- a/src/merge_mining_client.cpp +++ b/src/merge_mining_client.cpp @@ -31,6 +31,7 @@ MergeMiningClient::MergeMiningClient(p2pool* pool, const std::string& host, cons : m_host(host) , m_port(80) , m_auxAddress(address) + , m_ping(0.0) , m_pool(pool) , m_loop{} , m_loopThread{} @@ -99,11 +100,16 @@ void MergeMiningClient::on_timer() void MergeMiningClient::merge_mining_get_chain_id() { - constexpr char req[] = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"merge_mining_get_chain_id\"}"; + const std::string 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) { + [this](const char* data, size_t size, double ping) { if (parse_merge_mining_get_chain_id(data, size)) { + if (ping > 0.0) { + m_ping = ping; + } + + // Chain ID received successfully, we can start polling for new mining jobs now const int err = uv_timer_start(&m_timer, on_timer, 0, 500); if (err) { LOGERR(1, "failed to start timer, error " << uv_err_name(err)); @@ -167,9 +173,9 @@ void MergeMiningClient::merge_mining_get_job(uint64_t height, const hash& prev_i << ",\"aux_hash\":\"" << aux_hash << '"' << ",\"height\":" << height << ",\"prev_id\":\"" << prev_id << '"' - << "}}\0"; + << "}}"; - JSONRPCRequest::call(m_host, m_port, buf, std::string(), m_pool->params().m_socks5Proxy, + JSONRPCRequest::call(m_host, m_port, std::string(buf, s.m_pos), std::string(), m_pool->params().m_socks5Proxy, [this](const char* data, size_t size, double) { parse_merge_mining_get_job(data, size); }, @@ -219,7 +225,9 @@ bool MergeMiningClient::parse_merge_mining_get_job(const char* data, size_t size return true; } - if (!result.HasMember("aux_blob") || !result["aux_blob"].IsString()) { + std::vector aux_blob; + + if (!result.HasMember("aux_blob") || !result["aux_blob"].IsString() || !from_hex(result["aux_blob"].GetString(), result["aux_blob"].GetStringLength(), aux_blob)) { return err("invalid aux_blob"); } @@ -227,7 +235,7 @@ bool MergeMiningClient::parse_merge_mining_get_job(const char* data, size_t size return err("invalid aux_diff"); } - m_auxBlob = result["aux_blob"].GetString(); + m_auxBlob = std::move(aux_blob); m_auxHash = h; m_auxDiff.lo = result["aux_diff"].GetUint64(); m_auxDiff.hi = 0; @@ -235,6 +243,73 @@ bool MergeMiningClient::parse_merge_mining_get_job(const char* data, size_t size return true; } +void MergeMiningClient::merge_mining_submit_solution(const std::vector& blob, const std::vector& merkle_proof) +{ + std::vector buf((m_auxBlob.size() + HASH_SIZE + blob.size()) * 2 + merkle_proof.size() * (HASH_SIZE * 2 + 3) + 256); + log::Stream s(buf.data(), buf.size()); + + s << "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"merge_mining_submit_solution\",\"params\":{" + << "\"aux_blob\":\"" << log::hex_buf(m_auxBlob.data(), m_auxBlob.size()) << '"' + << ",\"aux_hash\":\"" << m_auxHash << '"' + << ",\"blob\":" << log::hex_buf(blob.data(), blob.size()) + << ",\"merkle_proof\":["; + + for (size_t i = 0, n = merkle_proof.size(); i < n; ++i) { + if (i > 0) { + s << ','; + } + s << '"' << merkle_proof[i] << '"'; + } + + s << "]}}"; + + JSONRPCRequest::call(m_host, m_port, std::string(buf.data(), s.m_pos), std::string(), m_pool->params().m_socks5Proxy, + [this](const char* data, size_t size, double) { + parse_merge_mining_submit_solution(data, size); + }, + [](const char* data, size_t size, double) { + if (size > 0) { + LOGERR(1, "couldn't submit merge mining solution, error " << log::const_buf(data, size)); + } + }, &m_loop); +} + +bool MergeMiningClient::parse_merge_mining_submit_solution(const char* data, size_t size) +{ + auto err = [](const char* msg) { + LOGWARN(1, "merge_mining_submit_solution RPC call failed: " << msg); + 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()) { + return err("couldn't parse result"); + } + + if (!result.HasMember("status") || !result["status"].IsString()) { + return err("invalid status"); + } + + const char* status = result["status"].GetString(); + LOGINFO(0, log::LightGreen() << "merge_mining_submit_solution: " << status); + + // Get new mining job + on_timer(); + + return true; +} + void MergeMiningClient::loop(void* data) { LOGINFO(1, "event loop started"); diff --git a/src/merge_mining_client.h b/src/merge_mining_client.h index 55bbbf5..54ca44f 100644 --- a/src/merge_mining_client.h +++ b/src/merge_mining_client.h @@ -29,6 +29,8 @@ public: MergeMiningClient(p2pool* pool, const std::string& host, const std::string& address); ~MergeMiningClient(); + void merge_mining_submit_solution(const std::vector& blob, const std::vector& merkle_proof); + private: static void loop(void* data); @@ -41,15 +43,18 @@ private: void merge_mining_get_job(uint64_t height, const hash& prev_id, const std::string& address, const hash& aux_hash); bool parse_merge_mining_get_job(const char* data, size_t size); + bool parse_merge_mining_submit_solution(const char* data, size_t size); + std::string m_host; uint32_t m_port; std::string m_auxAddress; - std::string m_auxBlob; + std::vector m_auxBlob; hash m_auxHash; difficulty_type m_auxDiff; hash m_chainID; + double m_ping; p2pool* m_pool; diff --git a/src/util.h b/src/util.h index 0233a26..cdbbfdc 100644 --- a/src/util.h +++ b/src/util.h @@ -118,6 +118,25 @@ static FORCEINLINE bool from_hex(const char* s, size_t len, hash& h) { return true; } +static FORCEINLINE bool from_hex(const char* s, size_t len, std::vector& data) { + if (len % 2) { + return false; + } + + std::vector result(len / 2); + + 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 false; + } + result[i] = (d[0] << 4) | d[1]; + } + + data = std::move(result); + return true; +} + template struct is_negative_helper {}; template struct is_negative_helper { static FORCEINLINE bool value(T) { return false; } }; template struct is_negative_helper { static FORCEINLINE bool value(T x) { return (x < 0); } };