1911 lines
69 KiB
C++
1911 lines
69 KiB
C++
#include <progminer/buildinfo.h>
|
|
#include <libdevcore/Log.h>
|
|
#include <ethash/ethash.hpp>
|
|
|
|
#include <boost/bind/bind.hpp>
|
|
|
|
#include "EthStratumClient.h"
|
|
|
|
#ifdef _WIN32
|
|
// Needed for certificates validation on TLS connections
|
|
#include <wincrypt.h>
|
|
#endif
|
|
|
|
using boost::asio::ip::tcp;
|
|
|
|
EthStratumClient::EthStratumClient(int worktimeout, int responsetimeout)
|
|
: PoolClient(),
|
|
m_worktimeout(worktimeout),
|
|
m_responsetimeout(responsetimeout),
|
|
m_io_service(g_io_service),
|
|
m_io_strand(g_io_service),
|
|
m_socket(nullptr),
|
|
m_workloop_timer(g_io_service),
|
|
m_response_plea_times(64),
|
|
m_txQueue(64),
|
|
m_resolver(g_io_service),
|
|
m_endpoints()
|
|
{
|
|
m_jSwBuilder.settings_["indentation"] = "";
|
|
|
|
// Initialize workloop_timer to infinite wait
|
|
m_workloop_timer.expires_at(boost::posix_time::pos_infin);
|
|
m_workloop_timer.async_wait(m_io_strand.wrap(boost::bind(
|
|
&EthStratumClient::workloop_timer_elapsed, this, boost::asio::placeholders::error)));
|
|
clear_response_pleas();
|
|
}
|
|
|
|
|
|
void EthStratumClient::init_socket()
|
|
{
|
|
// Prepare Socket
|
|
if (m_conn->SecLevel() != SecureLevel::NONE)
|
|
{
|
|
boost::asio::ssl::context::method method = boost::asio::ssl::context::tls_client;
|
|
if (m_conn->SecLevel() == SecureLevel::TLS12)
|
|
method = boost::asio::ssl::context::tlsv12;
|
|
|
|
boost::asio::ssl::context ctx(method);
|
|
m_securesocket = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>(
|
|
m_io_service, ctx);
|
|
m_socket = &m_securesocket->next_layer();
|
|
|
|
if (getenv("SSL_NOVERIFY"))
|
|
{
|
|
m_securesocket->set_verify_mode(boost::asio::ssl::verify_none);
|
|
}
|
|
else
|
|
{
|
|
m_securesocket->set_verify_mode(boost::asio::ssl::verify_peer);
|
|
m_securesocket->set_verify_callback(
|
|
make_verbose_verification(boost::asio::ssl::rfc2818_verification(m_conn->Host())));
|
|
}
|
|
#ifdef _WIN32
|
|
HCERTSTORE hStore = CertOpenSystemStore(0, "ROOT");
|
|
if (hStore == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
X509_STORE* store = X509_STORE_new();
|
|
PCCERT_CONTEXT pContext = nullptr;
|
|
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr)
|
|
{
|
|
X509* x509 = d2i_X509(
|
|
nullptr, (const unsigned char**)&pContext->pbCertEncoded, pContext->cbCertEncoded);
|
|
if (x509 != nullptr)
|
|
{
|
|
X509_STORE_add_cert(store, x509);
|
|
X509_free(x509);
|
|
}
|
|
}
|
|
|
|
CertFreeCertificateContext(pContext);
|
|
CertCloseStore(hStore, 0);
|
|
|
|
SSL_CTX_set_cert_store(ctx.native_handle(), store);
|
|
#else
|
|
char* certPath = getenv("SSL_CERT_FILE");
|
|
try
|
|
{
|
|
ctx.load_verify_file(certPath ? certPath : "/etc/ssl/certs/ca-certificates.crt");
|
|
}
|
|
catch (...)
|
|
{
|
|
cwarn << "Failed to load ca certificates. Either the file "
|
|
"'/etc/ssl/certs/ca-certificates.crt' does not exist";
|
|
cwarn << "or the environment variable SSL_CERT_FILE is set to an invalid or "
|
|
"inaccessible file.";
|
|
cwarn << "It is possible that certificate verification can fail.";
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
m_nonsecuresocket = std::make_shared<boost::asio::ip::tcp::socket>(m_io_service);
|
|
m_socket = m_nonsecuresocket.get();
|
|
}
|
|
|
|
// Activate keep alive to detect disconnects
|
|
unsigned int keepAlive = 10000;
|
|
|
|
#if defined(_WIN32)
|
|
int32_t timeout = keepAlive;
|
|
setsockopt(
|
|
m_socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
|
|
setsockopt(
|
|
m_socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
|
|
#else
|
|
timeval tv{
|
|
static_cast<suseconds_t>(keepAlive / 1000), static_cast<suseconds_t>(keepAlive % 1000)};
|
|
setsockopt(m_socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
setsockopt(m_socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
|
#endif
|
|
}
|
|
|
|
void EthStratumClient::connect()
|
|
{
|
|
// Prevent unnecessary and potentially dangerous recursion
|
|
if (m_connecting.load(std::memory_order::memory_order_relaxed))
|
|
return;
|
|
|
|
// Start timing operations
|
|
m_workloop_timer.expires_from_now(boost::posix_time::milliseconds(m_workloop_interval));
|
|
m_workloop_timer.async_wait(m_io_strand.wrap(boost::bind(
|
|
&EthStratumClient::workloop_timer_elapsed, this, boost::asio::placeholders::error)));
|
|
|
|
// Reset status flags
|
|
m_authpending.store(false, std::memory_order_relaxed);
|
|
|
|
// Initializes socket and eventually secure stream
|
|
if (!m_socket)
|
|
init_socket();
|
|
|
|
// Initialize a new queue of end points
|
|
m_endpoints = std::queue<boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>>();
|
|
m_endpoint = boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>();
|
|
|
|
if (m_conn->HostNameType() == dev::UriHostNameType::Dns ||
|
|
m_conn->HostNameType() == dev::UriHostNameType::Basic)
|
|
{
|
|
// Begin resolve all ips associated to hostname
|
|
// calling the resolver each time is useful as most
|
|
// load balancer will give Ips in different order
|
|
m_resolver = tcp::resolver(m_io_service);
|
|
tcp::resolver::query q(m_conn->Host(), toString(m_conn->Port()));
|
|
|
|
// Start resolving async
|
|
m_resolver.async_resolve(
|
|
q, m_io_strand.wrap(boost::bind(&EthStratumClient::resolve_handler, this,
|
|
boost::asio::placeholders::error, boost::asio::placeholders::iterator)));
|
|
}
|
|
else
|
|
{
|
|
// No need to use the resolver if host is already an IP address
|
|
m_endpoints.push(boost::asio::ip::tcp::endpoint(
|
|
boost::asio::ip::address::from_string(m_conn->Host()), m_conn->Port()));
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::start_connect, this)));
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::disconnect()
|
|
{
|
|
// Prevent unnecessary recursion
|
|
bool ex = false;
|
|
if (!m_disconnecting.compare_exchange_weak(ex, true, memory_order_relaxed))
|
|
return;
|
|
|
|
m_connected.store(false, memory_order_relaxed);
|
|
|
|
// Cancel any outstanding async operation
|
|
if (m_socket)
|
|
m_socket->cancel();
|
|
|
|
if (m_socket && m_socket->is_open())
|
|
{
|
|
try
|
|
{
|
|
boost::system::error_code sec;
|
|
|
|
if (m_conn->SecLevel() != SecureLevel::NONE)
|
|
{
|
|
// This will initiate the exchange of "close_notify" message among parties.
|
|
// If both client and server are connected then we expect the handler with success
|
|
// As there may be a connection issue we also endorse a timeout
|
|
m_securesocket->async_shutdown(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::onSSLShutdownCompleted, this,
|
|
boost::asio::placeholders::error)));
|
|
enqueue_response_plea();
|
|
|
|
|
|
// Rest of disconnection is performed asynchronously
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_nonsecuresocket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, sec);
|
|
m_socket->close();
|
|
}
|
|
}
|
|
catch (std::exception const& _e)
|
|
{
|
|
cwarn << "Error while disconnecting:" << _e.what();
|
|
}
|
|
}
|
|
|
|
disconnect_finalize();
|
|
}
|
|
|
|
void EthStratumClient::disconnect_finalize()
|
|
{
|
|
if (m_securesocket && m_securesocket->lowest_layer().is_open())
|
|
{
|
|
// Manage error code if layer is already shut down
|
|
boost::system::error_code ec;
|
|
m_securesocket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
|
|
m_securesocket->lowest_layer().close();
|
|
}
|
|
m_socket = nullptr;
|
|
m_nonsecuresocket = nullptr;
|
|
|
|
// Release locking flag and set connection status
|
|
#ifdef DEV_BUILD
|
|
if (g_logOptions & LOG_CONNECT)
|
|
cnote << "Socket disconnected from " << ActiveEndPoint();
|
|
#endif
|
|
|
|
// Release session if exits
|
|
if (m_session)
|
|
m_conn->addDuration(m_session->duration());
|
|
m_session = nullptr;
|
|
|
|
m_authpending.store(false, std::memory_order_relaxed);
|
|
m_disconnecting.store(false, std::memory_order_relaxed);
|
|
m_txPending.store(false, std::memory_order_relaxed);
|
|
|
|
if (!m_conn->IsUnrecoverable())
|
|
{
|
|
// If we got disconnected during autodetection phase
|
|
// reissue a connect lowering stratum mode checks
|
|
// m_canconnect flag is used to prevent never-ending loop when
|
|
// remote endpoint rejects connections attempts persistently since the first
|
|
if (!m_conn->StratumModeConfirmed() && m_conn->Responds())
|
|
{
|
|
// Repost a new connection attempt and advance to next stratum test
|
|
if (m_conn->StratumMode() > 0)
|
|
{
|
|
m_conn->SetStratumMode(m_conn->StratumMode() - 1);
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::start_connect, this)));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// There are no more stratum modes to test
|
|
// Mark connection as unrecoverable and trash it
|
|
m_conn->MarkUnrecoverable();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear plea queue and stop timing
|
|
clear_response_pleas();
|
|
m_solution_submitted_max_id = 0;
|
|
|
|
// Put the actor back to sleep
|
|
m_workloop_timer.expires_at(boost::posix_time::pos_infin);
|
|
m_workloop_timer.async_wait(m_io_strand.wrap(boost::bind(
|
|
&EthStratumClient::workloop_timer_elapsed, this, boost::asio::placeholders::error)));
|
|
|
|
// Trigger handlers
|
|
if (m_onDisconnected)
|
|
m_onDisconnected();
|
|
}
|
|
|
|
void EthStratumClient::resolve_handler(
|
|
const boost::system::error_code& ec, tcp::resolver::iterator i)
|
|
{
|
|
if (!ec)
|
|
{
|
|
while (i != tcp::resolver::iterator())
|
|
{
|
|
m_endpoints.push(i->endpoint());
|
|
i++;
|
|
}
|
|
m_resolver.cancel();
|
|
|
|
// Resolver has finished so invoke connection asynchronously
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::start_connect, this)));
|
|
}
|
|
else
|
|
{
|
|
cwarn << "Could not resolve host " << m_conn->Host() << ", " << ec.message();
|
|
|
|
// Release locking flag and set connection status
|
|
m_connecting.store(false, std::memory_order_relaxed);
|
|
|
|
// We "simulate" a disconnect, to ensure a fully shutdown state
|
|
disconnect_finalize();
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::start_connect()
|
|
{
|
|
if (m_connecting.load(std::memory_order_relaxed))
|
|
return;
|
|
m_connecting.store(true, std::memory_order::memory_order_relaxed);
|
|
|
|
if (!m_endpoints.empty())
|
|
{
|
|
// Pick the first endpoint in list.
|
|
// Eventually endpoints get discarded on connection errors
|
|
m_endpoint = m_endpoints.front();
|
|
|
|
// Re-init socket if we need to
|
|
if (m_socket == nullptr)
|
|
init_socket();
|
|
|
|
#ifdef DEV_BUILD
|
|
if (g_logOptions & LOG_CONNECT)
|
|
cnote << ("Trying " + toString(m_endpoint) + " ...");
|
|
#endif
|
|
|
|
clear_response_pleas();
|
|
m_connecting.store(true, std::memory_order::memory_order_relaxed);
|
|
enqueue_response_plea();
|
|
m_solution_submitted_max_id = 0;
|
|
|
|
// Start connecting async
|
|
if (m_conn->SecLevel() != SecureLevel::NONE)
|
|
{
|
|
m_securesocket->lowest_layer().async_connect(m_endpoint,
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::connect_handler, this, boost::placeholders::_1)));
|
|
}
|
|
else
|
|
{
|
|
m_socket->async_connect(m_endpoint,
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::connect_handler, this, boost::placeholders::_1)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_connecting.store(false, std::memory_order_relaxed);
|
|
cwarn << "No more IP addresses to try for host: " << m_conn->Host();
|
|
|
|
// We "simulate" a disconnect, to ensure a fully shutdown state
|
|
disconnect_finalize();
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::workloop_timer_elapsed(const boost::system::error_code& ec)
|
|
{
|
|
using namespace std::chrono;
|
|
|
|
// On timer cancelled or nothing to check for then early exit
|
|
if (ec == boost::asio::error::operation_aborted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// No msg from client (EthereumStratum/2.0.0)
|
|
if (m_conn->StratumMode() == 3 && m_session)
|
|
{
|
|
auto s = duration_cast<seconds>(steady_clock::now() - m_session->lastTxStamp).count();
|
|
if (s > ((int)m_session->timeout - 5))
|
|
{
|
|
// Send a message 5 seconds before expiration
|
|
Json::Value jReq;
|
|
jReq["id"] = unsigned(7);
|
|
jReq["method"] = "mining.noop";
|
|
send(jReq);
|
|
}
|
|
}
|
|
|
|
|
|
if (m_response_pleas_count.load(std::memory_order_relaxed))
|
|
{
|
|
milliseconds response_delay_ms(0);
|
|
steady_clock::time_point response_plea_time(
|
|
m_response_plea_older.load(std::memory_order_relaxed));
|
|
|
|
// Check responses while in connection/disconnection phase
|
|
if (isPendingState())
|
|
{
|
|
response_delay_ms =
|
|
duration_cast<milliseconds>(steady_clock::now() - response_plea_time);
|
|
|
|
if ((m_responsetimeout * 1000) >= response_delay_ms.count())
|
|
{
|
|
if (m_connecting.load(std::memory_order_relaxed))
|
|
{
|
|
// The socket is closed so that any outstanding
|
|
// asynchronous connection operations are cancelled.
|
|
m_socket->close();
|
|
return;
|
|
}
|
|
|
|
// This is set for SSL disconnection
|
|
if (m_disconnecting.load(std::memory_order_relaxed) &&
|
|
(m_conn->SecLevel() != SecureLevel::NONE))
|
|
{
|
|
if (m_securesocket->lowest_layer().is_open())
|
|
{
|
|
m_securesocket->lowest_layer().close();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check responses while connected
|
|
if (isConnected())
|
|
{
|
|
response_delay_ms =
|
|
duration_cast<milliseconds>(steady_clock::now() - response_plea_time);
|
|
|
|
// Delay timeout to a request
|
|
if (response_delay_ms.count() >= (m_responsetimeout * 1000))
|
|
{
|
|
if (!m_conn->StratumModeConfirmed() && !m_conn->IsUnrecoverable())
|
|
{
|
|
// Waiting for a response from pool to a login request
|
|
// Async self send a fake error response
|
|
Json::Value jRes;
|
|
jRes["id"] = unsigned(1);
|
|
jRes["result"] = Json::nullValue;
|
|
jRes["error"] = true;
|
|
clear_response_pleas();
|
|
m_io_service.post(m_io_strand.wrap(
|
|
boost::bind(&EthStratumClient::processResponse, this, jRes)));
|
|
}
|
|
else
|
|
{
|
|
// Waiting for a response to solution submission
|
|
cwarn << "No response received in " << m_responsetimeout << " seconds.";
|
|
m_endpoints.pop();
|
|
clear_response_pleas();
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
}
|
|
}
|
|
// No work timeout
|
|
else if (m_session &&
|
|
(duration_cast<seconds>(steady_clock::now() - m_current_timestamp).count() >
|
|
m_worktimeout))
|
|
{
|
|
cwarn << "No new work received in " << m_worktimeout << " seconds.";
|
|
m_endpoints.pop();
|
|
clear_response_pleas();
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Resubmit timing operations
|
|
m_workloop_timer.expires_from_now(boost::posix_time::milliseconds(m_workloop_interval));
|
|
m_workloop_timer.async_wait(m_io_strand.wrap(boost::bind(
|
|
&EthStratumClient::workloop_timer_elapsed, this, boost::asio::placeholders::error)));
|
|
}
|
|
|
|
void EthStratumClient::connect_handler(const boost::system::error_code& ec)
|
|
{
|
|
// Set status completion
|
|
m_connecting.store(false, std::memory_order_relaxed);
|
|
|
|
|
|
// Timeout has run before or we got error
|
|
if (ec || !m_socket->is_open())
|
|
{
|
|
cwarn << ("Error " + toString(m_endpoint) + " [ " + (ec ? ec.message() : "Timeout") +
|
|
" ]");
|
|
|
|
// We need to close the socket used in the previous connection attempt
|
|
// before starting a new one.
|
|
// In case of error, in fact, boost does not close the socket
|
|
// If socket is not opened it means we got timed out
|
|
if (m_socket->is_open())
|
|
m_socket->close();
|
|
|
|
// Discard this endpoint and try the next available.
|
|
// Eventually is start_connect which will check for an
|
|
// empty list.
|
|
m_endpoints.pop();
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::start_connect, this)));
|
|
|
|
return;
|
|
}
|
|
|
|
// We got a socket connection established
|
|
m_conn->Responds(true);
|
|
m_connected.store(true, memory_order_relaxed);
|
|
|
|
m_message.clear();
|
|
|
|
// Clear txqueue
|
|
m_txQueue.consume_all([](std::string* l) { delete l; });
|
|
|
|
#ifdef DEV_BUILD
|
|
if (g_logOptions & LOG_CONNECT)
|
|
cnote << "Socket connected to " << ActiveEndPoint();
|
|
#endif
|
|
|
|
if (m_conn->SecLevel() != SecureLevel::NONE)
|
|
{
|
|
boost::system::error_code hec;
|
|
m_securesocket->lowest_layer().set_option(boost::asio::socket_base::keep_alive(true));
|
|
m_securesocket->lowest_layer().set_option(tcp::no_delay(true));
|
|
|
|
m_securesocket->handshake(boost::asio::ssl::stream_base::client, hec);
|
|
|
|
if (hec)
|
|
{
|
|
cwarn << "SSL/TLS Handshake failed: " << hec.message();
|
|
if (hec.value() == 337047686)
|
|
{ // certificate verification failed
|
|
cwarn << "This can have multiple reasons:";
|
|
cwarn << "* Root certs are either not installed or not found";
|
|
cwarn << "* Pool uses a self-signed certificate";
|
|
cwarn << "* Pool hostname you're connecting to does not match the CN registered "
|
|
"for the certificate.";
|
|
cwarn << "Possible fixes:";
|
|
#ifndef _WIN32
|
|
cwarn << "* Make sure the file '/etc/ssl/certs/ca-certificates.crt' exists and "
|
|
"is accessible";
|
|
cwarn << "* Export the correct path via 'export "
|
|
"SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt' to the correct "
|
|
"file";
|
|
cwarn << " On most systems you can install the 'ca-certificates' package";
|
|
cwarn << " You can also get the latest file here: "
|
|
"https://curl.haxx.se/docs/caextract.html";
|
|
#endif
|
|
cwarn << "* Double check hostname in the -P argument.";
|
|
cwarn << "* Disable certificate verification all-together via environment "
|
|
"variable. See progminer --help for info about environment variables";
|
|
cwarn << "If you do the latter please be advised you might expose yourself to the "
|
|
"risk of seeing your shares stolen";
|
|
}
|
|
|
|
// This is a fatal error
|
|
// No need to try other IPs as the certificate is based on host-name
|
|
// not ip address. Trying other IPs would end up with the very same error.
|
|
m_conn->MarkUnrecoverable();
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_nonsecuresocket->set_option(boost::asio::socket_base::keep_alive(true));
|
|
m_nonsecuresocket->set_option(tcp::no_delay(true));
|
|
}
|
|
|
|
// Clean buffer from any previous stale data
|
|
m_sendBuffer.consume(m_sendBuffer.capacity());
|
|
clear_response_pleas();
|
|
|
|
/*
|
|
|
|
If connection has been set-up with a specific scheme then
|
|
set it's related stratum version as confirmed.
|
|
|
|
Otherwise let's go through an autodetection.
|
|
|
|
Autodetection process passes all known stratum modes.
|
|
- 1st pass EthStratumClient::ETHEREUMSTRATUM2 (3)
|
|
- 2nd pass EthStratumClient::ETHEREUMSTRATUM (2)
|
|
- 3rd pass EthStratumClient::ETHPROXY (1)
|
|
- 4th pass EthStratumClient::STRATUM (0)
|
|
*/
|
|
|
|
if (m_conn->Version() < 999)
|
|
{
|
|
m_conn->SetStratumMode(m_conn->Version(), true);
|
|
}
|
|
else
|
|
{
|
|
if (!m_conn->StratumModeConfirmed() && m_conn->StratumMode() == 999)
|
|
m_conn->SetStratumMode(3, false);
|
|
}
|
|
|
|
|
|
Json::Value jReq;
|
|
jReq["id"] = unsigned(1);
|
|
jReq["method"] = "mining.subscribe";
|
|
jReq["params"] = Json::Value(Json::arrayValue);
|
|
|
|
|
|
switch (m_conn->StratumMode())
|
|
{
|
|
case EthStratumClient::STRATUM:
|
|
|
|
jReq["jsonrpc"] = "2.0";
|
|
|
|
break;
|
|
|
|
case EthStratumClient::ETHPROXY:
|
|
|
|
jReq["method"] = "eth_submitLogin";
|
|
if (!m_conn->Workername().empty())
|
|
jReq["worker"] = m_conn->Workername();
|
|
jReq["params"].append(m_conn->User() + m_conn->Path());
|
|
if (!m_conn->Pass().empty())
|
|
jReq["params"].append(m_conn->Pass());
|
|
|
|
break;
|
|
|
|
case EthStratumClient::ETHEREUMSTRATUM:
|
|
|
|
jReq["params"].append(progminer_get_buildinfo()->project_name_with_version);
|
|
jReq["params"].append("EthereumStratum/1.0.0");
|
|
|
|
break;
|
|
|
|
case EthStratumClient::ETHEREUMSTRATUM2:
|
|
|
|
jReq["method"] = "mining.hello";
|
|
Json::Value jPrm;
|
|
jPrm["agent"] = progminer_get_buildinfo()->project_name_with_version;
|
|
jPrm["host"] = m_conn->Host();
|
|
jPrm["port"] = toCompactHex((uint32_t)m_conn->Port(), HexPrefix::DontAdd);
|
|
jPrm["proto"] = "EthereumStratum/2.0.0";
|
|
jReq["params"] = jPrm;
|
|
|
|
break;
|
|
}
|
|
|
|
// Begin receive data
|
|
recvSocketData();
|
|
|
|
/*
|
|
Send first message
|
|
NOTE !!
|
|
It's been tested that f2pool.com does not respond with json error to wrong
|
|
access message (which is needed to autodetect stratum mode).
|
|
IT DOES NOT RESPOND AT ALL !!
|
|
Due to this we need to set a timeout (arbitrary set to 1 second) and
|
|
if no response within that time consider the tentative login failed
|
|
and switch to next stratum mode test
|
|
*/
|
|
enqueue_response_plea();
|
|
send(jReq);
|
|
}
|
|
|
|
void EthStratumClient::startSession()
|
|
{
|
|
// Start a new session of data
|
|
m_session = unique_ptr<Session>(new Session());
|
|
m_current_timestamp = std::chrono::steady_clock::now();
|
|
|
|
// Invoke higher level handlers
|
|
if (m_onConnected)
|
|
m_onConnected();
|
|
}
|
|
|
|
std::string EthStratumClient::processError(Json::Value& responseObject)
|
|
{
|
|
std::string retVar;
|
|
|
|
if (responseObject.isMember("error") &&
|
|
!responseObject.get("error", Json::Value::null).isNull())
|
|
{
|
|
if (responseObject["error"].isConvertibleTo(Json::ValueType::stringValue))
|
|
{
|
|
retVar = responseObject.get("error", "Unknown error").asString();
|
|
}
|
|
else if (responseObject["error"].isConvertibleTo(Json::ValueType::arrayValue))
|
|
{
|
|
for (auto i : responseObject["error"])
|
|
{
|
|
retVar += i.asString() + " ";
|
|
}
|
|
}
|
|
else if (responseObject["error"].isConvertibleTo(Json::ValueType::objectValue))
|
|
{
|
|
for (Json::Value::iterator i = responseObject["error"].begin();
|
|
i != responseObject["error"].end(); ++i)
|
|
{
|
|
Json::Value k = i.key();
|
|
Json::Value v = (*i);
|
|
retVar += (std::string)i.name() + ":" + v.asString() + " ";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retVar = "Unknown error";
|
|
}
|
|
|
|
return retVar;
|
|
}
|
|
|
|
void EthStratumClient::processExtranonce(std::string& enonce)
|
|
{
|
|
m_session->extraNonceSizeBytes = enonce.length();
|
|
cnote << "Extranonce set to " EthWhite << enonce << EthReset;
|
|
enonce.resize(16, '0');
|
|
m_session->extraNonce = std::stoul(enonce, nullptr, 16);
|
|
}
|
|
|
|
void EthStratumClient::processResponse(Json::Value& responseObject)
|
|
{
|
|
// Store jsonrpc version to test against
|
|
int _rpcVer = responseObject.isMember("jsonrpc") ? 2 : 1;
|
|
|
|
bool _isNotification = false; // Whether or not this message is a reply to previous request or
|
|
// is a broadcast notification
|
|
bool _isSuccess = false; // Whether or not this is a succesful or failed response (implies
|
|
// _isNotification = false)
|
|
string _errReason = ""; // Content of the error reason
|
|
string _method = ""; // The method of the notification (or request from pool)
|
|
unsigned _id = 0; // This SHOULD be the same id as the request it is responding to (known
|
|
// exception is ethermine.org using 999)
|
|
|
|
|
|
// Retrieve essential values
|
|
_id = responseObject.get("id", unsigned(0)).asUInt();
|
|
_isSuccess = responseObject.get("error", Json::Value::null).empty();
|
|
_errReason = (_isSuccess ? "" : processError(responseObject));
|
|
_method = responseObject.get("method", "").asString();
|
|
_isNotification = (_method != "" || _id == unsigned(0));
|
|
|
|
// Notifications of new jobs are like responses to get_work requests
|
|
if (_isNotification && _method == "" && m_conn->StratumMode() == EthStratumClient::ETHPROXY &&
|
|
responseObject["result"].isArray())
|
|
{
|
|
_method = "mining.notify";
|
|
}
|
|
|
|
// Very minimal sanity checks
|
|
// - For rpc2 member "jsonrpc" MUST be valued to "2.0"
|
|
// - For responses ... well ... whatever
|
|
// - For notifications I must receive "method" member and a not empty "params" or "result"
|
|
// member
|
|
if ((_rpcVer == 2 && (!responseObject["jsonrpc"].isString() ||
|
|
responseObject.get("jsonrpc", "") != "2.0")) ||
|
|
(_isNotification && (responseObject["params"].empty() && responseObject["result"].empty())))
|
|
{
|
|
cwarn << "Pool sent an invalid jsonrpc message...";
|
|
cwarn << "Do not blame progminer for this. Ask pool devs to honor http://www.jsonrpc.org/ "
|
|
"specifications ";
|
|
cwarn << "Disconnecting...";
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
|
|
// Handle awaited responses to OUR requests (calc response times)
|
|
if (!_isNotification)
|
|
{
|
|
Json::Value jReq;
|
|
Json::Value jResult = responseObject.get("result", Json::Value::null);
|
|
std::chrono::milliseconds response_delay_ms(0);
|
|
|
|
if (_id == 1)
|
|
{
|
|
response_delay_ms = dequeue_response_plea();
|
|
|
|
/*
|
|
This is the response to very first message after connection.
|
|
Message request vary upon stratum flavour
|
|
I wish I could manage to have different Ids but apparently ethermine.org always replies
|
|
to first message with id=1 regardless the id originally sent.
|
|
*/
|
|
|
|
/*
|
|
If we're in autodetection phase an error message (of any kind) means
|
|
the selected stratum flavour does not comply with the one implemented by the
|
|
work provider (the pool) : thus exit, disconnect and try another one
|
|
*/
|
|
|
|
if (!_isSuccess && !m_conn->StratumModeConfirmed())
|
|
{
|
|
// Disconnect and Proceed with next step of autodetection
|
|
switch (m_conn->StratumMode())
|
|
{
|
|
case ETHEREUMSTRATUM2:
|
|
cnote << "Negotiation of EthereumStratum/2.0.0 failed. Trying another ...";
|
|
break;
|
|
case ETHEREUMSTRATUM:
|
|
cnote << "Negotiation of EthereumStratum/1.0.0 failed. Trying another ...";
|
|
break;
|
|
case ETHPROXY:
|
|
cnote << "Negotiation of Eth-Proxy compatible failed. Trying another ...";
|
|
break;
|
|
case STRATUM:
|
|
cnote << "Negotiation of Stratum failed.";
|
|
break;
|
|
default:
|
|
// Should not happen
|
|
break;
|
|
}
|
|
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
Process response for each stratum flavour :
|
|
ETHEREUMSTRATUM2 response to mining.hello
|
|
ETHEREUMSTRATUM response to mining.subscribe
|
|
ETHPROXY response to eth_submitLogin
|
|
STRATUM response to mining.subscribe
|
|
*/
|
|
|
|
switch (m_conn->StratumMode())
|
|
{
|
|
case EthStratumClient::ETHEREUMSTRATUM2:
|
|
|
|
_isSuccess = (jResult.isConvertibleTo(Json::ValueType::objectValue) &&
|
|
jResult.isMember("proto") &&
|
|
jResult["proto"].asString() == "EthereumStratum/2.0.0" &&
|
|
jResult.isMember("encoding") && jResult.isMember("resume") &&
|
|
jResult.isMember("timeout") && jResult.isMember("maxerrors") &&
|
|
jResult.isMember("node"));
|
|
|
|
if (_isSuccess)
|
|
{
|
|
// Selected flavour is confirmed
|
|
m_conn->SetStratumMode(3, true);
|
|
cnote << "Stratum mode : EthereumStratum/2.0.0";
|
|
startSession();
|
|
|
|
// Send request for subscription
|
|
jReq["id"] = unsigned(2);
|
|
jReq["method"] = "mining.subscribe";
|
|
enqueue_response_plea();
|
|
}
|
|
else
|
|
{
|
|
// If no autodetection the connection is not usable
|
|
// with this stratum flavor
|
|
if (m_conn->StratumModeConfirmed())
|
|
{
|
|
m_conn->MarkUnrecoverable();
|
|
cnote << "Negotiation of EthereumStratum/2.0.0 failed. Change your "
|
|
"connection parameters";
|
|
}
|
|
else
|
|
{
|
|
cnote << "Negotiation of EthereumStratum/2.0.0 failed. Trying another ...";
|
|
}
|
|
// Disconnect
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case EthStratumClient::ETHEREUMSTRATUM:
|
|
|
|
_isSuccess = (jResult.isArray() && jResult[0].isArray() && jResult[0].size() == 3 &&
|
|
jResult[0].get(Json::Value::ArrayIndex(2), "").asString() ==
|
|
"EthereumStratum/1.0.0");
|
|
if (_isSuccess)
|
|
{
|
|
// Selected flavour is confirmed
|
|
m_conn->SetStratumMode(2, true);
|
|
cnote << "Stratum mode : EthereumStratum/1.0.0 (NiceHash)";
|
|
startSession();
|
|
m_session->subscribed.store(true, memory_order_relaxed);
|
|
|
|
// Notify we're ready for extra nonce subscribtion on the fly
|
|
// reply to this message should not perform any logic
|
|
jReq["id"] = unsigned(2);
|
|
jReq["method"] = "mining.extranonce.subscribe";
|
|
jReq["params"] = Json::Value(Json::arrayValue);
|
|
send(jReq);
|
|
|
|
// Eventually request authorization
|
|
m_authpending.store(true, std::memory_order_relaxed);
|
|
jReq["id"] = unsigned(3);
|
|
jReq["method"] = "mining.authorize";
|
|
jReq["params"].append(m_conn->UserDotWorker() + m_conn->Path());
|
|
jReq["params"].append(m_conn->Pass());
|
|
enqueue_response_plea();
|
|
}
|
|
else
|
|
{
|
|
// If no autodetection the connection is not usable
|
|
// with this stratum flavor
|
|
if (m_conn->StratumModeConfirmed())
|
|
{
|
|
m_conn->MarkUnrecoverable();
|
|
cnote << "Negotiation of EthereumStratum/1.0.0 (NiceHash) failed. Change "
|
|
"your "
|
|
"connection parameters";
|
|
}
|
|
else
|
|
{
|
|
cnote << "Negotiation of EthereumStratum/1.0.0 (NiceHash) failed. Trying "
|
|
"another ...";
|
|
}
|
|
// Disconnect
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case EthStratumClient::ETHPROXY:
|
|
|
|
if (_isSuccess)
|
|
{
|
|
// Selected flavour is confirmed
|
|
m_conn->SetStratumMode(1, true);
|
|
cnote << "Stratum mode : Eth-Proxy compatible";
|
|
startSession();
|
|
|
|
m_session->subscribed.store(true, std::memory_order_relaxed);
|
|
m_session->authorized.store(true, std::memory_order_relaxed);
|
|
|
|
// Request initial work
|
|
jReq["id"] = unsigned(5);
|
|
jReq["method"] = "eth_getWork";
|
|
jReq["params"] = Json::Value(Json::arrayValue);
|
|
}
|
|
else
|
|
{
|
|
// If no autodetection the connection is not usable
|
|
// with this stratum flavor
|
|
if (m_conn->StratumModeConfirmed())
|
|
{
|
|
m_conn->MarkUnrecoverable();
|
|
cnote << "Negotiation of Eth-Proxy compatible failed. Change your "
|
|
"connection parameters";
|
|
}
|
|
else
|
|
{
|
|
cnote << "Negotiation of Eth-Proxy compatible failed. Trying "
|
|
"another ...";
|
|
}
|
|
// Disconnect
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case EthStratumClient::STRATUM:
|
|
|
|
if (_isSuccess)
|
|
{
|
|
// Selected flavour is confirmed
|
|
m_conn->SetStratumMode(0, true);
|
|
cnote << "Stratum mode : Stratum";
|
|
startSession();
|
|
m_session->subscribed.store(true, memory_order_relaxed);
|
|
|
|
// Request authorization
|
|
m_authpending.store(true, std::memory_order_relaxed);
|
|
jReq["id"] = unsigned(3);
|
|
jReq["jsonrpc"] = "2.0";
|
|
jReq["method"] = "mining.authorize";
|
|
jReq["params"] = Json::Value(Json::arrayValue);
|
|
jReq["params"].append(m_conn->UserDotWorker() + m_conn->Path());
|
|
jReq["params"].append(m_conn->Pass());
|
|
enqueue_response_plea();
|
|
}
|
|
else
|
|
{
|
|
// If no autodetection the connection is not usable
|
|
// with this stratum flavor
|
|
if (m_conn->StratumModeConfirmed())
|
|
{
|
|
m_conn->MarkUnrecoverable();
|
|
cnote << "Negotiation of Eth-Proxy compatible failed. Change your "
|
|
"connection parameters";
|
|
}
|
|
else
|
|
{
|
|
cnote << "Negotiation of Eth-Proxy compatible failed. Trying "
|
|
"another ...";
|
|
}
|
|
// Disconnect
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Should not happen
|
|
break;
|
|
}
|
|
|
|
|
|
send(jReq);
|
|
}
|
|
|
|
else if (_id == 2)
|
|
{
|
|
// For EthereumStratum/1.0.0
|
|
// This is the response to mining.extranonce.subscribe
|
|
// according to this
|
|
// https://github.com/nicehash/Specifications/blob/master/NiceHash_extranonce_subscribe_extension.txt
|
|
// In all cases, client does not perform any logic when receiving back these replies.
|
|
// With mining.extranonce.subscribe subscription, client should handle extranonce1
|
|
// changes correctly
|
|
// Nothing to do here.
|
|
|
|
// For EthereumStratum/2.0.0
|
|
// This is the response to mining.subscribe
|
|
// https://github.com/AndreaLanfranchi/EthereumStratum-2.0.0#session-handling---response-to-subscription
|
|
if (m_conn->StratumMode() == 3)
|
|
{
|
|
response_delay_ms = dequeue_response_plea();
|
|
|
|
if (!jResult.isString() || !jResult.asString().size())
|
|
{
|
|
// Got invalid session id which is mandatory
|
|
cwarn << "Got invalid or missing session id. Disconnecting ... ";
|
|
m_conn->MarkUnrecoverable();
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
m_session->sessionId = jResult.asString();
|
|
m_session->subscribed.store(true, memory_order_relaxed);
|
|
|
|
// Request authorization
|
|
m_authpending.store(true, std::memory_order_relaxed);
|
|
jReq["id"] = unsigned(3);
|
|
jReq["method"] = "mining.authorize";
|
|
jReq["params"] = Json::Value(Json::arrayValue);
|
|
jReq["params"].append(m_conn->UserDotWorker() + m_conn->Path());
|
|
jReq["params"].append(m_conn->Pass());
|
|
enqueue_response_plea();
|
|
send(jReq);
|
|
}
|
|
}
|
|
|
|
else if (_id == 3 && m_conn->StratumMode() != ETHEREUMSTRATUM2)
|
|
{
|
|
response_delay_ms = dequeue_response_plea();
|
|
|
|
// Response to "mining.authorize"
|
|
// (https://en.bitcoin.it/wiki/Stratum_mining_protocol#mining.authorize) Result should
|
|
// be boolean, some pools also throw an error, so _isSuccess can be false Due to this
|
|
// reevaluate _isSuccess
|
|
|
|
if (_isSuccess && jResult.isBool())
|
|
_isSuccess = jResult.asBool();
|
|
|
|
m_authpending.store(false, std::memory_order_relaxed);
|
|
m_session->authorized.store(_isSuccess, std::memory_order_relaxed);
|
|
|
|
if (!isAuthorized())
|
|
{
|
|
cnote << "Worker " << EthWhite << m_conn->UserDotWorker() << EthReset
|
|
<< " not authorized : " << _errReason;
|
|
m_conn->MarkUnrecoverable();
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
cnote << "Authorized worker " << m_conn->UserDotWorker();
|
|
}
|
|
}
|
|
|
|
else if (_id == 3 && m_conn->StratumMode() == ETHEREUMSTRATUM2)
|
|
{
|
|
response_delay_ms = dequeue_response_plea();
|
|
|
|
if (!_isSuccess || (!jResult.isString() || !jResult.asString().size()))
|
|
{
|
|
// Got invalid session id which is mandatory
|
|
cnote << "Worker " << EthWhite << m_conn->UserDotWorker() << EthReset
|
|
<< " not authorized : " << _errReason;
|
|
m_conn->MarkUnrecoverable();
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
m_authpending.store(false, memory_order_relaxed);
|
|
m_session->authorized.store(true, memory_order_relaxed);
|
|
m_session->workerId = jResult.asString();
|
|
cnote << "Authorized worker " << m_conn->UserDotWorker();
|
|
|
|
// Nothing else to here. Wait for notifications from pool
|
|
}
|
|
|
|
else if ((_id >= 40 && _id <= m_solution_submitted_max_id) &&
|
|
m_conn->StratumMode() != ETHEREUMSTRATUM2)
|
|
{
|
|
response_delay_ms = dequeue_response_plea();
|
|
|
|
// Response to solution submission mining.submit
|
|
// (https://en.bitcoin.it/wiki/Stratum_mining_protocol#mining.submit) Result should be
|
|
// boolean, some pools also throw an error, so _isSuccess can be false Due to this
|
|
// reevaluate _isSucess
|
|
|
|
if (_isSuccess && jResult.isBool())
|
|
_isSuccess = jResult.asBool();
|
|
|
|
const unsigned miner_index = _id - 40;
|
|
if (_isSuccess)
|
|
{
|
|
if (m_onSolutionAccepted)
|
|
m_onSolutionAccepted(response_delay_ms, miner_index, false);
|
|
}
|
|
else
|
|
{
|
|
if (m_onSolutionRejected)
|
|
{
|
|
cwarn << "Reject reason : "
|
|
<< (_errReason.empty() ? "Unspecified" : _errReason);
|
|
m_onSolutionRejected(response_delay_ms, miner_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if ((_id >= 40 && _id <= m_solution_submitted_max_id) &&
|
|
m_conn->StratumMode() == ETHEREUMSTRATUM2)
|
|
{
|
|
response_delay_ms = dequeue_response_plea();
|
|
|
|
// In EthereumStratum/2.0.0 we can evaluate the severity of the
|
|
// error. An 2xx error means the solution have been accepted but is
|
|
// likely stale
|
|
bool isStale = false;
|
|
if (!_isSuccess)
|
|
{
|
|
string errCode = responseObject["error"].get("code","").asString();
|
|
if (errCode.substr(0, 1) == "2")
|
|
_isSuccess = isStale = true;
|
|
}
|
|
|
|
|
|
const unsigned miner_index = _id - 40;
|
|
if (_isSuccess)
|
|
{
|
|
if (m_onSolutionAccepted)
|
|
m_onSolutionAccepted(response_delay_ms, miner_index, isStale);
|
|
}
|
|
else
|
|
{
|
|
|
|
if (m_onSolutionRejected)
|
|
{
|
|
cwarn << "Reject reason : "
|
|
<< (_errReason.empty() ? "Unspecified" : _errReason);
|
|
m_onSolutionRejected(response_delay_ms, miner_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (_id == 5)
|
|
{
|
|
// This is the response we get on first get_work request issued
|
|
// in mode EthStratumClient::ETHPROXY
|
|
// thus we change it to a mining.notify notification
|
|
if (m_conn->StratumMode() == EthStratumClient::ETHPROXY &&
|
|
responseObject["result"].isArray())
|
|
{
|
|
_method = "mining.notify";
|
|
_isNotification = true;
|
|
}
|
|
}
|
|
|
|
else if (_id == 9)
|
|
{
|
|
// Response to hashrate submit
|
|
// Shall we do anything ?
|
|
// Hashrate submit is actually out of stratum spec
|
|
if (!_isSuccess)
|
|
{
|
|
cwarn << "Submit hashRate failed : "
|
|
<< (_errReason.empty() ? "Unspecified error" : _errReason);
|
|
}
|
|
}
|
|
|
|
else if (_id == 999)
|
|
{
|
|
// This unfortunate case should not happen as none of the outgoing requests is marked
|
|
// with id 999 However it has been tested that ethermine.org responds with this id when
|
|
// error replying to either mining.subscribe (1) or mining.authorize requests (3) To
|
|
// properly handle this situation we need to rely on Subscribed/Authorized states
|
|
|
|
if (!_isSuccess && !m_conn->StratumModeConfirmed())
|
|
{
|
|
// Disconnect and Proceed with next step of autodetection
|
|
switch (m_conn->StratumMode())
|
|
{
|
|
case ETHEREUMSTRATUM2:
|
|
cnote << "Negotiation of EthereumStratum/2.0.0 failed. Trying another ...";
|
|
break;
|
|
case ETHEREUMSTRATUM:
|
|
cnote << "Negotiation of EthereumStratum/1.0.0 failed. Trying another ...";
|
|
break;
|
|
case ETHPROXY:
|
|
cnote << "Negotiation of Eth-Proxy compatible failed. Trying another ...";
|
|
break;
|
|
case STRATUM:
|
|
cnote << "Negotiation of Stratum failed.";
|
|
break;
|
|
default:
|
|
// Should not happen
|
|
break;
|
|
}
|
|
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
|
|
if (!_isSuccess)
|
|
{
|
|
if (!isSubscribed())
|
|
{
|
|
// Subscription pending
|
|
cnote << "Subscription failed : "
|
|
<< (_errReason.empty() ? "Unspecified error" : _errReason);
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
else if (isSubscribed() && !isAuthorized())
|
|
{
|
|
// Authorization pending
|
|
cnote << "Worker not authorized : "
|
|
<< (_errReason.empty() ? "Unspecified error" : _errReason);
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
return;
|
|
}
|
|
};
|
|
}
|
|
|
|
else
|
|
{
|
|
cnote << "Got response for unknown message id [" << _id << "] Discarding...";
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
|
|
Handle unsolicited messages FROM pool AKA notifications
|
|
|
|
NOTE !
|
|
Do not process any notification unless login validated
|
|
which means we have detected proper stratum mode.
|
|
|
|
*/
|
|
|
|
if (_isNotification && m_conn->StratumModeConfirmed())
|
|
{
|
|
Json::Value jReq;
|
|
Json::Value jPrm;
|
|
|
|
unsigned prmIdx;
|
|
|
|
if (_method == "mining.notify" && m_conn->StratumMode() != ETHEREUMSTRATUM2)
|
|
{
|
|
// Discard jobs if not properly subscribed
|
|
// or if a job for this transmission has already
|
|
// been processed
|
|
if (!isSubscribed() || m_newjobprocessed)
|
|
return;
|
|
|
|
/*
|
|
Workaround for Nanopool wrong implementation
|
|
see issue # 1348
|
|
*/
|
|
|
|
if (m_conn->StratumMode() == EthStratumClient::ETHPROXY &&
|
|
responseObject.isMember("result"))
|
|
{
|
|
jPrm = responseObject.get("result", Json::Value::null);
|
|
prmIdx = 0;
|
|
}
|
|
else
|
|
{
|
|
jPrm = responseObject.get("params", Json::Value::null);
|
|
prmIdx = 1;
|
|
}
|
|
|
|
|
|
if (jPrm.isArray() && !jPrm.empty())
|
|
{
|
|
m_current.job = jPrm.get(Json::Value::ArrayIndex(0), "").asString();
|
|
|
|
if (m_conn->StratumMode() == EthStratumClient::ETHEREUMSTRATUM)
|
|
{
|
|
string sSeedHash = jPrm.get(Json::Value::ArrayIndex(1), "").asString();
|
|
string sHeaderHash = jPrm.get(Json::Value::ArrayIndex(2), "").asString();
|
|
string sBlockHeight = jPrm.get(Json::Value::ArrayIndex(3), "").asString();
|
|
|
|
if (sHeaderHash != "" && sSeedHash != "")
|
|
{
|
|
m_current.seed = h256(sSeedHash);
|
|
m_current.header = h256(sHeaderHash);
|
|
m_current.boundary = m_session->nextWorkBoundary;
|
|
m_current.startNonce = m_session->extraNonce;
|
|
m_current.exSizeBytes = m_session->extraNonceSizeBytes;
|
|
m_current_timestamp = std::chrono::steady_clock::now();
|
|
m_current.block = strtoul(sBlockHeight.c_str(), nullptr, 0);
|
|
|
|
// This will signal to dispatch the job
|
|
// at the end of the transmission.
|
|
m_newjobprocessed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string sHeaderHash = jPrm.get(Json::Value::ArrayIndex(prmIdx++), "").asString();
|
|
string sSeedHash = jPrm.get(Json::Value::ArrayIndex(prmIdx++), "").asString();
|
|
string sShareTarget =
|
|
jPrm.get(Json::Value::ArrayIndex(prmIdx++), "").asString();
|
|
|
|
// check block number info
|
|
m_current.block = -1;
|
|
if (jPrm.size() > prmIdx &&
|
|
jPrm.get(Json::Value::ArrayIndex(prmIdx), "").asString().substr(0, 2) ==
|
|
"0x")
|
|
{
|
|
try
|
|
{
|
|
m_current.block =
|
|
std::stoul(jPrm.get(Json::Value::ArrayIndex(prmIdx), "").asString(),
|
|
nullptr, 16);
|
|
/*
|
|
check if the block number is in a valid range
|
|
A year has ~31536000 seconds
|
|
50 years have ~1576800000
|
|
assuming a (very fast) blocktime of 10s:
|
|
==> in 50 years we get 157680000 (=0x9660180) blocks
|
|
*/
|
|
if (m_current.block > 0x9660180)
|
|
throw new std::exception();
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
m_current.block = -1;
|
|
}
|
|
}
|
|
|
|
// coinmine.pl fix
|
|
int l = sShareTarget.length();
|
|
if (l < 66)
|
|
sShareTarget = "0x" + string(66 - l, '0') + sShareTarget.substr(2);
|
|
|
|
m_current.seed = h256(sSeedHash);
|
|
m_current.header = h256(sHeaderHash);
|
|
m_current.boundary = h256(sShareTarget);
|
|
m_current_timestamp = std::chrono::steady_clock::now();
|
|
|
|
// This will signal to dispatch the job
|
|
// at the end of the transmission.
|
|
m_newjobprocessed = true;
|
|
}
|
|
}
|
|
}
|
|
else if (_method == "mining.notify" && m_conn->StratumMode() == ETHEREUMSTRATUM2)
|
|
{
|
|
/*
|
|
{
|
|
"method": "mining.notify",
|
|
"params": [
|
|
"bf0488aa",
|
|
"6526d5"
|
|
"645cf20198c2f3861e947d4f67e3ab63b7b2e24dcc9095bd9123e7b33371f6cc",
|
|
"0"
|
|
]
|
|
}
|
|
*/
|
|
if (!m_session || !m_session->firstMiningSet)
|
|
{
|
|
cwarn << "Got mining.notify before mining.set message. Discarding ...";
|
|
return;
|
|
}
|
|
|
|
if (!responseObject.isMember("params") || !responseObject["params"].isArray() ||
|
|
responseObject["params"].empty() || responseObject["params"].size() != 4)
|
|
{
|
|
cwarn << "Got invalid mining.notify message. Discarding ...";
|
|
return;
|
|
}
|
|
|
|
jPrm = responseObject["params"];
|
|
m_current.job = jPrm.get(Json::Value::ArrayIndex(0), "").asString();
|
|
m_current.block =
|
|
stoul(jPrm.get(Json::Value::ArrayIndex(1), "").asString(), nullptr, 16);
|
|
|
|
string header =
|
|
"0x" + dev::padLeft(jPrm.get(Json::Value::ArrayIndex(2), "").asString(), 64, '0');
|
|
|
|
m_current.header = h256(header);
|
|
m_current.boundary = h256(m_session->nextWorkBoundary.hex(HexPrefix::Add));
|
|
m_current.epoch = m_session->epoch;
|
|
m_current.algo = m_session->algo;
|
|
m_current.startNonce = m_session->extraNonce;
|
|
m_current.exSizeBytes = m_session->extraNonceSizeBytes;
|
|
m_current_timestamp = std::chrono::steady_clock::now();
|
|
|
|
// This will signal to dispatch the job
|
|
// at the end of the transmission.
|
|
m_newjobprocessed = true;
|
|
}
|
|
else if (_method == "mining.set_difficulty" && m_conn->StratumMode() == ETHEREUMSTRATUM)
|
|
{
|
|
if (m_conn->StratumMode() == EthStratumClient::ETHEREUMSTRATUM)
|
|
{
|
|
jPrm = responseObject.get("params", Json::Value::null);
|
|
if (jPrm.isArray())
|
|
{
|
|
double nextWorkDifficulty =
|
|
max(jPrm.get(Json::Value::ArrayIndex(0), 1).asDouble(), 0.0001);
|
|
|
|
m_session->nextWorkBoundary = h256(dev::getTargetFromDiff(nextWorkDifficulty));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cwarn << "Invalid mining.set_difficulty rpc method. Disconnecting ...";
|
|
if (m_conn->StratumModeConfirmed())
|
|
{
|
|
m_conn->MarkUnrecoverable();
|
|
}
|
|
m_io_service.post(
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
}
|
|
}
|
|
else if (_method == "mining.set_extranonce" && m_conn->StratumMode() == ETHEREUMSTRATUM)
|
|
{
|
|
jPrm = responseObject.get("params", Json::Value::null);
|
|
if (jPrm.isArray())
|
|
{
|
|
std::string enonce = jPrm.get(Json::Value::ArrayIndex(0), "").asString();
|
|
if (!enonce.empty())
|
|
processExtranonce(enonce);
|
|
}
|
|
}
|
|
else if (_method == "mining.set" && m_conn->StratumMode() == ETHEREUMSTRATUM2)
|
|
{
|
|
/*
|
|
{
|
|
"method": "mining.set",
|
|
"params": {
|
|
"epoch" : "dc",
|
|
"target" : "0112e0be826d694b2e62d01511f12a6061fbaec8bc02357593e70e52ba",
|
|
"algo" : "ethash",
|
|
"extranonce" : "af4c"
|
|
}
|
|
}
|
|
*/
|
|
if (!responseObject.isMember("params") || !responseObject["params"].isObject() ||
|
|
responseObject["params"].empty())
|
|
{
|
|
cwarn << "Got invalid mining.set message. Discarding ...";
|
|
return;
|
|
}
|
|
m_session->firstMiningSet = true;
|
|
jPrm = responseObject["params"];
|
|
string timeout = jPrm.get("timeout", "").asString();
|
|
string epoch = jPrm.get("epoch", "").asString();
|
|
string target = jPrm.get("target", "").asString();
|
|
|
|
if (!timeout.empty())
|
|
m_session->timeout = stoi(timeout, nullptr, 16);
|
|
|
|
if (!epoch.empty())
|
|
m_session->epoch = stoul(epoch, nullptr, 16);
|
|
|
|
if (!target.empty())
|
|
{
|
|
target = "0x" + dev::padLeft(target, 64, '0');
|
|
m_session->nextWorkBoundary = h256(target);
|
|
}
|
|
|
|
m_session->algo = jPrm.get("algo", "ethash").asString();
|
|
string enonce = jPrm.get("extranonce", "").asString();
|
|
if (!enonce.empty())
|
|
processExtranonce(enonce);
|
|
}
|
|
else if (_method == "mining.bye" && m_conn->StratumMode() == ETHEREUMSTRATUM2)
|
|
{
|
|
cnote << m_conn->Host() << " requested connection close. Disconnecting ...";
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
}
|
|
else if (_method == "client.get_version")
|
|
{
|
|
jReq["id"] = _id;
|
|
jReq["result"] = progminer_get_buildinfo()->project_name_with_version;
|
|
|
|
if (_rpcVer == 1)
|
|
{
|
|
jReq["error"] = Json::Value::null;
|
|
}
|
|
else if (_rpcVer == 2)
|
|
{
|
|
jReq["jsonrpc"] = "2.0";
|
|
}
|
|
|
|
send(jReq);
|
|
}
|
|
else
|
|
{
|
|
cwarn << "Got unknown method [" << _method << "] from pool. Discarding...";
|
|
|
|
// Respond back to issuer
|
|
if (_rpcVer == 2)
|
|
jReq["jsonrpc"] = "2.0";
|
|
|
|
jReq["id"] = _id;
|
|
jReq["error"] = "Method not found";
|
|
|
|
send(jReq);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::submitHashrate(uint64_t const& rate, string const& id)
|
|
{
|
|
if (!isConnected())
|
|
return;
|
|
|
|
Json::Value jReq;
|
|
jReq["id"] = unsigned(9);
|
|
jReq["params"] = Json::Value(Json::arrayValue);
|
|
|
|
if (m_conn->StratumMode() != 3)
|
|
{
|
|
// There is no stratum method to submit the hashrate so we use the rpc variant.
|
|
// Note !!
|
|
// id = 6 is also the id used by ethermine.org and nanopool to push new jobs
|
|
// thus we will be in trouble if we want to check the result of hashrate submission
|
|
// actually change the id from 6 to 9
|
|
jReq["jsonrpc"] = "2.0";
|
|
if (!m_conn->Workername().empty())
|
|
jReq["worker"] = m_conn->Workername();
|
|
jReq["method"] = "eth_submitHashrate";
|
|
jReq["params"].append(toHex(rate, HexPrefix::Add, 32)); // Already expressed as hex
|
|
jReq["params"].append(id); // Already prefixed by 0x
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
{
|
|
"id" : 9,
|
|
"method": "mining.hashrate",
|
|
"params": [
|
|
"500000",
|
|
"w-123"
|
|
]
|
|
}
|
|
*/
|
|
|
|
jReq["method"] = "mining.hashrate";
|
|
jReq["params"].append(toCompactHex(rate, HexPrefix::DontAdd));
|
|
jReq["params"].append(m_session->workerId);
|
|
}
|
|
|
|
send(jReq);
|
|
}
|
|
|
|
void EthStratumClient::submitSolution(const Solution& solution)
|
|
{
|
|
if (!isAuthorized())
|
|
{
|
|
cwarn << "Solution not submitted. Not authorized.";
|
|
return;
|
|
}
|
|
|
|
Json::Value jReq;
|
|
|
|
unsigned id = 40 + solution.midx;
|
|
jReq["id"] = id;
|
|
m_solution_submitted_max_id = max(m_solution_submitted_max_id, id);
|
|
jReq["method"] = "mining.submit";
|
|
jReq["params"] = Json::Value(Json::arrayValue);
|
|
|
|
switch (m_conn->StratumMode())
|
|
{
|
|
case EthStratumClient::STRATUM:
|
|
|
|
jReq["jsonrpc"] = "2.0";
|
|
jReq["params"].append(m_conn->User());
|
|
jReq["params"].append(solution.work.job);
|
|
jReq["params"].append(toHex(solution.nonce, HexPrefix::Add));
|
|
jReq["params"].append(solution.work.header.hex(HexPrefix::Add));
|
|
jReq["params"].append(solution.mixHash.hex(HexPrefix::Add));
|
|
if (!m_conn->Workername().empty())
|
|
jReq["worker"] = m_conn->Workername();
|
|
|
|
break;
|
|
|
|
case EthStratumClient::ETHPROXY:
|
|
|
|
jReq["method"] = "eth_submitWork";
|
|
jReq["params"].append(toHex(solution.nonce, HexPrefix::Add));
|
|
jReq["params"].append(solution.work.header.hex(HexPrefix::Add));
|
|
jReq["params"].append(solution.mixHash.hex(HexPrefix::Add));
|
|
if (!m_conn->Workername().empty())
|
|
jReq["worker"] = m_conn->Workername();
|
|
|
|
break;
|
|
|
|
case EthStratumClient::ETHEREUMSTRATUM:
|
|
|
|
jReq["params"].append(m_conn->UserDotWorker());
|
|
jReq["params"].append(solution.work.job);
|
|
jReq["params"].append(
|
|
toHex(solution.nonce, HexPrefix::DontAdd).substr(solution.work.exSizeBytes));
|
|
break;
|
|
|
|
case EthStratumClient::ETHEREUMSTRATUM2:
|
|
|
|
jReq["params"].append(solution.work.job);
|
|
jReq["params"].append(
|
|
toHex(solution.nonce, HexPrefix::DontAdd).substr(solution.work.exSizeBytes));
|
|
jReq["params"].append(m_session->workerId);
|
|
break;
|
|
}
|
|
|
|
enqueue_response_plea();
|
|
send(jReq);
|
|
}
|
|
|
|
void EthStratumClient::recvSocketData()
|
|
{
|
|
if (m_conn->SecLevel() != SecureLevel::NONE)
|
|
{
|
|
async_read(*m_securesocket, m_recvBuffer, boost::asio::transfer_at_least(1),
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::onRecvSocketDataCompleted, this,
|
|
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
|
|
}
|
|
else
|
|
{
|
|
async_read(*m_nonsecuresocket, m_recvBuffer, boost::asio::transfer_at_least(1),
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::onRecvSocketDataCompleted, this,
|
|
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::onRecvSocketDataCompleted(
|
|
const boost::system::error_code& ec, std::size_t bytes_transferred)
|
|
{
|
|
// Due to the nature of io_service's queue and
|
|
// the implementation of the loop this event may trigger
|
|
// late after clean disconnection. Check status of connection
|
|
// before triggering all stack of calls
|
|
|
|
if (!ec)
|
|
{
|
|
// DO NOT DO THIS !!!!!
|
|
// std::istream is(&m_recvBuffer);
|
|
// std::string message;
|
|
// getline(is, message)
|
|
/*
|
|
There are three reasons :
|
|
1 - Previous async_read_until calls this handler (aside from error codes)
|
|
with the number of bytes in the buffer's get area up to and including
|
|
the delimiter. So we know where to split the line
|
|
2 - Boost's documentation clearly states that after a succesfull
|
|
async_read_until operation the stream buffer MAY contain additional
|
|
data which HAVE to be left in the buffer for subsequent read operations.
|
|
If another delimiter exists in the buffer then it will get caught
|
|
by the next async_read_until()
|
|
3 - std::istream is(&m_recvBuffer) will CONSUME ALL data in the buffer
|
|
thus invalidating the previous point 2
|
|
*/
|
|
|
|
// Extract received message and free the buffer
|
|
std::string rx_message(
|
|
boost::asio::buffer_cast<const char*>(m_recvBuffer.data()), bytes_transferred);
|
|
m_recvBuffer.consume(bytes_transferred);
|
|
m_message.append(rx_message);
|
|
|
|
// Process each line in the transmission
|
|
// NOTE : as multiple jobs may come in with
|
|
// a single transmission only the last will be dispatched
|
|
m_newjobprocessed = false;
|
|
std::string line;
|
|
size_t offset = m_message.find("\n");
|
|
while (offset != string::npos)
|
|
{
|
|
if (offset > 0)
|
|
{
|
|
line = m_message.substr(0, offset);
|
|
boost::trim(line);
|
|
|
|
if (!line.empty())
|
|
{
|
|
// Out received message only for debug purpouses
|
|
if (g_logOptions & LOG_JSON)
|
|
cnote << " << " << line;
|
|
|
|
// Test validity of chunk and process
|
|
Json::Value jMsg;
|
|
Json::Reader jRdr;
|
|
if (jRdr.parse(line, jMsg))
|
|
{
|
|
try
|
|
{
|
|
// Run in sync so no 2 different async reads may overlap
|
|
processResponse(jMsg);
|
|
}
|
|
catch (const std::exception& _ex)
|
|
{
|
|
cwarn << "Stratum got invalid Json message : " << _ex.what();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string what = jRdr.getFormattedErrorMessages();
|
|
boost::replace_all(what, "\n", " ");
|
|
cwarn << "Stratum got invalid Json message : " << what;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_message.erase(0, offset + 1);
|
|
offset = m_message.find("\n");
|
|
}
|
|
|
|
// There is a new job - dispatch it
|
|
if (m_newjobprocessed)
|
|
if (m_onWorkReceived)
|
|
m_onWorkReceived(m_current);
|
|
|
|
// Eventually keep reading from socket
|
|
if (isConnected())
|
|
recvSocketData();
|
|
}
|
|
else
|
|
{
|
|
if (isConnected())
|
|
{
|
|
if (m_authpending.load(std::memory_order_relaxed))
|
|
{
|
|
cwarn << "Error while waiting for authorization from pool";
|
|
cwarn << "Double check your pool credentials.";
|
|
m_conn->MarkUnrecoverable();
|
|
}
|
|
|
|
if ((ec.category() == boost::asio::error::get_ssl_category()) &&
|
|
(ERR_GET_REASON(ec.value()) == SSL_RECEIVED_SHUTDOWN))
|
|
{
|
|
cnote << "SSL Stream remotely closed by " << m_conn->Host();
|
|
}
|
|
else if (ec == boost::asio::error::eof)
|
|
{
|
|
cnote << "Connection remotely closed by " << m_conn->Host();
|
|
}
|
|
else
|
|
{
|
|
cwarn << "Socket read failed: " << ec.message();
|
|
}
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::send(Json::Value const& jReq)
|
|
{
|
|
std::string* line = new std::string(Json::writeString(m_jSwBuilder, jReq));
|
|
m_txQueue.push(line);
|
|
|
|
bool ex = false;
|
|
if (m_txPending.compare_exchange_weak(ex, true, std::memory_order_relaxed))
|
|
sendSocketData();
|
|
}
|
|
|
|
void EthStratumClient::sendSocketData()
|
|
{
|
|
if (!isConnected() || m_txQueue.empty())
|
|
{
|
|
m_sendBuffer.consume(m_sendBuffer.capacity());
|
|
m_txQueue.consume_all([](std::string* l) { delete l; });
|
|
m_txPending.store(false, std::memory_order_relaxed);
|
|
return;
|
|
}
|
|
|
|
std::string* line;
|
|
std::ostream os(&m_sendBuffer);
|
|
while (m_txQueue.pop(line))
|
|
{
|
|
os << *line << std::endl;
|
|
// Out received message only for debug purpouses
|
|
if (g_logOptions & LOG_JSON)
|
|
cnote << " >> " << *line;
|
|
|
|
delete line;
|
|
}
|
|
|
|
if (m_conn->SecLevel() != SecureLevel::NONE)
|
|
{
|
|
async_write(*m_securesocket, m_sendBuffer,
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::onSendSocketDataCompleted, this,
|
|
boost::asio::placeholders::error)));
|
|
}
|
|
else
|
|
{
|
|
async_write(*m_nonsecuresocket, m_sendBuffer,
|
|
m_io_strand.wrap(boost::bind(&EthStratumClient::onSendSocketDataCompleted, this,
|
|
boost::asio::placeholders::error)));
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::onSendSocketDataCompleted(const boost::system::error_code& ec)
|
|
{
|
|
if (ec)
|
|
{
|
|
m_sendBuffer.consume(m_sendBuffer.capacity());
|
|
m_txQueue.consume_all([](std::string* l) { delete l; });
|
|
m_txPending.store(false, std::memory_order_relaxed);
|
|
|
|
if ((ec.category() == boost::asio::error::get_ssl_category()) &&
|
|
(SSL_R_PROTOCOL_IS_SHUTDOWN == ERR_GET_REASON(ec.value())))
|
|
{
|
|
cnote << "SSL Stream error : " << ec.message();
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
}
|
|
|
|
if (isConnected())
|
|
{
|
|
cwarn << "Socket write failed : " << ec.message();
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect, this)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Register last transmission tstamp to prevent timeout
|
|
// in EthereumStratum/2.0.0
|
|
if (m_session && m_conn->StratumMode() == 3)
|
|
m_session->lastTxStamp = chrono::steady_clock::now();
|
|
|
|
if (m_txQueue.empty())
|
|
m_txPending.store(false, std::memory_order_relaxed);
|
|
else
|
|
sendSocketData();
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::onSSLShutdownCompleted(const boost::system::error_code& ec)
|
|
{
|
|
(void)ec;
|
|
clear_response_pleas();
|
|
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::disconnect_finalize, this)));
|
|
}
|
|
|
|
void EthStratumClient::enqueue_response_plea()
|
|
{
|
|
using namespace std::chrono;
|
|
steady_clock::time_point response_plea_time = steady_clock::now();
|
|
if (m_response_pleas_count++ == 0)
|
|
{
|
|
m_response_plea_older.store(
|
|
response_plea_time.time_since_epoch(), std::memory_order_relaxed);
|
|
}
|
|
m_response_plea_times.push(response_plea_time);
|
|
}
|
|
|
|
std::chrono::milliseconds EthStratumClient::dequeue_response_plea()
|
|
{
|
|
using namespace std::chrono;
|
|
|
|
steady_clock::time_point response_plea_time(
|
|
m_response_plea_older.load(std::memory_order_relaxed));
|
|
milliseconds response_delay_ms =
|
|
duration_cast<milliseconds>(steady_clock::now() - response_plea_time);
|
|
|
|
if (m_response_plea_times.pop(response_plea_time))
|
|
{
|
|
m_response_plea_older.store(
|
|
response_plea_time.time_since_epoch(), std::memory_order_relaxed);
|
|
}
|
|
if (m_response_pleas_count.load(std::memory_order_relaxed) > 0)
|
|
{
|
|
m_response_pleas_count--;
|
|
return response_delay_ms;
|
|
}
|
|
else
|
|
{
|
|
return milliseconds(0);
|
|
}
|
|
}
|
|
|
|
void EthStratumClient::clear_response_pleas()
|
|
{
|
|
using namespace std::chrono;
|
|
steady_clock::time_point response_plea_time;
|
|
m_response_pleas_count.store(0, std::memory_order_relaxed);
|
|
while (m_response_plea_times.pop(response_plea_time))
|
|
{
|
|
};
|
|
m_response_plea_older.store(((steady_clock::time_point)steady_clock::now()).time_since_epoch(),
|
|
std::memory_order_relaxed);
|
|
}
|