Skip to content
This repository was archived by the owner on Apr 24, 2022. It is now read-only.

Commit 7ebd167

Browse files
authoredJul 4, 2018
Stratum mode autodetection (#1333)
* Stratum mode autodetection Eliminates the hassle of knowing in advance which is the stratum mode implemented by the pool. No more stratum/1/2+tcp:// but only stratum+tcp:// Added support for invalid connections. Whenever a connection cannot be established due to login errors (invalid worker or bad address) it's marked as Unrecoverable and won't be used again. * CLang'ed * NiceHash detection * Proper log spacing * Handle bad-call disconnections Some pools may disconnect during autodetection phase. * Code cleanup
1 parent 010f9c3 commit 7ebd167

File tree

5 files changed

+304
-83
lines changed

5 files changed

+304
-83
lines changed
 

‎CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1111
- Basic API authentication to protect exposure of API port to the internet [#1228](https://github.com/ethereum-mining/ethminer/pull/1228).
1212
- Add `ispaused` information into response of `miner_getstathr` API query [#1232](https://github.com/ethereum-mining/ethminer/pull/1232).
1313
- API responses return "ethminer-" as version prefix. [#1300](https://github.com/ethereum-mining/ethminer/pull/1300).
14+
- Stratum mode autodetection. No need to specify stratum+tcp or stratum1+tcp or stratum2+tcp
15+
- Connection failed due to login errors (wrong address or worker) are marked Unrecoverable and no longer used
1416

1517
## 0.15.0rc1
1618

‎libpoolprotocols/PoolManager.cpp

+14-2
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ void PoolManager::workLoop()
215215

216216
if (!p_client->isConnected()) {
217217

218+
// If this connection is marked Unrecoverable then discard it
219+
if (m_connections[m_activeConnectionIdx].IsUnrecoverable())
220+
{
221+
m_connections.erase(m_connections.begin() + m_activeConnectionIdx);
222+
m_connectionAttempt = 0;
223+
if (m_activeConnectionIdx > 0)
224+
{
225+
m_activeConnectionIdx--;
226+
}
227+
}
228+
229+
218230
// Rotate connections if above max attempts threshold
219231
if (m_connectionAttempt >= m_maxConnectionAttempts) {
220232

@@ -238,7 +250,7 @@ void PoolManager::workLoop()
238250

239251
}
240252

241-
if (m_connections[m_activeConnectionIdx].Host() != "exit") {
253+
if (m_connections[m_activeConnectionIdx].Host() != "exit" && m_connections.size() > 0) {
242254

243255
// Count connectionAttempts
244256
m_connectionAttempt++;
@@ -252,7 +264,7 @@ void PoolManager::workLoop()
252264
}
253265
else {
254266

255-
cnote << "No more failover connections.";
267+
cnote << "No more connections to try. Exiting ...";
256268

257269
// Stop mining if applicable
258270
if (m_farm.isMining()) {

‎libpoolprotocols/PoolURI.h

+32-19
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,39 @@ enum class ProtocolFamily {GETWORK = 0, STRATUM};
1313
class URI
1414
{
1515
public:
16-
URI() {};
17-
URI(const std::string uri);
18-
19-
std::string Scheme() const;
20-
std::string Host() const;
21-
std::string Path() const;
22-
unsigned short Port() const;
23-
std::string User() const;
24-
std::string Pass() const;
25-
SecureLevel SecLevel() const;
26-
ProtocolFamily Family() const;
27-
unsigned Version() const;
28-
std::string string() { return m_uri.string(); }
29-
30-
bool KnownScheme();
31-
32-
static std::string KnownSchemes(ProtocolFamily family);
16+
URI(){};
17+
URI(const std::string uri);
18+
19+
std::string Scheme() const;
20+
std::string Host() const;
21+
std::string Path() const;
22+
unsigned short Port() const;
23+
std::string User() const;
24+
std::string Pass() const;
25+
SecureLevel SecLevel() const;
26+
ProtocolFamily Family() const;
27+
unsigned Version() const;
28+
std::string string() { return m_uri.string(); }
29+
30+
bool KnownScheme();
31+
32+
static std::string KnownSchemes(ProtocolFamily family);
33+
34+
void SetStratumMode(unsigned mode, bool confirmed)
35+
{
36+
m_stratumMode = mode;
37+
m_stratumModeConfirmed = confirmed;
38+
}
39+
void SetStratumMode(unsigned mode) { m_stratumMode = mode; }
40+
unsigned StratumMode() { return m_stratumMode; }
41+
bool StratumModeConfirmed() { return m_stratumModeConfirmed; }
42+
bool IsUnrecoverable() { return m_unrecoverable; }
43+
void MarkUnrecoverable() { m_unrecoverable = true; }
3344

3445
private:
35-
network::uri m_uri;
46+
network::uri m_uri;
47+
bool m_stratumModeConfirmed = false;
48+
unsigned m_stratumMode = 999; // Initial value 999 means not tested yet
49+
bool m_unrecoverable = false;
3650
};
37-
3851
}

‎libpoolprotocols/stratum/EthStratumClient.cpp

+250-57
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,28 @@ void EthStratumClient::disconnect_finalize() {
254254
m_connected.store(false, std::memory_order_relaxed);
255255
m_disconnecting.store(false, std::memory_order::memory_order_relaxed);
256256

257-
// Trigger handlers
258-
if (m_onDisconnected) { m_onDisconnected(); }
259-
260257

258+
// If we got disconnected during autodetection phase
259+
// reissue a connect lowering stratum mode checks
260+
if (!m_conn->StratumModeConfirmed())
261+
{
262+
unsigned l = m_conn->StratumMode();
263+
if (l > 0)
264+
{
265+
l--;
266+
m_conn->SetStratumMode(l);
267+
268+
// Repost a new connection attempt
269+
m_io_service.post(m_io_strand.wrap(boost::bind(&EthStratumClient::connect, this)));
270+
return;
271+
}
272+
}
273+
274+
// Trigger handlers
275+
if (m_onDisconnected)
276+
{
277+
m_onDisconnected();
278+
}
261279
}
262280

263281
void EthStratumClient::resolve_handler(const boost::system::error_code& ec, tcp::resolver::iterator i)
@@ -304,14 +322,24 @@ void EthStratumClient::reset_work_timeout()
304322
void EthStratumClient::start_connect()
305323
{
306324
if (!m_endpoints.empty()) {
325+
326+
// If still in the middle of autodetection keep
327+
// lastly used endpoint
328+
if (m_conn->StratumModeConfirmed() == true)
329+
{
330+
// Sets active end point and removes
331+
// it from queue
332+
m_endpoint = m_endpoints.front();
333+
m_endpoints.pop();
334+
}
335+
else
336+
{
337+
m_endpoint = m_endpoints.front();
338+
}
307339

308-
// Sets active end point and removes
309-
// it from queue
310-
m_endpoint = m_endpoints.front();
311-
m_endpoints.pop();
312340

313-
dev::setThreadName("stratum");
314-
cnote << ("Trying " + toString(m_endpoint) + " ...");
341+
dev::setThreadName("stratum");
342+
cnote << ("Trying " + toString(m_endpoint) + " ...");
315343

316344
m_conntimer.expires_from_now(boost::posix_time::seconds(m_responsetimeout));
317345
m_conntimer.async_wait(m_io_strand.wrap(boost::bind(&EthStratumClient::check_connect_timeout, this, boost::asio::placeholders::error)));
@@ -461,53 +489,110 @@ void EthStratumClient::connect_handler(const boost::system::error_code& ec)
461489
if (m_onConnected) { m_onConnected(); }
462490
reset_work_timeout();
463491

464-
string user;
465-
size_t p;
466-
467-
Json::Value jReq;
468-
jReq["id"] = unsigned(1);
469-
jReq["method"] = "mining.subscribe";
470-
jReq["params"] = Json::Value(Json::arrayValue);
471-
472-
m_worker.clear();
473-
p = m_conn->User().find_first_of(".");
474-
if (p != string::npos) {
475-
user = m_conn->User().substr(0, p);
476-
477-
// There should be at least one char after dot
478-
// returned p is zero based
479-
if (p < (m_conn->User().length() -1))
480-
m_worker = m_conn->User().substr(++p);
481-
}
482-
else
483-
user = m_conn->User();
484-
485-
switch (m_conn->Version()) {
492+
// Extract user and worker
493+
size_t p;
494+
m_worker.clear();
495+
p = m_conn->User().find_first_of(".");
496+
if (p != string::npos)
497+
{
498+
m_user = m_conn->User().substr(0, p);
499+
500+
// There should be at least one char after dot
501+
// returned p is zero based
502+
if (p < (m_conn->User().length() - 1))
503+
m_worker = m_conn->User().substr(++p);
504+
}
505+
else
506+
{
507+
m_user = m_conn->User();
508+
}
509+
510+
/*
511+
If this connection has not gone through an autodetection of stratum mode
512+
begin it now.
513+
Autodetection process passes all known stratum modes.
514+
- 1st pass EthStratumClient::ETHEREUMSTRATUM (2)
515+
- 2nd pass EthStratumClient::ETHPROXY (1)
516+
- 3rd pass EthStratumClient::STRATUM (0)
517+
*/
518+
519+
Json::Value jReq;
520+
jReq["id"] = unsigned(1);
521+
jReq["method"] = "mining.subscribe";
522+
jReq["params"] = Json::Value(Json::arrayValue);
523+
524+
if (!m_conn->StratumModeConfirmed())
525+
{
526+
switch (m_conn->StratumMode())
527+
{
486528

487-
case EthStratumClient::STRATUM:
529+
case 0:
488530

531+
m_conn->SetStratumMode(0, false);
532+
jReq["id"] = unsigned(1);
489533
jReq["jsonrpc"] = "2.0";
534+
jReq["method"] = "mining.subscribe";
535+
jReq["params"] = Json::Value(Json::arrayValue);
490536

491-
break;
492-
493-
case EthStratumClient::ETHPROXY:
537+
case 1:
494538

539+
m_conn->SetStratumMode(1, false);
540+
jReq["id"] = unsigned(1);
495541
jReq["method"] = "eth_submitLogin";
496-
if (m_worker.length()) jReq["worker"] = m_worker;
497-
jReq["params"].append(user + m_conn->Path());
498-
if (!m_email.empty()) jReq["params"].append(m_email);
542+
jReq["params"] = Json::Value(Json::arrayValue);
543+
if (m_worker.length())
544+
jReq["worker"] = m_worker;
545+
jReq["params"].append(m_user + m_conn->Path());
546+
if (!m_email.empty())
547+
jReq["params"].append(m_email);
499548

500549
break;
501550

502-
case EthStratumClient::ETHEREUMSTRATUM:
503-
504-
jReq["params"].append("ethminer " + std::string(ethminer_get_buildinfo()->project_version));
551+
case 999:
552+
case 2:
553+
m_conn->SetStratumMode(2, false);
554+
jReq["params"].append(
555+
"ethminer " + std::string(ethminer_get_buildinfo()->project_version));
505556
jReq["params"].append("EthereumStratum/1.0.0");
557+
break;
506558

559+
default:
507560
break;
508-
}
561+
}
562+
563+
}
564+
else
565+
{
566+
switch (m_conn->StratumMode())
567+
{
568+
case EthStratumClient::STRATUM:
569+
570+
jReq["jsonrpc"] = "2.0";
509571

510-
// Send first message
572+
break;
573+
574+
case EthStratumClient::ETHPROXY:
575+
576+
jReq["method"] = "eth_submitLogin";
577+
if (m_worker.length())
578+
jReq["worker"] = m_worker;
579+
jReq["params"].append(m_user + m_conn->Path());
580+
if (!m_email.empty())
581+
jReq["params"].append(m_email);
582+
583+
break;
584+
585+
case EthStratumClient::ETHEREUMSTRATUM:
586+
587+
jReq["params"].append(
588+
"ethminer " + std::string(ethminer_get_buildinfo()->project_version));
589+
jReq["params"].append("EthereumStratum/1.0.0");
590+
591+
break;
592+
}
593+
}
594+
595+
// Send first message
511596
sendSocketData(jReq);
512597

513598
// Begin receive data
@@ -588,7 +673,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
588673
_isNotification = (_id == unsigned(0) || _method != "");
589674

590675
// Notifications of new jobs are like responses to get_work requests
591-
if (_isNotification && _method == "" && m_conn->Version() == EthStratumClient::ETHPROXY && responseObject["result"].isArray()) {
676+
if (_isNotification && _method == "" && m_conn->StratumMode() == EthStratumClient::ETHPROXY && responseObject["result"].isArray()) {
592677
_method = "mining.notify";
593678
}
594679

@@ -622,17 +707,123 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
622707

623708
case 1:
624709

625-
// Response to "mining.subscribe" (https://en.bitcoin.it/wiki/Stratum_mining_protocol#mining.subscribe)
710+
/*
711+
This is the response to very first message after connection.
712+
I wish I could manage to have different Ids but apparently ethermine.org always replies
713+
to first message with id=1 regardless the id originally sent.
714+
*/
715+
if (!m_conn->StratumModeConfirmed())
716+
{
717+
switch (m_conn->StratumMode())
718+
{
719+
case EthStratumClient::ETHEREUMSTRATUM:
720+
721+
// In case of success we also need to verify third parameter of "result" array
722+
// member is exactly "EthereumStratum/1.0.0". Otherwise try with another mode
723+
if (_isSuccess)
724+
{
725+
if (!jResult.isArray() || !jResult[0].isArray() || jResult[0].size() != 3 ||
726+
jResult[0].get((Json::Value::ArrayIndex)2, "").asString() !=
727+
"EthereumStratum/1.0.0")
728+
{
729+
// This is not a proper ETHEREUMSTRATUM response.
730+
// Proceed with next step of autodetection ETHPROXY compatible
731+
m_conn->SetStratumMode(1);
732+
jReq["id"] = unsigned(1);
733+
jReq["method"] = "eth_submitLogin";
734+
jReq["params"] = Json::Value(Json::arrayValue);
735+
if (m_worker.length())
736+
jReq["worker"] = m_worker;
737+
jReq["params"].append(m_user + m_conn->Path());
738+
if (!m_email.empty())
739+
jReq["params"].append(m_email);
740+
741+
sendSocketData(jReq);
742+
return;
743+
}
744+
else
745+
{
746+
// ETHEREUMSTRATUM is confirmed
747+
cnote << "Stratum mode detected : ETHEREUMSTRATUM (NiceHash)";
748+
m_conn->SetStratumMode(2, true);
749+
}
750+
}
751+
else
752+
{
753+
// This is not a proper ETHEREUMSTRATUM response.
754+
// Proceed with next step of autodetection ETHPROXY compatible
755+
m_conn->SetStratumMode(1);
756+
jReq["id"] = unsigned(1);
757+
jReq["method"] = "eth_submitLogin";
758+
jReq["params"] = Json::Value(Json::arrayValue);
759+
if (m_worker.length())
760+
jReq["worker"] = m_worker;
761+
jReq["params"].append(m_user + m_conn->Path());
762+
if (!m_email.empty())
763+
jReq["params"].append(m_email);
764+
765+
sendSocketData(jReq);
766+
return;
767+
}
768+
769+
break;
770+
771+
case EthStratumClient::ETHPROXY:
772+
773+
if (!_isSuccess)
774+
{
775+
// In case of failure try next step which is STRATUM
776+
m_conn->SetStratumMode(0);
777+
jReq["id"] = unsigned(1);
778+
jReq["jsonrpc"] = "2.0";
779+
jReq["method"] = "mining.subscribe";
780+
jReq["params"] = Json::Value(Json::arrayValue);
781+
782+
sendSocketData(jReq);
783+
return;
784+
}
785+
else
786+
{
787+
// ETHPROXY is confirmed
788+
cnote << "Stratum mode detected : ETHPROXY compatible";
789+
m_conn->SetStratumMode(1, true);
790+
}
791+
792+
break;
793+
794+
case EthStratumClient::STRATUM:
795+
796+
if (!_isSuccess)
797+
{
798+
// In case of failure we can't manage this connection
799+
cwarn << "Unable to find suitable Stratum Mode";
800+
m_conn->MarkUnrecoverable();
801+
disconnect();
802+
return;
803+
}
804+
else
805+
{
806+
// STRATUM is confirmed
807+
cnote << "Stratum mode detected : STRATUM";
808+
m_conn->SetStratumMode(0, true);
809+
}
810+
811+
break;
812+
}
813+
}
814+
815+
816+
// Response to "mining.subscribe" (https://en.bitcoin.it/wiki/Stratum_mining_protocol#mining.subscribe)
626817
// Result should be an array with multiple dimensions, we only care about the data if EthStratumClient::ETHEREUMSTRATUM
627-
628-
switch (m_conn->Version()) {
818+
switch (m_conn->StratumMode()) {
629819

630820
case EthStratumClient::STRATUM:
631821

632822
m_subscribed.store(_isSuccess, std::memory_order_relaxed);
633823
if (!m_subscribed)
634824
{
635825
cnote << "Could not subscribe to stratum server";
826+
m_conn->MarkUnrecoverable();
636827
disconnect();
637828
return;
638829
}
@@ -656,6 +847,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
656847
if (!m_subscribed)
657848
{
658849
cnote << "Could not login to ethproxy server:" << _errReason;
850+
m_conn->MarkUnrecoverable();
659851
disconnect();
660852
return;
661853
}
@@ -678,6 +870,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
678870
if (!m_subscribed)
679871
{
680872
cnote << "Could not subscribe to stratum server:" << _errReason;
873+
m_conn->MarkUnrecoverable();
681874
disconnect();
682875
return;
683876
}
@@ -785,7 +978,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
785978
// This is the response we get on first get_work request issued
786979
// in mode EthStratumClient::ETHPROXY
787980
// thus we change it to a mining.notify notification
788-
if (m_conn->Version() == EthStratumClient::ETHPROXY && responseObject["result"].isArray()) {
981+
if (m_conn->StratumMode() == EthStratumClient::ETHPROXY && responseObject["result"].isArray()) {
789982
_method = "mining.notify";
790983
_isNotification = true;
791984
}
@@ -850,7 +1043,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
8501043

8511044
unsigned prmIdx;
8521045

853-
if (m_conn->Version() == EthStratumClient::ETHPROXY) {
1046+
if (m_conn->StratumMode() == EthStratumClient::ETHPROXY) {
8541047

8551048
jPrm = responseObject.get("result", Json::Value::null);
8561049
prmIdx = 0;
@@ -874,7 +1067,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
8741067
if (m_response_pending)
8751068
m_stale = true;
8761069

877-
if (m_conn->Version() == EthStratumClient::ETHEREUMSTRATUM)
1070+
if (m_conn->StratumMode() == EthStratumClient::ETHEREUMSTRATUM)
8781071
{
8791072
string sSeedHash = jPrm.get(1, "").asString();
8801073
string sHeaderHash = jPrm.get(2, "").asString();
@@ -889,7 +1082,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
8891082
m_current.startNonce = bswap(*((uint64_t*)m_extraNonce.data()));
8901083
m_current.exSizeBits = m_extraNonceHexSize * 4;
8911084
m_current.job_len = job.size();
892-
if (m_conn->Version() == EthStratumClient::ETHEREUMSTRATUM)
1085+
if (m_conn->StratumMode() == EthStratumClient::ETHEREUMSTRATUM)
8931086
job.resize(64, '0');
8941087
m_current.job = h256(job);
8951088

@@ -932,7 +1125,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
9321125
}
9331126
}
9341127
}
935-
else if (_method == "mining.set_difficulty" && m_conn->Version() == EthStratumClient::ETHEREUMSTRATUM)
1128+
else if (_method == "mining.set_difficulty" && m_conn->StratumMode() == EthStratumClient::ETHEREUMSTRATUM)
9361129
{
9371130
jPrm = responseObject.get("params", Json::Value::null);
9381131
if (jPrm.isArray())
@@ -946,7 +1139,7 @@ void EthStratumClient::processReponse(Json::Value& responseObject)
9461139
}
9471140
}
9481141
}
949-
else if (_method == "mining.set_extranonce" && m_conn->Version() == EthStratumClient::ETHEREUMSTRATUM)
1142+
else if (_method == "mining.set_extranonce" && m_conn->StratumMode() == EthStratumClient::ETHEREUMSTRATUM)
9501143
{
9511144
jPrm = responseObject.get("params", Json::Value::null);
9521145
if (jPrm.isArray())
@@ -1056,7 +1249,7 @@ void EthStratumClient::submitSolution(const Solution& solution) {
10561249
jReq["method"] = "mining.submit";
10571250
jReq["params"] = Json::Value(Json::arrayValue);
10581251

1059-
switch (m_conn->Version()) {
1252+
switch (m_conn->StratumMode()) {
10601253

10611254
case EthStratumClient::STRATUM:
10621255

@@ -1160,14 +1353,14 @@ void EthStratumClient::onRecvSocketDataCompleted(const boost::system::error_code
11601353
(ERR_GET_REASON(ec.value()) == SSL_RECEIVED_SHUTDOWN)
11611354
)
11621355
{
1163-
cnote << "SSL Stream remotely closed by" << m_conn->Host();
1356+
cnote << "SSL Stream remotely closed by " << m_conn->Host();
11641357
}
11651358
else if (ec == boost::asio::error::eof)
11661359
{
1167-
cnote << "Connection remotely closed by" << m_conn->Host();
1360+
cnote << "Connection remotely closed by " << m_conn->Host();
11681361
}
11691362
else {
1170-
cwarn << "Socket read failed:" << ec.message();
1363+
cwarn << "Socket read failed: " << ec.message();
11711364
}
11721365
disconnect();
11731366
}

‎libpoolprotocols/stratum/EthStratumClient.h

+6-5
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ class EthStratumClient : public PoolClient
6666
void onSendSocketDataCompleted(const boost::system::error_code& ec);
6767
void onSSLShutdownCompleted(const boost::system::error_code& ec);
6868

69-
string m_worker; // eth-proxy only; No ! It's for all !!!
69+
string m_user; // Only user part
70+
string m_worker; // eth-proxy only; No ! It's for all !!!
7071

71-
std::atomic<bool> m_disconnecting = { false };
72+
std::atomic<bool> m_disconnecting = { false };
7273
std::atomic<bool> m_connecting = { false };
7374

7475
// seconds to trigger a work_timeout (overwritten in constructor)
@@ -81,9 +82,9 @@ class EthStratumClient : public PoolClient
8182

8283
bool m_stale = false;
8384

84-
boost::asio::io_service & m_io_service; // The IO service reference passed in the constructor
85-
boost::asio::io_service::strand m_io_strand;
86-
boost::asio::ip::tcp::socket *m_socket;
85+
boost::asio::io_service& m_io_service; // The IO service reference passed in the constructor
86+
boost::asio::io_service::strand m_io_strand;
87+
boost::asio::ip::tcp::socket *m_socket;
8788

8889
// Use shared ptrs to avoid crashes due to async_writes
8990
// see https://stackoverflow.com/questions/41526553/can-async-write-cause-segmentation-fault-when-this-is-deleted

0 commit comments

Comments
 (0)
This repository has been archived.