Files
dragonx/src/stratum.cpp
T
DanS fa16e740b6 stratum: reject low-diff shares before spending a RandomX hash on them
SubmitBlock validated in the wrong order. After two O(1) length checks it
went straight to CheckRandomXSolution() -- a full ~65ms
randomx_calculate_hash -- and only afterwards tested the share target. So
32 arbitrary bytes from any peer bought a RandomX hash, and the cost was
paid in three bad places at once: on the shared HTTP/RPC libevent thread
(stratum uses EventBase(), the same base ThreadHTTP dispatches, and RPC
replies are posted back onto it), inside the read loop that holds
cs_stratum so BlockWatcher cannot push new work to real miners, and
holding the global cs_randomx_validator that block validation also takes.

One connection writing submit lines pins that thread indefinitely.

Move the share-target test above the RandomX verify. CBlockHeader::
GetHash() is SerializeHash over the header including nSolution, so
meeting the target still requires real SHA256d grinding -- an attacker
now pays for the hash instead of the node. Semantics are unchanged,
including that an empty local_diff parses to zero and still rejects; only
the position moved, and the diagnostics are recomputed locally since the
old message used variables declared further down.

This does not make the submit path cheap, only bounded: at the default
share target the grind is small, so a submit rate limit and moving
SubmitBlock off the event loop are both still wanted.

Also add the authorization check every sibling handler has and
mining.submit lacked. Being explicit about what that is worth:
mining.authorize validates no credential, so the gate is parity and
handshake-ordering, not authentication. The reorder above is the part
that actually bounds an unknown peer.
2026-08-28 23:35:06 -05:00

2213 lines
96 KiB
C++

// Copyright (c) 2021-2024 The Hush developers
// Copyright (c) 2020-2023 The Freicoin Developers
// Copyright (c) 2021-2023 Decker
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Stratum protocol:
// - https://en.bitcoin.it/wiki/Stratum_mining_protocol - Stratum mining protocol
// - https://github.com/slushpool/poclbm-zcash/wiki/Stratum-protocol-changes-for-ZCash - Stratum protocol changes for ZCash
#include "stratum.h"
#include "base58.h"
#include "chainparams.h"
#include "consensus/validation.h"
#include "crypto/sha256.h"
#include "httpserver.h"
#include "miner.h"
#include "netbase.h"
#include "pow.h" // RandomX PoW: CheckRandomXSolution / GetRandomXKey / GetRandomXInput; and CheckEquihashSolution
#include "net.h"
#include "rpc/server.h"
#include "serialize.h"
#include "streams.h"
#include "sync.h"
#include "txmempool.h"
#include "uint256.h"
#include "util.h"
#include "util/strencodings.h"
#include <univalue.h>
#include <algorithm> // for std::reverse
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp> // for boost::trim
#include <boost/lexical_cast.hpp>
#include <boost/none.hpp>
#include <boost/optional.hpp>
#include <boost/thread.hpp>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <errno.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#endif
#include "main.h" // cs_main
#include <boost/foreach.hpp>
#include "ui_interface.h"
#include <memory> // make_unique
#include <locale>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <chrono>
#include <thread>
// https://en.cppreference.com/w/cpp/types/integer - cinttypes for format constants, like PRId64, etc.
#include <cinttypes>
extern uint16_t ASSETCHAINS_RPCPORT; // don't want to include hush_globals.h
UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false); // rpc/blockchain.cpp
bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx); // src/core_read.cpp
static const long jobRebroadcastTimeout = 30;
static const long txMemPoolCheckTimeout = 10;
/**
* Begin of helper routines,
* included: missed in httpserver.cpp in our codebase, missed
* constructors for CSubNet(...), etc.
*/
namespace { // better to use anonymous namespace for helper routines
class CStratumParams {
private:
const arith_uint256 defaultHashTarget;
arith_uint256 currentHashTarget;
public:
CStratumParams() : defaultHashTarget(UintToArith256(uint256S("00ffff0000000000000000000000000000000000000000000000000000000000"))),
currentHashTarget(defaultHashTarget), fAllowLowDiffShares(false), fCheckEquihashSolution(true),
fstdErrDebugOutput(false) { }
~CStratumParams() {}
void setTarget(const arith_uint256& target) { currentHashTarget = target; }
arith_uint256 getTarget() { return currentHashTarget; }
bool fAllowLowDiffShares;
bool fCheckEquihashSolution;
bool fstdErrDebugOutput;
} instance_of_cstratumparams;
/** Check if a network address is allowed to access the Stratum server */
static bool ClientAllowed(const std::vector<CSubNet>& allowed_subnets, const CNetAddr& netaddr)
{
if (!netaddr.IsValid())
return false;
for(const CSubNet& subnet : allowed_subnets)
if (subnet.Match(netaddr))
return true;
return false;
}
/** Initialize ACL list for Stratum server */
static bool InitStratumAllowList(std::vector<CSubNet>& allowed_subnets)
{
allowed_subnets.clear();
CNetAddr localv4, localv6;
LookupHost("127.0.0.1", localv4, false);
LookupHost("::1", localv6, false);
allowed_subnets.push_back(CSubNet(localv4, 8)); // always allow IPv4 local subnet
allowed_subnets.push_back(CSubNet(localv6)); // always allow IPv6 localhost
if (mapMultiArgs.count("-stratumallowip")) {
const std::vector<std::string>& vAllow = mapMultiArgs["-stratumallowip"];
for(const std::string& strAllow : vAllow) {
CSubNet subnet;
LookupSubNet(strAllow.c_str(), subnet);
if (!subnet.IsValid()) {
uiInterface.ThreadSafeMessageBox(
strprintf("Invalid -stratumallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow),
"", CClientUIInterface::MSG_ERROR);
return false;
}
allowed_subnets.push_back(subnet);
}
}
return true;
}
double GetDifficultyFromBits(uint32_t bits) {
uint32_t powLimit = UintToArith256(Params().GetConsensus().powLimit).GetCompact();
int nShift = (bits >> 24) & 0xff;
int nShiftAmount = (powLimit >> 24) & 0xff;
double dDiff =
(double)(powLimit & 0x00ffffff) /
(double)(bits & 0x00ffffff);
while (nShift < nShiftAmount)
{
dDiff *= 256.0;
nShift++;
}
while (nShift > nShiftAmount)
{
dDiff /= 256.0;
nShift--;
}
return dDiff;
}
std::string DateTimeStrPrecise() // or we can use standart one, like DateTimeStrFormat("[%Y-%m-%d %H:%M:%S.%f]", GetTime())
{
// https://stackoverflow.com/questions/28136660/format-a-posix-time-with-just-3-digits-in-fractional-seconds
// https://www.boost.org/doc/libs/1_35_0/doc/html/date_time/date_time_io.html#date_time.format_flags
// std::locale takes ownership of the pointer
boost::posix_time::ptime const date_time = boost::posix_time::microsec_clock::local_time();
std::locale loc(std::locale::classic(), new boost::posix_time::time_facet("[%Y-%m-%d %H:%M:%S.%f] "));
std::stringstream ss;
ss.imbue(loc);
// ss << boost::posix_time::from_time_t(nTime);
ss << date_time;
return ss.str();
}
std::string get_stripped_username(const std::string& username) {
std::string res(username);
size_t dotpos = username.find('.');
if (dotpos != std::string::npos)
res.resize(dotpos);
return res;
}
// C++11 map initialization
const std::map<std::string,std::string> mapColors = {
{ "cl_N", "\x1B[0m" },
{ "cl_RED", "\x1B[31m" },
{ "cl_GRN", "\x1B[32m" },
{ "cl_YLW", "\x1B[33m" },
{ "cl_BLU", "\x1B[34m" },
{ "cl_MAG", "\x1B[35m" },
{ "cl_CYN", "\x1B[36m" },
{ "cl_BLK", "\x1B[22;30m" }, /* black */
{ "cl_RD2", "\x1B[22;31m" }, /* red */
{ "cl_GR2", "\x1B[22;32m" }, /* green */
{ "cl_YL2", "\x1B[22;33m" }, /* dark yellow */
{ "cl_BL2", "\x1B[22;34m" }, /* blue */
{ "cl_MA2", "\x1B[22;35m" }, /* magenta */
{ "cl_CY2", "\x1B[22;36m" }, /* cyan */
{ "cl_SIL", "\x1B[22;37m" }, /* gray */
{ "cl_LRD", "\x1B[01;31m" }, /* light red */
{ "cl_LGR", "\x1B[01;32m" }, /* light green */
{ "cl_LYL", "\x1B[01;33m" }, /* tooltips */
{ "cl_LBL", "\x1B[01;34m" }, /* light blue */
{ "cl_LMA", "\x1B[01;35m" }, /* light magenta */
{ "cl_LCY", "\x1B[01;36m" }, /* light cyan */
{ "cl_WHT", "\x1B[01;37m" }, /* white */
};
enum ColorType
{
cl_N, cl_RED, cl_GRN, cl_YLW, cl_BLU, cl_MAG, cl_CYN, cl_BLK, cl_RD2, cl_GR2, cl_YL2, cl_BL2, cl_MA2, cl_CY2, cl_SIL, cl_LRD, cl_LGR, cl_LYL, cl_LBL, cl_LMA, cl_LCY, cl_WHT
};
char const* ColorTypeNames[]=
{
"\x1B[0m" , "\x1B[31m" , "\x1B[32m" , "\x1B[33m" , "\x1B[34m" ,
"\x1B[35m" , "\x1B[36m" , "\x1B[22;30m", "\x1B[22;31m", "\x1B[22;32m",
"\x1B[22;33m", "\x1B[22;34m", "\x1B[22;35m", "\x1B[22;36m", "\x1B[22;37m",
"\x1B[01;31m", "\x1B[01;32m", "\x1B[01;33m", "\x1B[01;34m", "\x1B[01;35m",
"\x1B[01;36m", "\x1B[01;37m"
};
}
namespace ccminer {
bool hex2bin(void *output, const char *hexstr, size_t len)
{
unsigned char *p = (unsigned char *) output;
char hex_byte[4];
char *ep;
hex_byte[2] = '\0';
while (*hexstr && len) {
if (!hexstr[1]) {
LogPrint("stratum", "hex2bin str truncated");
return false;
}
hex_byte[0] = hexstr[0];
hex_byte[1] = hexstr[1];
*p = (unsigned char) strtol(hex_byte, &ep, 16);
if (*ep) {
LogPrint("stratum", "hex2bin failed on '%s'", hex_byte);
return false;
}
p++;
hexstr += 2;
len--;
}
return (len == 0 && *hexstr == 0) ? true : false;
}
// equi/equi-stratum.cpp
double target_to_diff_equi(uint32_t* target)
{
unsigned char* tgt = (unsigned char*) target;
uint64_t m =
(uint64_t)tgt[30] << 24 |
(uint64_t)tgt[29] << 16 |
(uint64_t)tgt[28] << 8 |
(uint64_t)tgt[27] << 0;
if (!m)
return 0.;
else
return (double)0xffff0000UL/m;
}
void diff_to_target_equi(uint32_t *target, double diff)
{
uint64_t m;
int k;
for (k = 6; k > 0 && diff > 1.0; k--)
diff /= 4294967296.0;
m = (uint64_t)(4294901760.0 / diff);
if (m == 0 && k == 6)
memset(target, 0xff, 32);
else {
memset(target, 0, 32);
target[k + 1] = (uint32_t)(m >> 8);
target[k + 2] = (uint32_t)(m >> 40);
//memset(target, 0xff, 6*sizeof(uint32_t));
for (k = 0; k < 28 && ((uint8_t*)target)[k] == 0; k++)
((uint8_t*)target)[k] = 0xff;
}
}
void hush_diff_to_target_equi(uint32_t *target, double diff)
{
uint64_t m;
int k;
for (k = 6; k > 0 && diff > 1.0; k--)
diff /= (double)((uint64_t)0x100000000);
m = (uint64_t)((uint64_t)0x0f0f0f0f / diff);
if (m == 0 && k == 6)
memset(target, 0xff, 32);
else {
memset(target, 0, 32);
target[k + 1] = (uint32_t)(m >> 8);
target[k + 2] = (uint32_t)(m >> 40);
//memset(target, 0xff, 6*sizeof(uint32_t));
for (k = 0; k < 28 && ((uint8_t*)target)[k] == 0; k++)
((uint8_t*)target)[k] = 0xff;
for (k = 0; k < 32; k++) ((uint8_t*)target)[31-k] = ((uint8_t*)target)[31-k-1];
((uint8_t*)target)[0] = 0xff;
}
}
/* compute nbits to get the network diff */
double equi_network_diff(uint32_t nbits)
{
//HUSH bits: "1e 015971",
//HUSH target: "00 00 015971000000000000000000000000000000000000000000000000000000",
//HUSH bits: "1d 686aaf",
//HUSH target: "00 0000 686aaf0000000000000000000000000000000000000000000000000000",
// uint32_t nbits = work->data[26];
uint32_t bits = (nbits & 0xffffff);
int16_t shift = (/*swab32*/bswap_32(nbits) & 0xff);
shift = (31 - shift) * 8; // 8 bits shift for 0x1e, 16 for 0x1d
uint64_t tgt64 = /*swab32*/bswap_32(bits);
tgt64 = tgt64 << shift;
// applog_hex(&tgt64, 8);
uint8_t net_target[32] = { 0 };
for (int b=0; b<8; b++)
net_target[31-b] = ((uint8_t*)&tgt64)[b];
// applog_hex(net_target, 32);
double d = target_to_diff_equi((uint32_t*)net_target);
return d;
}
double equi_stratum_target_to_diff(const std::string& target)
{
uint8_t target_bin[32], target_be[32];
const char *target_hex = target.c_str();
if (!target_hex || strlen(target_hex) == 0)
return false;
hex2bin(target_bin, target_hex, 32);
memset(target_be, 0xff, 32);
int filled = 0;
for (int i=0; i<32; i++) {
if (filled == 3) break;
target_be[31-i] = target_bin[i];
if (target_bin[i]) filled++;
}
double d = target_to_diff_equi((uint32_t*) &target_be);
return d;
}
/* Subtract the `struct timeval' values X and Y,
storing the result in RESULT.
Return 1 if the difference is negative, otherwise 0. */
int timeval_subtract(struct timeval *result, struct timeval *x,
struct timeval *y)
{
/* Perform the carry for the later subtraction by updating Y. */
if (x->tv_usec < y->tv_usec) {
int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
y->tv_usec -= 1000000 * nsec;
y->tv_sec += nsec;
}
if (x->tv_usec - y->tv_usec > 1000000) {
int nsec = (x->tv_usec - y->tv_usec) / 1000000;
y->tv_usec += 1000000 * nsec;
y->tv_sec -= nsec;
}
/* Compute the time remaining to wait.
* `tv_usec' is certainly positive. */
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_usec = x->tv_usec - y->tv_usec;
/* Return 1 if result is negative. */
return x->tv_sec < y->tv_sec;
}
}
/**
* End of helper routines
*/
struct StratumClient
{
evconnlistener* m_listener;
evutil_socket_t m_socket;
bufferevent* m_bev;
CService m_from;
int m_nextid;
uint256 m_secret;
CService GetPeer() const
{ return m_from; }
std::string m_client;
bool m_authorized;
CBitcoinAddress m_addr;
double m_mindiff;
uint32_t m_version_rolling_mask;
CBlockIndex* m_last_tip;
bool m_second_stage;
bool m_send_work;
bool m_supports_aux;
std::set<CBitcoinAddress> m_aux_addr;
bool m_supports_extranonce;
StratumClient() : m_listener(0), m_socket(0), m_bev(0), m_nextid(0), m_authorized(false), m_mindiff(0.0), m_version_rolling_mask(0x00000000), m_last_tip(0), m_second_stage(false), m_send_work(false), m_supports_aux(false), m_supports_extranonce(false) { GenSecret(); }
StratumClient(evconnlistener* listener, evutil_socket_t socket, bufferevent* bev, CService from) : m_listener(listener), m_socket(socket), m_bev(bev), m_nextid(0), m_from(from), m_authorized(false), m_mindiff(0.0), m_version_rolling_mask(0x00000000), m_last_tip(0), m_second_stage(false), m_send_work(false), m_supports_aux(false), m_supports_extranonce(false) { GenSecret(); }
void GenSecret();
std::vector<unsigned char> ExtraNonce1(uint256 job_id) const;
};
void StratumClient::GenSecret()
{
GetRandBytes(m_secret.begin(), 32);
}
std::vector<unsigned char> StratumClient::ExtraNonce1(uint256 job_id) const
{
CSHA256 nonce_hasher;
nonce_hasher.Write(m_secret.begin(), 32);
if (m_supports_extranonce) {
nonce_hasher.Write(job_id.begin(), 32);
}
uint256 job_nonce;
nonce_hasher.Finalize(job_nonce.begin());
return {job_nonce.begin(), job_nonce.begin()+8};
}
struct StratumWork {
CBlockTemplate m_block_template;
// First we generate the segwit commitment for the miner's coinbase with
// ComputeFastMerkleBranch.
std::vector<uint256> m_cb_wit_branch;
// Then we compute the initial right-branch for the block-tx Merkle tree
// using ComputeStableMerkleBranch...
std::vector<uint256> m_bf_branch;
// ...which is appended to the end of m_cb_branch so we can compute the
// block's hashMerkleRoot with ComputeMerkleBranch.
std::vector<uint256> m_cb_branch;
bool m_is_witness_enabled;
int32_t nHeight;
std::string local_diff;
// The cached 2nd-stage auxiliary hash value, if an auxiliary proof-of-work
// solution has been found.
boost::optional<uint256> m_aux_hash2;
StratumWork() : m_is_witness_enabled(false),nHeight(0) { };
StratumWork(const CBlockTemplate& block_template, bool is_witness_enabled);
CBlock& GetBlock()
{ return m_block_template.block; }
const CBlock& GetBlock() const
{ return m_block_template.block; }
};
StratumWork::StratumWork(const CBlockTemplate& block_template, bool is_witness_enabled)
: m_block_template(block_template)
, m_is_witness_enabled(is_witness_enabled), nHeight(0)
{
// Generate the block-witholding secret for the work unit.
std::vector<uint256> leaves;
for (const auto& tx : m_block_template.block.vtx) {
leaves.push_back(tx.GetHash());
}
std::vector<uint256> vMerkleTree; uint256 merkleRoot; bool fMutated;
merkleRoot = BuildMerkleTree(&fMutated, leaves, vMerkleTree);
m_cb_branch = GetMerkleBranch(0, leaves.size(), vMerkleTree);
// m_cb_branch = ComputeMerkleBranch(leaves, 0);
};
//! Critical seciton guarding access to any of the stratum global state
static CCriticalSection cs_stratum;
//! List of subnets to allow stratum connections from
static std::vector<CSubNet> stratum_allow_subnets;
//! Bound stratum listening sockets
static std::map<evconnlistener*, CService> bound_listeners;
//! Active miners connected to us
static std::map<bufferevent*, StratumClient> subscriptions;
//! Mapping of stratum method names -> handlers
static std::map<std::string, boost::function<UniValue(StratumClient&, const UniValue&)> > stratum_method_dispatch;
//! A mapping of job_id -> work templates
static std::map<uint256, StratumWork> work_templates;
//! The job_id of the first work unit to have its auxiliary proof-of-work solved
//! for the current block, or boost::none if no solution has been returned yet.
static boost::optional<uint256> half_solved_work;
//! A thread to watch for new blocks and send mining notifications
static boost::thread block_watcher_thread;
std::string HexInt4(uint32_t val)
{
std::vector<unsigned char> vch;
vch.push_back((val >> 24) & 0xff);
vch.push_back((val >> 16) & 0xff);
vch.push_back((val >> 8) & 0xff);
vch.push_back( val & 0xff);
return HexStr(vch);
}
uint32_t ParseHexInt4(const UniValue& hex, const std::string& name)
{
std::vector<unsigned char> vch = ParseHexV(hex, name);
if (vch.size() != 4) {
throw JSONRPCError(RPC_INVALID_PARAMETER, name+" must be exactly 4 bytes / 8 hex");
}
uint32_t ret = 0;
ret |= vch[0] << 24;
ret |= vch[1] << 16;
ret |= vch[2] << 8;
ret |= vch[3];
return ret;
}
uint256 ParseUInt256(const UniValue& hex, const std::string& name)
{
if (!hex.isStr()) {
throw std::runtime_error(name+" must be a hexidecimal string");
}
std::vector<unsigned char> vch = ParseHex(hex.get_str());
if (vch.size() != 32) {
throw std::runtime_error(name+" must be exactly 32 bytes / 64 hex");
}
uint256 ret;
std::copy(vch.begin(), vch.end(), ret.begin());
return ret;
}
static double ClampDifficulty(const StratumClient& client, double diff)
{
if (client.m_mindiff > 0) {
diff = client.m_mindiff;
}
diff = std::max(diff, 0.001);
return diff;
}
static std::string GetExtraNonceRequest(StratumClient& client, const uint256& job_id)
{
// https://en.bitcoin.it/wiki/Stratum_mining_protocol#mining.set_extranonce
// mining.set_extranonce("extranonce1", extranonce2_size)
std::string ret;
if (client.m_supports_extranonce)
{
std::vector<unsigned char> extranonce1 = client.ExtraNonce1(job_id);
const std::string k_extranonce_req = std::string()
+ "{"
+ "\"id\":";
const std::string k_extranonce_req2 = std::string()
+ ","
+ "\"method\":\"mining.set_extranonce\","
+ "\"params\":["
+ "\"";
const std::string k_extranonce_req3 = std::string()
+ "\"," // extranonce1
+ strprintf("%d", 32 - extranonce1.size()) // extranonce2.size() = 32 - extranonce1.size()
+ "]"
+ "}"
+ "\n";
ret = k_extranonce_req
+ strprintf("%d", client.m_nextid++)
+ k_extranonce_req2
+ HexStr(extranonce1)
+ k_extranonce_req3;
}
return ret;
}
/**
* @brief
*
* @param client
* @param current_work
* @param addr
* @param extranonce1
* @param extranonce2
* @param cb
* @param bf
* @param cb_branch
*/
void CustomizeWork(const StratumClient& client, const StratumWork& current_work, const CBitcoinAddress& addr, const std::vector<unsigned char>& extranonce1, const std::vector<unsigned char>& extranonce2, CMutableTransaction& cb, CMutableTransaction& bf, std::vector<uint256>& cb_branch)
{
if (current_work.GetBlock().vtx.empty()) {
const std::string msg = strprintf("%s: no transactions in block template; unable to submit work", __func__);
LogPrint("stratum", "%s\n", msg);
throw std::runtime_error(msg);
}
cb = CMutableTransaction(current_work.GetBlock().vtx[0]);
if (cb.vin.size() != 1) {
const std::string msg = strprintf("%s: unexpected number of inputs; is this even a coinbase transaction?", __func__);
LogPrint("stratum", "%s\n", msg);
throw std::runtime_error(msg);
}
std::vector<unsigned char> nonce(extranonce1);
if ((nonce.size() + extranonce2.size()) != 32) {
const std::string msg = strprintf("%s: unexpected combined nonce length: extranonce1(%d) + extranonce2(%d) != 32; unable to submit work", __func__, nonce.size(), extranonce2.size());
LogPrint("stratum", "%s\n", msg);
throw std::runtime_error(msg);
}
nonce.insert(nonce.end(), extranonce2.begin(), extranonce2.end());
// nonce = extranonce1 + extranonce2
if (cb.vin.empty()) {
const std::string msg = strprintf("%s: first transaction is missing coinbase input; unable to customize work to miner", __func__);
LogPrint("stratum", "%s\n", msg);
throw std::runtime_error(msg);
}
// cb.vin[0].scriptSig =
// CScript()
// << cb.lock_height
// << nonce;
/* actually we will change only cb destination on the miner address */
{
if (cb.vout.empty()) {
const std::string msg = strprintf("%s: coinbase transaction is missing outputs; unable to customize work to miner", __func__);
LogPrint("stratum", "%s\n", msg);
throw std::runtime_error(msg);
}
// Unconditional. This used to be guarded on the coinbase still carrying the OP_FALSE
// placeholder, which made it a no-op for every client after the first once a customized
// coinbase had been written back into the shared template -- so those miners silently
// mined the first miner's payout address. The template is now left pristine (see
// GetWorkUnit), and stamping unconditionally means a coinbase that somehow arrives
// already customized can never be inherited by a different miner.
if (!addr.IsValid()) {
const std::string msg = strprintf("%s: no valid payout address for this client; unable to customize work", __func__);
LogPrint("stratum", "%s\n", msg);
throw std::runtime_error(msg);
}
cb.vout[0].scriptPubKey = GetScriptForDestination(addr.Get());
}
// cb_branch = current_work.m_cb_branch;
}
// DragonX PoW is RandomX (32-byte solution); Equihash is legacy (1347-byte solution). The stratum
// work and submit paths branch on this: RandomX hands the miner the per-height RandomX key (which it
// cannot derive without the chain) and validates a 32-byte solution via CheckRandomXSolution();
// Equihash keeps the legacy path (1347-byte solution + the 3-byte prefix + CheckEquihashSolution).
extern uint32_t ASSETCHAINS_ALGO, ASSETCHAINS_RANDOMX; // hush_defs.h — active PoW algorithm selector
extern int32_t HUSH_TESTNODE; // hush_globals.h — -testnode: relax IBD/sync guards for isolated test nodes
static inline bool StratumIsRandomX() { return ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX; }
static const size_t RX_STRATUM_SOLUTION_SIZE = 32; // == RANDOMX_HASH_SIZE (kept local to avoid pulling randomx.h into stratum)
std::string GetWorkUnit(StratumClient& client)
{
// LOCK(cs_main);
/* if (!g_connman) {
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
} */
/* if (!Params().MineBlocksOnDemand() && g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) {
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "DragonX is not connected!");
} */
bool fvNodesEmpty;
{
LOCK(cs_vNodes);
fvNodesEmpty = vNodes.empty();
}
if (Params().MiningRequiresPeers() && fvNodesEmpty)
{
const std::string msg = strprintf("%s: Unable to get work unit, DragonX is not connected!", __func__);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "DragonX is not connected!");
}
if (IsInitialBlockDownload() && HUSH_TESTNODE == 0) {
const std::string msg = strprintf("%s: Unable to get work unit, DragonX is still downloading blocks!", __func__);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "DragonX is downloading blocks...");
}
if (!client.m_authorized && client.m_aux_addr.empty()) {
const std::string msg = strprintf("%s: Unable to get work unit, client not authorized! Use address 'x' to mine to the default address", __func__);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_INVALID_REQUEST, "Stratum client not authorized. Use mining.authorize first, with a DragonX R.. address as the username or 'x' to mine to the default address.");
}
static CBlockIndex* tip = NULL; // pindexPrev
static uint256 job_id;
static unsigned int transactions_updated_last = 0;
static int64_t last_update_time = 0;
// rpc/mining.cpp -> getblocktemplate -> Update block
if ( tip != chainActive.Tip() ||
(mempool.GetTransactionsUpdated() != transactions_updated_last && (GetTime() - last_update_time) > 5) ||
!work_templates.count(job_id))
{
CBlockIndex *tip_new = chainActive.Tip();
/**
* We will check script later inside CustomizeWork, if it will be == CScript() << OP_FALSE it will mean
* that work need to be customized, and in that case cb.vout[0].scriptPubKey will be set to GetScriptForDestination(addr.Get()) .
* In other words to the address with which stratum client is authorized.
*/
const CScript scriptDummy = CScript() << OP_FALSE;
std::unique_ptr<CBlockTemplate> new_work(CreateNewBlock(CPubKey(), scriptDummy, HUSH_MAXGPUCOUNT, false)); // std::unique_ptr<CBlockTemplate> new_work = BlockAssembler(Params()).CreateNewBlock(script);
/* test values for debug */
// new_work->block.nBits = 0x200f0f0f;
// new_work->block.nTime = 1623567886;
// new_work->block.hashPrevBlock = uint256S("027e3758c3a65b12aa1046462b486d0a63bfa1beae327897f56c5cfb7daaae71");
// new_work->block.hashMerkleRoot = uint256S("29f0e769c762b691d81d31bbb603719a94ef04d53d332f7de5e5533ddfd08e19");
// new_work->block.hashFinalSaplingRoot = uint256S("3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb");
// DecodeHexTx(new_work->block.vtx[0], "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff01aa2ce73b0000000023210325b4ca6736f90679f712be1454c5302050aae6edb51b0d2a051156bc868fec16ac4aabc560");
if (!new_work) {
const std::string msg = strprintf("%s: Out of memory!", __func__);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
}
// So that block.GetHash() is correct
//new_work->block.hashMerkleRoot = BlockMerkleRoot(new_work->block);
new_work->block.hashMerkleRoot = new_work->block.BuildMerkleTree();
// NB! here we have merkle with scriptDummy script in coinbase, after CustomizeWork we should recalculate it (!)
job_id = new_work->block.GetHash();
//work_templates[job_id] = StratumWork(*new_work, new_work->block.vtx[0]->HasWitness());
work_templates[job_id] = StratumWork(*new_work, false);
// Height of the block being mined — used for RandomX key derivation (GetRandomXKey) and
// CheckRandomXSolution/CheckProofOfWork on submit. Previously left 0 (Equihash didn't need it).
work_templates[job_id].nHeight = tip_new->GetHeight() + 1;
tip = tip_new;
transactions_updated_last = mempool.GetTransactionsUpdated();
last_update_time = GetTime();
LogPrint("stratum", "New stratum block template (%d total): %s\n", work_templates.size(), HexStr(job_id.begin(), job_id.end()));
// Remove any old templates
std::vector<uint256> old_job_ids;
boost::optional<uint256> oldest_job_id = boost::none;
uint32_t oldest_job_nTime = last_update_time;
for (const auto& work_template : work_templates) {
// If, for whatever reason the new work was generated with
// an old nTime, don't erase it!
if (work_template.first == job_id) {
continue;
}
// Build a list of outdated work units to free.
if (work_template.second.GetBlock().nTime < (last_update_time - 900)) {
old_job_ids.push_back(work_template.first);
}
// Track the oldest work unit, in case we have too much
// recent work.
if (work_template.second.GetBlock().nTime <= oldest_job_nTime) {
oldest_job_id = work_template.first;
oldest_job_nTime = work_template.second.GetBlock().nTime;
}
}
// Remove all outdated work.
for (const auto& old_job_id : old_job_ids) {
work_templates.erase(old_job_id);
LogPrint("stratum", "Removed outdated stratum block template (%d total): %s\n", work_templates.size(), HexStr(old_job_id.begin(), old_job_id.end()));
}
// Remove the oldest work unit if we're still over the maximum
// number of stored work templates.
if (work_templates.size() > 30 && oldest_job_id) {
work_templates.erase(oldest_job_id.get());
LogPrint("stratum", "Removed oldest stratum block template (%d total): %s\n", work_templates.size(), HexStr(oldest_job_id.get().begin(), oldest_job_id.get().end()));
}
}
StratumWork& current_work = work_templates[job_id];
CBlockIndex tmp_index;
// Native proof-of-work difficulty
tmp_index.nBits = current_work.GetBlock().nBits;
double diff = ClampDifficulty(client, GetDifficulty(&tmp_index));
UniValue set_target(UniValue::VOBJ);
set_target.push_back(Pair("id", client.m_nextid++));
set_target.push_back(Pair("method", "mining.set_target"));
UniValue set_target_params(UniValue::VARR);
std::string strTarget; // set_target
{
arith_uint256 hashTarget; bool fNegative,fOverflow;
/*
// Targets Table Example: hush diff and ccminer diff are different (!),
// Hush diff = NiceHash diff, ccminer_diff = Yiimp diff.
hashTarget.SetCompact(HUSH_MINDIFF_NBITS,&fNegative,&fOverflow); // blkhdr.nBits
hashTarget = UintToArith256(uint256S("0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"));
hashTarget.SetHex("0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f");
hashTarget.SetHex("00ffff0000000000000000000000000000000000000000000000000000000000"); // hush_diff = 15.0591, ccminer_diff = 1
hashTarget.SetHex("003fffc000000000000000000000000000000000000000000000000000000000"); // hush_diff = 60.2362, ccminer_diff = 4
hashTarget.SetHex("0007fff800000000000000000000000000000000000000000000000000000000"); // hush_diff = 481.89, ccminer_diff = 31.9999
hashTarget.SetHex("c7ff3800ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // hush_diff = 0.0752956, ccminer_diff = 1.00303
hashTarget.SetHex("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); // hush_diff = 1, ccminer_diff = 16.9956
*/
arith_uint256 aHashTarget = instance_of_cstratumparams.getTarget();
// arith_uint256 aHashTarget = UintToArith256(uint256S("00ffff0000000000000000000000000000000000000000000000000000000000")); // 1.0
// aHashTarget = aHashTarget / 8704; // hush_diff = 131074 (NiceHash), ccminer_diff = 8704 (Yiimp)
/* here we can adjust diff by some algo, note that 00ffff0000000000000000000000000000000000000000000000000000000000 / 8704 =
0000078780000000000000000000000000000000000000000000000000000000, which is equivalent to hush_diff = 131074 (NiceHash),
ccminer_diff = 8704 (Yiimp)
*/
hashTarget = aHashTarget;
strTarget = hashTarget.GetHex();
current_work.local_diff = strTarget;
if (instance_of_cstratumparams.fstdErrDebugOutput) {
std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ <<
strprintf(" target = %s, hush_diff = %g, ccminer_diff = %g",
strTarget, GetDifficultyFromBits(hashTarget.GetCompact(false)), ccminer::equi_stratum_target_to_diff(strTarget)) << std::endl;
}
}
set_target_params.push_back(UniValue(strTarget)); // TODO: send real local diff (!)
set_target.push_back(Pair("params", set_target_params));
CMutableTransaction cb, bf;
std::vector<uint256> cb_branch;
{
std::vector<unsigned char> extranonce1 = client.ExtraNonce1(job_id);
static const std::vector<unsigned char> dummy(32-extranonce1.size(), 0x00); // extranonce2
CustomizeWork(client, current_work, client.m_addr, extranonce1, dummy, cb, bf, cb_branch);
}
CBlockHeader blkhdr;
// Setup native proof-of-work
// The shared template MUST keep its pristine OP_FALSE coinbase. This previously did
// current_work.GetBlock().vtx[0] = cb;
// current_work.GetBlock().hashMerkleRoot = current_work.GetBlock().BuildMerkleTree();
// which published one client's coinbase to every other client on the same job: the merkle
// root they were told to mine, and the block they eventually submitted, both committed to
// the first client's payout address. Derive this client's header from a local copy instead,
// which is what the TODO that used to sit here was asking for.
{
CBlock tmp(current_work.GetBlock());
tmp.vtx[0] = cb;
blkhdr = tmp.GetBlockHeader();
blkhdr.hashMerkleRoot = tmp.BuildMerkleTree();
}
// CDataStream ds(SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS);
CDataStream ds(SER_GETHASH, PROTOCOL_VERSION);
ds << cb;
/*
HexInt4(blkhdr.nVersion) = 00000004, so we can't use it here, will use swab conversion via 1 of 3 methods:
(1) params.push_back(HexStr((unsigned char *)&blkhdr.nVersion, (unsigned char *)&blkhdr.nVersion + sizeof(blkhdr.nVersion))); // VERSION
(2) std::vector<unsigned char> vnVersion(4, 0);
WriteLE64(&vnVersion[0], blkhdr.nVersion);
params.push_back(HexStr(vnVersion));
(3) params.push_back(HexInt4(bswap_32(blkhdr.nVersion)));
Bytes order, cheatsheet:
[ need ] fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e - HexStr(ToByteVector(blkhdr.hashFinalSaplingRoot))
[ need ] fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e - HexStr(blkhdr.hashFinalSaplingRoot)
[ ---- ] 3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb - blkhdr.hashFinalSaplingRoot.GetHex()
[ ---- ] 3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb - blkhdr.hashFinalSaplingRoot.ToString()
*/
/* mining.notify params */
UniValue params(UniValue::VARR); // mining.notify params
params.push_back(HexStr(job_id.begin(), job_id.end())); // JOB_ID
params.push_back(HexInt4(bswap_32(blkhdr.nVersion))); // VERSION (0x4 -> "04000000")
params.push_back(HexStr(blkhdr.hashPrevBlock)); // PREVHASH
params.push_back(HexStr(blkhdr.hashMerkleRoot)); // MERKLEROOT
params.push_back(HexStr(blkhdr.hashFinalSaplingRoot)); // RESERVED -> hashFinalSaplingRoot
UpdateTime(&blkhdr, Params().GetConsensus(), tip /* or pindexPrev [tip-1] is needed? */);
// blkhdr.nTime = GetTime();
params.push_back(HexInt4(bswap_32(blkhdr.nTime))); // TIME
params.push_back(HexInt4(bswap_32(blkhdr.nBits))); // BITS
// Clean Jobs. If true, miners should abort their current work and immediately use the new job.
// If false, they can still use the current job, but should move to the new one after exhausting the current nonce range.
UniValue clean_jobs(UniValue::VBOOL);
clean_jobs = client.m_last_tip != tip;
params.push_back(clean_jobs); // CLEAN_JOBS
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << "New job: " << HexStr(job_id) << " " << strprintf("%08x", blkhdr.nTime) << std::endl;
client.m_last_tip = tip;
UniValue mining_notify(UniValue::VOBJ);
mining_notify.push_back(Pair("id", client.m_nextid++));
mining_notify.push_back(Pair("method", "mining.notify"));
mining_notify.push_back(Pair("params", params));
// RandomX: the miner cannot derive the per-height RandomX key on its own (it depends on a block
// hash deep in the chain), so hand it the key bytes + height explicitly. Sent as its own
// mining.set_randomx_key message so the equihash-format mining.notify above stays byte-compatible
// with legacy miners; a RandomX miner reads this before hashing.
std::string randomx_key_msg;
if (StratumIsRandomX()) {
const std::string rxKey = GetRandomXKey(current_work.nHeight);
UniValue set_rxkey(UniValue::VOBJ);
set_rxkey.push_back(Pair("id", client.m_nextid++));
set_rxkey.push_back(Pair("method", "mining.set_randomx_key"));
UniValue rxparams(UniValue::VARR);
rxparams.push_back(HexStr(rxKey.begin(), rxKey.end())); // RandomX key bytes (hex)
rxparams.push_back(current_work.nHeight); // block height (sanity/logging)
set_rxkey.push_back(Pair("params", rxparams));
randomx_key_msg = set_rxkey.write() + "\n";
}
return GetExtraNonceRequest(client, job_id)
+ randomx_key_msg
+ set_target.write() + "\n"
+ mining_notify.write() + "\n";
}
bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork& current_work,
const std::vector<unsigned char>& extranonce1, const std::vector<unsigned char>& extranonce2,
boost::optional<uint32_t> nVersion, uint32_t nTime, const std::vector<unsigned char>& sol)
{
// Submit path handles BOTH proof-of-works, branched on StratumIsRandomX():
// * RandomX (DragonX): `sol` is the 32-byte RandomX hash and IS nSolution verbatim; validated
// via CheckRandomXSolution(&blkhdr, height). The target check (GetHash() < target) and the
// nNonce = extranonce1||extranonce2 assembly are identical to the equihash path.
// * Equihash (legacy): `sol` is the 1347-byte solution; the 3-byte zcash prefix is stripped
// and CheckEquihashSolution() validates it.
//
// called from stratum_mining_submit and uses following data, came from client:
// ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "SOLUTION"]
// all other params we have saved in other places
if (extranonce1.size() + extranonce2.size() != 32) {
std::string msg = strprintf("extranonce1 [%d] length + extranonce2 [%d] length != %d", extranonce1.size(), extranonce2.size(), 32);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_INVALID_PARAMETER, msg);
}
const size_t expected_sol_size = StratumIsRandomX() ? RX_STRATUM_SOLUTION_SIZE : 1347;
if (sol.size() != expected_sol_size) {
std::string msg = strprintf("%s: solution is wrong length (received %d bytes; expected %d bytes)", __func__, sol.size(), (int)expected_sol_size);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_INVALID_PARAMETER, msg);
}
CMutableTransaction cb, bf;
std::vector<uint256> cb_branch;
CustomizeWork(client, current_work, client.m_addr, extranonce1, extranonce2, cb, bf, cb_branch);
bool res = false;
{
// Check native proof-of-work
uint32_t version = current_work.GetBlock().nVersion;
if (nVersion) version = *nVersion;
CBlockHeader blkhdr(current_work.GetBlock());
blkhdr.nVersion = version;
blkhdr.hashPrevBlock = current_work.GetBlock().hashPrevBlock;
// blkhdr.hashMerkleRoot = ComputeMerkleRootFromBranch(cb.GetHash(), cb_branch, 0);
// blkhdr.hashMerkleRoot = blkhdr.BuildMerkleTree();
blkhdr.nTime = nTime;
blkhdr.nBits = current_work.GetBlock().nBits;
// just an example of how-to reverse the things, don't needed in real life
// std::vector<unsigned char> noncerev(extranonce1);
// std::reverse(noncerev.begin(), noncerev.end());
// noncerev.insert(noncerev.begin(), extranonce2.rbegin(), extranonce2.rend());
std::vector<unsigned char> nonce(extranonce1);
nonce.insert(nonce.end(), extranonce2.begin(), extranonce2.end());
// RandomX: nSolution IS the 32-byte RandomX hash (verbatim). Equihash: strip the 3-byte
// zcash solution-size prefix.
blkhdr.nSolution = StratumIsRandomX() ? sol
: std::vector<unsigned char>(sol.begin() + 3, sol.end());
blkhdr.hashFinalSaplingRoot = current_work.GetBlock().hashFinalSaplingRoot;
// Recompute from the coinbase CustomizeWork() just derived for THIS client. Reading the
// shared template's root would be wrong now that the template is left pristine, and was
// wrong before too -- it carried whichever client happened to request work first.
{
CBlock tmp(current_work.GetBlock());
tmp.vtx[0] = cb;
blkhdr.hashMerkleRoot = tmp.BuildMerkleTree();
}
blkhdr.nNonce = (uint256) nonce;
// Cheap SHA256d filter first. This test used to sit *below* the RandomX verify, so 32
// arbitrary bytes from any peer bought a full ~65ms randomx_calculate_hash before anything
// rejected them -- on the shared HTTP/RPC libevent thread, and holding the global
// cs_randomx_validator that block validation also takes. GetHash() is SerializeHash over the
// header including nSolution, so passing this costs real grinding. Semantics are unchanged:
// an empty local_diff still parses to zero and still rejects, exactly as before.
if (!instance_of_cstratumparams.fAllowLowDiffShares &&
UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff)) {
CBlockIndex diff_index;
diff_index.nBits = UintToArith256(blkhdr.GetHash()).GetCompact();
const double share_diff = GetDifficulty(&diff_index);
diff_index.nBits = arith_uint256(current_work.local_diff).GetCompact();
const double target_diff = GetDifficulty(&diff_index);
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", share_diff, target_diff));
}
// block is constructed, now it's time to VerifyEH
if (StratumIsRandomX()) {
// Verify the submitted 32-byte solution really is the RandomX hash of this header
// (nSolution == randomx_hash(GetRandomXInput(blkhdr), GetRandomXKey(height))). This is the
// consensus authority for the solution; without it a miner could submit a low-GetHash()
// block with a bogus nSolution. Rejects fake shares before we count/relay them.
if (!CheckRandomXSolution(&blkhdr, current_work.nHeight))
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid RandomX solution");
} else if (instance_of_cstratumparams.fCheckEquihashSolution && !CheckEquihashSolution(&blkhdr, Params())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid equihash solution");
}
arith_uint256 bnTarget; bool fNegative, fOverflow;
bnTarget.SetCompact(blkhdr.nBits, &fNegative, &fOverflow);
// check range
// if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit))
// return false;
if (UintToArith256(blkhdr.GetHash()) > bnTarget) {
res = false;
} else {
uint8_t pubkey33[33]; int32_t height = current_work.nHeight;
res = CheckProofOfWork(blkhdr, pubkey33, height, Params().GetConsensus());
}
uint256 hash = blkhdr.GetHash();
// bits = GetNextWorkRequired(blockindex, nullptr, Params().GetConsensus());
static uint64_t counter_TotalBlocks, counter_TotalShares, counter_prev;
if (res)
counter_TotalBlocks++;
counter_TotalShares++;
// https://en.cppreference.com/w/cpp/types/integer - PRId64, PRIu64 (people always forget about various OS and format specifications)
// native proof-of-work difficulty
CBlockIndex tmp_index;
tmp_index.nBits = blkhdr.nBits;
double hush_target_diff = GetDifficulty(&tmp_index); // diff from nbits (target)
tmp_index.nBits = UintToArith256(hash).GetCompact();
double hush_real_diff = GetDifficulty(&tmp_index); // real diff (from hash)
tmp_index.nBits = arith_uint256(current_work.local_diff).GetCompact();
double hush_local_diff = GetDifficulty(&tmp_index); // local diff (from local port diff)
double ccminer_real_diff = ccminer::equi_stratum_target_to_diff(hash.ToString());
double ccminer_target_diff = ccminer::equi_stratum_target_to_diff(arith_uint256().SetCompact(blkhdr.nBits).ToString());
double ccminer_local_diff = ccminer::equi_stratum_target_to_diff(current_work.local_diff);
static std::chrono::high_resolution_clock::time_point start;
std::chrono::high_resolution_clock::time_point finish = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> elapsed;
uint64_t shares_accepted_since_last;
// (the low-diff share check moved above the RandomX verify -- see SubmitBlock's cheap
// SHA256d filter -- so that attacker-controlled bytes cannot buy a RandomX hash)
if (finish > start)
{
elapsed = finish - start;
shares_accepted_since_last = counter_TotalShares - counter_prev;
start = finish;
counter_prev = counter_TotalShares;
}
// (diff %g, target %g), %
if (res) {
LogPrintf("GOT BLOCK!!! by %s: %s\n", client.m_addr.ToString(), hash.ToString());
CBlock block(current_work.GetBlock());
// block.vtx[0] = MakeTransactionRef(std::move(cb));
block.vtx[0] = cb;
// if (!current_work.m_aux_hash2 && current_work.m_is_witness_enabled) {
// block.vtx.back() = MakeTransactionRef(std::move(bf));
// }
block.nVersion = version;
// block.hashMerkleRoot = BlockMerkleRoot(block);
block.hashMerkleRoot = block.BuildMerkleTree();
block.nTime = nTime;
// block.nNonce = nNonce;
// nNonce <<= 32; nNonce >>= 16; // clear the top and bottom 16 bits (for local use as thread flags and counters)
block.nNonce = (uint256) nonce;
block.nSolution = StratumIsRandomX() ? sol
: std::vector<unsigned char>(sol.begin() + 3, sol.end());
// std::shared_ptr<const CBlock> pblock = std::make_shared<const CBlock>(block);
// res = ProcessNewBlock(Params(), pblock, true, NULL);
CValidationState state;
res = ProcessNewBlock(0,0,state, NULL, &block, true /* forceProcessing */ , NULL);
// we haven't PreciousBlock, so we can't prioritize the block this way for now
/*
if (res) {
// LOCK(cs_main);
if (!mapBlockIndex.count(hash)) {
LogPrintf("Unable to find new block index entry; cannot prioritise block 0x%s\n", hash.ToString());
} else
{
CBlockIndex* block_index = mapBlockIndex.at(hash);
CValidationState state;
// PreciousBlock(state, Params(), block_index);
// if (!state.IsValid()) {
// LogPrintf("Database error while prioritising new block 0x%s: %d (%s) %s\n", hash.ToString(), state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage());
// }
}
}
*/
} else {
LogPrintf("NEW SHARE!!! by %s: %s\n", client.m_addr.ToString(), hash.ToString());
}
}
if (res) {
client.m_send_work = true;
}
return res;
}
void BoundParams(const std::string& method, const UniValue& params, size_t min, size_t max)
{
if (params.size() < min) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s expects at least %d parameters; received %d", method, min, params.size()));
}
if (params.size() > max) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s receives no more than %d parameters; got %d", method, max, params.size()));
}
}
UniValue stratum_mining_subscribe(StratumClient& client, const UniValue& params)
{
const std::string method("mining.subscribe");
BoundParams(method, params, 0, 4);
if (params.size() >= 1) {
client.m_client = params[0].get_str();
LogPrint("stratum", "Received subscription from client %s\n", client.m_client);
}
// According to 'Stratum protocol changes for ZCash' - https://github.com/slushpool/poclbm-zcash/wiki/Stratum-protocol-changes-for-ZCash
// mining.subscribe params looks like following:
// {"id": 1, "method": "mining.subscribe", "params": ["CONNECT_HOST", CONNECT_PORT, "MINER_USER_AGENT", "SESSION_ID"]}
// So, params[params.size()-1] should be SESSION_ID, but currently we don't support it.
// Also we should answer with these:
// {"id": 1, "result": ["SESSION_ID", "NONCE_1"], "error": null}
// {"id":1,"result":[null,"81000001"],"error":null}
// NONCE_1 is first part of the block header nonce (in hex).
// By protocol, Zcash's nonce is 32 bytes long. The miner will pick NONCE_2 such that len(NONCE_2) = 32 - len(NONCE_1).
// Please note that Stratum use hex encoding, so you have to convert NONCE_1 from hex to binary before.
// ["CONNECT_HOST", CONNECT_PORT, "MINER_USER_AGENT", "SESSION_ID"]
// ["NiceHash/1.0.0", null, "stratum.hush.is", 28030] // ua, session_id, host, port?
// ["ccminer/2.3.1"]
UniValue ret(UniValue::VARR);
// ExtraNonce1 -> client.m_supports_extranonce is false, so the job_id isn't used
std::vector<unsigned char> vExtraNonce1 = client.ExtraNonce1(uint256());
// std::string sExtraNonce1 = HexStr(vExtraNonce1.begin(), vExtraNonce1.begin() + (vExtraNonce1.size() > 3 ? 4 : vExtraNonce1.size()));
std::string sExtraNonce1 = HexStr(vExtraNonce1);
/**
* Potentially we can use something like strprintf("%08x", GetRand(std::numeric_limits<uint64_t>::max())
* here to generate sExtraNonce1, but don't forget that client.ExtraNonce1 method return 8 bytes
* job_nonce:8 = sha256(client.m_secret:32 + client.job_id:32) , so, somewhere in future we can re-calculate
* sExtraNonce1 for a given client based on m_secret.
*/
ret.push_back(NullUniValue);
ret.push_back(sExtraNonce1);
// On mining.subscribe we don't need to send anything else, we will send
// mining.set_target and mining.notify bit later, inside GetWorkUnit.
// Scheme is the following:
// 1. stratum_read_cb(bufferevent * bev, void * ctx)
// 2. if (client.m_send_work) -> GetWorkUnit
// 3. CustomizeWork (throw if error and exit from GetWorkUnit)
// 4. set_target
// 5. ...
// Last. GetWorkUnit returns string data (!) to send to client ( ... + mining.set_target + mining.notify + ... )
return ret;
}
UniValue stratum_mining_authorize(StratumClient& client, const UniValue& params)
{
const std::string method("mining.authorize");
BoundParams(method, params, 1, 2);
std::string username = params[0].get_str();
boost::trim(username);
// params[1] is the client-provided password. We do not perform
// user authorization, so we ignore this value.
double mindiff = 0.0;
size_t pos = username.find('+');
if (pos != std::string::npos) {
// Extract the suffix and trim it
std::string suffix(username, pos+1);
boost::trim_left(suffix);
// Extract the minimum difficulty request
mindiff = boost::lexical_cast<double>(suffix);
// Remove the '+' and everything after
username.resize(pos);
boost::trim_right(username);
}
CBitcoinAddress addr(get_stripped_username(username));
// If given the special address "x", mine to the default address given by -stratumaddress
// This means a miner can run a private pool without TLS and not
// worry about MITM attacks that change addresses, and leaks less metadata.
// It also means many miners can be used and updating their mining address does not
// require any changes on each miner, just restart dragonxd with a new -stratumaddress
if(addr.ToString() == "x") {
addr = CBitcoinAddress(GetArg("-stratumaddress", ""));
const std::string msg = strprintf("%s: Authorized client with default stratum address=%s", __func__, addr.ToString());
LogPrint("stratum", "%s\n", msg);
}
if (!addr.IsValid()) {
const std::string msg = strprintf("%s: Invalid DragonX address=%s", __func__, addr.ToString());
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid DragonX address: %s", username));
}
client.m_addr = addr;
client.m_mindiff = mindiff;
client.m_authorized = true;
client.m_send_work = true;
LogPrintf("Authorized stratum miner %s from %s, mindiff=%f\n", addr.ToString(), client.GetPeer().ToString(), mindiff);
return true;
}
UniValue stratum_mining_configure(StratumClient& client, const UniValue& params)
{
const std::string method("mining.configure");
BoundParams(method, params, 2, 2);
UniValue res(UniValue::VOBJ);
UniValue extensions = params[0].get_array();
UniValue config = params[1].get_obj();
for (int i = 0; i < extensions.size(); ++i) {
std::string name = extensions[i].get_str();
if ("version-rolling" == name) {
uint32_t mask = ParseHexInt4(find_value(config, "version-rolling.mask"), "version-rolling.mask");
size_t min_bit_count = find_value(config, "version-rolling.min-bit-count").get_int();
client.m_version_rolling_mask = mask & 0x1fffe000;
res.push_back(Pair("version-rolling", true));
res.push_back(Pair("version-rolling.mask", HexInt4(client.m_version_rolling_mask)));
LogPrint("stratum", "Received version rolling request from %s\n", client.GetPeer().ToString());
}
else {
LogPrint("stratum", "Unrecognized stratum extension '%s' sent by %s\n", name, client.GetPeer().ToString());
}
}
return res;
}
UniValue stratum_mining_submit(StratumClient& client, const UniValue& params)
{
// Share submission. On RandomX (DragonX) the SOLUTION param is the 32-byte RandomX hash; on
// Equihash (legacy) it is the 1347-byte solution. The size is validated below and the branch is
// handled in SubmitBlock(). NONCE_2 is the miner-chosen tail of the 32-byte block nNonce.
//
// {"id": 4, "method": "mining.submit", "params": ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "SOLUTION"]}\n
// NONCE_1 is first part of the block header nonce (in hex).
// By protocol, Zcash's nonce is 32 bytes long. The miner will pick NONCE_2 such that len(NONCE_2) = 32 - len(NONCE_1). Please note that Stratum use hex encoding, so you have to convert NONCE_1 from hex to binary before.
// NONCE_2 is the second part of the block header nonce.
/**
*
* {"method":"mining.submit","params":[
[0] WORKER_NAME "RDeckerSubnU8QVgrhj27apzUvbVK3pnTk",
[1] JOB_ID "1",
[2] TIME "0eaec560",
[3] NONCE_2 "0000000000000000d890000001000000000000000000000000000000",
[4] EQUIHASH_SOLUTION(1347) "fd400501762fe7c0d228a4b249727f52d85c3d5d989b3f9d07148506820a50e6db2ba3456b4ccfb168d7eb65651c7d7b893d87fb77077b56224a6fc9b9ca283b7a44a25be67d956ee55f9aaeca80eae765076495fd2eb50cf3e279a68dfd15ae6b30e911db6331d6717f352510b5834d3045db3833cdf74d1fe8379ab7b4fe46fe0d855c964085d5779701a25dbcd601ea87fb5d4bbe16c39e9c5fa22c874b4922605ed21411353cef39ce02b954a09961742d8011060a3c45f6b5b316d4a1d75530bd45722945d7a8d4698e75f49b86a485b7f1851b47d10d66d74eebb492c4269d34ca3691a459a80427f79f6d01e469bb250715fc49420d6e87383b598804bdf8b50b8510e44fd0740aa5650ed5ba19543c8657f67b5164d610bbb0ab75da1c48e81e9a8a9861bc119a31c695c5c3530ae271cf9ab4a2fa08d2b4fc851e273c324dc926d6901ca20ba5fed13118f12925760871909e8351d9e944c2959a61bf74238a587dd32826de63ab4819473bb3fad67c9a54baeddd137cb6350a25969531fa055dee51464b36cbdfb6afc4be0cee0f0fe11188c8d70d0238b3ba0c6459cd34d8b7bd8b1cdaa2b7728d51269707a70c54faac778eb4bcb6492e5fcc32406ed87fdfaecc52c9f461af3f4c3c51b529e2ab9a0e15a15b3cdcb35fe3bfe4854952ae975e3171cd2600a54509d386d45ecf668b5a17249b157a13212d0e465bc1796048d63c7b4027cb0850b9607261800e4fe6217e1fba2a28601aec9b524dac787a6c14df668a7c4fabf51f8885be7ed84ca72d0ff9a7491fddae1f5309441d243cb6d5c5c4f45a08b1b858bd15ef4d1ca1565c39000f9298b52e4221723457a0ec2e904aa6cd96e854cd8c1bbd07f1c9237c831d694817227aafe7873c43826e691d3971e82e87b538c42a48603696075b19c72b85c7d20863635621da1939d9024a434f6d840cac7a30058a51650485eabf9c0735163fd9b468249ea5889c62b4f739f58665d7f8c5010a661c1355ea7e9d85b6a18424b0027e86df5aa42b1bc2bb7a38b69c8db620251c4138b69956235640c502e26185d923a045777919984e71558edb77fb54981c6ac3dc979cd0b4f704874f02536daae894da78f31913554f91a30d6badc935fe58cc9d29d152138dde520ddb9906966e077ee3380641ce88fa74a658245202a8183e1807100c3f7d22df6577f309e4d85429e94a6f6f5dbaec3653ca6414bf6ed8794db84b7860be1984cb525b235cdb263cd527c74aa6d336615e1d361f4965ddad1fd191bc4a72fa92acc13a7c92b6e0ee077d70911004f422813e408a49ba38b950ead458b72cacb1ede9e35e2fd002eaaad0ecc2cf62801e4fe010a2cfd7190c51337513f1819acf170dda5f3b23452f36d28c20509a39fedd658f45c5e58a02feb64b0e027e05804350afc3220e53fe1761e93d018f3be9eb3554ecc98fe9fdc584ac06c0dcd63812180e94876f42f2955e242358d590a8b521b641b9729e6c7dcf6164571758ac2b2ee7656f0b0e986abf7f6b569daca304c944ded083ff202a80e8636fe9aeae39707401b321a6094c4a59cc7bcec9852189c746697963f7062304d57335795ec60dd49081a4329d3b1a8c9d55f67d11f36fb54133e67fe8a362a1f8db601aa054d97d3002f898374fd201f10af65393c9c3634e0139551e362da976b7aa0f4f8156aef59620bd24a216663784d205ef5976aa3cf6a9eed571de7cb350a355c35b67c621184608f72357d32d49842e5534f232567ed7ef9a0edc109b3b487e86d1cdd9231969a76e5d7c54bc3e28942e99301a89c13895c2bc5acac2111f53182951183f50c839601dc5fabfd39d95258c79b93a140ab727288179ce1262b13e8cc5a829edf26e7d241fbf6b"
],
"id":10}
*/
const std::string method("mining.submit");
BoundParams(method, params, 5,5);
// Parity with every other handler (GetWorkUnit, mining.aux.*, mining.extranonce.*), which all
// refuse an unauthorized client. NOTE this is not authentication: mining.authorize validates no
// credential, so it only costs an attacker one extra line. It is here so the submit path cannot
// be reached without at least completing the handshake; the cheap-target check below is what
// actually bounds the work an unknown peer can force.
if (!client.m_authorized && client.m_aux_addr.empty()) {
const std::string msg = strprintf("%s: share submitted by an unauthorized client", __func__);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_INVALID_REQUEST, "Stratum client not authorized. Use mining.authorize first, with a DragonX R.. address as the username or 'x' to mine to the default address.");
}
// First parameter is the client username, which is ignored.
/* EWBF 31 bytes job_id fix */
bool fEWBFJobIDFixNeeded = false;
uint256 ret;
if (params[1].isStr()) {
const std::string job_id_str = params[1].get_str();
const std::string hexDigits = "0123456789abcdef";
if (job_id_str.length() == 63) {
fEWBFJobIDFixNeeded = true;
for(const auto& hexDigit : hexDigits) {
// ParseHex() stops at the first non-hex character and returns a SHORT vector
// without signalling an error, and base_blob(const std::vector<unsigned char>&)
// asserts vch.size() == 32. Constructing without checking therefore lets any
// 63-character job_id containing a non-hex byte abort the daemon -- from an
// unauthenticated client, before any other validation. Skip bad candidates
// instead; if none of the 16 completions parse, ret stays null, misses
// work_templates below, and the handler returns false cleanly.
std::vector<unsigned char> vch = ParseHex(job_id_str + hexDigit);
if (vch.size() != 32) continue;
ret = uint256(vch);
if (work_templates.count(ret)) break;
}
}
}
uint256 job_id;
if (!fEWBFJobIDFixNeeded)
job_id = ParseUInt256(params[1], "job_id");
else
job_id = ret;
// uint256 job_id = ParseUInt256(params[1], "job_id");
if (!work_templates.count(job_id)) {
LogPrint("stratum", "Received completed share for unknown job_id : %s\n", HexStr(job_id.begin(), job_id.end()));
return false;
}
StratumWork &current_work = work_templates[job_id];
uint32_t nTime = bswap_32(ParseHexInt4(params[2], "nTime"));
std::vector<unsigned char> sol = ParseHexV(params[4], "solution");
const size_t expected_sol_size = StratumIsRandomX() ? RX_STRATUM_SOLUTION_SIZE : 1347;
if (sol.size() != expected_sol_size) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("solution is wrong length (received %d bytes; expected %d bytes)", sol.size(), (int)expected_sol_size));
}
std::vector<unsigned char> extranonce1 = client.ExtraNonce1(job_id);
std::vector<unsigned char> extranonce2 = ParseHexV(params[3], "extranonce2");
boost::optional<uint32_t> nVersion = 4; // block version always 4
SubmitBlock(client, job_id, current_work, extranonce1, extranonce2, nVersion, nTime, sol);
return true;
}
UniValue stratum_mining_extranonce_subscribe(StratumClient& client, const UniValue& params)
{
const std::string method("mining.extranonce.subscribe");
BoundParams(method, params, 0, 0);
/*client.m_supports_extranonce = true;
return true;*/
client.m_supports_extranonce = false;
return true;
}
UniValue stratum_mining_multi_version(StratumClient& client, const UniValue& params)
{
const std::string method("mining.multi_version");
BoundParams(method, params, 0, 1);
/*
Received stratum request from Miner: {"id": 135828, "method": "mining.multi_version", "params": [1]}
Sending stratum response to Miner : {"result":null,"error":{"code":-32601,"message":"Method 'mining.multi_version' not found"},"id":135828}
*/
return false;
}
/** Callback to write from a stratum connection. */
static void stratum_write_cb(bufferevent *bev, void *ctx)
{
/* template */
}
/** Callback to read from a stratum connection. */
static void stratum_read_cb(bufferevent *bev, void *ctx)
{
evconnlistener *listener = (evconnlistener*)ctx;
LOCK(cs_stratum);
// Lookup the client record for this connection
if (!subscriptions.count(bev)) {
LogPrint("stratum", "Received read notification for unknown stratum connection 0x%x\n", (size_t)bev);
return;
}
StratumClient& client = subscriptions[bev];
// Get links to the input and output buffers
evbuffer *input = bufferevent_get_input(bev);
assert(input);
evbuffer *output = bufferevent_get_output(bev);
assert(output);
// Process each line of input that we have received
char *cstr = 0;
size_t len = 0;
while ((cstr = evbuffer_readln(input, &len, EVBUFFER_EOL_CRLF))) {
std::string line(cstr, len);
free(cstr);
LogPrint("stratum", "Received stratum request from %s : %s\n", client.GetPeer().ToString(), line);
JSONRequest jreq;
std::string reply;
try {
// Parse request
UniValue valRequest;
if (!valRequest.read(line)) {
// Not JSON; is this even a stratum miner?
throw JSONRPCError(RPC_PARSE_ERROR, strprintf("Invalid JSON, Parse error on: %s",line) );
}
if (!valRequest.isObject()) {
// Not a JSON object; don't know what to do.
throw JSONRPCError(RPC_PARSE_ERROR, "Not a JSON object");
}
if (valRequest.exists("result")) {
// JSON-RPC reply. Ignore.
LogPrint("stratum", "Ignoring JSON-RPC response\n");
continue;
}
jreq.parse(valRequest);
// Dispatch to method handler
UniValue result = NullUniValue;
if (stratum_method_dispatch.count(jreq.strMethod)) {
result = stratum_method_dispatch[jreq.strMethod](client, jreq.params);
} else {
throw JSONRPCError(RPC_METHOD_NOT_FOUND, strprintf("Method '%s' not found", jreq.strMethod));
}
// Compose reply
reply = JSONRPCReply(result, NullUniValue, jreq.id);
} catch (const UniValue& objError) {
reply = JSONRPCReply(NullUniValue, objError, jreq.id);
} catch (const std::exception& e) {
reply = JSONRPCReply(NullUniValue, JSONRPCError(RPC_INTERNAL_ERROR, e.what()), jreq.id);
}
LogPrint("stratum", "Sending stratum response to %s : %s", client.GetPeer().ToString(), reply);
assert(output);
if (evbuffer_add(output, reply.data(), reply.size())) {
LogPrint("stratum", "Sending stratum response failed. (Reason: %d, '%s')\n", errno, evutil_socket_error_to_string(errno));
}
}
// If required, send new work to the client.
if (client.m_send_work) {
std::string data;
try {
data = GetWorkUnit(client);
} catch (const UniValue& objError) {
data = JSONRPCReply(NullUniValue, objError, NullUniValue);
} catch (const std::exception& e) {
data = JSONRPCReply(NullUniValue, JSONRPCError(RPC_INTERNAL_ERROR, e.what()), NullUniValue);
}
LogPrint("stratum", "Sending requested stratum work unit to %s : %s", client.GetPeer().ToString(), data);
assert(output);
if (evbuffer_add(output, data.data(), data.size())) {
LogPrint("stratum", "Sending stratum work unit failed. (Reason: %d, '%s')\n", errno, evutil_socket_error_to_string(errno));
}
client.m_send_work = false;
}
}
/** Callback to handle unrecoverable errors in a stratum link. */
static void stratum_event_cb(bufferevent *bev, short what, void *ctx)
{
evconnlistener *listener = (evconnlistener*)ctx;
LOCK(cs_stratum);
// Fetch the return address for this connection, for the debug log.
std::string from("UNKNOWN");
if (!subscriptions.count(bev)) {
LogPrint("stratum", "Received event notification for unknown stratum connection 0x%x\n", (size_t)bev);
return;
} else {
from = subscriptions[bev].GetPeer().ToString();
}
// Report the reason why we are closing the connection.
if (what & BEV_EVENT_ERROR) {
LogPrint("stratum", "Error detected on stratum connection from %s\n", from);
}
if (what & BEV_EVENT_EOF) {
LogPrint("stratum", "Remote disconnect received on stratum connection from %s\n", from);
}
// Remove the connection from our records, and tell libevent to
// disconnect and free its resources.
if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
LogPrint("stratum", "Closing stratum connection from %s\n", from);
subscriptions.erase(bev);
if (bev) {
bufferevent_free(bev);
bev = NULL;
}
}
}
/** Callback to accept a stratum connection. */
static void stratum_accept_conn_cb(evconnlistener *listener, evutil_socket_t fd, sockaddr *address, int socklen, void *ctx)
{
// Parse the return address
CService from;
from.SetSockAddr(address);
// Early address-based allow check
if (!ClientAllowed(stratum_allow_subnets, from))
{
// evconnlistener_free(listener);
/*
Here we shouldn't free listener, bcz if somebody will connect on stratum port from
disallowed network -> future connections will be anavailable.
*/
LogPrint("stratum", "Rejected connection from disallowed subnet: %s\n", from.ToString());
return;
}
{
LOCK(cs_stratum);
// Should be the same as EventBase(), but let's get it the official way.
event_base *base = evconnlistener_get_base(listener);
// Create a buffer for sending/receiving from this connection.
bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
// Disable Nagle's algorithm, so that TCP packets are sent
// immediately, even if it results in a small packet.
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
// Setup the read and event callbacks to handle receiving requests
// from the miner and error handling. A write callback isn't
// needed because we're not sending enough data to fill buffers.
bufferevent_setcb(bev, stratum_read_cb, NULL, stratum_event_cb, (void*)listener);
// Enable bidirectional communication on the connection.
bufferevent_enable(bev, EV_READ|EV_WRITE);
// Record the connection state
subscriptions[bev] = StratumClient(listener, fd, bev, from);
// Log the connection.
LogPrint("stratum", "Accepted stratum connection from %s\n", from.ToString());
}
}
/** Setup the stratum connection listening services */
static bool StratumBindAddresses(event_base* base)
{
int stratumPort = BaseParams().StratumPort();
int defaultPort = GetArg("-stratumport", stratumPort);
std::vector<std::pair<std::string, uint16_t> > endpoints;
// Determine what addresses to bind to
if (!mapArgs.count("-stratumallowip")) { // Default to loopback if not allowing external IPs
endpoints.push_back(std::make_pair("::1", defaultPort));
endpoints.push_back(std::make_pair("127.0.0.1", defaultPort));
if (mapArgs.count("-stratumbind")) {
LogPrintf("WARNING: option -stratumbind was ignored because -stratumallowip was not specified, refusing to allow everyone to connect\n");
}
} else if (mapArgs.count("-stratumbind")) { // Specific bind address
const std::vector<std::string>& vbind = mapMultiArgs["-stratumbind"];
for (std::vector<std::string>::const_iterator i = vbind.begin(); i != vbind.end(); ++i) {
int port = defaultPort;
std::string host;
SplitHostPort(*i, port, host);
endpoints.push_back(std::make_pair(host, port));
}
} else { // No specific bind address specified, bind to any
endpoints.push_back(std::make_pair("::", defaultPort));
endpoints.push_back(std::make_pair("0.0.0.0", defaultPort));
}
// Bind each addresses
for (const auto& endpoint : endpoints) {
LogPrint("stratum", "Binding stratum on address %s port %i\n", endpoint.first, endpoint.second);
// Use CService to translate string -> sockaddr
CNetAddr netaddr;
std::vector<CNetAddr> vIP;
LookupHost(endpoint.first.c_str(), vIP, 1, true);
assert(vIP.size() >= 1);
netaddr = vIP[0];
CService socket(netaddr, endpoint.second);
union {
sockaddr ipv4;
sockaddr_in6 ipv6;
} addr;
socklen_t len = sizeof(addr);
socket.GetSockAddr((sockaddr*)&addr, &len);
// Setup an event listener for the endpoint
evconnlistener *listener = evconnlistener_new_bind(base, stratum_accept_conn_cb, NULL, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1, (sockaddr*)&addr, len);
// Only record successful binds
if (listener) {
bound_listeners[listener] = socket;
} else {
LogPrintf("Binding stratum on address %s port %i failed. (Reason: %d, '%s')\n", endpoint.first, endpoint.second, errno, evutil_socket_error_to_string(errno));
}
}
return !bound_listeners.empty();
}
/** Watches for new blocks and send updated work to miners. */
static bool g_shutdown = false;
void BlockWatcher()
{
RenameThread("hush-stratum-blkwatcher");
boost::unique_lock<boost::mutex> lock(csBestBlock);
boost::system_time checktxtime;
boost::system_time starttime = boost::get_system_time();
unsigned int txns_updated_last = 0;
boost::posix_time::time_duration time_passed = boost::posix_time::seconds(0);
bool fRebroadcastAnyway = false;
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << " time = " << boost::get_system_time() << " checktxtime = " << checktxtime << std::endl;
while (true) { // (A)
/* This will execute before waiting of cvBlockChange */
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << " time = " << boost::get_system_time() << " checktxtime = " << checktxtime << std::endl;
checktxtime = boost::get_system_time() + boost::posix_time::seconds(txMemPoolCheckTimeout);
// - time_passed
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << " time = " << boost::get_system_time() << " checktxtime = " << checktxtime << std::endl;
if (!cvBlockChange.timed_wait(lock, checktxtime)) {
// Timeout: Check to see if mempool was updated.
/* This will execute after txMemPoolCheckTimeout seconds */
unsigned int txns_updated_next = mempool.GetTransactionsUpdated();
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << ColorTypeNames[cl_WHT] << " seconds_passed = " << (boost::get_system_time() - starttime) << ColorTypeNames[cl_N] << " txns_updated_last = " << txns_updated_last << " txns_updated_next = " << txns_updated_next << std::endl;
time_passed = boost::posix_time::time_duration(boost::get_system_time() - starttime);
if ((boost::get_system_time() - starttime) < boost::posix_time::seconds(jobRebroadcastTimeout)) {
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << "seconds_passed < jobRebroadcastTimeout" << std::endl;
fRebroadcastAnyway = false;
if (txns_updated_last == txns_updated_next) continue; // (A)
} else {
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << ColorTypeNames[cl_GRN] << "Force update work!"<< ColorTypeNames[cl_N] << " seconds_passed >= jobRebroadcastTimeout" << std::endl;
// in case of rebroadcast we should "emulate" that everything is changed and clients must go for new work
mempool.AddTransactionsUpdated(1);
for (auto& subscription : subscriptions) {
subscription.second.m_last_tip = (subscription.second.m_last_tip ? nullptr : chainActive.Tip());
}
fRebroadcastAnyway = true;
starttime += boost::posix_time::seconds(jobRebroadcastTimeout);
}
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << std::endl;
txns_updated_last = txns_updated_next;
}
/* This will excute after wait cvBlockChange will completed, or if 'timeout branch' will allow
execution goes here (mean, if it will not use condition with `continue`) */
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << " time = " << boost::get_system_time() << " checktxtime = " << checktxtime << std::endl;
if (g_shutdown) {
break;
}
// Either new block, or updated transactions. Either way,
// send updated work to miners.
{
LOCK(cs_stratum);
for (auto& subscription : subscriptions) {
bufferevent* bev = subscription.first;
if (!bev)
continue;
evbuffer *output = bufferevent_get_output(bev);
if (!output)
continue;
StratumClient& client = subscription.second;
// Ignore clients that aren't authorized yet.
if (!client.m_authorized && client.m_aux_addr.empty()) {
continue;
}
// Ignore clients that are already working on the new block.
// Typically this is just the miner that found the block, who was
// immediately sent a work update. This check avoids sending that
// work notification again, moments later. Due to race conditions
// there could be more than one miner that have already received an
// update, however.
if (!fRebroadcastAnyway && client.m_last_tip == chainActive.Tip()) {
continue;
}
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << " time = " << boost::get_system_time() << " checktxtime = " << checktxtime << std::endl;
// Get new work
std::string data;
try {
data = GetWorkUnit(client);
} catch (const UniValue& objError) {
data = JSONRPCReply(NullUniValue, objError, NullUniValue);
} catch (const std::exception& e) {
// Some sort of error. Ignore.
std::string msg = strprintf("Error generating updated work for stratum client: %s", e.what());
LogPrint("stratum", "%s\n", msg);
data = JSONRPCReply(NullUniValue, JSONRPCError(RPC_INTERNAL_ERROR, msg), NullUniValue);
}
// Send the new work to the client
assert(output);
if (evbuffer_add(output, data.data(), data.size())) {
LogPrint("stratum", "Sending stratum work unit failed. (Reason: %d, '%s')\n", errno, evutil_socket_error_to_string(errno));
}
}
}
if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << __func__ << ": " << __FILE__ << "," << __LINE__ << " time = " << boost::get_system_time() << " checktxtime = " << checktxtime << std::endl;
}
}
void SendKeepAlivePackets()
{
RenameThread("hush-stratum-keepalive");
while (true) {
// Run the notifier on an integer second in the steady clock.
auto now = std::chrono::steady_clock::now().time_since_epoch();
auto nextFire = std::chrono::duration_cast<std::chrono::seconds>(
now + std::chrono::seconds(10));
std::this_thread::sleep_until(
std::chrono::time_point<std::chrono::steady_clock>(nextFire));
boost::this_thread::interruption_point();
// Either new block, or updated transactions. Either way,
// send updated work to miners.
for (auto& subscription : subscriptions) {
bufferevent* bev = subscription.first;
if (!bev)
continue;
evbuffer *output = bufferevent_get_output(bev);
if (!output)
continue;
evbuffer *input = bufferevent_get_input(bev);
if (!input)
continue;
StratumClient& client = subscription.second;
if (instance_of_cstratumparams.fstdErrDebugOutput) {
std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << std::endl <<
"client.m_authorized = " << client.m_authorized << std::endl <<
"client.m_aux_addr.size() = " << client.m_aux_addr.size() << std::endl <<
"client.m_last_tip = " << strprintf("%p", client.m_last_tip) << std::endl <<
(client.m_last_tip ? strprintf("client.m_last_tip->GetHeight() = %d", client.m_last_tip->GetHeight()) : "") << std::endl <<
"chainActive.Tip()->GetHeight() = " << chainActive.Tip()->GetHeight() << std::endl <<
"client.m_supports_extranonce = " << client.m_supports_extranonce << std::endl <<
"client.m_send_work = " << client.m_send_work << std::endl <<
std::endl;
}
// Ignore clients that aren't authorized yet.
if (!client.m_authorized && client.m_aux_addr.empty()) {
continue;
}
std::string data = "\r\n";
// to see the socket / connection is alive, we will see bunch of
// JSON decode failed(1): '[' or '{' expected near end of file
// on client if will send "\r\n" every second
assert(output);
if (evbuffer_add(output, data.data(), data.size())) {
LogPrint("stratum", "Sending stratum keepalive unit failed. (Reason: %d, '%s')\n", errno, evutil_socket_error_to_string(errno));
}
if ( (client.m_last_tip && client.m_last_tip->GetHeight() == chainActive.Tip()->GetHeight()) || (!client.m_last_tip) )
{
LOCK(cs_stratum);
LogPrint("stratum", "%s seems stucked (ccminer issue), need to emulate new block incoming to unstuck!\n", client.m_from.ToString());
mempool.AddTransactionsUpdated(1);
client.m_last_tip = (client.m_last_tip ? nullptr : chainActive.Tip());
client.m_nextid++;
cvBlockChange.notify_all(); // change the state of all threads waiting on *this to ready
// cvBlockChange.notify_one(); // if there is a thread waiting on *this, change that thread state to ready
}
}
}
}
/** Configure the DragonX stratum server */
bool InitStratumServer()
{
LOCK(cs_stratum);
int stratumPort = BaseParams().StratumPort();
int defaultPort = GetArg("-stratumport", stratumPort);
LogPrintf("%s: Starting built-in stratum server on port %d\n",__func__, defaultPort );
// Optional pool share-target override (64-hex, big-endian like getblocktemplate's "target").
// Loosens/tightens the accepted share difficulty; also lets a solo/test miner accept easy shares
// on a low-difficulty chain (default is the diff-1 target 00ffff00..). Larger value = easier.
if (mapArgs.count("-stratumtarget")) {
const std::string t = GetArg("-stratumtarget", "");
if (!t.empty()) {
instance_of_cstratumparams.setTarget(arith_uint256(t));
LogPrintf("%s: stratum pool share target overridden to %s\n", __func__, t);
}
}
if (!InitStratumAllowList(stratum_allow_subnets)) {
LogPrint("stratum", "Unable to bind stratum server to an endpoint.\n");
return false;
}
std::string strAllowed;
for(const CSubNet& subnet : stratum_allow_subnets)
strAllowed += subnet.ToString() + " ";
LogPrint("stratum", "Allowing Stratum connections from: %s\n", strAllowed);
event_base* base = EventBase();
if (!base) {
LogPrint("stratum", "No event_base object, cannot setup stratum server.\n");
return false;
}
if (!StratumBindAddresses(base)) {
LogPrintf("Unable to bind any endpoint for stratum server\n");
} else {
LogPrint("stratum", "Initialized stratum server\n");
}
stratum_method_dispatch["mining.subscribe"] = stratum_mining_subscribe;
stratum_method_dispatch["mining.authorize"] = stratum_mining_authorize;
stratum_method_dispatch["mining.configure"] = stratum_mining_configure;
stratum_method_dispatch["mining.submit"] = stratum_mining_submit;
stratum_method_dispatch["mining.extranonce.subscribe"] = stratum_mining_extranonce_subscribe;
stratum_method_dispatch["mining.multi_version"] = stratum_mining_multi_version;
// Start thread to wait for block notifications and send updated
// work to miners.
block_watcher_thread = boost::thread(BlockWatcher);
// block_watcher_thread = boost::thread(SendKeepAlivePackets);
return true;
}
/** Interrupt the stratum server connections */
void InterruptStratumServer()
{
LOCK(cs_stratum);
// Stop listening for connections on stratum sockets
for (const auto& binding : bound_listeners) {
LogPrint("stratum", "Interrupting stratum service on %s\n", binding.second.ToString());
evconnlistener_disable(binding.first);
}
// Tell the block watching thread to stop
g_shutdown = true;
}
/** Cleanup stratum server network connections and free resources. */
void StopStratumServer()
{
LOCK(cs_stratum);
/* Tear-down active connections. */
for (const auto& subscription : subscriptions) {
LogPrint("stratum", "Closing stratum server connection to %s due to process termination\n", subscription.second.GetPeer().ToString());
bufferevent_free(subscription.first);
}
subscriptions.clear();
/* Un-bind our listeners from their network interfaces. */
for (const auto& binding : bound_listeners) {
LogPrint("stratum", "Removing stratum server binding on %s\n", binding.second.ToString());
evconnlistener_free(binding.first);
}
bound_listeners.clear();
/* Free any allocated block templates. */
work_templates.clear();
}
/* RPC */
UniValue rpc_stratum_updatework(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 0)
throw std::runtime_error(
"stratum_updatework\n"
"Tries to immediatelly update work on all connected miners.\n"
"\nExamples:\n"
+ HelpExampleCli("stratum_updatework", "")
+ HelpExampleRpc("stratum_updatework", "")
);
UniValue obj(UniValue::VOBJ);
UniValue json_clients(UniValue::VARR);
uint64_t skipped = 0;
// send updated work to miners
// if (cs_stratum.try_lock())
{
LOCK(cs_stratum);
for (auto& subscription : subscriptions) {
bufferevent* bev = subscription.first;
if (!bev)
continue;
evbuffer *output = bufferevent_get_output(bev);
if (!output)
continue;
evbuffer *input = bufferevent_get_input(bev);
if (!input)
continue;
StratumClient& client = subscription.second;
if (instance_of_cstratumparams.fstdErrDebugOutput) {
std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << std::endl <<
"client.m_authorized = " << client.m_authorized << std::endl <<
"client.m_aux_addr.size() = " << client.m_aux_addr.size() << std::endl <<
"client.m_last_tip = " << strprintf("%p", client.m_last_tip) << std::endl <<
(client.m_last_tip ? strprintf("client.m_last_tip->GetHeight() = %d", client.m_last_tip->GetHeight()) : "") << std::endl <<
"chainActive.Tip()->GetHeight() = " << chainActive.Tip()->GetHeight() << std::endl <<
"client.m_supports_extranonce = " << client.m_supports_extranonce << std::endl <<
"client.m_send_work = " << client.m_send_work << std::endl <<
std::endl;
}
// Ignore clients that aren't authorized yet.
if (!client.m_authorized && client.m_aux_addr.empty()) {
LogPrint("stratum", "%s: Ignoring unauthorized client\n", __func__);
continue;
}
if ( (client.m_last_tip && client.m_last_tip->GetHeight() == chainActive.Tip()->GetHeight()) || (!client.m_last_tip) )
{
mempool.AddTransactionsUpdated(1);
client.m_last_tip = (client.m_last_tip ? nullptr : chainActive.Tip());
cvBlockChange.notify_all();
}
std::string data = "";
try {
data = GetWorkUnit(client);
UniValue json_client(UniValue::VOBJ);
json_client.push_back(Pair("addr", client.m_addr.ToString()));
json_client.push_back(Pair("service", client.m_from.ToString()));
json_clients.push_back(json_client);
} catch (const UniValue& objError) {
data = JSONRPCReply(NullUniValue, objError, NullUniValue);
skipped++;
} catch (const std::exception& e) {
// Some sort of error. Ignore.
std::string msg = strprintf("Error generating updated work for stratum client: %s", e.what());
LogPrint("stratum", "%s\n", msg);
data = JSONRPCReply(NullUniValue, JSONRPCError(RPC_INTERNAL_ERROR, msg), NullUniValue);
skipped++;
}
assert(output);
if (evbuffer_add(output, data.data(), data.size())) {
LogPrint("stratum", "Sending stratum work unit failed. (Reason: %d, '%s')\n", errno, evutil_socket_error_to_string(errno));
}
}
}
// else {
// throw JSONRPCError(RPC_INTERNAL_ERROR, "Something went wrong, plz try again!");
// }
uint64_t clientsSize = json_clients.size();
uint64_t subscriptionsSize = subscriptions.size();
obj.push_back(Pair("clients", json_clients));
obj.push_back(Pair("updated", clientsSize));
obj.push_back(Pair("skipped", skipped));
obj.push_back(Pair("total", subscriptionsSize));
return obj;
}
UniValue rpc_stratum_getdifficulty (const UniValue& params, bool fHelp, const CPubKey& mypk) {
if (fHelp || params.size() != 0)
throw std::runtime_error(
"stratum_getdifficulty\n"
"Show the current local diff of a stratum port.\n"
"\nExamples:\n"
+ HelpExampleCli("stratum_getdifficulty", "")
+ HelpExampleRpc("stratum_getdifficulty", "")
);
UniValue obj(UniValue::VOBJ);
arith_uint256 aHashTarget = instance_of_cstratumparams.getTarget();
std::string strTarget = aHashTarget.GetHex();
CBlockIndex tmp_index;
tmp_index.nBits = arith_uint256(strTarget).GetCompact();
double hush_diff = GetDifficulty(&tmp_index);
double ccminer_diff = ccminer::equi_stratum_target_to_diff(strTarget);
obj.push_back(Pair("target", strTarget));
obj.push_back(Pair("target_compact", strprintf("%08x",tmp_index.nBits)));
obj.push_back(Pair("hush_diff_str", strprintf("%g",hush_diff)));
obj.push_back(Pair("ccminer_diff_str", strprintf("%g", ccminer_diff)));
obj.push_back(Pair("hush_diff", hush_diff));
obj.push_back(Pair("ccminer_diff", ccminer_diff));
return obj;
};
UniValue rpc_stratum_setdifficulty (const UniValue& params, bool fHelp, const CPubKey& mypk) {
/*
https://bitcoin.stackexchange.com/questions/30467/what-are-the-equations-to-convert-between-bits-and-difficulty
There are 3 representations of the same thing (with varying degrees of precision) in Bitcoin:
- bits - unsigned int 32-bit
- target - unsigned int 256-bit
- difficulty - double-precision float (64-bit)
and 6 methods are necessary to convert between any two of these:
- bits -> target (SetCompact() in bitcoin/src/arith_uint256.cpp)
- bits -> difficulty (GetDifficulty() in bitcoin/src/rpc/blockchain.cpp)
- target -> bits (GetCompact() in bitcoin/src/arith_uint256.cpp)
- target -> difficulty (same as target -> bits -> difficulty)
- difficulty -> bits (not done in bitcoin/src) -> we will use hush_diff_to_target_equi for that
- difficulty -> target (same as difficulty -> bits -> target)
*/
if (fHelp || params.size() != 1)
throw std::runtime_error(
"stratum_setdifficulty\n"
"Set the diff on a stratum port.\n"
"\nExamples:\n"
+ HelpExampleCli("stratum_setdifficulty", "")
+ HelpExampleRpc("stratum_setdifficulty", "")
);
// diff can be accepted in two ways: as a hex target or as a hush_diff, both variants assume
// passing a string with 32 bytes hex target or string (!) with a double value, or double
// value as a double
double hush_diff; // calculated value: diff_str -> hush_diff
std::string diff_str = instance_of_cstratumparams.getTarget().ToString();
if (params[0].getType() == UniValue::VSTR) {
std::string param_str = params[0].get_str();
if (IsHex(param_str) && param_str.size() == 64) {
// hex target passed
diff_str = param_str;
} else {
if (ParseDouble(param_str, &hush_diff)) {
// hush diff as a str passed
// difficulty = difficulty_1_target / current_target
arith_uint256 target;
ccminer::hush_diff_to_target_equi((uint32_t *)&target, hush_diff);
diff_str = target.ToString();
} else
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid difficulty (not hex target, not hush_diff)");
}
} else if (params[0].getType() == UniValue::VNUM) {
// hush diff as a num passed
hush_diff = params[0].get_real();
// difficulty = difficulty_1_target / current_target
arith_uint256 target;
ccminer::hush_diff_to_target_equi((uint32_t *)&target, hush_diff);
diff_str = target.ToString();
} else
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid difficulty");
instance_of_cstratumparams.setTarget(arith_uint256(diff_str));
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("target", diff_str));
CBlockIndex tmp_index;
tmp_index.nBits = arith_uint256(diff_str).GetCompact();
double new_hush_diff = GetDifficulty(&tmp_index);
obj.push_back(Pair("hush_diff_str", strprintf("%g",new_hush_diff)));
obj.push_back(Pair("hush_diff", new_hush_diff));
return obj;
};
UniValue rpc_stratum_getclientscount (const UniValue& params, bool fHelp, const CPubKey& mypk) {
if (fHelp || params.size() != 0)
throw std::runtime_error(
"stratum_getclientscount\n"
"Show the the number of stratum clients.\n"
"\nExamples:\n"
+ HelpExampleCli("stratum_getclientscount", "")
+ HelpExampleRpc("stratum_getclientscount", "")
);
UniValue obj(UniValue::VOBJ);
uint64_t subscriptionsSize = subscriptions.size();
obj.push_back(Pair("total", subscriptionsSize));
return obj;
};
static const CRPCCommand commands[] =
{ // category name actor (function) okSafeMode
// --------------------- ------------------------ ----------------------- ----------
{ "stratum", "stratum_updatework", &rpc_stratum_updatework, true },
{ "stratum", "stratum_getdifficulty", &rpc_stratum_getdifficulty, true },
{ "stratum", "stratum_setdifficulty", &rpc_stratum_setdifficulty, true },
{ "stratum", "stratum_getclientscount", &rpc_stratum_getclientscount, true },
};
void RegisterStratumRPCCommands(CRPCTable &tableRPC)
{
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
// End of File