/* XMRig * Copyright (c) 2019 jtgrassie * Copyright (c) 2021 Hansie Odendaal * Copyright (c) 2018-2021 SChernykh * Copyright (c) 2016-2021 XMRig , * * 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, either version 3 of the License, or * (at your option) any later version. * * 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 "base/net/stratum/SelfSelectClient.h" #include "3rdparty/rapidjson/document.h" #include "3rdparty/rapidjson/error/en.h" #include "base/io/json/Json.h" #include "base/io/json/JsonRequest.h" #include "base/io/log/Log.h" #include "base/io/log/Tags.h" #include "base/net/http/Fetch.h" #include "base/net/http/HttpData.h" #include "base/net/stratum/Client.h" #include "net/JobResult.h" #include "base/tools/Cvt.h" namespace xmrig { static const char *kBlob = "blob"; static const char *kBlockhashingBlob = "blockhashing_blob"; static const char *kBlocktemplateBlob = "blocktemplate_blob"; static const char *kDifficulty = "difficulty"; static const char *kHeight = "height"; static const char *kId = "id"; static const char *kJobId = "job_id"; static const char *kNextSeedHash = "next_seed_hash"; static const char *kPrevHash = "prev_hash"; static const char *kSeedHash = "seed_hash"; static const char * const required_fields[] = { kBlocktemplateBlob, kBlockhashingBlob, kHeight, kDifficulty, kPrevHash }; } /* namespace xmrig */ xmrig::SelfSelectClient::SelfSelectClient(int id, const char *agent, IClientListener *listener, bool submitToOrigin) : m_submitToOrigin(submitToOrigin), m_listener(listener) { m_httpListener = std::make_shared(this); m_client = new Client(id, agent, this); } xmrig::SelfSelectClient::~SelfSelectClient() { delete m_client; } int64_t xmrig::SelfSelectClient::submit(const JobResult &result) { if (m_submitToOrigin) { submitOriginDaemon(result); } uint64_t submit_result = m_client->submit(result); if (m_submitToOrigin) { // Ensure that the latest block template is available after block submission getBlockTemplate(); } return submit_result; } void xmrig::SelfSelectClient::tick(uint64_t now) { m_client->tick(now); if (m_state == RetryState) { if (Chrono::steadyMSecs() - m_timestamp < m_retryPause) { return; } getBlockTemplate(); } } void xmrig::SelfSelectClient::onJobReceived(IClient *, const Job &job, const rapidjson::Value &) { m_job = job; getBlockTemplate(); } void xmrig::SelfSelectClient::onLogin(IClient *, rapidjson::Document &doc, rapidjson::Value ¶ms) { params.AddMember("mode", "self-select", doc.GetAllocator()); m_listener->onLogin(this, doc, params); } bool xmrig::SelfSelectClient::parseResponse(int64_t id, rapidjson::Value &result, const rapidjson::Value &error) { if (id == -1) { return false; } if (error.IsObject()) { LOG_ERR("[%s] error: " RED_BOLD("\"%s\"") RED_S ", code: %d", pool().daemon().url().data(), Json::getString(error, "message"), Json::getInt(error, "code")); return false; } if (!result.IsObject()) { return false; } for (auto field : required_fields) { if (!result.HasMember(field)) { LOG_ERR("[%s] required field " RED_BOLD("\"%s\"") RED_S " not found", pool().daemon().url().data(), field); return false; } } const char *blobData = Json::getString(result, kBlockhashingBlob); if (pool().coin().isValid()) { uint8_t blobVersion = 0; if (blobData) { Cvt::fromHex(&blobVersion, 1, blobData, 2); } m_job.setAlgorithm(pool().coin().algorithm(blobVersion)); } if (!m_job.setBlob(blobData)) { return false; } m_job.setHeight(Json::getUint64(result, kHeight)); m_job.setSeedHash(Json::getString(result, kSeedHash)); submitBlockTemplate(result); return true; } void xmrig::SelfSelectClient::getBlockTemplate() { setState(WaitState); using namespace rapidjson; Document doc(kObjectType); auto &allocator = doc.GetAllocator(); Value params(kObjectType); params.AddMember("wallet_address", m_job.poolWallet().toJSON(), allocator); params.AddMember("extra_nonce", m_job.extraNonce().toJSON(), allocator); JsonRequest::create(doc, m_sequence++, "getblocktemplate", params); FetchRequest req(HTTP_POST, pool().daemon().host(), pool().daemon().port(), "/json_rpc", doc, pool().daemon().isTLS(), isQuiet()); fetch(tag(), std::move(req), m_httpListener); } void xmrig::SelfSelectClient::retry() { setState(RetryState); } void xmrig::SelfSelectClient::setState(State state) { if (m_state == state) { return; } switch (state) { case IdleState: m_timestamp = 0; m_failures = 0; break; case WaitState: m_timestamp = Chrono::steadyMSecs(); break; case RetryState: m_timestamp = Chrono::steadyMSecs(); if (m_failures > m_retries) { m_listener->onClose(this, static_cast(m_failures)); } m_failures++; break; } m_state = state; } void xmrig::SelfSelectClient::submitBlockTemplate(rapidjson::Value &result) { using namespace rapidjson; Document doc(kObjectType); auto &allocator = doc.GetAllocator(); m_blocktemplate = Json::getString(result,kBlocktemplateBlob); m_blockDiff = Json::getUint64(result, kDifficulty); Value params(kObjectType); params.AddMember(StringRef(kId), m_job.clientId().toJSON(), allocator); params.AddMember(StringRef(kJobId), m_job.id().toJSON(), allocator); params.AddMember(StringRef(kBlob), result[kBlocktemplateBlob], allocator); params.AddMember(StringRef(kHeight), m_job.height(), allocator); params.AddMember(StringRef(kDifficulty), result[kDifficulty], allocator); params.AddMember(StringRef(kPrevHash), result[kPrevHash], allocator); params.AddMember(StringRef(kSeedHash), result[kSeedHash], allocator); params.AddMember(StringRef(kNextSeedHash), result[kNextSeedHash], allocator); JsonRequest::create(doc, sequence(), "block_template", params); send(doc, [this](const rapidjson::Value &result, bool success, uint64_t) { if (!success) { if (!isQuiet()) { LOG_ERR("[%s] error: " RED_BOLD("\"%s\"") RED_S ", code: %d", pool().daemon().url().data(), Json::getString(result, "message"), Json::getInt(result, "code")); } return retry(); } if (!m_active) { return; } if (m_failures > m_retries) { m_listener->onLoginSuccess(this); } setState(IdleState); m_listener->onJobReceived(this, m_job, rapidjson::Value{}); }); } void xmrig::SelfSelectClient::submitOriginDaemon(const JobResult& result) { if (result.diff == 0 || m_blockDiff == 0) { return; } if (result.actualDiff() < m_blockDiff) { m_originNotSubmitted++; LOG_DEBUG("%s " RED_BOLD("not submitted to origin daemon, difficulty too low") " (%" PRId64 "/%" PRId64 ") " BLACK_BOLD(" diff ") BLACK_BOLD("%" PRIu64) BLACK_BOLD(" vs. ") BLACK_BOLD("%" PRIu64), Tags::origin(), m_originSubmitted, m_originNotSubmitted, m_blockDiff, result.actualDiff()); return; } char *data = m_blocktemplate.data(); Cvt::toHex(data + 78, 8, reinterpret_cast(&result.nonce), 4); using namespace rapidjson; Document doc(kObjectType); Value params(kArrayType); params.PushBack(m_blocktemplate.toJSON(), doc.GetAllocator()); JsonRequest::create(doc, m_sequence, "submitblock", params); m_results[m_sequence] = SubmitResult(m_sequence, result.diff, result.actualDiff(), 0, result.backend); FetchRequest req(HTTP_POST, pool().daemon().host(), pool().daemon().port(), "/json_rpc", doc, pool().daemon().isTLS(), isQuiet()); fetch(tag(), std::move(req), m_httpListener); m_originSubmitted++; LOG_INFO("%s " GREEN_BOLD("submitted to origin daemon") " (%" PRId64 "/%" PRId64 ") " " diff " WHITE("%" PRIu64) " vs. " WHITE("%" PRIu64), Tags::origin(), m_originSubmitted, m_originNotSubmitted, m_blockDiff, result.actualDiff(), result.diff); } void xmrig::SelfSelectClient::onHttpData(const HttpData &data) { if (data.status != 200) { return retry(); } rapidjson::Document doc; if (doc.Parse(data.body.c_str()).HasParseError()) { if (!isQuiet()) { LOG_ERR("[%s] JSON decode failed: \"%s\"", pool().daemon().url().data(), rapidjson::GetParseError_En(doc.GetParseError())); } return retry(); } const int64_t id = Json::getInt64(doc, "id", -1); if (id > 0 && m_sequence - id != 1) { return; } if (!parseResponse(id, doc["result"], Json::getObject(doc, "error"))) { retry(); } }