// 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 #include // for std::reverse #include #include #include // for boost::trim #include #include #include #include #include #include #include #include #include #ifdef WIN32 #include #else #include #include #include #endif #include "main.h" // cs_main #include #include "ui_interface.h" #include // make_unique #include #include #include #include // https://en.cppreference.com/w/cpp/types/integer - cinttypes for format constants, like PRId64, etc. #include 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& 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& 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& 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 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 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 ExtraNonce1(uint256 job_id) const; }; void StratumClient::GenSecret() { GetRandBytes(m_secret.begin(), 32); } std::vector 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 m_cb_wit_branch; // Then we compute the initial right-branch for the block-tx Merkle tree // using ComputeStableMerkleBranch... std::vector 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 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 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 leaves; for (const auto& tx : m_block_template.block.vtx) { leaves.push_back(tx.GetHash()); } std::vector 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 stratum_allow_subnets; //! Bound stratum listening sockets static std::map bound_listeners; //! Active miners connected to us static std::map subscriptions; //! Mapping of stratum method names -> handlers static std::map > stratum_method_dispatch; //! A mapping of job_id -> work templates static std::map 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 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 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 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 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 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& extranonce1, const std::vector& extranonce2, CMutableTransaction& cb, CMutableTransaction& bf, std::vector& 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 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 new_work(CreateNewBlock(CPubKey(), scriptDummy, HUSH_MAXGPUCOUNT, false)); // std::unique_ptr 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 old_job_ids; boost::optional 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 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 extranonce1 = client.ExtraNonce1(job_id); static const std::vector 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 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& extranonce1, const std::vector& extranonce2, boost::optional nVersion, uint32_t nTime, const std::vector& 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 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 noncerev(extranonce1); // std::reverse(noncerev.begin(), noncerev.end()); // noncerev.insert(noncerev.begin(), extranonce2.rbegin(), extranonce2.rend()); std::vector nonce(extranonce1); nonce.insert(nonce.end(), extranonce2.begin(), extranonce2.end()); blkhdr.nSolution = std::vector(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 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(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 pblock = std::make_shared(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 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::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(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(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 ¤t_work = work_templates[job_id]; uint32_t nTime = bswap_32(ParseHexInt4(params[2], "nTime")); std::vector 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 extranonce1 = client.ExtraNonce1(job_id); std::vector extranonce2 = ParseHexV(params[3], "extranonce2"); boost::optional 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 > 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& vbind = mapMultiArgs["-stratumbind"]; for (std::vector::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 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 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( now + std::chrono::seconds(10)); std::this_thread::sleep_until( std::chrono::time_point(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