diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aa8c7a..fda6f55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,9 @@ if(CUDA_FOUND) src/stratum/stratum.cpp src/miner/miner.cpp src/util/util.cpp - src/nvml/nvml.cpp) + src/nvml/nvml.cpp + src/merkle/merkle.cpp) + else() set(COMBINE_LIBS $ @@ -68,7 +70,8 @@ else() src/blake2/blake2b-ref.c src/stratum/stratum.cpp src/miner/miner.cpp - src/util/util.cpp) + src/util/util.cpp + src/merkle/merkle.cpp) endif() if(CMAKE_HOST_WIN32) @@ -117,6 +120,7 @@ file(GLOB H_PICO include/merit/PicoSHA2/*.h) file(GLOB H_STRATUM include/merit/stratum/*.hpp) file(GLOB H_UTIL include/merit/util/*.hpp) file(GLOB H_NVML include/merit/nvml/*.h) +file(GLOB H_MERKLE include/merit/merkle/*.hpp) install(FILES ${H_PUB} DESTINATION include/merit) install(FILES ${H_BLAKE} DESTINATION include/merit/blake) install(FILES ${H_CRYPTO} DESTINATION include/merit/crypto) @@ -127,5 +131,6 @@ install(FILES ${H_PICO} DESTINATION include/merit/PicoSHA2) install(FILES ${H_STRATUM} DESTINATION include/merit/stratum) install(FILES ${H_UTIL} DESTINATION include/merit/util) install(FILES ${H_NVML} DESTINATION include/merit/nvml) +install(FILES ${H_MERKLE} DESTINATION include/merit/merkle) include(CPack) diff --git a/include/merit/merkle/merkle.hpp b/include/merit/merkle/merkle.hpp new file mode 100644 index 0000000..c7c1a85 --- /dev/null +++ b/include/merit/merkle/merkle.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 The Merit Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either vedit_refsion 3 of the License, or + * (at your option) any later vedit_refsion. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * Botan library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * + * You must obey the GNU General Public License in all respects for + * all of the code used other than Botan. If you modify file(s) with + * this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + */ + +#ifndef MERIT_MERKLE_H +#define MERIT_MERKLE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "merit/PicoSHA2/picosha2.h" +#include "merit/util/util.hpp" + +namespace merit { + + namespace merkle { + + class MerkleTree { + public: + explicit MerkleTree(const std::vector &hashes_list) { + steps = calculateSteps(hashes_list); + } + + std::vector branches(); + + private: + std::vector steps; + + /** + * As input takes array of bytes + * So, before use this function you have to convert hex string into byte array(const char*) + */ + std::vector calculateSteps(const std::vector &hashes_list); + void merkleJoin(const char* h1, const char* h2, char* dest); + }; + + const unsigned char *double_hash(const unsigned char* str, unsigned int size); + + } +} + +#endif //MERIT_MERKLE_H diff --git a/include/merit/miner.hpp b/include/merit/miner.hpp index 28031f5..0246029 100644 --- a/include/merit/miner.hpp +++ b/include/merit/miner.hpp @@ -23,11 +23,17 @@ namespace merit const char* user, const char* pass); + bool connect_solo_stratum( + Context* c, + const char* url, + const char* user, + const char* pass); + void disconnect_stratum(Context* c); bool is_stratum_connected(Context* c); void init(); - bool run_stratum(Context*); + bool run_stratum(Context*, bool solo_mining); void stop_stratum(Context*); struct GPUInfo { @@ -40,7 +46,8 @@ namespace merit int fan_speed; }; - bool run_miner(Context*, int workers, int threads_per_worker, const std::vector& gpu_devices); + bool run_miner(Context*, int workers, int threads_per_worker, const std::vector& gpu_devices, + bool solo_mining, const std::string& auth_token); void stop_miner(Context*); bool is_stratum_running(Context*); bool is_miner_running(Context*); diff --git a/include/merit/stratum/stratum.hpp b/include/merit/stratum/stratum.hpp index 4971a0f..5bb91fd 100644 --- a/include/merit/stratum/stratum.hpp +++ b/include/merit/stratum/stratum.hpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -88,13 +89,16 @@ namespace merit void disconnect(); bool subscribe(); bool authorize(); - bool run(); + bool run(bool solo_mining); void stop(); bool connected() const; bool running() const; bool stopping() const; MaybeJob get_job(); + MaybeJob get_solo_job(const std::string& auth_token); + + unsigned int get_solo_job_id(); void submit_work(const util::Work&); @@ -102,11 +106,14 @@ namespace merit bool reconnect(); bool send(const std::string&); bool recv(std::string&); + bool recv_with_headers(std::string&); void cleanup(); bool subscribe_resp(); bool handle_command(const pt::ptree&, const std::string& res); bool mining_notify(const pt::ptree& params); bool mining_difficulty(const pt::ptree& params); + bool mining_set_solo_job(const pt::ptree& params); + pt::ptree convert_blocktemplate(const pt::ptree& params); bool client_reconnect(const pt::ptree& params); bool client_get_version(const pt::ptree& params); bool client_show_message(const pt::ptree& params, const pt::ptree& id); @@ -139,6 +146,8 @@ namespace merit std::string _port; util::bytes _sockbuf; + unsigned int static _solo_job_id; + std::atomic _next_diff; mutable std::mutex _sock_mutex; mutable std::mutex _job_mutex; diff --git a/include/merit/util/util.hpp b/include/merit/util/util.hpp index fd46257..71f3226 100644 --- a/include/merit/util/util.hpp +++ b/include/merit/util/util.hpp @@ -85,6 +85,9 @@ static inline void le32enc(void *pp, uint32_t x) #if defined(__linux__) // Linux #include // for htole32/64 +#include +#include +#include #elif defined(__APPLE__) // macOS @@ -170,6 +173,9 @@ namespace merit unsigned char* digest, const unsigned char* data, size_t len); + + int char2int(char input); + void hex2bin(const char* src, char* target); } } #endif diff --git a/src/merkle/README.md b/src/merkle/README.md new file mode 100644 index 0000000..85a6b32 --- /dev/null +++ b/src/merkle/README.md @@ -0,0 +1,7 @@ +# Merkle tree + +Contains an implementation of the [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) data structure. + +| Files | Description | +|:---------------------------------------|:-----------------------------------------| +| [merkle.hpp](merkle.hpp) | Interface to the merkle tree | diff --git a/src/merkle/merkle.cpp b/src/merkle/merkle.cpp new file mode 100644 index 0000000..5eea7a0 --- /dev/null +++ b/src/merkle/merkle.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 The Merit Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either vedit_refsion 3 of the License, or + * (at your option) any later vedit_refsion. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * Botan library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * + * You must obey the GNU General Public License in all respects for + * all of the code used other than Botan. If you modify file(s) with + * this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + */ + +#include + +#include "merit/merkle/merkle.hpp" + +namespace merit { + + namespace merkle { + + std::vector MerkleTree::branches() { + std::vector branches; + std::string tmp_hash; + std::stringstream stream; + + for (const auto &step: steps) { + std::string tmp{step, picosha2::k_digest_size}; + std::string step_hex; + + merit::util::to_hex(tmp.begin(), tmp.end(), step_hex); + + branches.push_back(step_hex); + } + + return branches; + } + + std::vector MerkleTree::calculateSteps(const std::vector &hashes_list) { + std::vector steps{}; + std::vector L{nullptr}; + for (const auto &el: hashes_list) { + L.push_back(el); + } + + int startL = 2; + unsigned long Ll = L.size(); + + if (Ll > 1) { + while (true) { + + if(Ll == 1) + break; + + steps.push_back(L[1]); + + if(Ll % 2 == 1) + L.push_back(L[L.size() - 1]); + + std::vector Ld; + + for (int i = startL; i < Ll; i += 2) { + char *join_res = new char[picosha2::k_digest_size]; + merkleJoin(L[i], L[i+1], join_res); + Ld.push_back(join_res); + } + + L.clear(); L.push_back(nullptr); + for(const auto& el: Ld) + L.push_back(el); + + Ll = L.size(); + + } + } + + return steps; + } + + void MerkleTree::merkleJoin(const char *h1, const char *h2, char* dest){ + std::string joined{h1}; + joined += h2; + + unsigned char buf[joined.size()]; // array to hold the result. + std::copy(joined.begin(), joined.end(), buf); + + auto res = double_hash(buf, joined.size()); + + // cast from unsigned to the ordinary + std::string final{reinterpret_cast(res), 32}; + + std::copy(final.begin(), final.end(), dest); + } + + const unsigned char* double_hash(const unsigned char* str, unsigned int size){ + auto *result = new unsigned char[picosha2::k_digest_size]; + merit::util::double_sha256(result, str, size); + return result; + } + + } +} + diff --git a/src/miner/miner.cpp b/src/miner/miner.cpp index f38328e..d91d10d 100644 --- a/src/miner/miner.cpp +++ b/src/miner/miner.cpp @@ -193,6 +193,8 @@ namespace merit void Miner::submit_job(const stratum::Job& j) { + std::cout << "submit_job" << std::endl; + auto w = stratum::work_from_job(j); util::MaybeWork prev_work; { @@ -384,9 +386,11 @@ namespace merit _state = Running; while(_miner.state() == Miner::Running) { +// std::cout << "in miner cycle" << std::endl; auto work = _miner.next_work(); if(!work) { +// std::cout << "There is no work!" << std::endl; std::this_thread::sleep_for(10ms); continue; } @@ -469,6 +473,8 @@ namespace merit stat.attempts++; if(found) { + std::cout << "CYCLE FOUND" << std::endl; + stat.cycles+=cycles.size(); int idx = 0; diff --git a/src/minerd.cpp b/src/minerd.cpp index d6b7053..5f1d319 100644 --- a/src/minerd.cpp +++ b/src/minerd.cpp @@ -55,18 +55,28 @@ int main(int argc, char** argv) std::string url; std::vector gpu_devices; std::string address; + bool solo_mining; + std::string solo_url; + std::string auth_token; desc.add_options() - ("help", "show the help message") - ("infogpu", "show the info about GPU in your system") - ("url", po::value(&url)->default_value("stratum+tcp://pool.merit.me:3333"), "The stratum pool url") - ("address", po::value(&address), "The address to send mining rewards to.") - ("gpu", po::value>(&gpu_devices)->multitoken(), "Index of GPU device to use in mining(can use multiple times). For more info check --infogpu") - ("cores", po::value()->default_value(merit::number_of_cores()), "The number of CPU cores to use."); + ("help", "show the help message") + ("infogpu", "show the info about GPU in your system") + ("url", po::value(&url)->default_value("stratum+tcp://pool.merit.me:3333"), + "The stratum pool url") + ("address", po::value(&address), "The address to send mining rewards to.") + ("solo", po::value(&solo_mining)->default_value(false), "Enable solo-mining or not?") + ("token", po::value(&auth_token)->default_value("bWVyaXRycGM6TERwaWtWYkRpM2VYX042UHdQZy1OVVk3Q0RCSGtMOG11Z0pjX0JYNTdnVT0="), + "RPC token for solo-mining. You can set it in the merit.conf") + ("solourl", po::value(&solo_url)->default_value("=stratum+tcp://127.0.0.1:8332"), // 18332 -> testnet, 8332 -> livenet + "Solo mining stratum server url") + ("gpu", po::value>(&gpu_devices)->multitoken(), + "Index of GPU device to use in mining(can use multiple times). For more info check --infogpu") + ("cores", po::value()->default_value(merit::number_of_cores()), "The number of CPU cores to use."); po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); - po::notify(vm); + po::notify(vm); if (vm.count("help")) { std::cout << desc << std::endl;; @@ -76,7 +86,7 @@ int main(int argc, char** argv) if (vm.count("infogpu")) { auto info = merit::gpus_info(); std::cout << "GPU info:" << std::endl; - for(const auto &item: info){ + for (const auto &item: info) { std::cout << "Device number: " << item.id << std::endl; std::cout << "Total memory: " << item.total_memory << std::endl; std::cout << "Title: " << item.title << std::endl; @@ -85,11 +95,11 @@ int main(int argc, char** argv) std::cout << "Memory util: " << item.memory_util << std::endl; std::cout << "Fan speed: " << item.fan_speed << std::endl << std::endl; } - + return 1; } - if(address.empty()) { + if (address.empty()) { std::cout << "forgot to set your reward address. use --address" << std::endl; return 1; } @@ -100,19 +110,29 @@ int main(int argc, char** argv) auto utilization = determine_utilization(cores); std::unique_ptr c{ - merit::create_context(), &merit::delete_context}; + merit::create_context(), &merit::delete_context}; merit::set_agent(c.get(), "merit-minerd", "0.2"); - if(!merit::connect_stratum(c.get(), url.c_str(), address.c_str(), "")) { - std::cerr << "Error connecting" << std::endl; - return 1; + + if(!solo_mining){ + if (!merit::connect_stratum(c.get(), url.c_str(), address.c_str(), "")) { + std::cerr << "Error connecting" << std::endl; + return 1; + } + } else { + if (!merit::connect_solo_stratum(c.get(), solo_url.c_str(), address.c_str(), "")) { + std::cerr << "Error connecting" << std::endl; + return 1; + } } - merit::run_stratum(c.get()); - merit::run_miner(c.get(), utilization.first ,utilization.second, gpu_devices); + + merit::run_stratum(c.get(), solo_mining); + + merit::run_miner(c.get(), utilization.first, utilization.second, gpu_devices, solo_mining, auth_token); int prev_graphs = 0; - while(true) { + while (true) { using namespace std::chrono_literals; std::this_thread::sleep_for(5s); @@ -123,15 +143,15 @@ int main(int argc, char** argv) auto graphps = stats.total.attempts_per_second; auto cyclesps = stats.total.cycles_per_second; auto sharesps = stats.total.shares_per_second; - if(graphs > prev_graphs) { + if (graphs > prev_graphs) { std::cout << "graphs: " << graphs << " cycles: " << cycles << " shares: " << shares; - if(stats.total.attempts > 0) { - std::cout << " graphs/s: " << graphps << " cycles/s: " << cyclesps << " shares/s: " << sharesps << std::endl; + if (stats.total.attempts > 0) { + std::cout << " graphs/s: " << graphps << " cycles/s: " << cyclesps << " shares/s: " << sharesps + << std::endl; } std::cout << std::endl; } prev_graphs = graphs; } - return 0; } diff --git a/src/public.cpp b/src/public.cpp index 0da23de..2bc2379 100644 --- a/src/public.cpp +++ b/src/public.cpp @@ -33,19 +33,13 @@ #include "merit/miner/miner.hpp" #include -#include -#include -#include -#include //forward declare SetupBuffers which is in kernel.cu int SetupKernelBuffers(); -namespace merit -{ +namespace merit { - struct Context - { + struct Context { stratum::Client stratum; std::unique_ptr miner; util::SubmitWorkFunc submit_work_func; @@ -55,247 +49,299 @@ namespace merit std::thread collab_thread; }; - Context* create_context() - { + Context *create_context() { return new Context; } - void delete_context(Context* c) - { - if(c) { delete c;} + void delete_context(Context *c) { + if (c) { delete c; } } - void set_agent(Context* c, const char* software, const char* version) - { + void set_agent(Context *c, const char *software, const char *version) { assert(c); c->stratum.set_agent(software, version); } bool connect_stratum( - Context* c, - const char* url, - const char* user, - const char* pass) - try - { + Context *c, + const char *url, + const char *user, + const char *pass) + try { assert(c); - std::cerr << "info: " << "connecting to: " << url<< std::endl; - if(!c->stratum.connect(url, user, pass)) { - std::cerr << "error: " << "error connecting to stratum server: " << url<< std::endl; + std::cerr << "info: " << "connecting to: " << url << " with user = " << user << " pass = " << pass << std::endl; + if (!c->stratum.connect(url, user, pass)) { + std::cerr << "error: " << "error connecting to stratum server: " << url << std::endl; return false; } - std::cerr << "info: " << "subscribing to: " << url<< std::endl; - if(!c->stratum.subscribe()) { - std::cerr << "error: " << "error subscribing to stratum server: " << url<< std::endl; + std::cerr << "info: " << "subscribing to: " << url << std::endl; + if (!c->stratum.subscribe()) { + std::cerr << "error: " << "error subscribing to stratum server: " << url << std::endl; return false; } - std::cerr << "info: " << "authorizing as: " << user<< std::endl; - if(!c->stratum.authorize()) { - std::cerr << "error: " << "error authorize to stratum server: " << url<< std::endl; + std::cerr << "info: " << "authorizing as: " << user << std::endl; + if (!c->stratum.authorize()) { + std::cerr << "error: " << "error authorize to stratum server: " << url << std::endl; return false; } - c->submit_work_func = [c](const util::Work& w) { + c->submit_work_func = [c](const util::Work &w) { c->stratum.submit_work(w); }; - std::cerr << "info: " << "connected to: " << url<< std::endl; + std::cerr << "info: " << "connected to: " << url << std::endl; return true; } - catch(std::exception& e) - { - std::cerr << "error: " << "error connecting to stratum server: " << e.what()<< std::endl; + catch (std::exception &e) { + std::cerr << "error: " << "error connecting to stratum server: " << e.what() << std::endl; c->stratum.disconnect(); return false; } - void disconnect_stratum(Context* c) + bool connect_solo_stratum( + Context *c, + const char *url, + const char *user, + const char *pass) { + try { + assert(c); + + std::cerr << "info: " << "connecting to: " << url << " with user = " << user << " pass = " << pass + << std::endl; + if (!c->stratum.connect(url, user, pass)) { + std::cerr << "error: " << "error connecting to stratum server: " << url << std::endl; + return false; + } + +// c->submit_work_func = [c](const util::Work &w) { +// c->stratum.submit_work(w); +// }; + + std::cerr << "info: " << "connected to: " << url << std::endl; + + return true; + } catch (std::exception &e) { + std::cerr << "error: " << "error connecting to stratum server: " << e.what() << std::endl; + c->stratum.disconnect(); + return false; + } + } + + void disconnect_stratum(Context *c) { assert(c); c->stratum.disconnect(); } - bool is_stratum_connected(Context* c) - { + bool is_stratum_connected(Context *c) { assert(c); return c->stratum.connected(); } - - void init() - { + + void init() { ::SetupKernelBuffers(); } - bool run_stratum(Context* c) - { + bool run_stratum(Context *c, bool solo_mining) { assert(c); - if(c->stratum.running()) { + if (c->stratum.running()) { stop_stratum(c); return false; } - if(c->stratum_thread.joinable()) { + if (c->stratum_thread.joinable()) { c->stratum_thread.join(); } - c->stratum_thread = std::thread([c]() { - try { - c->stratum.run(); - } catch(std::exception& e) { - std::cerr << "error: " << "error running stratum: " << e.what()<< std::endl; - } - c->stratum.disconnect(); - std::cerr << "info: " << "stopped stratum."<< std::endl; + c->stratum_thread = std::thread([c, solo_mining]() { + try { + c->stratum.run(solo_mining); + } catch (std::exception &e) { + std::cerr << "error: " << "error running stratum: " << e.what() << std::endl; + } + c->stratum.disconnect(); + std::cerr << "info: " << "stopped stratum." << std::endl; }); return true; } - void stop_stratum(Context* c) - { + void stop_stratum(Context *c) { assert(c); - std::cerr << "info: " << "stopping stratum..."<< std::endl; + std::cerr << "info: " << "stopping stratum..." << std::endl; c->stratum.stop(); } - bool run_miner(Context* c, int workers, int threads_per_worker, const std::vector& gpu_devices) - try - { + bool run_miner(Context *c, int workers, int threads_per_worker, + const std::vector &gpu_devices, bool solo_mining, + const std::string& auth_token) + try { assert(c); using namespace std::chrono_literals; - if(c->miner && c->miner->running()) { + if (c->miner && c->miner->running()) { stop_miner(c); return false; } c->miner.reset(); - std::cerr << "info: " << "setting up miner..."<< std::endl; + std::cerr << "info: " << "setting up miner..." << std::endl; c->miner = std::make_unique( workers, threads_per_worker, gpu_devices, c->submit_work_func); - std::cerr << "info: " << "starting miner..."<< std::endl; - if(c->mining_thread.joinable()) { + std::cerr << "info: " << "starting miner..." << std::endl; + if (c->mining_thread.joinable()) { c->mining_thread.join(); } c->mining_thread = std::thread([c]() { - try { - c->miner->run(); - } catch(std::exception& e) { - c->miner->stop(); - std::cerr << "error: " << e.what() << std::endl; - - return false; - } + try { + c->miner->run(); + } catch (std::exception &e) { + c->miner->stop(); + std::cerr << "error: " << e.what() << std::endl; + + return false; + } }); //TODO: different logic depending on stratum vs solo - std::cerr << "info: " << "starting collab thread..."<< std::endl; - if(c->collab_thread.joinable()) { + std::cerr << "info: " << "starting collab thread..." << std::endl; + if (c->collab_thread.joinable()) { c->collab_thread.join(); } - c->collab_thread = std::thread([c]() { - while(c->miner->state() != miner::Miner::Running) {} - while(c->miner->running()) - try { - auto j = c->stratum.get_job(); - if(!j) { - if(!c->stratum.connected()) { - c->miner->clear_job(); + + if (solo_mining) { + std::cout << "info: " << "Starting solo mining..." << std::endl; + + c->collab_thread = std::thread([c, auth_token]() { + while (c->miner->state() != miner::Miner::Running) {} + + std::cout << "Miner is Running and ready to start getting job and then mine it" << std::endl; + + // TODO: fix this when implementing server support for solo mining +// c->stratum.stop(); // disconnect from server + + while (c->miner->running()) + try { + c->stratum.get_solo_job(auth_token); + + // get block + auto j = c->stratum.get_job(); + while(!j.is_initialized()){ + std::this_thread::sleep_for(1s); + j = c->stratum.get_job(); } + + // submit and then finding cycles starts + c->miner->submit_job(*j); + + std::this_thread::sleep_for(10s); // just for testing + + // if good cycle, then submit block + + // again... + +// std::cout << "Clearing the job" << std::endl; +// c->miner->clear_job(); + } catch (std::exception &e) { + std::cerr << "error: " << "error getting job for solo mining: " << e.what() << std::endl; std::this_thread::sleep_for(50ms); - continue; } + }); + } else { + c->collab_thread = std::thread([c]() { + while (c->miner->state() != miner::Miner::Running) {} + while (c->miner->running()) + try { + auto j = c->stratum.get_job(); + if (!j) { + if (!c->stratum.connected()) { + c->miner->clear_job(); + } + std::this_thread::sleep_for(50ms); + continue; + } - c->miner->submit_job(*j); + c->miner->submit_job(*j); - } catch(std::exception& e) { - std::cerr << "error: " << "error getting job: " << e.what()<< std::endl; - std::this_thread::sleep_for(50ms); - } - }); - return true; + } catch (std::exception &e) { + std::cerr << "error: " << "error getting job: " << e.what() << std::endl; + std::this_thread::sleep_for(50ms); + } + }); + return true; + } } - catch(std::exception& e) - { - std::cerr << "error: " << "error starting miners: " << e.what()<< std::endl; + catch (std::exception &e) { + std::cerr << "error: " << "error starting miners: " << e.what() << std::endl; return false; } - void stop_miner(Context* c) - { + void stop_miner(Context *c) { assert(c); - if(!c->miner) { + if (!c->miner) { return; } c->miner->stop(); } - bool is_stratum_running(Context* c) - { + bool is_stratum_running(Context *c) { assert(c); return c->stratum.running(); } - bool is_miner_running(Context* c) - { + bool is_miner_running(Context *c) { assert(c); return c->miner && c->miner->running(); } - bool is_stratum_stopping(Context* c) - { + bool is_stratum_stopping(Context *c) { assert(c); return c->stratum.stopping(); } - bool is_miner_stopping(Context* c) - { + bool is_miner_stopping(Context *c) { assert(c); - return c->miner && c->miner->stopping(); + return c->miner && c->miner->stopping(); } - int number_of_cores() - { + int number_of_cores() { return std::thread::hardware_concurrency(); } - int number_of_gpus() - { + int number_of_gpus() { return miner::GpuDevices(); } - size_t free_memory_on_gpu(int device){ + size_t free_memory_on_gpu(int device) { return miner::CudaGetFreeMemory(device); }; - MinerStat to_public_stat(const miner::Stat& s) - { + MinerStat to_public_stat(const miner::Stat &s) { return { - s.start.time_since_epoch().count(), - s.end.time_since_epoch().count(), - s.seconds(), - s.attempts_per_second(), - s.cycles_per_second(), - s.shares_per_second(), - s.attempts, - s.cycles, - s.shares + s.start.time_since_epoch().count(), + s.end.time_since_epoch().count(), + s.seconds(), + s.attempts_per_second(), + s.cycles_per_second(), + s.shares_per_second(), + s.attempts, + s.cycles, + s.shares }; } - MinerStats get_miner_stats(Context* c) - { + MinerStats get_miner_stats(Context *c) { assert(c); - if(!c->miner) return {}; + if (!c->miner) return {}; auto total = c->miner->total_stats(); auto history = c->miner->stats(); @@ -315,7 +361,7 @@ namespace merit return s; } - std::vector gpus_info(){ + std::vector gpus_info() { return miner::GPUInfo(); }; diff --git a/src/stratum/stratum.cpp b/src/stratum/stratum.cpp index 8a098f6..d37cf58 100644 --- a/src/stratum/stratum.cpp +++ b/src/stratum/stratum.cpp @@ -29,6 +29,7 @@ * also delete it here. */ #include "merit/stratum/stratum.hpp" +#include "merit/merkle/merkle.hpp" #include #include @@ -36,9 +37,12 @@ #include +#include #include #include #include +#include +#include #if defined _WIN32 || defined WIN32 || defined OS_WIN64 || defined _WIN64 || defined WIN64 || defined WINNT #include @@ -77,6 +81,8 @@ namespace merit { } + unsigned int Client::_solo_job_id = 0; + Client::~Client() { disconnect(); @@ -194,11 +200,13 @@ namespace merit boost::asio::connect(_socket, endpoints, e); if(e) { + std::cout << "Error: " << e << std::endl; disconnect(); return false; } if(!set_socket_opts(_socket)) { + std::cout << "Error while setting socket options" << std::endl; return false; } @@ -358,6 +366,198 @@ namespace merit return true; } + bool Client::mining_set_solo_job(const pt::ptree& params) + { + auto res = convert_blocktemplate(params); + + auto job_id = res.get("id"); + auto prevhash = res.get("prevhash"); + auto coinbase1 = res.get("coinbase1"); + auto coinbase2 = res.get("coinbase2"); + auto merkle_array = res.get_child("merkle_branches"); + auto version = res.get("version"); + auto nbits = res.get("bits"); + auto edgebits = res.get("edgebits"); + auto time = res.get("time"); + auto is_clean = res.get("is_clean"); + + if(prevhash.size() != 64) { return false; } + if(version.size() != 8) { return false; } + if(nbits.size() != 8) { return false; } + if(time.size() != 8) { return false; } + + std::lock_guard guard{_job_mutex}; + Job j; + + if(!util::parse_hex(prevhash, j.prevhash)) { return false;} + if(!util::parse_hex(version, j.version)) { return false;} + if(!util::parse_hex(nbits, j.nbits)) { return false;} + if(!util::parse_hex(time, j.time)) { return false;} + + j.nedgebits = edgebits; + + j.coinbase1_size = coinbase1.size()/2; + + if(!util::parse_hex(coinbase1, j.coinbase)) { return false; } + j.coinbase.insert(j.coinbase.end(), _xnonce1.begin(), _xnonce1.end()); + + j.xnonce2_start = j.coinbase.size(); + j.xnonce2_size = _xnonce2_size; + + j.coinbase.insert(j.coinbase.end(), _xnonce2_size, 0); + if(!util::parse_hex(coinbase2, j.coinbase)) { return false; } + + j.id = job_id; + + for(const auto& hex : merkle_array) { + std::string s = hex.second.get_value(); + util::ubytes bin; + if(!util::parse_hex(s, bin)) { + return false; + } + + j.merkle.push_back(bin); + } + + j.diff = _next_diff; + j.clean = is_clean; + _new_job = true; + std::cerr << "info: " << "new solo job: " << j.id << " time: " << time << " nbits: " << nbits << " edgebits: " << j.nedgebits << " prevhash: " << prevhash << std::endl; + + _job = j; + + return true; + + } + + pt::ptree Client::convert_blocktemplate(const pt::ptree& params) + { + std::cout << "converting blocktemplate" << std::endl; + + pt::ptree res{}; + std::string tmp; + std::stringstream stream; + + res.add("id", get_solo_job_id()); + + auto prev = params.get("result.previousblockhash"); + + // then divide to the 8 sybmols parts, reverse array of this parts and join them together + std::string reversed_prevblockhash; + std::vector parts_of_hash; + for (int i = 0; i < 8; ++i) + parts_of_hash.push_back(prev.substr(8 * i, 8)); + + std::reverse(parts_of_hash.begin(), parts_of_hash.end()); + + for(const auto& part: parts_of_hash) + reversed_prevblockhash += part; + + res.add("prevhash", reversed_prevblockhash); + + // TODO: fix this + res.add("coinbase1", "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1d03693e0503366c1708"); + res.add("coinbase2", "0b2f4d65726974506f6f6c2fffffffff160aca9a3b000000001976a9144bfac9c3a88aab06ae1dcdf59a22eef6dbab361b88acd2665703000000001976a91429b3b93cf44ffa4f14415ca00e4b7cd0a44e0b2988ac07c36003000000001976a9145cdcaad0a61feeff8c474d50daf79885bd08694188acc8d3ef02000000001976a914c5d1ce565e99547c2bf056c996b5b8409356b27d88acca231603000000001976a9144904ee33f9345b6532ade7e043f6f61e8c69e3b388acafc79703000000001976a9142c930dff417ce135d133a0d6b4bd91b27885390788acc1916f03000000001976a914d7552b88da89bce65f8e8f6825391413e03d7b7488acfeb95b03000000001976a914a6080afe276ac0f5ae29926cf3fd67539dd0dbd888ac28bf5103000000001976a9145aeb58b3a35dc7282e15ff71a6b21ee1c21021c088ac03ac3d03000000001976a9145fab2bf7f998f84bf085e44a7edb61868361a1cf88acf50e4203000000001976a91401a2071430bdd5f5264bf9970ec0cc2649cbe88988ac0e4c6a02000000001976a914dbc5297edb42d494a7de6d6e126306b83f3b805288ac397f8302000000001976a91470e932c20dc49f72eb669a6a3ad7fe026175708988aca64e3603000000001976a9149b1b860efdb47e2dd0f9504326d5217beedbba5488ac6842ad02000000001976a914efe006f514be4e91118c835966909ddabd5a633a88ac98155c02000000001976a914e5f369d2151e8591766912d406a8f884d7690cd188ac6018b702000000001976a914a5f97cb5333d20ac335431dba391d2e8b09cc33e88ac8eb6b702000000001976a91492f32bed913273f7a11dc26a21ad3c3675732d0e88ac79626502000000001976a914c82be3807dfb9f02a2996bc6ad0b5ca29297fca888ace2f38802000000001976a9143fd2058fb6faeb7090798692d1f51ad44e46960b88acc7831d03000000001976a914ee34a60e8fe4223c0ad57600b7d70713c73c216e88ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf900000000"); + + // Merkle array of transactions, invites and referrals + std::vector hashes{}; + std::vector bin_hashes{}; + int i = 0; + BOOST_FOREACH(const pt::ptree::value_type &v, params.get_child("result.transactions")){ + // to skip the first transaction + if (i != 0) + hashes.push_back(v.second.get("txid")); + + i++; + } + // 2... for refs + BOOST_FOREACH(const pt::ptree::value_type &v, params.get_child("result.referrals")){ + hashes.push_back(v.second.get("hash")); + } + + // 3... for invites + BOOST_FOREACH(const pt::ptree::value_type &v, params.get_child("result.invites")){ + hashes.push_back(v.second.get("hash")); + } + + // prepare hashes + // to do it: divide it by 2 bytes and reverse(similar as with prevblockhash) + // not sure why should we do that, but pool code doing the same thing + std::string reversed_hash{}; + std::vector parts_of_merkle_hash{}; + + for (int i = 0; i < hashes.size(); ++i) { + // 1. divive by 2 bytes(chars) + for (unsigned long offset = 0; offset < hashes[i].size(); offset += 2) + parts_of_merkle_hash.push_back(hashes[i].substr(offset, 2)); + + // 2. reverse + std::reverse(parts_of_merkle_hash.begin(), parts_of_merkle_hash.end()); + + // 3. join + for(const auto& part: parts_of_merkle_hash) + reversed_hash += part; + + hashes[i] = reversed_hash; + parts_of_merkle_hash.clear(); reversed_hash = ""; + } + + // unhex hashes: hex to byte array + for(unsigned int i = 0; i < hashes.size(); ++i){ + char* target = new char[hashes[i].size() / 2 + 1]; + merit::util::hex2bin(hashes[i].c_str(), target); + target[hashes[i].size() / 2] = '\0'; + bin_hashes.push_back(target); + } + + + try { + auto merkle_tree = merit::merkle::MerkleTree(bin_hashes); + auto branches = merkle_tree.branches(); + + // push merkle branches to the result + pt::ptree children; + for(const auto& branch: branches){ + pt::ptree child; + child.put("", branch); + children.push_back(std::make_pair("", child)); + } + + res.add_child("merkle_branches", children); + } catch (std::exception &e) { + std::cout << "An error occurred during 'Merkle Tree' computations = " << e.what() << std::endl; + } + + auto block_version = boost::lexical_cast(params.get("result.version")); // get version parameter + + // divide into bytes and take hex of each of them + // e.g. 671088640 -> 2800000000 + std::bitset<32> version_bits(block_version); + stream << std::hex << version_bits.to_ulong(); + + res.add("version", stream.str()); + stream.clear(); stream.str(""); + + res.add("bits", params.get("result.bits")); + + res.add("edgebits", params.get("result.edgebits")); + + uint32_t time = static_cast(atoi(params.get("result.curtime").c_str())); + std::bitset<32> time_bits(time); + stream << std::hex << time_bits.to_ulong(); + res.add("time", stream.str()); + stream.clear(); stream.str(""); + + res.add("is_clean", true); + + std::cout << "=== CONVERTING BLOCKTEMPLATE ===" << std::endl; + std::ostringstream oss; + pt::write_json(oss, res); + std::cout << oss.str() << std::endl; + + return res; + } + bool Client::client_reconnect(const pt::ptree& params) { auto v = params.begin(); @@ -413,6 +613,15 @@ namespace merit return true; } + if(*method == "mining.get_solo_job"){ + if(!mining_set_solo_job(val)){ + std::cerr << "error: " << "unable to set mining.set_solo_job" << std::endl; + return false; + } + + return true; + } + auto params = val.get_child_optional("params"); if(!params) { std::cerr << "error: " << "unable to get params from response" << std::endl; @@ -458,6 +667,7 @@ namespace merit _state = Authorizing; std::stringstream req; req << "{\"id\": 2, \"method\": \"mining.authorize\", \"params\": [\"" << _user << "\", \"" << _pass << "\"]}"; + std::cout << "=== Authorizing: " << req.str() << std::endl; if (!send(req.str())) { std::cerr << "error: " << "error sending authorize request" << std::endl; @@ -470,6 +680,8 @@ namespace merit bool Client::reconnect() { + + std::cout << std::endl << " !!! === RECONNECTING === !!! " << std::endl << std::endl; using namespace std::chrono_literals; disconnect(); bool connected = false; @@ -505,13 +717,13 @@ namespace merit return true; } - bool Client::run() + bool Client::run(bool solo_mining) { _run_state = Running; while (_run_state == Running) try { std::string res; - if(!recv(res)) { + if((solo_mining && !recv_with_headers(res)) || (!solo_mining && !recv(res))) { std::cerr << "error: " << "error receiving" << std::endl; throw std::runtime_error("error receiving"); } @@ -521,22 +733,34 @@ namespace merit } pt::ptree val; + + std::cout << "RES: " << res << std::endl; if(!parse_json(res, val)) { _sockbuf.clear(); std::cerr << "error parsing stratum response: " << res << std::endl; continue; } + if(solo_mining){ + val.put("method", val.get("id")); + } + + std::ostringstream oss; + pt::write_json(oss, val); + std::cout << "JSON: " << oss.str() << std::endl; + if(!handle_command(val, res)) { continue; } } catch(std::exception& e) { - if(!reconnect()) { - std::cerr << "error: " << "failed to reconnect" << std::endl; - return false; - } else if(_run_state == Running) { - std::cerr << "info: reconnected!" << std::endl; + if(!solo_mining){ // TODO: remove this if statement later + if(!reconnect()) { + std::cerr << "error: " << "failed to reconnect" << std::endl; + return false; + } else if(_run_state == Running) { + std::cerr << "info: reconnected!" << std::endl; + } } } @@ -578,6 +802,28 @@ namespace merit return _job; } + MaybeJob Client::get_solo_job(const std::string& auth_token) + { + std::stringstream req; + req << "POST / HTTP/1.1\n" << + "Content-Type: text/plain\n" << + "Authorization: Basic "<< auth_token << "\n" << + "Accept: */*\n" << + "Content-Length: " << 157 + _user.size() << "\n\n" << + "{\"method\": \"getblocktemplate\", \"jsonrpc\": \"2.0\", \"params\": [{\"capabilities\": [\"coinbasetxn\", \"workid\", \"coinbase/append\"]}, \""<< _user <<"\"], \"id\": \"mining.get_solo_job\"}"; + + std::cout << "=== req: " << req.str() << std::endl; + + if(!send(req.str())) { + std::cerr << "error: " << "Error getting blocktemplate for mining: " << req.str() << std::endl; + disconnect(); + } else { + std::cout << "info: " << "getting blocktemplate for " << _user << std::endl; + } + + return _job; + } + void Client::submit_work(const util::Work& w) { std::string xnonce2_hex; @@ -647,6 +893,8 @@ namespace merit return false; } + std::cout << "=== Another response(subscribe_resp): " << resp_line << std::endl; + pt::ptree resp; if(!parse_json(resp_line, resp)) { std::cerr << "error: " << "error parsing response: " << resp_line << std::endl; @@ -711,6 +959,7 @@ namespace merit { auto message_with_nl = message + "\n"; std::lock_guard guard{_sock_mutex}; + std::cout << "=== message to send: " << message_with_nl << std::endl; boost::system::error_code error; asio::write(_socket, boost::asio::buffer(message_with_nl), error); return !error; @@ -753,6 +1002,8 @@ namespace merit message.resize(size); std::copy(_sockbuf.begin(), nl, message.begin()); +// std::cout << "=== receive message: " << message << " ===" << std::endl; + if (_sockbuf.size() > size + 1) { std::copy(_sockbuf.begin() + size + 1, _sockbuf.end(), _sockbuf.begin()); _sockbuf.resize(_sockbuf.size() - size - 1); @@ -763,6 +1014,22 @@ namespace merit return true; } + bool Client::recv_with_headers(std::string& res) + { + // Skip 4 lines with HTTP headers + blank line + recv(res); recv(res); recv(res); recv(res); recv(res); + + return recv(res); + } + + unsigned int Client::get_solo_job_id() + { + // return and increment(so all ids will be different) + return _solo_job_id++; + } + + + void diff_to_target(std::array& target, double diff) { int k; diff --git a/src/util/util.cpp b/src/util/util.cpp index 2594543..d734192 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -48,5 +48,28 @@ namespace merit picosha2::hash256(data, data+len, d.begin(), d.end()); picosha2::hash256(d.begin(), d.end(), digest, digest+picosha2::k_digest_size); } + + int char2int(char input) + { + if(input >= '0' && input <= '9') + return input - '0'; + if(input >= 'A' && input <= 'F') + return input - 'A' + 10; + if(input >= 'a' && input <= 'f') + return input - 'a' + 10; + throw std::invalid_argument("Invalid input string"); + } + + // This function assumes src to be a zero terminated sanitized string with + // an even number of [0-9a-f] characters, and target to be sufficiently large + void hex2bin(const char* src, char* target) + { + while(*src && src[1]) + { + *(target++) = char2int(*src)*16 + char2int(src[1]); + src += 2; + } + } + } }