Replace ProgPowZ with RandomX for ASIC-resistant proof-of-work. The full dataset is initialized multi-threaded at startup with the key "LetheanRandomXv1". Thread-local VMs are created on demand. Switch difficulty algorithm from Zano's 720-block window to LWMA-1 (zawy12) with a 60-block window for much faster convergence after hashrate changes. Target block time set to 10s for PoW. Add standard stratum mining.* protocol handlers (subscribe, authorize, submit, extranonce.subscribe) alongside existing EthProxy eth_* handlers, with automatic protocol detection and mining.notify translation for XMRig-based miners. Generate fresh Lethean genesis block and premine wallet. Replace all remaining hardcoded Zano addresses in tests with runtime-generated keys to avoid prefix mismatches. Link RandomX library across all build targets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1323 lines
58 KiB
C++
1323 lines
58 KiB
C++
// Copyright (c) 2014-2018 Zano Project
|
||
// Copyright (c) 2014-2018 The Louisdor Project
|
||
// Copyright (c) 2012-2013 The Boolberry developers
|
||
// Copyright (c) 2017-2025 Lethean (https://lt.hn)
|
||
//
|
||
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||
// You may obtain a copy of the licence at:
|
||
//
|
||
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
|
||
//
|
||
// The EUPL is a copyleft licence that is compatible with the MIT/X11
|
||
// licence used by the original projects; the MIT terms are therefore
|
||
// considered “grandfathered” under the EUPL for this code.
|
||
//
|
||
// SPDX‑License‑Identifier: EUPL-1.2
|
||
//
|
||
|
||
#include "stratum_server.h"
|
||
#include "stratum_helpers.h"
|
||
#include "net/abstract_tcp_server2.h"
|
||
#include "config/currency_config.h"
|
||
#include "currency_core/currency_core.h"
|
||
#include "common/command_line.h"
|
||
#include "common/int-util.h"
|
||
#include "version.h"
|
||
#include "currency_protocol/currency_protocol_handler.h"
|
||
|
||
#undef LOG_DEFAULT_CHANNEL
|
||
#define LOG_DEFAULT_CHANNEL "stratum"
|
||
ENABLE_CHANNEL_BY_DEFAULT("stratum");
|
||
|
||
using namespace stratum;
|
||
|
||
namespace currency
|
||
{
|
||
|
||
namespace
|
||
{
|
||
// This username should be used by stratum clients to log in IN CASE stratum server was started with "stratum-miner-address" option.
|
||
// Alternatevly, valid and the very same wallet address should be user among all the workers.
|
||
#define WORKER_ALLOWED_USERNAME "miner"
|
||
|
||
#define STRATUM_BIND_IP_DEFAULT "0.0.0.0"
|
||
#define STRATUM_THREADS_COUNT_DEFAULT 2
|
||
#define STRATUM_BLOCK_TEMPLATE_UPD_PERIOD_DEFAULT 30 // sec
|
||
#define STRATUM_TOTAL_HR_PRINT_INTERVAL_S_DEFAULT 60 // sec
|
||
#define VDIFF_TARGET_MIN_DEFAULT 100000000ull // = 100 Mh
|
||
#define VDIFF_TARGET_MAX_DEFAULT 100000000000ull // = 100 Gh
|
||
#define VDIFF_TARGET_TIME_DEFAULT 30 // sec
|
||
#define VDIFF_RETARGET_TIME_DEFAULT 240 // sec
|
||
#define VDIFF_RETARGET_SHARES_COUNT 12 // enforce retargeting if this many shares are received (high performace in terms of current difficulty)
|
||
#define VDIFF_VARIANCE_PERCENT_DEFAULT 25 // %
|
||
|
||
const command_line::arg_descriptor<bool> arg_stratum ("stratum", "Stratum server: enable" );
|
||
const command_line::arg_descriptor<std::string> arg_stratum_bind_ip ("stratum-bind-ip", "Stratum server: IP to bind", STRATUM_BIND_IP_DEFAULT );
|
||
const command_line::arg_descriptor<std::string> arg_stratum_bind_port ("stratum-bind-port", "Stratum server: port to listen at", std::to_string(STRATUM_DEFAULT_PORT) );
|
||
const command_line::arg_descriptor<size_t> arg_stratum_threads ("stratum-threads-count", "Stratum server: number of server threads", STRATUM_THREADS_COUNT_DEFAULT );
|
||
const command_line::arg_descriptor<std::string> arg_stratum_miner_address = {"stratum-miner-address", "Stratum server: miner address. All workers"
|
||
" will mine to this address. If not set here, ALL workers should use the very same wallet address as username."
|
||
" If set here - they're allowed to log in with username '" WORKER_ALLOWED_USERNAME "' instead of address."};
|
||
|
||
const command_line::arg_descriptor<size_t> arg_stratum_block_template_update_period = {"stratum-template-update-period",
|
||
"Stratum server: if there are no new blocks, update block template this often (sec.)", STRATUM_BLOCK_TEMPLATE_UPD_PERIOD_DEFAULT };
|
||
const command_line::arg_descriptor<uint64_t> arg_stratum_hr_print_interval ("stratum-hr-print-interval", "Stratum server: how often to print hashrate stats (sec.)", STRATUM_TOTAL_HR_PRINT_INTERVAL_S_DEFAULT );
|
||
|
||
const command_line::arg_descriptor<uint64_t> arg_stratum_vdiff_target_min ("stratum-vdiff-target-min", "Stratum server: minimum worker difficulty", VDIFF_TARGET_MIN_DEFAULT );
|
||
const command_line::arg_descriptor<uint64_t> arg_stratum_vdiff_target_max ("stratum-vdiff-target-max", "Stratum server: maximum worker difficulty", VDIFF_TARGET_MAX_DEFAULT );
|
||
const command_line::arg_descriptor<uint64_t> arg_stratum_vdiff_target_time ("stratum-vdiff-target-time", "Stratum server: target time per share (i.e. try to get one share per this many seconds)", VDIFF_TARGET_TIME_DEFAULT );
|
||
const command_line::arg_descriptor<uint64_t> arg_stratum_vdiff_retarget_time ("stratum-vdiff-retarget-time", "Stratum server: check to see if we should retarget this often (sec.)", VDIFF_RETARGET_TIME_DEFAULT );
|
||
const command_line::arg_descriptor<uint64_t> arg_stratum_vdiff_retarget_shares ("stratum-vdiff-retarget-shares", "Stratum server: enforce retargeting if got this many shares", VDIFF_RETARGET_SHARES_COUNT );
|
||
const command_line::arg_descriptor<uint64_t> arg_stratum_vdiff_variance_percent ("stratum-vdiff-variance-percent", "Stratum server: allow average time to very this % from target without retarget", VDIFF_VARIANCE_PERCENT_DEFAULT );
|
||
const command_line::arg_descriptor<bool> arg_stratum_always_online ( "stratum-always-online", "Stratum server consider the core being synchronized regardless of online status, useful for debugging with --offline-mode" );
|
||
|
||
//==============================================================================================================================
|
||
|
||
static jsonrpc_id_null_t jsonrpc_id_null; // object of jsonrpc_id_null_t for convenience
|
||
|
||
// JSON-RPC error codes
|
||
#define JSONRPC_ERROR_CODE_DEFAULT -32000 // -32000 to -32099 : Reserved for implementation-defined server-errors.
|
||
#define JSONRPC_ERROR_CODE_PARSE -32700
|
||
#define JSONRPC_ERROR_CODE_METHOD_NOT_FOUND -32601
|
||
|
||
#define LP_CC_WORKER( ct, message, log_level) LOG_PRINT_CC( ct, "WORKER " << ct.m_worker_name << ": " << message, log_level)
|
||
#define LP_CC_WORKER_GREEN( ct, message, log_level) LOG_PRINT_CC_GREEN( ct, "WORKER " << ct.m_worker_name << ": " << message, log_level)
|
||
#define LP_CC_WORKER_RED( ct, message, log_level) LOG_PRINT_CC_RED( ct, "WORKER " << ct.m_worker_name << ": " << message, log_level)
|
||
#define LP_CC_WORKER_BLUE( ct, message, log_level) LOG_PRINT_CC_BLUE( ct, "WORKER " << ct.m_worker_name << ": " << message, log_level)
|
||
#define LP_CC_WORKER_YELLOW( ct, message, log_level) LOG_PRINT_CC_YELLOW( ct, "WORKER " << ct.m_worker_name << ": " << message, log_level)
|
||
#define LP_CC_WORKER_CYAN( ct, message, log_level) LOG_PRINT_CC_CYAN( ct, "WORKER " << ct.m_worker_name << ": " << message, log_level)
|
||
#define LP_CC_WORKER_MAGENTA( ct, message, log_level) LOG_PRINT_CC_MAGENTA( ct, "WORKER " << ct.m_worker_name << ": " << message, log_level)
|
||
|
||
#define HR_TO_STREAM_IN_MHS_1P(hr) std::fixed << std::setprecision(1) << hr / 1000000.0
|
||
#define HR_TO_STREAM_IN_MHS_3P(hr) std::fixed << std::setprecision(3) << hr / 1000000.0
|
||
|
||
// debug stuff
|
||
#define DBG_NETWORK_DIFFICULTY 0 // if non-zero: use this value as net difficulty when checking shares (useful for debugging on testnet, recommended value is 3000000000ull)
|
||
#define DBG_CORE_ALWAYS_SYNCRONIZED 0 // if set to 1: allows the server to start even if the core is not syncronized, useful for debugging with --offline-mode
|
||
#define STRINGIZE_DETAIL(x) #x
|
||
#define STRINGIZE(x) STRINGIZE_DETAIL(x)
|
||
#define DP(x) LOG_PRINT_L0("LINE " STRINGIZE(__LINE__) ": " #x " = " << x)
|
||
|
||
//==============================================================================================================================
|
||
struct vdiff_params_t
|
||
{
|
||
explicit vdiff_params_t()
|
||
: target_min(0)
|
||
, target_max(0)
|
||
, target_time_ms(0)
|
||
, retarget_time_ms(0)
|
||
, retarget_shares_count(0)
|
||
, variance_percent(0)
|
||
{}
|
||
|
||
vdiff_params_t(uint64_t target_min, uint64_t target_max, uint64_t target_time_s, uint64_t retarget_time_s, uint64_t retarget_shares_count, uint64_t variance_percent)
|
||
: target_min(target_min)
|
||
, target_max(target_max)
|
||
, target_time_ms(target_time_s * 1000)
|
||
, retarget_time_ms(retarget_time_s * 1000)
|
||
, retarget_shares_count(retarget_shares_count)
|
||
, variance_percent(variance_percent)
|
||
{}
|
||
|
||
uint64_t target_min;
|
||
uint64_t target_max;
|
||
uint64_t target_time_ms;
|
||
uint64_t retarget_time_ms;
|
||
uint64_t retarget_shares_count;
|
||
uint64_t variance_percent;
|
||
};
|
||
|
||
struct stratum_connection_context : public epee::net_utils::connection_context_base
|
||
{
|
||
explicit stratum_connection_context()
|
||
: m_worker_name("?")
|
||
, m_worker_difficulty(1)
|
||
, m_ts_started(0)
|
||
, m_ts_share_period_timer(0)
|
||
, m_ts_difficulty_updated(0)
|
||
, m_valid_shares_count(0)
|
||
, m_wrong_shares_count(0)
|
||
, m_hashes_calculated(0)
|
||
, m_blocks_count(0)
|
||
{}
|
||
|
||
void set_worker_name(const std::string& worker_name)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
m_worker_name = worker_name;
|
||
}
|
||
|
||
uint64_t get_hr_estimate_duration()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
return (epee::misc_utils::get_tick_count() - m_ts_started) / 1000;
|
||
}
|
||
|
||
uint64_t estimate_worker_hashrate()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
if (m_ts_started == 0)
|
||
return 0;
|
||
uint64_t duration = (epee::misc_utils::get_tick_count() - m_ts_started) / 1000;
|
||
if (duration == 0)
|
||
return 0;
|
||
return (m_hashes_calculated / duration).convert_to<uint64_t>();
|
||
}
|
||
|
||
uint64_t get_average_share_period_ms()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
if (m_ts_share_period_timer == 0)
|
||
return 0;
|
||
uint64_t duration_ms = (epee::misc_utils::get_tick_count() - m_ts_share_period_timer);
|
||
if (m_valid_shares_count == 0)
|
||
return 0;
|
||
return duration_ms / m_valid_shares_count;
|
||
}
|
||
|
||
void reset_average_share_period_ms()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
m_ts_share_period_timer = epee::misc_utils::get_tick_count();
|
||
m_valid_shares_count = 0;
|
||
}
|
||
|
||
void set_worker_difficulty(const wide_difficulty_type& d)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
m_worker_difficulty = d;
|
||
m_ts_difficulty_updated = epee::misc_utils::get_tick_count();
|
||
}
|
||
|
||
void init_and_start_timers(const vdiff_params_t& vd_params)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
m_ts_started = epee::misc_utils::get_tick_count();
|
||
m_vd_params = vd_params;
|
||
set_worker_difficulty(m_vd_params.target_min);
|
||
reset_average_share_period_ms();
|
||
}
|
||
|
||
wide_difficulty_type get_worker_difficulty()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
return m_worker_difficulty;
|
||
}
|
||
|
||
// returns true if worker difficulty has just been changed
|
||
bool adjust_worker_difficulty_if_needed()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
|
||
// check retarget condition if 1) there're too many shares received; 2) difficulty was updated long enough time ago
|
||
if (m_valid_shares_count >= m_vd_params.retarget_shares_count ||
|
||
epee::misc_utils::get_tick_count() - m_ts_difficulty_updated > m_vd_params.retarget_time_ms)
|
||
{
|
||
int64_t average_share_period_ms = 0;
|
||
wide_difficulty_type new_d = 0;
|
||
if (m_valid_shares_count != 0)
|
||
{
|
||
// there were shares, calculate using average share period
|
||
average_share_period_ms = static_cast<int64_t>(get_average_share_period_ms());
|
||
if (average_share_period_ms != 0 &&
|
||
uint64_t(llabs(average_share_period_ms - int64_t(m_vd_params.target_time_ms))) > m_vd_params.target_time_ms * m_vd_params.variance_percent / 100)
|
||
{
|
||
new_d = m_worker_difficulty * m_vd_params.target_time_ms / average_share_period_ms;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// no shares are found during retarget_time_ms, perhaps the difficulty is too high, lower it significantly
|
||
new_d = (m_vd_params.target_min + m_worker_difficulty) / 4;
|
||
}
|
||
|
||
if (new_d != 0)
|
||
{
|
||
if (new_d < m_vd_params.target_min)
|
||
new_d = m_vd_params.target_min;
|
||
if (new_d > m_vd_params.target_max)
|
||
new_d = m_vd_params.target_max;
|
||
|
||
if (new_d != m_worker_difficulty)
|
||
{
|
||
LP_CC_WORKER_YELLOW((*this), "difficulty update: " << m_worker_difficulty << " -> " << new_d <<
|
||
" (av. share pediod was: " << average_share_period_ms << ", target: " << m_vd_params.target_time_ms <<
|
||
", shares: " << m_valid_shares_count << ", variance: " << int64_t(100 * average_share_period_ms / m_vd_params.target_time_ms - 100) << "%)", LOG_LEVEL_2);
|
||
set_worker_difficulty(new_d);
|
||
reset_average_share_period_ms();
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void increment_normal_shares_count()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
++m_valid_shares_count;
|
||
m_hashes_calculated += m_worker_difficulty;
|
||
}
|
||
|
||
void increment_stale_shares_count()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
++m_valid_shares_count;
|
||
m_hashes_calculated += m_worker_difficulty;
|
||
}
|
||
|
||
void increment_wrong_shares_count()
|
||
{
|
||
//++m_wrong_shares_count; not implemented yet
|
||
}
|
||
|
||
void increment_blocks_count()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
++m_blocks_count;
|
||
}
|
||
|
||
size_t get_blocks_count()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
return m_blocks_count;
|
||
}
|
||
|
||
uint64_t get_current_valid_shares_count()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_lock);
|
||
return m_valid_shares_count;
|
||
}
|
||
|
||
mutable epee::critical_section m_lock;
|
||
std::string m_worker_name;
|
||
wide_difficulty_type m_worker_difficulty;
|
||
uint64_t m_ts_started;
|
||
uint64_t m_ts_share_period_timer;
|
||
uint64_t m_ts_difficulty_updated;
|
||
size_t m_valid_shares_count; // number of shares that satisfy worker's difficulty (valid + stale)
|
||
size_t m_wrong_shares_count;
|
||
size_t m_blocks_count;
|
||
wide_difficulty_type m_hashes_calculated;
|
||
vdiff_params_t m_vd_params;
|
||
}; // struct stratum_connection_context
|
||
|
||
//==============================================================================================================================
|
||
template<typename connection_context_t>
|
||
class stratum_protocol_handler;
|
||
|
||
template<typename connection_context_t>
|
||
struct stratum_protocol_handler_config : public i_blockchain_update_listener
|
||
{
|
||
typedef stratum_protocol_handler_config<connection_context_t> this_t;
|
||
typedef stratum_protocol_handler<connection_context_t> protocol_handler_t;
|
||
|
||
stratum_protocol_handler_config()
|
||
: m_max_packet_size(10240)
|
||
, m_p_core(nullptr)
|
||
, m_network_difficulty(0)
|
||
, m_miner_addr(null_pub_addr)
|
||
, m_block_template_ethash(null_hash)
|
||
, m_blockchain_last_block_id(null_hash)
|
||
, m_block_template_height(0)
|
||
, m_block_template_update_ts(0)
|
||
, m_last_ts_total_hr_was_printed(epee::misc_utils::get_tick_count())
|
||
, m_total_hr_print_interval_ms(STRATUM_TOTAL_HR_PRINT_INTERVAL_S_DEFAULT * 1000)
|
||
, m_block_template_update_pediod_ms(STRATUM_BLOCK_TEMPLATE_UPD_PERIOD_DEFAULT * 1000)
|
||
, m_nameless_worker_id(0)
|
||
, m_total_blocks_found(0)
|
||
, m_stop_flag(false)
|
||
, m_blocktemplate_update_thread(&this_t::block_template_update_thread, this)
|
||
, m_is_core_always_online(false)
|
||
{
|
||
LOG_PRINT_L4("stratum_protocol_handler_config::ctor()");
|
||
}
|
||
|
||
~stratum_protocol_handler_config()
|
||
{
|
||
NESTED_TRY_ENTRY();
|
||
|
||
LOG_PRINT_L4("stratum_protocol_handler_config::dtor()");
|
||
|
||
m_stop_flag = true;
|
||
m_blocktemplate_update_thread.join();
|
||
|
||
if (m_p_core)
|
||
m_p_core->remove_blockchain_update_listener(this);
|
||
|
||
NESTED_CATCH_ENTRY(__func__);
|
||
}
|
||
|
||
void add_protocol_handler(protocol_handler_t* p_ph)
|
||
{
|
||
CRITICAL_REGION_BEGIN(m_ph_map_lock);
|
||
m_protocol_handlers[p_ph->get_context().m_connection_id] = p_ph;
|
||
CRITICAL_REGION_END();
|
||
LOG_PRINT_CC(p_ph->get_context(), "stratum_protocol_handler_config: protocol handler added", LOG_LEVEL_4);
|
||
//m_pcommands_handler->on_connection_new(pconn->m_connection_context);
|
||
}
|
||
|
||
void remove_protocol_handler(protocol_handler_t* p_ph)
|
||
{
|
||
CRITICAL_REGION_BEGIN(m_ph_map_lock);
|
||
m_protocol_handlers.erase(p_ph->get_context().m_connection_id);
|
||
CRITICAL_REGION_END();
|
||
LOG_PRINT_CC(p_ph->get_context(), "stratum_protocol_handler_config: protocol handler removed", LOG_LEVEL_4);
|
||
//m_pcommands_handler->on_connection_close(pconn->m_connection_context);
|
||
}
|
||
|
||
void test()
|
||
{
|
||
//protocol_handler_t* p_ph = m_protocol_handlers.begin()->second;
|
||
//std::string test = " \t { \n \"{ \\\\ \":\"}\", \"id\":1,\"jsonrpc\":\"2.0\",\"result\":[\"0x63de85850f7e02baba2dc0765ebfb01040e56354729be70358ff341b48c608ad\",\"0xa92afa474bb50595e467a1722ed7a74fc00b3307de5022d5b76cf979490f2222\",\"0x00000000dbe6fecebdedd5beb573440e5a884d1b2fbf06fcce912adcb8d8422e\"]}";
|
||
//std::string test = "{\"error\": [0], \"id\" : 555, \"result\" : \"AAA!\"}";
|
||
//std::string test = "{\"id\":10,\"method\":\"eth_submitWork\",\"params\":[\"0x899624c0078e824f\",\"0xb98617aec14a2872fb45ab755f6273a1ae719d0ff0d441a25bb8235d82cb4123\",\"0x30f599f39276df17656727f16c3230c072dd8f2dd780161625479d352e8b2a97\"]}";
|
||
//std::string test = "{\"id\":10,\"method\":\"eth_submitWork\",\"parasms\":[\"0x899624c0078e824f\"]}";
|
||
//std::string test = R"({"worker": "", "jsonrpc": "2.0", "params": [], "id": 3, "method": "eth_getWork"})";
|
||
//std::string test = R"({"worker": "eth1.0", "jsonrpc": "2.0", "params": ["HeyPnNRKwWzPF3JQMi1dcTJBRr3anA3iEMyY5vokAusYj73grFg8VhiYgWXJ4S31oT5ZV7rCjXn3QgZxQ9xr6DQJ1LThkj3", "x"], "id": 2, "method": "eth_submitLogin"})";
|
||
//p_ph->handle_recv(test.c_str(), test.size());
|
||
}
|
||
|
||
void block_template_update_thread()
|
||
{
|
||
epee::log_space::log_singletone::set_thread_log_prefix("[ST]");
|
||
while (!m_stop_flag)
|
||
{
|
||
if (is_core_syncronized() && epee::misc_utils::get_tick_count() - m_block_template_update_ts >= m_block_template_update_pediod_ms)
|
||
{
|
||
update_block_template();
|
||
}
|
||
print_total_hashrate_if_needed();
|
||
epee::misc_utils::sleep_no_w(200);
|
||
}
|
||
}
|
||
|
||
bool update_block_template(bool enforce_update = false)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_work_change_lock);
|
||
uint64_t stub;
|
||
crypto::hash top_block_id = null_hash;
|
||
m_p_core->get_blockchain_top(stub, top_block_id);
|
||
if (!enforce_update && top_block_id == m_blockchain_last_block_id && epee::misc_utils::get_tick_count() - m_block_template_update_ts < m_block_template_update_pediod_ms)
|
||
return false;// no new blocks since last update, keep the same work
|
||
|
||
LOG_PRINT("stratum_protocol_handler_config::update_block_template(" << (enforce_update ? "true" : "false") << ")", LOG_LEVEL_4);
|
||
m_block_template = AUTO_VAL_INIT(m_block_template);
|
||
wide_difficulty_type block_template_difficulty;
|
||
blobdata extra = AUTO_VAL_INIT(extra);
|
||
bool r = m_p_core->get_block_template(m_block_template, m_miner_addr, m_miner_addr, block_template_difficulty, m_block_template_height, extra);
|
||
CHECK_AND_ASSERT_MES(r, false, "get_block_template failed");
|
||
#if DBG_NETWORK_DIFFICULTY == 0
|
||
m_network_difficulty = block_template_difficulty;
|
||
#else
|
||
m_network_difficulty = DBG_NETWORK_DIFFICULTY; // for debug purpose only
|
||
#endif
|
||
m_blockchain_last_block_id = top_block_id;
|
||
|
||
m_block_template_hash_blob = get_block_hashing_blob(m_block_template);
|
||
if (get_nonce_from_blockblob(m_block_template_hash_blob) != 0)
|
||
{
|
||
LOG_PRINT_RED("non-zero nonce in generated block template", LOG_LEVEL_0);
|
||
set_nonce_to_blockblob(m_block_template_hash_blob, 0);
|
||
}
|
||
m_prev_block_template_ethash = m_block_template_ethash;
|
||
m_block_template_ethash = crypto::cn_fast_hash(m_block_template_hash_blob.data(), m_block_template_hash_blob.size());
|
||
m_block_template_update_ts = epee::misc_utils::get_tick_count();
|
||
|
||
set_work_for_all_workers(); // notify all workers of updated work
|
||
|
||
return true;
|
||
}
|
||
|
||
void set_work_for_all_workers(protocol_handler_t* ph_to_skip = nullptr)
|
||
{
|
||
LOG_PRINT("stratum_protocol_handler_config::set_work_for_all_workers()", LOG_LEVEL_4);
|
||
CRITICAL_REGION_LOCAL(m_ph_map_lock);
|
||
for (auto& ph : m_protocol_handlers)
|
||
{
|
||
if (ph.second == ph_to_skip)
|
||
continue;
|
||
ph.second->get_context().adjust_worker_difficulty_if_needed(); // some miners seem to not give a f*ck about updated job taget if the block hash wasn't changed, so change difficulty only on work update
|
||
std::string new_work_json = get_work_json(ph.second->get_context().get_worker_difficulty());
|
||
ph.second->set_work(new_work_json);
|
||
ph.second->send_notification(new_work_json);
|
||
}
|
||
}
|
||
|
||
std::string get_work_json(const wide_difficulty_type& worker_difficulty)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_work_change_lock);
|
||
if (!is_core_syncronized())
|
||
return R"("result":[])";
|
||
|
||
crypto::hash target_boundary = null_hash;
|
||
difficulty_to_boundary_long(worker_difficulty, target_boundary);
|
||
|
||
ethash_hash256 seed_hash = ethash_calculate_epoch_seed(ethash_height_to_epoch(m_block_template_height));
|
||
return R"("result":[")" + pod_to_net_format(m_block_template_ethash) + R"(",")" + pod_to_net_format(seed_hash) + R"(",")" + pod_to_net_format_reverse(target_boundary) + R"(",")" + pod_to_net_format_reverse(m_block_template_height) + R"("])";
|
||
}
|
||
|
||
void update_work(protocol_handler_t* p_ph)
|
||
{
|
||
LOG_PRINT_CC(p_ph->get_context(), "stratum_protocol_handler_config::update_work()", LOG_LEVEL_4);
|
||
bool updated = false;
|
||
if (is_core_syncronized())
|
||
{
|
||
updated = update_block_template();
|
||
}
|
||
else
|
||
{
|
||
LP_CC_WORKER_YELLOW(p_ph->get_context(), "core is NOT synchronized, respond with empty job package", LOG_LEVEL_2);
|
||
}
|
||
|
||
if (!updated)
|
||
p_ph->set_work(get_work_json(p_ph->get_context().get_worker_difficulty()));
|
||
}
|
||
|
||
bool handle_work(protocol_handler_t* p_ph, const jsonrpc_id_t& id, const std::string& worker, uint64_t nonce, const crypto::hash& block_ethash)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_work_change_lock);
|
||
bool r = false;
|
||
|
||
if (!is_core_syncronized())
|
||
{
|
||
// TODO: make an option for more aggressive mining in case there's a little difference in blockchain size (1..2)
|
||
LP_CC_WORKER_BLUE(p_ph->get_context(), "Work received, but the core is NOT syncronized. Skip...", LOG_LEVEL_1);
|
||
p_ph->send_response_default(id);
|
||
return true;
|
||
}
|
||
|
||
const uint64_t height = get_block_height(m_block_template);
|
||
|
||
// make sure worker sent work with correct block ethash
|
||
if (block_ethash != m_block_template_ethash)
|
||
{
|
||
if (block_ethash == m_prev_block_template_ethash)
|
||
{
|
||
// Got stale share, do nothing. In future it can be used for more aggressive mining strategies
|
||
LP_CC_WORKER_BLUE(p_ph->get_context(), "got stale share, skip it", LOG_LEVEL_1);
|
||
p_ph->send_response_default(id);
|
||
p_ph->get_context().increment_stale_shares_count();
|
||
return true;
|
||
}
|
||
|
||
LP_CC_WORKER_RED(p_ph->get_context(), "wrong work submitted, ethhash " << block_ethash << ", expected: " << m_block_template_ethash, LOG_LEVEL_0);
|
||
p_ph->send_response_error(id, JSONRPC_ERROR_CODE_DEFAULT, "wrong work");
|
||
p_ph->get_context().increment_wrong_shares_count();
|
||
return false;
|
||
}
|
||
|
||
crypto::hash block_pow_hash = get_block_longhash(height, m_block_template_ethash, nonce);
|
||
wide_difficulty_type worker_difficulty = p_ph->get_context().get_worker_difficulty();
|
||
|
||
if (!check_hash(block_pow_hash, worker_difficulty))
|
||
{
|
||
LP_CC_WORKER_RED(p_ph->get_context(), "block pow hash " << block_pow_hash << " doesn't meet worker difficulty: " << worker_difficulty << ENDL <<
|
||
"nonce: " << nonce << " (0x" << epee::string_tools::pod_to_hex(nonce) << ")", LOG_LEVEL_0);
|
||
p_ph->send_response_error(id, JSONRPC_ERROR_CODE_DEFAULT, "not enough work was done");
|
||
p_ph->get_context().increment_wrong_shares_count();
|
||
return false;
|
||
}
|
||
|
||
p_ph->send_response_default(id);
|
||
p_ph->get_context().increment_normal_shares_count();
|
||
m_shares_per_minute.chick();
|
||
|
||
if (!check_hash(block_pow_hash, m_network_difficulty))
|
||
{
|
||
// work is enough for worker difficulty, but not enough for network difficulty -- it's okay, move on!
|
||
LP_CC_WORKER_GREEN(p_ph->get_context(), "share found for difficulty " << worker_difficulty << ", nonce: 0x" << epee::string_tools::pod_to_hex(nonce), LOG_LEVEL_1);
|
||
LP_CC_WORKER_GREEN(p_ph->get_context(), "shares: " << p_ph->get_context().get_current_valid_shares_count() << ", share period av: " << p_ph->get_context().get_average_share_period_ms() << ", target: " << p_ph->get_context().m_vd_params.target_time_ms
|
||
<< ", variance: " << int64_t(100 * p_ph->get_context().get_average_share_period_ms() / p_ph->get_context().m_vd_params.target_time_ms - 100) << " %", LOG_LEVEL_3);
|
||
return true;
|
||
}
|
||
|
||
// seems we've just found a block!
|
||
// create a block template and push it to the core
|
||
m_block_template.nonce = nonce;
|
||
crypto::hash block_hash = get_block_hash(m_block_template);
|
||
|
||
LP_CC_WORKER_GREEN(p_ph->get_context(), "block found " << block_hash << " at height " << height << " for difficulty " << m_network_difficulty << " pow: " << block_pow_hash << ENDL <<
|
||
"nonce: " << nonce << " (0x" << epee::string_tools::pod_to_hex(nonce) << ")", LOG_LEVEL_1);
|
||
|
||
block_verification_context bvc = AUTO_VAL_INIT(bvc);
|
||
r = m_p_core->handle_block_found(m_block_template, &bvc, false);
|
||
if (r)
|
||
{
|
||
if (!bvc.m_verification_failed && !bvc.m_added_to_altchain && bvc.m_added_to_main_chain && !bvc.m_already_exists && !bvc.m_marked_as_orphaned)
|
||
{
|
||
LP_CC_WORKER_GREEN(p_ph->get_context(), "found block " << block_hash << " at height " << height << " was successfully added to the blockchain, difficulty " << m_network_difficulty, LOG_LEVEL_0);
|
||
r = update_block_template();
|
||
CHECK_AND_ASSERT_MES_NO_RET(r, "Stratum: internal error. Block template wasn't updated as expected after handling found block.");
|
||
p_ph->get_context().increment_blocks_count();
|
||
++m_total_blocks_found;
|
||
}
|
||
else
|
||
{
|
||
LP_CC_WORKER_RED(p_ph->get_context(), "block " << block_hash << " at height " << height << " was NOT added to the blockchain:" << ENDL <<
|
||
" verification_failed: " << bvc.m_verification_failed << ENDL <<
|
||
" added_to_altchain: " << bvc.m_added_to_altchain << ENDL <<
|
||
" added_to_main_chain: " << bvc.m_added_to_main_chain << ENDL <<
|
||
" already_exists: " << bvc.m_already_exists << ENDL <<
|
||
" marked_as_orphaned: " << bvc.m_marked_as_orphaned, LOG_LEVEL_0);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LP_CC_WORKER_RED(p_ph->get_context(), "block " << block_hash << " was rejected by the core", LOG_LEVEL_0);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool handle_login(protocol_handler_t* p_ph, const jsonrpc_id_t& id, const std::string& user_str, const std::string& pass_str, const std::string& worker_str, uint64_t start_difficulty)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_work_change_lock);
|
||
bool r = false, error = false;
|
||
std::stringstream error_str;
|
||
|
||
if (user_str == WORKER_ALLOWED_USERNAME)
|
||
{
|
||
// it's a valid login only in case miner address was already set
|
||
if (m_miner_addr == null_pub_addr)
|
||
{
|
||
error = true;
|
||
error_str << "trying to log in with '" WORKER_ALLOWED_USERNAME "' username, while mining address WAS NOT previously set in daemon. Set mining address in daemon OR use it as a username.";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// mining address is used as username, make sure it's correct and match with previously set
|
||
account_public_address address = null_pub_addr;
|
||
r = get_account_address_from_str(address, user_str);
|
||
if (!r)
|
||
{
|
||
error = true;
|
||
error_str << "can't parse wallet address from username: " << user_str << ".";
|
||
}
|
||
|
||
if (m_miner_addr != null_pub_addr && address != m_miner_addr)
|
||
{
|
||
error = true;
|
||
error_str << "wallet address " << user_str << " doesn't match the address previously set in daemon and/or other workers.";
|
||
}
|
||
|
||
if (!error)
|
||
set_miner_address(address);
|
||
}
|
||
|
||
if (error)
|
||
{
|
||
LP_CC_WORKER_RED(p_ph->get_context(), error_str.str() << " Connection will be dropped.", LOG_LEVEL_0);
|
||
p_ph->send_response_error(id, JSONRPC_ERROR_CODE_DEFAULT, error_str.str());
|
||
return false;
|
||
}
|
||
|
||
p_ph->get_context().set_worker_name(worker_str);
|
||
if (start_difficulty != 0)
|
||
p_ph->get_context().set_worker_difficulty(start_difficulty);
|
||
|
||
LP_CC_WORKER_GREEN(p_ph->get_context(), "logged in with username " << user_str << ", start difficulty: " << (start_difficulty != 0 ? std::to_string(start_difficulty) : "default"), LOG_LEVEL_0);
|
||
p_ph->send_response_default(id);
|
||
|
||
// send initial work
|
||
update_work(p_ph);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool handle_submit_hashrate(protocol_handler_t* p_ph, uint64_t rate, const crypto::hash& rate_submit_id)
|
||
{
|
||
LP_CC_WORKER_CYAN(p_ph->get_context(), "reported hashrate: " << HR_TO_STREAM_IN_MHS_3P(rate) << " Mh/s" <<
|
||
", estimated hashrate: " << HR_TO_STREAM_IN_MHS_3P(p_ph->get_context().estimate_worker_hashrate()) << " Mh/s, run time: " <<
|
||
epee::misc_utils::get_time_interval_string(p_ph->get_context().get_hr_estimate_duration()), LOG_LEVEL_3);
|
||
return true;
|
||
}
|
||
|
||
// required member for epee::net_utils::boosted_tcp_server concept
|
||
void on_send_stop_signal()
|
||
{
|
||
LOG_PRINT_L4("stratum_protocol_handler_config::on_send_stop_signal()");
|
||
CRITICAL_REGION_LOCAL(m_ph_map_lock);
|
||
m_protocol_handlers.clear();
|
||
}
|
||
|
||
// i_blockchain_update_listener member
|
||
virtual void on_blockchain_update() override
|
||
{
|
||
LOG_PRINT_L3("stratum_protocol_handler_config::on_blockchain_update()");
|
||
|
||
if (!is_core_syncronized())
|
||
return; // don't notify workers when blockchain is synchronizing
|
||
|
||
update_block_template();
|
||
}
|
||
|
||
void set_core(currency::core* c)
|
||
{
|
||
m_p_core = c;
|
||
m_p_core->add_blockchain_update_listener(this);
|
||
}
|
||
|
||
void set_is_core_always_online(bool is_core_always_online)
|
||
{
|
||
m_is_core_always_online = is_core_always_online;
|
||
}
|
||
|
||
void set_miner_address(const account_public_address& miner_addr)
|
||
{
|
||
m_miner_addr = miner_addr;
|
||
}
|
||
|
||
bool is_core_syncronized()
|
||
{
|
||
if (!m_p_core)
|
||
return false;
|
||
|
||
#if DBG_CORE_ALWAYS_SYNCRONIZED == 1
|
||
return true; // standalone mode, usefull for debugging WITH --offline-mode option
|
||
#endif
|
||
if (m_is_core_always_online)
|
||
return true;
|
||
|
||
// TODO!!! Bad design, need more correct way of getting this information
|
||
currency::i_currency_protocol* proto = m_p_core->get_protocol();
|
||
currency::t_currency_protocol_handler<currency::core>* protocol = dynamic_cast<currency::t_currency_protocol_handler<currency::core>*>(proto);
|
||
if (!protocol)
|
||
return false;
|
||
return protocol->is_synchronized();
|
||
}
|
||
|
||
void set_vdiff_params(const vdiff_params_t& p)
|
||
{
|
||
m_vdiff_params = p;
|
||
}
|
||
|
||
const vdiff_params_t& get_vdiff_params() const
|
||
{
|
||
return m_vdiff_params;
|
||
}
|
||
|
||
void print_total_hashrate_if_needed()
|
||
{
|
||
if (epee::misc_utils::get_tick_count() - m_last_ts_total_hr_was_printed < m_total_hr_print_interval_ms)
|
||
return;
|
||
|
||
if (!is_core_syncronized())
|
||
{
|
||
LOG_PRINT_L0("Blockchain is synchronizing...");
|
||
}
|
||
|
||
CRITICAL_REGION_LOCAL(m_ph_map_lock);
|
||
if (m_protocol_handlers.empty())
|
||
{
|
||
LOG_PRINT_CYAN("Blocks found: [" << m_total_blocks_found << "], no miners connected", LOG_LEVEL_0);
|
||
}
|
||
else
|
||
{
|
||
std::stringstream ss;
|
||
uint64_t total_reported_hr = 0, total_estimated_hr = 0;
|
||
for (auto& ph : m_protocol_handlers)
|
||
{
|
||
uint64_t reported_hr = 0, estimated_hr = 0;
|
||
ph.second->get_hashrate(reported_hr, estimated_hr);
|
||
total_reported_hr += reported_hr;
|
||
total_estimated_hr += estimated_hr;
|
||
ss << ph.second->get_context().m_worker_name << ": [" << ph.second->get_context().get_blocks_count() << "] " << HR_TO_STREAM_IN_MHS_1P(reported_hr) << " (" << HR_TO_STREAM_IN_MHS_1P(estimated_hr) << "), ";
|
||
}
|
||
auto s = ss.str();
|
||
LOG_PRINT_CYAN("Blocks found: [" << m_total_blocks_found << "], total speed: " << HR_TO_STREAM_IN_MHS_3P(total_reported_hr) << " Mh/s as reported by miners (" << HR_TO_STREAM_IN_MHS_3P(total_estimated_hr) << " Mh/s estimated by the server), current shares/min: " << m_shares_per_minute.get_speed() << ENDL <<
|
||
m_protocol_handlers.size() << " worker(s): " << s.substr(0, s.length() > 2 ? s.length() - 2 : 0), LOG_LEVEL_0);
|
||
}
|
||
|
||
m_last_ts_total_hr_was_printed = epee::misc_utils::get_tick_count();
|
||
}
|
||
|
||
void set_total_hr_print_interval_s(uint64_t s)
|
||
{
|
||
m_total_hr_print_interval_ms = s * 1000;
|
||
}
|
||
|
||
void set_block_template_update_period(uint64_t s)
|
||
{
|
||
m_block_template_update_pediod_ms = s * 1000;
|
||
}
|
||
|
||
size_t get_number_id_for_nameless_worker()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_generic_lock);
|
||
return m_nameless_worker_id++;
|
||
}
|
||
|
||
|
||
size_t m_max_packet_size;
|
||
|
||
private:
|
||
typedef std::unordered_map<boost::uuids::uuid, protocol_handler_t* , boost::hash<boost::uuids::uuid> > protocol_handlers_map;
|
||
typedef epee::math_helper::speed<60 * 1000 /* ms */> shares_per_minute_rate_t;
|
||
|
||
protocol_handlers_map m_protocol_handlers;
|
||
mutable epee::critical_section m_ph_map_lock;
|
||
mutable epee::critical_section m_work_change_lock;
|
||
mutable epee::critical_section m_generic_lock;
|
||
|
||
// job data
|
||
block m_block_template;
|
||
std::string m_block_template_hash_blob;
|
||
crypto::hash m_block_template_ethash;
|
||
crypto::hash m_blockchain_last_block_id;
|
||
uint64_t m_block_template_height;
|
||
std::atomic<uint64_t> m_block_template_update_ts;
|
||
|
||
// previous job (for handling stale shares)
|
||
crypto::hash m_prev_block_template_ethash;
|
||
|
||
vdiff_params_t m_vdiff_params;
|
||
wide_difficulty_type m_network_difficulty;
|
||
|
||
core* m_p_core;
|
||
account_public_address m_miner_addr; // all workers will share the same miner address
|
||
std::atomic<uint64_t> m_last_ts_total_hr_was_printed;
|
||
uint64_t m_total_hr_print_interval_ms;
|
||
uint64_t m_block_template_update_pediod_ms;
|
||
size_t m_nameless_worker_id;
|
||
size_t m_total_blocks_found;
|
||
shares_per_minute_rate_t m_shares_per_minute;
|
||
bool m_is_core_always_online;
|
||
|
||
std::atomic<bool> m_stop_flag;
|
||
std::thread m_blocktemplate_update_thread;
|
||
}; // struct stratum_protocol_handler_config
|
||
|
||
//==============================================================================================================================
|
||
|
||
template<typename connection_context_t>
|
||
class stratum_protocol_handler
|
||
{
|
||
public:
|
||
typedef stratum_protocol_handler<connection_context_t> this_t;
|
||
typedef epee::net_utils::connection<this_t> connection_t;
|
||
|
||
typedef connection_context_t connection_context; // required type for epee::net_utils::boosted_tcp_server concept
|
||
typedef stratum_protocol_handler_config<connection_context_t> config_type; // required type for epee::net_utils::boosted_tcp_server concept
|
||
|
||
// required member for epee::net_utils::boosted_tcp_server concept
|
||
stratum_protocol_handler(connection_t* p_connection, config_type& config, connection_context_t& context)
|
||
: m_p_connection(p_connection)
|
||
, m_config(config)
|
||
, m_context(context)
|
||
, m_connection_initialized(false)
|
||
, m_last_reported_hashrate(0)
|
||
, m_use_standard_stratum(false)
|
||
{
|
||
LOG_PRINT_CC(m_context, "stratum_protocol_handler::ctor()", LOG_LEVEL_4);
|
||
}
|
||
|
||
~stratum_protocol_handler()
|
||
{
|
||
NESTED_TRY_ENTRY();
|
||
|
||
if (m_connection_initialized)
|
||
{
|
||
m_config.remove_protocol_handler(this);
|
||
m_connection_initialized = false;
|
||
}
|
||
|
||
LOG_PRINT_CC(m_context, "stratum_protocol_handler::dtor()", LOG_LEVEL_4);
|
||
|
||
NESTED_CATCH_ENTRY(__func__);
|
||
}
|
||
|
||
// required member for epee::net_utils::boosted_tcp_server concept
|
||
bool handle_recv(const void* p_data, size_t data_size)
|
||
{
|
||
const char* str = static_cast<const char*>(p_data);
|
||
|
||
if (!m_connection_initialized)
|
||
return false;
|
||
|
||
if (data_size > m_config.m_max_packet_size)
|
||
{
|
||
LP_CC_WORKER_RED(m_context, "Maximum packet size exceeded! maximum: " << m_config.m_max_packet_size << ", received: " << data_size << ". Connection will be closed.", LOG_LEVEL_0);
|
||
return false;
|
||
}
|
||
|
||
m_json_helper.feed(str, data_size);
|
||
LP_CC_WORKER(m_context, "DATA received <<<<<<<<<<<<< " << data_size << " bytes:" << ENDL << std::string(str, data_size), LOG_LEVEL_4);
|
||
|
||
if (m_json_helper.has_objects())
|
||
{
|
||
std::string json;
|
||
while (m_json_helper.pop_object(json))
|
||
{
|
||
epee::serialization::portable_storage ps;
|
||
if (ps.load_from_json(json))
|
||
{
|
||
if (!handle_json_request(ps, json))
|
||
{
|
||
LP_CC_WORKER_RED(m_context, "JSON request handling failed. JSON:" << ENDL << json << ENDL, LOG_LEVEL_1);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LP_CC_WORKER_RED(m_context, "JSON object detected, but can't be parsed. JSON:" << ENDL << json << ENDL, LOG_LEVEL_1);
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool handle_json_request(epee::serialization::portable_storage& ps, const std::string& json)
|
||
{
|
||
std::stringstream error_stream;
|
||
jsonrpc_id_t id = jsonrpc_id_null;
|
||
read_jsonrpc_id(ps, id);
|
||
|
||
std::string method;
|
||
if (ps_get_value_noexcept(ps, "method", method, nullptr))
|
||
{
|
||
epee::serialization::portable_storage::hsection params_section = ps.open_section("params", nullptr);
|
||
auto handler_it = m_methods_handlers.find(method);
|
||
if (handler_it == m_methods_handlers.end())
|
||
{
|
||
error_stream << "unknown method is requested: " << method << ENDL << "JSON request: " << json;
|
||
LP_CC_WORKER_RED(m_context, error_stream.str(), LOG_LEVEL_1);
|
||
send_response_error(id, JSONRPC_ERROR_CODE_METHOD_NOT_FOUND, error_stream.str());
|
||
return false;
|
||
}
|
||
|
||
return (this->*(handler_it->second))(id, ps, params_section);
|
||
}
|
||
|
||
// if it's no a method call -- it should be result
|
||
std::string result;
|
||
if (ps_get_value_noexcept(ps, "result", result, nullptr))
|
||
{
|
||
std::string error;
|
||
ps_get_value_noexcept(ps, "error", error, nullptr);
|
||
epee::serialization::portable_storage::hsection error_section = ps.open_section("error", nullptr);
|
||
if (error_section != nullptr || !error.empty())
|
||
{
|
||
LP_CC_WORKER_RED(m_context, "received an error: " << json, LOG_LEVEL_1);
|
||
return true; // means it is handled ok
|
||
}
|
||
}
|
||
|
||
LP_CC_WORKER_YELLOW(m_context, "Unrecongized request received: " << json, LOG_LEVEL_2);
|
||
return true; // means it handled ok
|
||
}
|
||
|
||
static void init()
|
||
{
|
||
if (m_methods_handlers.empty())
|
||
{
|
||
m_methods_handlers.insert(std::make_pair("eth_submitLogin", &this_t::handle_method_eth_submitLogin));
|
||
m_methods_handlers.insert(std::make_pair("eth_getWork", &this_t::handle_method_eth_getWork));
|
||
m_methods_handlers.insert(std::make_pair("eth_submitHashrate", &this_t::handle_method_eth_submitHashrate));
|
||
m_methods_handlers.insert(std::make_pair("eth_submitWork", &this_t::handle_method_eth_submitWork));
|
||
// Standard ETH stratum (mining.*) — used by XMRig-based miners
|
||
m_methods_handlers.insert(std::make_pair("mining.subscribe", &this_t::handle_method_mining_subscribe));
|
||
m_methods_handlers.insert(std::make_pair("mining.authorize", &this_t::handle_method_mining_authorize));
|
||
m_methods_handlers.insert(std::make_pair("mining.submit", &this_t::handle_method_mining_submit));
|
||
m_methods_handlers.insert(std::make_pair("mining.extranonce.subscribe", &this_t::handle_method_mining_extranonce_subscribe));
|
||
}
|
||
}
|
||
|
||
bool handle_method_eth_submitLogin(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
std::string user_str, pass_str;
|
||
epee::serialization::harray params_array = ps.get_first_value("params", user_str, nullptr);
|
||
if (params_array != nullptr)
|
||
ps.get_next_value(params_array, pass_str);
|
||
|
||
std::string worker_str;
|
||
ps_get_value_noexcept(ps, "worker", worker_str, nullptr);
|
||
if (worker_str.empty())
|
||
worker_str = std::to_string(m_config.get_number_id_for_nameless_worker());
|
||
|
||
uint64_t start_difficulty = 0; // default
|
||
size_t start_diff_delim_pos = user_str.find('-');
|
||
if (start_diff_delim_pos != std::string::npos)
|
||
{
|
||
// extract start difficulty from username presuming it complies format "username.difficulty"
|
||
std::string start_difficulty_str = user_str.substr(start_diff_delim_pos + 1);
|
||
TRY_ENTRY()
|
||
start_difficulty = std::stoull(start_difficulty_str);
|
||
CATCH_ENTRY_CUSTOM("submitLogin", { LOG_PRINT_L0(worker_str << ": Can't parse start difficulty from " << start_difficulty_str); }, false);
|
||
user_str = user_str.substr(0, start_diff_delim_pos);
|
||
}
|
||
|
||
LOG_PRINT_CC(m_context, "Stratum [submitLogin] USER: " << user_str << ", pass: " << pass_str << ", worker: " << worker_str << ", start diff.: " << (start_difficulty == 0 ? std::string("default") : std::to_string(start_difficulty)), LOG_LEVEL_3);
|
||
return m_config.handle_login(this, id, user_str, pass_str, worker_str, start_difficulty);
|
||
}
|
||
|
||
bool handle_method_eth_getWork(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
m_config.update_work(this);
|
||
|
||
CRITICAL_REGION_LOCAL(m_work_change_lock);
|
||
if (!m_cached_work_json.empty())
|
||
send_response(id, m_cached_work_json);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool handle_method_eth_submitHashrate(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
std::string rate_str, rate_submit_id_str;
|
||
epee::serialization::harray params_array = ps.get_first_value("params", rate_str, nullptr);
|
||
bool r = params_array != nullptr && ps.get_next_value(params_array, rate_submit_id_str);
|
||
CHECK_AND_ASSERT_MES(r, false, "Incorrect parameters");
|
||
|
||
struct { uint64_t low, high, higher, highest; } rate_256 = { 0 }; // this will allow any length of data from 64 to 256 bits (helpful for odd miners)
|
||
CHECK_AND_ASSERT_MES(pod_from_net_format_reverse(rate_str, rate_256, true), false, "Can't parse rate from " << rate_str);
|
||
CHECK_AND_ASSERT_MES(rate_256.high == 0 && rate_256.higher == 0 && rate_256.highest == 0, false, "rate overflow, rate str: " << rate_str);
|
||
crypto::hash rate_submit_id = null_hash;
|
||
CHECK_AND_ASSERT_MES(pod_from_net_format(rate_submit_id_str, rate_submit_id), false, "Can't parse rate_submit_id from " << rate_submit_id_str);
|
||
|
||
m_last_reported_hashrate = rate_256.low;
|
||
return m_config.handle_submit_hashrate(this, rate_256.low, rate_submit_id);
|
||
}
|
||
|
||
bool handle_method_eth_submitWork(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
bool r = true;
|
||
std::string nonce_str, header_str, mixhash_str;
|
||
epee::serialization::harray params_array = ps.get_first_value("params", nonce_str, nullptr);
|
||
r = params_array != nullptr && ps.get_next_value(params_array, header_str);
|
||
r = params_array != nullptr && ps.get_next_value(params_array, mixhash_str);
|
||
CHECK_AND_ASSERT_MES(r, false, "Incorrect parameters");
|
||
|
||
std::string worker;
|
||
ps_get_value_noexcept(ps, "worker", worker, nullptr);
|
||
|
||
uint64_t nonce = 0;
|
||
CHECK_AND_ASSERT_MES(pod_from_net_format_reverse(nonce_str, nonce, true), false, "Can't parse nonce from " << nonce_str);
|
||
crypto::hash header_hash = null_hash;
|
||
CHECK_AND_ASSERT_MES(pod_from_net_format(header_str, header_hash), false, "Can't parse header hash from " << header_str);
|
||
|
||
return m_config.handle_work(this, id, worker, nonce, header_hash);
|
||
}
|
||
|
||
// --- Standard ETH stratum (mining.*) handlers ---
|
||
|
||
bool handle_method_mining_subscribe(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
m_use_standard_stratum = true;
|
||
// Respond with: [[["mining.notify", "subscription_id"]], "extranonce"]
|
||
// Use "0000" as a default 2-byte extra nonce
|
||
send_response(id, R"("result":[["mining.notify","1"],"0000"])");
|
||
return true;
|
||
}
|
||
|
||
bool handle_method_mining_authorize(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
// params: [user, pass] — same format as eth_submitLogin
|
||
std::string user_str, pass_str;
|
||
epee::serialization::harray params_array = ps.get_first_value("params", user_str, nullptr);
|
||
if (params_array != nullptr)
|
||
ps.get_next_value(params_array, pass_str);
|
||
|
||
std::string worker_str = std::to_string(m_config.get_number_id_for_nameless_worker());
|
||
|
||
LOG_PRINT_CC(m_context, "Stratum [mining.authorize] USER: " << user_str << ", pass: " << pass_str << ", worker: " << worker_str, LOG_LEVEL_3);
|
||
return m_config.handle_login(this, id, user_str, pass_str, worker_str, 0);
|
||
}
|
||
|
||
bool handle_method_mining_submit(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
// params: [worker, job_id, nonce, header_hash, mix_hash]
|
||
std::string worker_str, job_id_str, nonce_str, header_str, mixhash_str;
|
||
epee::serialization::harray params_array = ps.get_first_value("params", worker_str, nullptr);
|
||
bool r = params_array != nullptr && ps.get_next_value(params_array, job_id_str);
|
||
r = r && ps.get_next_value(params_array, nonce_str);
|
||
r = r && ps.get_next_value(params_array, header_str);
|
||
r = r && ps.get_next_value(params_array, mixhash_str);
|
||
CHECK_AND_ASSERT_MES(r, false, "Incorrect parameters for mining.submit");
|
||
|
||
uint64_t nonce = 0;
|
||
CHECK_AND_ASSERT_MES(pod_from_net_format_reverse(nonce_str, nonce, true), false, "Can't parse nonce from " << nonce_str);
|
||
crypto::hash header_hash = null_hash;
|
||
CHECK_AND_ASSERT_MES(pod_from_net_format(header_str, header_hash), false, "Can't parse header hash from " << header_str);
|
||
|
||
return m_config.handle_work(this, id, worker_str, nonce, header_hash);
|
||
}
|
||
|
||
bool handle_method_mining_extranonce_subscribe(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section)
|
||
{
|
||
send_response(id, R"("result":true)");
|
||
return true;
|
||
}
|
||
|
||
// ---
|
||
|
||
void send(const std::string& data)
|
||
{
|
||
static_cast<epee::net_utils::i_service_endpoint*>(m_p_connection)->do_send(data.c_str(), data.size());
|
||
LOG_PRINT_CC(m_context, "DATA sent >>>>>>>>>>>>> " << ENDL << data, LOG_LEVEL_4);
|
||
}
|
||
|
||
void send_notification(const std::string& json)
|
||
{
|
||
if (m_use_standard_stratum)
|
||
{
|
||
// Convert EthProxy "result":["header","seed","target","height"] to
|
||
// standard stratum mining.notify format: "method":"mining.notify","params":["job_id","header","seed","target",true,"height"]
|
||
// json looks like: "result":["0xHEADER","0xSEED","0xTARGET","0xHEIGHT"]
|
||
// We need: "method":"mining.notify","params":["job_id","0xHEADER","0xSEED","0xTARGET",true,"0xHEIGHT"]
|
||
std::string notify_json = json;
|
||
size_t pos = notify_json.find(R"("result":)");
|
||
if (pos != std::string::npos)
|
||
{
|
||
// Extract the array content from "result":[...]
|
||
size_t arr_start = notify_json.find('[', pos);
|
||
size_t arr_end = notify_json.rfind(']');
|
||
if (arr_start != std::string::npos && arr_end != std::string::npos)
|
||
{
|
||
std::string arr_content = notify_json.substr(arr_start + 1, arr_end - arr_start - 1);
|
||
// Insert job ID at beginning and clean_jobs=true before height
|
||
// arr_content is: "0xHEADER","0xSEED","0xTARGET","0xHEIGHT"
|
||
// We need: "job_id","0xHEADER","0xSEED","0xTARGET",true,"0xHEIGHT"
|
||
static uint64_t s_job_counter = 0;
|
||
std::string job_id = "\"" + std::to_string(++s_job_counter) + "\"";
|
||
|
||
// Find the last comma to insert clean_jobs before height
|
||
size_t last_comma = arr_content.rfind(',');
|
||
if (last_comma != std::string::npos)
|
||
{
|
||
std::string params = job_id + "," + arr_content.substr(0, last_comma) + ",true" + arr_content.substr(last_comma);
|
||
send(R"({"jsonrpc":"2.0","method":"mining.notify","params":[)" + params + "]}\n");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// EthProxy mode (default): send as-is
|
||
// JSON-RPC 2.0 spec: "A Notification is a Request object without an "id" member."
|
||
send(R"({"jsonrpc":"2.0",)" + json + "}" "\n"); // LF character is not specified by JSON-RPC standard, but it is REQUIRED by ethminer 0.12 to work
|
||
}
|
||
|
||
void send_response_method(const jsonrpc_id_t& id, const std::string& method, const std::string& response)
|
||
{
|
||
send_response(id, std::string(R"("method":")") + method + R"(",)" + response);
|
||
}
|
||
|
||
void send_response(const jsonrpc_id_t& id, const std::string& response)
|
||
{
|
||
send(R"({"jsonrpc":"2.0","id":)" + jsonrpc_id_to_value_str(id) + "," + response + "}" "\n"); // LF character is not specified by JSON-RPC standard, but it is REQUIRED by ethminer 0.12 to work
|
||
}
|
||
|
||
void send_response_default(const jsonrpc_id_t& id)
|
||
{
|
||
send_response(id, R"("result":true)");
|
||
}
|
||
|
||
void send_response_error(const jsonrpc_id_t& id, int64_t error_code, const std::string& error_message)
|
||
{
|
||
send_response(id, R"("error":{"code":)" + std::to_string(error_code) + R"(,"message":")" + error_message + R"("})");
|
||
}
|
||
|
||
void set_work(const std::string& json)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_work_change_lock);
|
||
if (m_cached_work_json != json)
|
||
{
|
||
LP_CC_WORKER(m_context, "work updated: " << json, LOG_LEVEL_2);
|
||
}
|
||
m_cached_work_json = json;
|
||
}
|
||
|
||
// required member for epee::net_utils::boosted_tcp_server concept
|
||
void handle_qued_callback()
|
||
{
|
||
}
|
||
|
||
// required member for epee::net_utils::boosted_tcp_server concept
|
||
bool after_init_connection()
|
||
{
|
||
LOG_PRINT_CC(m_context, "stratum_protocol_handler::after_init_connection()", LOG_LEVEL_4);
|
||
if (!m_connection_initialized)
|
||
{
|
||
m_connection_initialized = true;
|
||
m_config.add_protocol_handler(this);
|
||
m_context.init_and_start_timers(m_config.get_vdiff_params());
|
||
}
|
||
LP_CC_WORKER_CYAN(m_context, "connected", LOG_LEVEL_1);
|
||
return true;
|
||
}
|
||
|
||
// required member for epee::net_utils::boosted_tcp_server concept
|
||
bool release_protocol()
|
||
{
|
||
LOG_PRINT_CC(m_context, "stratum_protocol_handler::release_protocol()", LOG_LEVEL_4);
|
||
LP_CC_WORKER(m_context, "disconnected", LOG_LEVEL_0);
|
||
return true;
|
||
}
|
||
|
||
connection_context_t& get_context() { return m_context; }
|
||
const connection_context_t& get_context() const { return m_context; }
|
||
|
||
void get_hashrate(uint64_t& last_reported_hr, uint64_t& estimated_hr)
|
||
{
|
||
last_reported_hr = m_last_reported_hashrate;
|
||
estimated_hr = m_context.estimate_worker_hashrate();
|
||
}
|
||
|
||
|
||
private:
|
||
connection_t* m_p_connection; // m_p_connection owns this protocol handler as data member, so back link is a simple pointer
|
||
config_type& m_config;
|
||
connection_context_t& m_context;
|
||
|
||
json_helper m_json_helper;
|
||
std::string m_cached_work_json;
|
||
|
||
epee::critical_section m_work_change_lock;
|
||
uint64_t m_last_reported_hashrate;
|
||
bool m_use_standard_stratum; // true = mining.* protocol, false = eth_* EthProxy protocol
|
||
|
||
typedef bool (this_t::*method_handler_func_t)(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section);
|
||
static std::unordered_map<std::string, method_handler_func_t> m_methods_handlers;
|
||
|
||
std::atomic<bool> m_connection_initialized;
|
||
}; // class stratum_protocol_handler
|
||
//==============================================================================================================================
|
||
typedef stratum_protocol_handler<stratum_connection_context> protocol_handler_t;
|
||
typedef epee::net_utils::boosted_tcp_server<protocol_handler_t> tcp_server_t;
|
||
|
||
// static memeber definition
|
||
template<typename connection_context_t>
|
||
std::unordered_map<std::string, typename stratum_protocol_handler<connection_context_t>::method_handler_func_t> stratum_protocol_handler<connection_context_t>::m_methods_handlers;
|
||
} // anonumous namespace
|
||
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
struct stratum_server_impl
|
||
{
|
||
tcp_server_t server;
|
||
};
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
stratum_server::stratum_server(core* c)
|
||
: m_p_core(c)
|
||
, m_threads_count(0)
|
||
{
|
||
m_impl = new stratum_server_impl();
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
stratum_server::~stratum_server()
|
||
{
|
||
delete m_impl;
|
||
m_impl = nullptr;
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
void stratum_server::init_options(boost::program_options::options_description& desc)
|
||
{
|
||
stratum_protocol_handler<stratum_connection_context>::init();
|
||
|
||
command_line::add_arg(desc, arg_stratum);
|
||
command_line::add_arg(desc, arg_stratum_bind_ip);
|
||
command_line::add_arg(desc, arg_stratum_bind_port);
|
||
command_line::add_arg(desc, arg_stratum_threads);
|
||
command_line::add_arg(desc, arg_stratum_miner_address);
|
||
command_line::add_arg(desc, arg_stratum_vdiff_target_min);
|
||
command_line::add_arg(desc, arg_stratum_vdiff_target_max);
|
||
command_line::add_arg(desc, arg_stratum_vdiff_target_time);
|
||
command_line::add_arg(desc, arg_stratum_vdiff_retarget_time);
|
||
command_line::add_arg(desc, arg_stratum_vdiff_retarget_shares);
|
||
command_line::add_arg(desc, arg_stratum_vdiff_variance_percent);
|
||
command_line::add_arg(desc, arg_stratum_block_template_update_period);
|
||
command_line::add_arg(desc, arg_stratum_hr_print_interval);
|
||
command_line::add_arg(desc, arg_stratum_always_online);
|
||
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
bool stratum_server::should_start(const boost::program_options::variables_map& vm)
|
||
{
|
||
return command_line::get_arg(vm, arg_stratum);
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
bool stratum_server::init(const boost::program_options::variables_map& vm)
|
||
{
|
||
bool r = false;
|
||
m_impl->server.set_threads_prefix("ST");
|
||
|
||
std::string bind_ip_str = command_line::get_arg(vm, arg_stratum_bind_ip);
|
||
std::string bind_port_str = command_line::get_arg(vm, arg_stratum_bind_port);
|
||
m_threads_count = command_line::get_arg(vm, arg_stratum_threads);
|
||
|
||
auto& config = m_impl->server.get_config_object();
|
||
config.set_core(m_p_core);
|
||
|
||
if (command_line::has_arg(vm, arg_stratum_always_online))
|
||
{
|
||
config.set_is_core_always_online(command_line::get_arg(vm, arg_stratum_always_online));
|
||
}
|
||
|
||
|
||
if (command_line::has_arg(vm, arg_stratum_miner_address))
|
||
{
|
||
std::string miner_address_str = command_line::get_arg(vm, arg_stratum_miner_address);
|
||
account_public_address miner_address = null_pub_addr;
|
||
r = get_account_address_from_str(miner_address, miner_address_str);
|
||
CHECK_AND_ASSERT_MES(r, false, "Stratum server: invalid miner address given: " << miner_address_str);
|
||
config.set_miner_address(miner_address);
|
||
}
|
||
|
||
config.set_vdiff_params(
|
||
vdiff_params_t(
|
||
command_line::get_arg(vm, arg_stratum_vdiff_target_min),
|
||
command_line::get_arg(vm, arg_stratum_vdiff_target_max),
|
||
command_line::get_arg(vm, arg_stratum_vdiff_target_time),
|
||
command_line::get_arg(vm, arg_stratum_vdiff_retarget_time),
|
||
command_line::get_arg(vm, arg_stratum_vdiff_retarget_shares),
|
||
command_line::get_arg(vm, arg_stratum_vdiff_variance_percent)
|
||
)
|
||
);
|
||
|
||
config.set_block_template_update_period(command_line::get_arg(vm, arg_stratum_block_template_update_period));
|
||
|
||
config.set_total_hr_print_interval_s(command_line::get_arg(vm, arg_stratum_hr_print_interval));
|
||
|
||
LOG_PRINT_L0("Stratum server: start listening at " << bind_ip_str << ":" << bind_port_str << "...");
|
||
r = m_impl->server.init_server(bind_port_str, bind_ip_str);
|
||
CHECK_AND_ASSERT_MES(r, false, "Stratum server: initialization failure");
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
bool stratum_server::run(bool wait /* = true */)
|
||
{
|
||
//m_impl->server.get_config_object().test();
|
||
|
||
LOG_PRINT("Stratum server: start net server with " << m_threads_count << " threads...", LOG_LEVEL_0);
|
||
if (!m_impl->server.run_server(m_threads_count, wait))
|
||
{
|
||
LOG_ERROR("Stratum server: net server failure");
|
||
return false;
|
||
}
|
||
|
||
if (wait)
|
||
LOG_PRINT("Stratum server: net server stopped", LOG_LEVEL_0);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
bool stratum_server::deinit()
|
||
{
|
||
return m_impl->server.deinit_server();
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
bool stratum_server::timed_wait_server_stop(uint64_t ms)
|
||
{
|
||
return m_impl->server.timed_wait_server_stop(ms);
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
bool stratum_server::send_stop_signal()
|
||
{
|
||
m_impl->server.send_stop_signal();
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
} // namespace currency
|