diff --git a/src/base/kernel/config/BaseTransform.cpp b/src/base/kernel/config/BaseTransform.cpp index 12e7d848..e6739923 100644 --- a/src/base/kernel/config/BaseTransform.cpp +++ b/src/base/kernel/config/BaseTransform.cpp @@ -165,6 +165,9 @@ void xmrig::BaseTransform::transform(rapidjson::Document &doc, int key, const ch case IConfig::FingerprintKey: /* --tls-fingerprint */ return add(doc, kPools, "tls-fingerprint", arg); + case IConfig::SelfSelectKey: /* --self-select */ + return add(doc, kPools, "self-select", arg); + case IConfig::LogFileKey: /* --log-file */ return set(doc, "log-file", arg); diff --git a/src/base/kernel/interfaces/IConfig.h b/src/base/kernel/interfaces/IConfig.h index 86b8067c..9b2d125c 100644 --- a/src/base/kernel/interfaces/IConfig.h +++ b/src/base/kernel/interfaces/IConfig.h @@ -71,6 +71,7 @@ public: ProxyDonateKey = 1017, DaemonKey = 1018, DaemonPollKey = 1019, + SelfSelectKey = 1024, // xmrig common CPUPriorityKey = 1021, diff --git a/src/base/net/stratum/Client.cpp b/src/base/net/stratum/Client.cpp index 229147ba..6cc29e99 100644 --- a/src/base/net/stratum/Client.cpp +++ b/src/base/net/stratum/Client.cpp @@ -7,6 +7,7 @@ * Copyright 2017-2018 XMR-Stak , * Copyright 2018-2019 SChernykh * Copyright 2016-2019 XMRig , + * Copyright 2019 jtgrassie * * 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 @@ -37,12 +38,15 @@ #endif +#include "3rdparty/http-parser/http_parser.h" #include "base/io/json/Json.h" #include "base/io/json/JsonRequest.h" #include "base/io/log/Log.h" #include "base/kernel/interfaces/IClientListener.h" #include "base/net/dns/Dns.h" #include "base/net/stratum/Client.h" +#include "base/net/http/HttpClient.h" +#include "base/net/http/HttpsClient.h" #include "base/tools/Buffer.h" #include "base/tools/Chrono.h" #include "net/JobResult.h" @@ -264,6 +268,60 @@ void xmrig::Client::onResolved(const Dns &dns, int status) } +void xmrig::Client::onHttpData(const HttpData &data) +{ + using namespace rapidjson; + LOG_DEBUG("[%s:%d] received (%d bytes): \"%.*s\"", m_pool.host().data(), m_pool.port(), static_cast(data.body.size()), static_cast(data.body.size()), data.body.c_str()); + if (m_job.blob()[0]) { + LOG_ERR("Job already has a blob"); + return; + } + Document doc; + if (doc.Parse(data.body.c_str()).HasParseError()) { + LOG_ERR("[getblocktemplate] Failed to parse daemon response"); + return; + } + if (!doc.HasMember("result")) { + LOG_ERR("[getblocktemplate] No result"); + return; + } + Value &result = doc["result"]; + if (!result.HasMember("blocktemplate_blob")) { + LOG_ERR("[getblocktemplate] No blocktemplate_blob"); + return; + } + if (!result.HasMember("blockhashing_blob")) { + LOG_ERR("[getblocktemplate] No blockhashing_blob"); + return; + } + if (!result.HasMember("height")) { + LOG_ERR("[getblocktemplate] No height"); + return; + } + if (!result.HasMember("difficulty")) { + LOG_ERR("[getblocktemplate] No difficulty"); + return; + } + if (!result.HasMember("prev_hash")) { + LOG_ERR("[getblocktemplate] No prev_hash"); + return; + } + m_job.setHeight(Json::getUint64(result, "height")); + Document docBt(kObjectType); + auto &allocator = docBt.GetAllocator(); + Value params(kObjectType); + params.AddMember("id", m_job.clientId().toJSON(), allocator); + params.AddMember("job_id", m_job.id().toJSON(), allocator); + params.AddMember("blob", result["blocktemplate_blob"], allocator); + params.AddMember("height", m_job.height(), allocator); + params.AddMember("difficulty", result["difficulty"], allocator); + params.AddMember("prev_hash", result["prev_hash"], allocator); + JsonRequest::create(docBt, 2, "block_template", params); + send(docBt); + m_job.setBlob(result["blockhashing_blob"].GetString()); + m_listener->onJobReceived(this, m_job, Value{}); +} + bool xmrig::Client::close() { if (m_state == ClosingState) { @@ -320,7 +378,11 @@ bool xmrig::Client::parseJob(const rapidjson::Value ¶ms, int *code) return false; } +# ifdef XMRIG_FEATURE_HTTP + if (m_pool.selfSelect().isNull() && !job.setBlob(params["blob"].GetString())) { +# else if (!job.setBlob(params["blob"].GetString())) { +# endif *code = 4; return false; } @@ -353,6 +415,18 @@ bool xmrig::Client::parseJob(const rapidjson::Value ¶ms, int *code) m_job.setClientId(m_rpcId); +# ifdef XMRIG_FEATURE_HTTP + if (!m_pool.selfSelect().isNull() && m_job != job) + { + job.setPoolWallet(Json::getString(params, "pool_wallet")); + job.setExtraNonce(Json::getString(params, "extra_nonce")); + m_jobs++; + m_job = std::move(job); + getBlockTemplate(); + return true; + } +# endif + if (m_job != job) { m_jobs++; m_job = std::move(job); @@ -469,7 +543,11 @@ int64_t xmrig::Client::send(const rapidjson::Document &doc) { using namespace rapidjson; +# ifdef XMRIG_FEATURE_HTTP + StringBuffer buffer(nullptr, 8192); +# else StringBuffer buffer(nullptr, 512); +# endif Writer writer(buffer); doc.Accept(writer); @@ -479,7 +557,6 @@ int64_t xmrig::Client::send(const rapidjson::Document &doc) close(); return -1; } - memcpy(m_sendBuf, buffer.GetString(), size); m_sendBuf[size] = '\n'; m_sendBuf[size + 1] = '\0'; @@ -575,6 +652,14 @@ void xmrig::Client::login() params.AddMember("rigid", m_pool.rigId().toJSON(), allocator); } + if (!m_pool.selfSelect().isNull()) { +# ifndef XMRIG_FEATURE_HTTP + LOG_ERR("Cannot use mode self-select as not compiled with XMRIG_FEATURE_HTTP"); +# else + params.AddMember("mode", "self-select", allocator); +# endif + } + m_listener->onLogin(this, doc, params); JsonRequest::create(doc, 1, "login", params); @@ -696,7 +781,9 @@ void xmrig::Client::parseNotification(const char *method, const rapidjson::Value if (strcmp(method, "job") == 0) { int code = -1; if (parseJob(params, &code)) { - m_listener->onJobReceived(this, m_job, params); + if (m_job.blob()[0]) { + m_listener->onJobReceived(this, m_job, params); + } } else { close(); @@ -742,7 +829,9 @@ void xmrig::Client::parseResponse(int64_t id, const rapidjson::Value &result, co m_failures = 0; m_listener->onLoginSuccess(this); - m_listener->onJobReceived(this, m_job, result["job"]); + if (m_job.blob()[0]) { + m_listener->onJobReceived(this, m_job, result["job"]); + } return; } @@ -859,6 +948,84 @@ void xmrig::Client::startTimeout() } +void xmrig::Client::send(int method, const char *url, const char *data, size_t size) +{ +# ifndef XMRIG_FEATURE_HTTP + LOG_ERR("Not enabled without XMRIG_FEATURE_HTTP"); + return; +# else + LOG_DEBUG("[%s] " MAGENTA_BOLD("\"%s %s\"") BLACK_BOLD_S " send (%zu bytes): \"%.*s\"", + m_pool.selfSelect().data(), + http_method_str(static_cast(method)), + url, + size, + static_cast(size), + data); + + if (daemonHost.isNull()) + { + const char *hp = m_pool.selfSelect().data(); + char host[255] = {0}; + strncpy(host, hp, 255); + char *p = strrchr(host, ':'); + if (!p) + { + LOG_ERR("Cannot get host/port"); + return; + } + *p = 0; + int port = atoi(++p); + daemonHost = (const char*)host; + daemonPort = port; + } + + + HttpClient *client; +# ifdef XMRIG_FEATURE_TLS + if (strstr(daemonHost.data(), "https:")) { + client = new HttpsClient(method, url, this, data, size, m_pool.fingerprint()); + } + else +# endif + { + client = new HttpClient(method, url, this, data, size); + } + + client->setQuiet(isQuiet()); + client->connect(daemonHost, daemonPort); +# endif +} + + +void xmrig::Client::send(int method, const char *url, const rapidjson::Document &doc) +{ + using namespace rapidjson; + + StringBuffer buffer(nullptr, 512); + Writer writer(buffer); + doc.Accept(writer); + + send(method, url, buffer.GetString(), buffer.GetSize()); +} + + +int64_t xmrig::Client::getBlockTemplate() +{ + 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); + + send(HTTP_POST, "/json_rpc", doc); + + return 0; +} + void xmrig::Client::onAllocBuffer(uv_handle_t *handle, size_t, uv_buf_t *buf) { auto client = getClient(handle->data); diff --git a/src/base/net/stratum/Client.h b/src/base/net/stratum/Client.h index ff2bf7f6..7a0faa14 100644 --- a/src/base/net/stratum/Client.h +++ b/src/base/net/stratum/Client.h @@ -7,6 +7,7 @@ * Copyright 2017-2018 XMR-Stak , * Copyright 2018-2019 SChernykh * Copyright 2016-2019 XMRig , + * Copyright 2019 jtgrassie * * 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 @@ -34,6 +35,7 @@ #include "base/kernel/interfaces/IDnsListener.h" #include "base/kernel/interfaces/ILineListener.h" +#include "base/kernel/interfaces/IHttpListener.h" #include "base/net/stratum/BaseClient.h" #include "base/net/stratum/Job.h" #include "base/net/stratum/Pool.h" @@ -53,7 +55,7 @@ class IClientListener; class JobResult; -class Client : public BaseClient, public IDnsListener, public ILineListener +class Client : public BaseClient, public IDnsListener, public ILineListener, public IHttpListener { public: constexpr static uint64_t kConnectTimeout = 20 * 1000; @@ -84,6 +86,7 @@ protected: inline bool hasExtension(Extension extension) const noexcept override { return m_extensions.test(extension); } inline const char *mode() const override { return "pool"; } inline void onLine(char *line, size_t size) override { parse(line, size); } + void onHttpData(const HttpData &data) override; private: class Tls; @@ -110,6 +113,9 @@ private: void reconnect(); void setState(SocketState state); void startTimeout(); + void send(int method, const char *url, const char *data, size_t size); + void send(int method, const char *url, const rapidjson::Document &doc); + int64_t getBlockTemplate(); inline const char *url() const { return m_pool.url(); } inline SocketState state() const { return m_state; } @@ -123,7 +129,11 @@ private: static inline Client *getClient(void *data) { return m_storage.get(data); } - char m_sendBuf[2048] = { 0 }; +# ifdef XMRIG_FEATURE_HTTP + char m_sendBuf[8192] = {0}; +# else + char m_sendBuf[2048] = {0}; +# endif const char *m_agent; Dns *m_dns; RecvBuf m_recvBuf; @@ -136,6 +146,8 @@ private: uintptr_t m_key = 0; uv_stream_t *m_stream = nullptr; uv_tcp_t *m_socket = nullptr; + String daemonHost; + int daemonPort; static Storage m_storage; }; diff --git a/src/base/net/stratum/Job.cpp b/src/base/net/stratum/Job.cpp index 04bd82d8..e3475cbd 100644 --- a/src/base/net/stratum/Job.cpp +++ b/src/base/net/stratum/Job.cpp @@ -162,17 +162,31 @@ void xmrig::Job::setDiff(uint64_t diff) } +void xmrig::Job::setPoolWallet(const String &poolWallet) +{ + m_poolWallet = poolWallet; +} + + +void xmrig::Job::setExtraNonce(const String &extraNonce) +{ + m_extraNonce = extraNonce; +} + + void xmrig::Job::copy(const Job &other) { - m_algorithm = other.m_algorithm; - m_nicehash = other.m_nicehash; - m_size = other.m_size; - m_clientId = other.m_clientId; - m_id = other.m_id; - m_diff = other.m_diff; - m_height = other.m_height; - m_target = other.m_target; - m_index = other.m_index; + m_algorithm = other.m_algorithm; + m_nicehash = other.m_nicehash; + m_size = other.m_size; + m_clientId = other.m_clientId; + m_id = other.m_id; + m_diff = other.m_diff; + m_height = other.m_height; + m_target = other.m_target; + m_index = other.m_index; + m_poolWallet = other.m_poolWallet; + m_extraNonce = other.m_extraNonce; memcpy(m_blob, other.m_blob, sizeof(m_blob)); memcpy(m_seedHash, other.m_seedHash, sizeof(m_seedHash)); diff --git a/src/base/net/stratum/Job.h b/src/base/net/stratum/Job.h index 2b256a12..4b19179e 100644 --- a/src/base/net/stratum/Job.h +++ b/src/base/net/stratum/Job.h @@ -55,6 +55,8 @@ public: bool setSeedHash(const char *hash); bool setTarget(const char *target); void setDiff(uint64_t diff); + void setPoolWallet(const String &poolWallet); + void setExtraNonce(const String &extraNonce); inline bool isNicehash() const { return m_nicehash; } inline bool isValid() const { return m_size > 0 && m_diff > 0; } @@ -72,6 +74,8 @@ public: inline uint64_t target() const { return m_target; } inline uint8_t fixedByte() const { return *(m_blob + 42); } inline uint8_t index() const { return m_index; } + inline const String &poolWallet() { return m_poolWallet; } + inline const String &extraNonce() { return m_extraNonce; } inline void reset() { m_size = 0; m_diff = 0; } inline void setAlgorithm(const char *algo) { m_algorithm = algo; } inline void setClientId(const String &id) { m_clientId = id; } @@ -106,6 +110,8 @@ private: uint8_t m_blob[kMaxBlobSize]; uint8_t m_index = 0; uint8_t m_seedHash[32]; + String m_poolWallet; + String m_extraNonce; # ifdef XMRIG_PROXY_PROJECT char m_rawBlob[kMaxBlobSize * 2 + 8]; diff --git a/src/base/net/stratum/Pool.cpp b/src/base/net/stratum/Pool.cpp index ba31c35d..a278c9bb 100644 --- a/src/base/net/stratum/Pool.cpp +++ b/src/base/net/stratum/Pool.cpp @@ -48,6 +48,7 @@ namespace xmrig { static const char *kAlgo = "algo"; +static const char *kSelfSelect = "self-select"; static const char *kDaemon = "daemon"; static const char *kDaemonPollInterval = "daemon-poll-interval"; static const char *kEnabled = "enabled"; @@ -121,6 +122,8 @@ xmrig::Pool::Pool(const rapidjson::Value &object) : m_pollInterval = Json::getUint64(object, kDaemonPollInterval, kDefaultPollInterval); m_algorithm = Json::getString(object, kAlgo); + m_selfSelect = Json::getString(object, kSelfSelect); + m_flags.set(FLAG_ENABLED, Json::getBool(object, kEnabled, true)); m_flags.set(FLAG_NICEHASH, Json::getBool(object, kNicehash)); m_flags.set(FLAG_TLS, Json::getBool(object, kTls, m_flags.test(FLAG_TLS))); @@ -296,6 +299,8 @@ rapidjson::Value xmrig::Pool::toJSON(rapidjson::Document &doc) const obj.AddMember(StringRef(kDaemonPollInterval), m_pollInterval, allocator); } + obj.AddMember(StringRef(kSelfSelect), m_selfSelect.toJSON(), allocator); + return obj; } @@ -312,6 +317,7 @@ void xmrig::Pool::print() const LOG_DEBUG ("algo: %s", m_algorithm.name()); LOG_DEBUG ("nicehash: %d", static_cast(m_flags.test(FLAG_NICEHASH))); LOG_DEBUG ("keepAlive: %d", m_keepAlive); + LOG_DEBUG ("selfSelect %s", m_selfSelect.data()); } #endif diff --git a/src/base/net/stratum/Pool.h b/src/base/net/stratum/Pool.h index 36c3ed1b..83bbe949 100644 --- a/src/base/net/stratum/Pool.h +++ b/src/base/net/stratum/Pool.h @@ -80,6 +80,7 @@ public: inline const String &rigId() const { return m_rigId; } inline const String &url() const { return m_url; } inline const String &user() const { return !m_user.isNull() ? m_user : kDefaultUser; } + inline const String &selfSelect() const { return m_selfSelect; } inline int keepAlive() const { return m_keepAlive; } inline uint16_t port() const { return m_port; } inline uint64_t pollInterval() const { return m_pollInterval; } @@ -87,6 +88,7 @@ public: inline void setPassword(const String &password) { m_password = password; } inline void setRigId(const String &rigId) { m_rigId = rigId; } inline void setUser(const String &user) { m_user = user; } + inline void setSelfSelect(const String &selfSelect) { m_selfSelect = selfSelect; } inline bool operator!=(const Pool &other) const { return !isEqual(other); } inline bool operator==(const Pool &other) const { return isEqual(other); } @@ -115,6 +117,7 @@ private: String m_rigId; String m_url; String m_user; + String m_selfSelect; uint16_t m_port; uint64_t m_pollInterval; }; diff --git a/src/config.json b/src/config.json index c9f53f26..bfdd5297 100644 --- a/src/config.json +++ b/src/config.json @@ -43,7 +43,8 @@ "enabled": true, "tls": false, "tls-fingerprint": null, - "daemon": false + "daemon": false, + "self-select": null } ], "print-time": 60, @@ -52,4 +53,4 @@ "syslog": false, "user-agent": null, "watch": true -} \ No newline at end of file +} diff --git a/src/core/config/Config_default.h b/src/core/config/Config_default.h index c8b2e6e6..149f0097 100644 --- a/src/core/config/Config_default.h +++ b/src/core/config/Config_default.h @@ -77,7 +77,8 @@ R"===( "enabled": true, "tls": false, "tls-fingerprint": null, - "daemon": false + "daemon": false, + "self-select": null } ], "print-time": 60, diff --git a/src/core/config/Config_platform.h b/src/core/config/Config_platform.h index b7415f4d..47229321 100644 --- a/src/core/config/Config_platform.h +++ b/src/core/config/Config_platform.h @@ -63,6 +63,7 @@ static const option options[] = { { "keepalive", 0, nullptr, IConfig::KeepAliveKey }, { "log-file", 1, nullptr, IConfig::LogFileKey }, { "nicehash", 0, nullptr, IConfig::NicehashKey }, + { "self-select", 1, nullptr, IConfig::SelfSelectKey }, { "no-color", 0, nullptr, IConfig::ColorKey }, { "no-huge-pages", 0, nullptr, IConfig::HugePagesKey }, { "pass", 1, nullptr, IConfig::PasswordKey }, diff --git a/src/core/config/usage.h b/src/core/config/usage.h index b41ec6db..66047717 100644 --- a/src/core/config/usage.h +++ b/src/core/config/usage.h @@ -68,7 +68,8 @@ Options:\n\ -t, --threads=N number of miner threads\n\ -v, --av=N algorithm variation, 0 auto select\n\ -k, --keepalive send keepalived packet for prevent timeout (needs pool support)\n\ - --nicehash enable nicehash.com support\n" + --nicehash enable nicehash.com support\n\ + --self-select=URL self-select block templates from URL\n" #ifdef XMRIG_FEATURE_TLS "\ --tls enable SSL/TLS support (needs pool support)\n\