Hush Full Node software. We were censored from Github, this is where all development happens now. https://hush.is
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2159 lines
92 KiB

// 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 "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 (instance_of_cstratumparams.fstdErrDebugOutput) {
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " nonce = " << HexStr(nonce) << std::endl;
// }
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);
}
if (cb.vout[0].scriptPubKey == (CScript() << OP_FALSE)) {
cb.vout[0].scriptPubKey = GetScriptForDestination(addr.Get());
}
}
// cb_branch = current_work.m_cb_branch;
}
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, "Hush 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, Hush is not connected!", __func__);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Hush is not connected!");
}
if (IsInitialBlockDownload()) {
const std::string msg = strprintf("%s: Unable to get work unit, Hush is still downloading blocks!", __func__);
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Hush 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 Hush 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");
}
// if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << "hashMerkleRoot = " << new_work->block.hashMerkleRoot.ToString() << std::endl;
// 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 (!)
// if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << "hashMerkleRoot = " << new_work->block.hashMerkleRoot.ToString() << std::endl;
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);
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;
// if (instance_of_cstratumparams.fstdErrDebugOutput)
// {
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [1] cb = " << CTransaction(cb).ToString() << std::endl;
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [1] current_work.GetBlock().vtx[0] = " << current_work.GetBlock().vtx[0].ToString() << std::endl;
// }
{
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);
// without 2 lines below equihash solutinon on SubmitWork will be incorrect, bcz we should
// change vtx[0] in current work and re-calc hashMerkleRoot
// TODO: refactor all of these ... may be change this in current_work directly is bad idea,
// and we should do all checks and hashMerkleRoot at SubmitBlock(...)
current_work.GetBlock().vtx[0] = cb;
current_work.GetBlock().hashMerkleRoot = current_work.GetBlock().BuildMerkleTree();
}
// if (instance_of_cstratumparams.fstdErrDebugOutput)
// {
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [2] cb = " << CTransaction(cb).ToString() << std::endl;
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [2] current_work.GetBlock().vtx[0] = " << current_work.GetBlock().vtx[0].ToString() << std::endl;
// }
CBlockHeader blkhdr;
// Setup native proof-of-work
blkhdr = current_work.GetBlock().GetBlockHeader(); // copy entire blockheader created with CreateNewBlock to blkhdr
// 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));
return GetExtraNonceRequest(client, job_id)
+ 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)
{
// called from stratum_mining_submit and uses following data, came from client:
// ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "EQUIHASH_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);
}
// TODO: change hardcoded constants on actual determine of solution size, depends on equihash algo type: 200.9, etc.
if (sol.size() != 1347) {
std::string msg = strprintf("%s: solution is wrong length (received %d bytes; expected %d bytes", __func__, extranonce2.size(), 1347);
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());
blkhdr.nSolution = std::vector<unsigned char>(sol.begin() + 3, sol.end());
blkhdr.hashFinalSaplingRoot = current_work.GetBlock().hashFinalSaplingRoot;
blkhdr.hashMerkleRoot = current_work.GetBlock().hashMerkleRoot;
blkhdr.nNonce = (uint256) nonce;
// example how to display constructed block
// if (instance_of_cstratumparams.fstdErrDebugOutput) {
// CBlockIndex index {blkhdr};
// index.SetHeight(current_work.nHeight);
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " blkhdr.hashPrevBlock = " << blkhdr.hashPrevBlock.GetHex() << std::endl;
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " blkhdr = " << blockToJSON(blkhdr, &index).write() << std::endl;
// }
// block is constructed, now it's time to VerifyEH
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());
}
// if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << "res[1] = " << res << std::endl;
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;
// TODO: we need to check hash > local port diff, and if it's true -> throw an exception -> diff too low (!)
if (!instance_of_cstratumparams.fAllowLowDiffShares)
if (UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff))
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", hush_real_diff, hush_local_diff));
if (finish > start)
{
elapsed = finish - start;
shares_accepted_since_last = counter_TotalShares - counter_prev;
start = finish;
counter_prev = counter_TotalShares;
// std::cerr << strprintf("%f ms - %" PRIu64 "", elapsed.count(), shares_accepted_since_last) << std::endl;
}
bool fDisplayDiffHUSH = true; // otherwise it will display ccminer diff
std::cerr << DateTimeStrPrecise() <<
strprintf("%saccepted: %" PRIu64 "/%" PRIu64 "%s ", ColorTypeNames[cl_WHT], counter_TotalBlocks, counter_TotalShares, ColorTypeNames[cl_N] );
if (fDisplayDiffHUSH) {
/* hushd diff display */
std::cerr << strprintf("%slocal %g%s ", "\x1B[90m", hush_local_diff, ColorTypeNames[cl_N]) <<
strprintf("%s(diff %g, target %g) %s ", ColorTypeNames[cl_WHT], hush_real_diff, hush_target_diff, ColorTypeNames[cl_N]);
} else { /* ccminer diff display */
std::cerr << strprintf("%slocal %.3f%s ", "\x1B[90m", ccminer_local_diff, ColorTypeNames[cl_N]) <<
strprintf("%s(diff %.3f, target %.3f) %s", ColorTypeNames[cl_WHT], ccminer_real_diff, ccminer_target_diff, ColorTypeNames[cl_N]); // ccminer diff
}
std::cerr << "" <<
strprintf("%f ms ", elapsed.count()) << // 1 share took elapsed ms
strprintf("%s%s%s ", ColorTypeNames[cl_LGR], (res ? "yay!!!": "yes!"), ColorTypeNames[cl_N]) <<
std::endl;
// (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();
//if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << "hashMerkleRoot = " << block.hashMerkleRoot.GetHex() << std::endl;
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 = std::vector<unsigned char>(sol.begin() + 3, sol.end());
// example how to pre-check the equihash solution
// if(instance_of_cstratumparams.fstdErrDebugOutput) {
// CBlockIndex index {blkhdr};
// index.SetHeight(-1);
// std::cerr << "block = " << blockToJSON(block, &index, true).write(1) << std::endl;
// std::cerr << "CheckEquihashSolution = " << CheckEquihashSolution(&block, Params()) << std::endl;
// }
// 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);
//if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << "res[2] = " << res << std::endl;
// 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.
*/
// if (instance_of_cstratumparams.fstdErrDebugOutput && vExtraNonce1.size() > 3) {
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " " << strprintf("client.m_supports_extranonce = %d, [%d, %d, %d, %d], %s", client.m_supports_extranonce, vExtraNonce1[0], vExtraNonce1[1], vExtraNonce1[2], vExtraNonce1[3], sExtraNonce1) << std::endl;
// // recalc from client.m_secret example
// uint256 sha256;
// CSHA256().Write(client.m_secret.begin(), 32).Finalize(sha256.begin());
// std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " " << HexStr(std::vector<unsigned char>(sha256.begin(), sha256.begin() + 4)) << std::endl;
// }
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 hushd 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 Hush address=%s", __func__, addr.ToString());
LogPrint("stratum", "%s\n", msg);
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid Hush 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)
{
// {"id": 4, "method": "mining.submit", "params": ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "EQUIHASH_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);
// First parameter is the client username, which is ignored.
/* EWBF 31 bytes job_id fix */
bool fEWBFJobIDFixNeeded = false;
uint256 ret;
if (params[1].isStr()) {
//std::cerr << "\"" << params[1].get_str() << "\"" << std::endl;
const std::string job_id_str = params[1].get_str();
const std::string hexDigits = "0123456789abcdef";
// std::cerr << strprintf("\"%s\" (%d)", job_id_str, job_id_str.length()) << std::endl;
if (job_id_str.length() == 63) {
fEWBFJobIDFixNeeded = true;
for(const auto& hexDigit : hexDigits) {
ret = uint256(ParseHex(job_id_str + hexDigit));
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");
if (sol.size() != 1347) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("solution is wrong length (received %d bytes; expected %d bytes", sol.size(), 1347));
}
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);
std::cerr << DateTimeStrPrecise() << "\033[31m" << client.m_from.ToString() << "\033[0m seems stucked (ccminer issue), need to emulate new block incoming to unstuck!" << std::endl;
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 Hush stratum server */
bool InitStratumServer()
{
LOCK(cs_stratum);
int stratumPort = BaseParams().StratumPort();
int defaultPort = GetArg("-stratumport", stratumPort);
fprintf(stderr,"%s: Starting built-in stratum server on port %d\n",__func__, defaultPort );
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()) {
fprintf(stderr,"%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