diff --git a/src/common.h b/src/common.h index 8d61ad9..e50c8cd 100644 --- a/src/common.h +++ b/src/common.h @@ -195,6 +195,11 @@ struct difficulty_type return 1; } + // Safeguard against division by zero (CPU will trigger it even if lo = 1 because result doesn't fit in 64 bits) + if (lo <= 1) { + return std::numeric_limits::max(); + } + uint64_t rem; uint64_t result = udiv128(1, 0, lo, &rem); return rem ? (result + 1) : result; diff --git a/src/stratum_server.cpp b/src/stratum_server.cpp index 121ea9e..d53c434 100644 --- a/src/stratum_server.cpp +++ b/src/stratum_server.cpp @@ -127,7 +127,35 @@ void StratumServer::on_block(const BlockTemplate& block) } } -bool StratumServer::on_login(StratumClient* client, uint32_t id) +bool StratumServer::get_custom_diff(const char* s, difficulty_type& diff) +{ + const char* diff_str = nullptr; + + // Find last of '+' or '.' + while (s) { + const char c = *s; + if (!c) { + break; + } + if ((c == '+') || (c == '.')) { + diff_str = s; + } + ++s; + } + + if (diff_str) { + const uint64_t t = strtoull(diff_str + 1, nullptr, 10); + if (t) { + // Don't let clients set difficulty less than 1000 + diff = { std::max(t + 1, 1000), 0 }; + return true; + } + } + + return false; +} + +bool StratumServer::on_login(StratumClient* client, uint32_t id, const char* login) { const uint32_t extra_nonce = m_extraNonce.fetch_add(1); @@ -140,7 +168,13 @@ bool StratumServer::on_login(StratumClient* client, uint32_t id) uint32_t template_id; const size_t blob_size = m_pool->block_template().get_hashing_blob(extra_nonce, hashing_blob, height, difficulty, sidechain_difficulty, seed_hash, nonce_offset, template_id); - const uint64_t target = std::max(difficulty.target(), sidechain_difficulty.target()); + + uint64_t target = std::max(difficulty.target(), sidechain_difficulty.target()); + + if (get_custom_diff(login, client->m_customDiff)) { + LOGINFO(5, "client " << log::Gray() << static_cast(client->m_addrString) << " set custom difficulty " << client->m_customDiff); + target = std::max(target, client->m_customDiff.target()); + } uint32_t job_id; { @@ -176,7 +210,7 @@ bool StratumServer::on_login(StratumClient* client, uint32_t id) return result; } -bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* job_id_str, const char* nonce_str) +bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* job_id_str, const char* nonce_str, const char* result_str) { uint32_t job_id = 0; @@ -200,6 +234,17 @@ bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* jo nonce = (nonce << 8) | (d[0] << 4) | d[1]; } + hash resultHash; + + for (size_t i = 0; i < HASH_SIZE; ++i) { + uint32_t d[2]; + if (!from_hex(result_str[i * 2], d[0]) || !from_hex(result_str[i * 2 + 1], d[1])) { + LOGWARN(4, "Client: invalid params ('result' is not a hex value)"); + return false; + } + resultHash.h[i] = static_cast((d[0] << 4) | d[1]); + } + uint32_t template_id = 0; uint32_t extra_nonce = 0; @@ -239,6 +284,7 @@ bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* jo share->m_templateId = template_id; share->m_nonce = nonce; share->m_extraNonce = extra_nonce; + share->m_resultHash = resultHash; const int err = uv_queue_work(&m_loop, &share->m_req, on_share_found, on_after_share_found); if (err) { @@ -328,14 +374,19 @@ void StratumServer::on_blobs_ready() saved_job.target = data->m_target; } + uint64_t target = data->m_target; + if (client->m_customDiff.lo) { + target = std::max(target, client->m_customDiff.target()); + } + const bool result = send(client, - [data, client, hashing_blob, &job_id](void* buf) + [data, target, client, hashing_blob, &job_id](void* buf) { log::Stream s(reinterpret_cast(buf)); s << "{\"jsonrpc\":\"2.0\",\"method\":\"job\",\"params\":{\"blob\":\""; s << log::hex_buf(hashing_blob, data->m_blobSize) << "\",\"job_id\":\""; s << log::Hex(job_id) << "\",\"target\":\""; - s << log::hex_buf(reinterpret_cast(&data->m_target), sizeof(data->m_target)) << "\",\"algo\":\"rx/0\",\"height\":"; + s << log::hex_buf(reinterpret_cast(&target), sizeof(target)) << "\",\"algo\":\"rx/0\",\"height\":"; s << data->m_height << ",\"seed_hash\":\""; s << data->m_seedHash << "\"}}\n"; return s.m_pos; @@ -385,41 +436,56 @@ void StratumServer::on_share_found(uv_work_t* req) return; } - for (uint32_t i = 0, nonce = share->m_nonce; i < sizeof(share->m_nonce); ++i) { - blob[nonce_offset + i] = nonce & 255; - nonce >>= 8; - } + const bool mainchain_solution = difficulty.check_pow(share->m_resultHash); + const bool sidechain_solution = sidechain_difficulty.check_pow(share->m_resultHash); - hash pow_hash; - if (!pool->calculate_hash(blob, blob_size, seed_hash, pow_hash)) { - LOGWARN(4, "client: couldn't check share PoW"); - share->m_result = SubmittedShare::Result::COULDNT_CHECK_POW; - return; + if (mainchain_solution || sidechain_solution) { + for (uint32_t i = 0, nonce = share->m_nonce; i < sizeof(share->m_nonce); ++i) { + blob[nonce_offset + i] = nonce & 255; + nonce >>= 8; + } + + hash pow_hash; + if (!pool->calculate_hash(blob, blob_size, seed_hash, pow_hash)) { + LOGWARN(4, "client: couldn't check share PoW"); + share->m_result = SubmittedShare::Result::COULDNT_CHECK_POW; + return; + } + + if (pow_hash != share->m_resultHash) { + LOGWARN(4, "client: submitted a share with invalid PoW"); + share->m_result = SubmittedShare::Result::INVALID_POW; + return; + } + + LOGINFO(0, log::Green() << "SHARE FOUND at mainchain height " << height); + + if (mainchain_solution) { + pool->submit_block_async(share->m_templateId, share->m_nonce, share->m_extraNonce); + block.update_tx_keys(); + } + + if (sidechain_solution) { + pool->submit_sidechain_block(share->m_templateId, share->m_nonce, share->m_extraNonce); + } } // Send the response to miner - const uint64_t value = *reinterpret_cast(pow_hash.h + HASH_SIZE - sizeof(uint64_t)); - const uint64_t target = std::max(difficulty.target(), sidechain_difficulty.target()); + const uint64_t value = *reinterpret_cast(share->m_resultHash.h + HASH_SIZE - sizeof(uint64_t)); + + uint64_t target = std::max(difficulty.target(), sidechain_difficulty.target()); + + if (client->m_customDiff.lo) { + target = std::max(target, client->m_customDiff.target()); + } if (LIKELY(value < target)) { - LOGINFO(0, log::Green() << "SHARE FOUND at mainchain height " << height); share->m_result = SubmittedShare::Result::OK; } else { LOGWARN(4, "got a low diff share from " << static_cast(client->m_addrString)); share->m_result = SubmittedShare::Result::LOW_DIFF; } - - // First check if we found a mainnet block - if (difficulty.check_pow(pow_hash)) { - pool->submit_block_async(share->m_templateId, share->m_nonce, share->m_extraNonce); - block.update_tx_keys(); - } - - // Then check if we found a sidechain block - if (sidechain_difficulty.check_pow(pow_hash)) { - pool->submit_sidechain_block(share->m_templateId, share->m_nonce, share->m_extraNonce); - } } void StratumServer::on_after_share_found(uv_work_t* req, int /*status*/) @@ -454,6 +520,9 @@ void StratumServer::on_after_share_found(uv_work_t* req, int /*status*/) case SubmittedShare::Result::LOW_DIFF: s << "{\"id\":" << share->m_id << ",\"jsonrpc\":\"2.0\",\"error\":{\"message\":\"Low diff share\"}}\n"; break; + case SubmittedShare::Result::INVALID_POW: + s << "{\"id\":" << share->m_id << ",\"jsonrpc\":\"2.0\",\"error\":{\"message\":\"Invalid PoW\"}}\n"; + break; case SubmittedShare::Result::OK: s << "{\"id\":" << share->m_id << ",\"jsonrpc\":\"2.0\",\"error\":null,\"result\":{\"status\":\"OK\"}}\n"; break; @@ -475,6 +544,7 @@ StratumServer::StratumClient::StratumClient() : m_rpcId(0) , m_jobs{} , m_perConnectionJobId(0) + , m_customDiff{} { uv_mutex_init_checked(&m_jobsLock); } @@ -490,6 +560,7 @@ void StratumServer::StratumClient::reset() m_rpcId = 0; memset(m_jobs, 0, sizeof(m_jobs)); m_perConnectionJobId = 0; + m_customDiff = {}; } bool StratumServer::StratumClient::on_read(char* data, uint32_t size) @@ -562,11 +633,11 @@ bool StratumServer::StratumClient::process_request(char* data, uint32_t /*size*/ const char* s = method.GetString(); if (strcmp(s, "login") == 0) { - LOGINFO(5, "incoming login from " << log::Gray() << static_cast(m_addrString)); + LOGINFO(6, "incoming login from " << log::Gray() << static_cast(m_addrString)); return process_login(doc, id.GetUint()); } else if (strcmp(s, "submit") == 0) { - LOGINFO(3, "incoming share from " << log::Gray() << static_cast(m_addrString)); + LOGINFO(6, "incoming share from " << log::Gray() << static_cast(m_addrString)); return process_submit(doc, id.GetUint()); } else { @@ -577,63 +648,101 @@ bool StratumServer::StratumClient::process_request(char* data, uint32_t /*size*/ return true; } -bool StratumServer::StratumClient::process_login(rapidjson::Document& /*doc*/, uint32_t id) -{ - return static_cast(m_owner)->on_login(this, id); -} - -bool StratumServer::StratumClient::process_submit(rapidjson::Document& doc, uint32_t id) +bool StratumServer::StratumClient::process_login(rapidjson::Document& doc, uint32_t id) { if (!doc.HasMember("params")) { - LOGWARN(4, "client: invalid JSON request ('params' field not found)"); + LOGWARN(4, "client: invalid JSON login request ('params' field not found)"); return false; } auto& params = doc["params"]; if (!params.IsObject()) { - LOGWARN(4, "client: invalid JSON request ('params' field is not an object)"); + LOGWARN(4, "client: invalid JSON login request ('params' field is not an object)"); + return false; + } + + if (!params.HasMember("login")) { + LOGWARN(4, "client: invalid login params ('login' field not found)"); + return false; + } + + auto& login = params["login"]; + if (!login.IsString()) { + LOGWARN(4, "client: invalid login params ('login' field is not a string)"); + return false; + } + + return static_cast(m_owner)->on_login(this, id, login.GetString()); +} + +bool StratumServer::StratumClient::process_submit(rapidjson::Document& doc, uint32_t id) +{ + if (!doc.HasMember("params")) { + LOGWARN(4, "client: invalid JSON submit request ('params' field not found)"); + return false; + } + + auto& params = doc["params"]; + if (!params.IsObject()) { + LOGWARN(4, "client: invalid JSON submit request ('params' field is not an object)"); return false; } if (!params.HasMember("id")) { - LOGWARN(4, "client: invalid params ('id' field not found)"); + LOGWARN(4, "client: invalid submit params ('id' field not found)"); return false; } auto& rpcId = params["id"]; if (!rpcId.IsString()) { - LOGWARN(4, "client: invalid params ('id' field is not a string)"); + LOGWARN(4, "client: invalid submit params ('id' field is not a string)"); return false; } if (!params.HasMember("job_id")) { - LOGWARN(4, "client: invalid params ('job_id' field not found)"); + LOGWARN(4, "client: invalid submit params ('job_id' field not found)"); return false; } auto& job_id = params["job_id"]; if (!job_id.IsString()) { - LOGWARN(4, "client: invalid params ('job_id' field is not a string)"); + LOGWARN(4, "client: invalid submit params ('job_id' field is not a string)"); return false; } if (!params.HasMember("nonce")) { - LOGWARN(4, "client: invalid params ('nonce' field not found)"); + LOGWARN(4, "client: invalid submit params ('nonce' field not found)"); return false; } auto& nonce = params["nonce"]; if (!nonce.IsString()) { - LOGWARN(4, "client: invalid params ('nonce' field is not a string)"); + LOGWARN(4, "client: invalid submit params ('nonce' field is not a string)"); return false; } if (nonce.GetStringLength() != sizeof(uint32_t) * 2) { - LOGWARN(4, "client: invalid params ('nonce' field has invalid length)"); + LOGWARN(4, "client: invalid submit params ('nonce' field has invalid length)"); return false; } - return static_cast(m_owner)->on_submit(this, id, job_id.GetString(), nonce.GetString()); + if (!params.HasMember("result")) { + LOGWARN(4, "client: invalid submit params ('result' field not found)"); + return false; + } + + auto& result = params["result"]; + if (!result.IsString()) { + LOGWARN(4, "client: invalid submit params ('result' field is not a string)"); + return false; + } + + if (result.GetStringLength() != HASH_SIZE * 2) { + LOGWARN(4, "client: invalid submit params ('result' field has invalid length)"); + return false; + } + + return static_cast(m_owner)->on_submit(this, id, job_id.GetString(), nonce.GetString(), result.GetString()); } } // namespace p2pool diff --git a/src/stratum_server.h b/src/stratum_server.h index a803cfc..a7134da 100644 --- a/src/stratum_server.h +++ b/src/stratum_server.h @@ -63,13 +63,16 @@ public: } m_jobs[4]; uint32_t m_perConnectionJobId; + difficulty_type m_customDiff; }; - bool on_login(StratumClient* client, uint32_t id); - bool on_submit(StratumClient* client, uint32_t id, const char* job_id_str, const char* nonce_str); + bool on_login(StratumClient* client, uint32_t id, const char* login); + bool on_submit(StratumClient* client, uint32_t id, const char* job_id_str, const char* nonce_str, const char* result_str); uint64_t get_random64(); private: + static bool get_custom_diff(const char* s, difficulty_type& diff); + static void on_share_found(uv_work_t* req); static void on_after_share_found(uv_work_t* req, int status); @@ -110,11 +113,13 @@ private: uint32_t m_templateId; uint32_t m_nonce; uint32_t m_extraNonce; + hash m_resultHash; enum class Result { STALE, COULDNT_CHECK_POW, LOW_DIFF, + INVALID_POW, OK } m_result; };