// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers // // This file is part of Bytecoin. // // Bytecoin is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Bytecoin 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . #include "miner.h" #include #include #include #include #include #include #include #include #include #include "cryptonote_format_utils.h" #include "Common/command_line.h" // epee #include "storages/portable_storage_template_helper.h" using namespace Logging; namespace CryptoNote { miner::miner(const Currency& currency, i_miner_handler& handler, Logging::ILogger& log) : m_currency(currency), logger(log, "miner"), m_stop(true), m_template(boost::value_initialized()), m_template_no(0), m_diffic(0), m_handler(handler), m_pausers_count(0), m_threads_total(0), m_starter_nonce(0), m_last_hr_merge_time(0), m_hashes(0), m_do_print_hashrate(false), m_do_mining(false), m_current_hash_rate(0), m_update_block_template_interval(5), m_update_merge_hr_interval(2) { } //----------------------------------------------------------------------------------------------------- miner::~miner() { stop(); } //----------------------------------------------------------------------------------------------------- bool miner::set_block_template(const Block& bl, const difficulty_type& di) { std::lock_guard lk(m_template_lock); m_template = bl; if (BLOCK_MAJOR_VERSION_2 == m_template.majorVersion) { CryptoNote::tx_extra_merge_mining_tag mm_tag; mm_tag.depth = 0; if (!CryptoNote::get_aux_block_header_hash(m_template, mm_tag.merkle_root)) { return false; } m_template.parentBlock.minerTx.extra.clear(); if (!CryptoNote::append_mm_tag_to_extra(m_template.parentBlock.minerTx.extra, mm_tag)) { return false; } } m_diffic = di; ++m_template_no; m_starter_nonce = crypto::rand(); return true; } //----------------------------------------------------------------------------------------------------- bool miner::on_block_chain_update() { if (!is_mining()) { return true; } return request_block_template(); } //----------------------------------------------------------------------------------------------------- bool miner::request_block_template() { Block bl = boost::value_initialized(); difficulty_type di = 0; uint32_t height; CryptoNote::blobdata extra_nonce; if(m_extra_messages.size() && m_config.current_extra_message_index < m_extra_messages.size()) { extra_nonce = m_extra_messages[m_config.current_extra_message_index]; } if(!m_handler.get_block_template(bl, m_mine_address, di, height, extra_nonce)) { logger(ERROR) << "Failed to get_block_template(), stopping mining"; return false; } set_block_template(bl, di); return true; } //----------------------------------------------------------------------------------------------------- bool miner::on_idle() { m_update_block_template_interval.call([&](){ if(is_mining()) request_block_template(); return true; }); m_update_merge_hr_interval.call([&](){ merge_hr(); return true; }); return true; } //----------------------------------------------------------------------------------------------------- void miner::do_print_hashrate(bool do_hr) { m_do_print_hashrate = do_hr; } uint64_t millisecondsSinceEpoch() { auto now = std::chrono::steady_clock::now(); return std::chrono::duration_cast(now.time_since_epoch()).count(); } //----------------------------------------------------------------------------------------------------- void miner::merge_hr() { if(m_last_hr_merge_time && is_mining()) { m_current_hash_rate = m_hashes * 1000 / (millisecondsSinceEpoch() - m_last_hr_merge_time + 1); std::lock_guard lk(m_last_hash_rates_lock); m_last_hash_rates.push_back(m_current_hash_rate); if(m_last_hash_rates.size() > 19) m_last_hash_rates.pop_front(); if(m_do_print_hashrate) { uint64_t total_hr = std::accumulate(m_last_hash_rates.begin(), m_last_hash_rates.end(), static_cast(0)); float hr = static_cast(total_hr)/static_cast(m_last_hash_rates.size()); std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << ENDL; } } m_last_hr_merge_time = millisecondsSinceEpoch(); m_hashes = 0; } bool miner::init(const MinerConfig& config) { if (!config.extraMessages.empty()) { std::string buff; if (!Common::loadFileToString(config.extraMessages, buff)) { logger(ERROR, BRIGHT_RED) << "Failed to load file with extra messages: " << config.extraMessages; return false; } std::vector extra_vec; boost::split(extra_vec, buff, boost::is_any_of("\n"), boost::token_compress_on ); m_extra_messages.resize(extra_vec.size()); for(size_t i = 0; i != extra_vec.size(); i++) { boost::algorithm::trim(extra_vec[i]); if(!extra_vec[i].size()) continue; std::string buff = Common::base64Decode(extra_vec[i]); if(buff != "0") m_extra_messages[i] = buff; } m_config_folder_path = boost::filesystem::path(config.extraMessages).parent_path().string(); m_config = boost::value_initialized(); epee::serialization::load_t_from_json_file(m_config, m_config_folder_path + "/" + CryptoNote::parameters::MINER_CONFIG_FILE_NAME); logger(INFO) << "Loaded " << m_extra_messages.size() << " extra messages, current index " << m_config.current_extra_message_index; } if(!config.startMining.empty()) { if (!m_currency.parseAccountAddressString(config.startMining, m_mine_address)) { LOG_ERROR("Target account address " << config.startMining << " has wrong format, starting daemon canceled"); return false; } m_threads_total = 1; m_do_mining = true; if(config.miningThreads > 0) { m_threads_total = config.miningThreads; } } return true; } //----------------------------------------------------------------------------------------------------- bool miner::is_mining() { return !m_stop; } //----------------------------------------------------------------------------------------------------- bool miner::start(const AccountPublicAddress& adr, size_t threads_count, const boost::thread::attributes& attrs) { if (is_mining()) { logger(ERROR) << "Starting miner but it's already started"; return false; } std::lock_guard lk(m_threads_lock); if(!m_threads.empty()) { logger(ERROR) << "Unable to start miner because there are active mining threads"; return false; } m_mine_address = adr; m_threads_total = static_cast(threads_count); m_starter_nonce = crypto::rand(); if (!m_template_no) { request_block_template(); //lets update block template } m_stop = false; for (uint32_t i = 0; i != threads_count; i++) { m_threads.push_back(boost::thread(attrs, boost::bind(&miner::worker_thread, this, i))); } logger(INFO) << "Mining has started with " << threads_count << " threads, good luck!"; return true; } //----------------------------------------------------------------------------------------------------- uint64_t miner::get_speed() { if(is_mining()) return m_current_hash_rate; else return 0; } //----------------------------------------------------------------------------------------------------- void miner::send_stop_signal() { m_stop = true; } //----------------------------------------------------------------------------------------------------- bool miner::stop() { send_stop_signal(); std::lock_guard lk(m_threads_lock); for (auto& th : m_threads) { th.join(); } m_threads.clear(); logger(INFO) << "Mining has been stopped, " << m_threads.size() << " finished" ; return true; } //----------------------------------------------------------------------------------------------------- bool miner::find_nonce_for_given_block(crypto::cn_context &context, Block& bl, const difficulty_type& diffic) { unsigned nthreads = std::thread::hardware_concurrency(); if (nthreads > 0 && diffic > 5) { std::vector> threads(nthreads); std::atomic foundNonce; std::atomic found(false); uint32_t startNonce = crypto::rand(); for (unsigned i = 0; i < nthreads; ++i) { threads[i] = std::async(std::launch::async, [&, i]() { crypto::cn_context localctx; crypto::hash h; Block lb(bl); // copy to local block for (uint32_t nonce = startNonce + i; !found; nonce += nthreads) { lb.nonce = nonce; if (!get_block_longhash(localctx, lb, h)) { return; } if (check_hash(h, diffic)) { foundNonce = nonce; found = true; return; } } }); } for (auto& t : threads) { t.wait(); } if (found) { bl.nonce = foundNonce.load(); } return found; } else { for (; bl.nonce != std::numeric_limits::max(); bl.nonce++) { crypto::hash h; if (!get_block_longhash(context, bl, h)) { return false; } if (check_hash(h, diffic)) { return true; } } } return false; } //----------------------------------------------------------------------------------------------------- void miner::on_synchronized() { if(m_do_mining) { boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); start(m_mine_address, m_threads_total, attrs); } } //----------------------------------------------------------------------------------------------------- void miner::pause() { std::lock_guard lk(m_miners_count_lock); ++m_pausers_count; if(m_pausers_count == 1 && is_mining()) logger(TRACE) << "MINING PAUSED"; } //----------------------------------------------------------------------------------------------------- void miner::resume() { std::lock_guard lk(m_miners_count_lock); --m_pausers_count; if(m_pausers_count < 0) { m_pausers_count = 0; logger(ERROR) << "Unexpected miner::resume() called"; } if(!m_pausers_count && is_mining()) logger(TRACE) << "MINING RESUMED"; } //----------------------------------------------------------------------------------------------------- bool miner::worker_thread(uint32_t th_local_index) { logger(INFO) << "Miner thread was started ["<< th_local_index << "]"; uint32_t nonce = m_starter_nonce + th_local_index; difficulty_type local_diff = 0; uint32_t local_template_ver = 0; crypto::cn_context context; Block b; while(!m_stop) { if(m_pausers_count) //anti split workaround { std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } if(local_template_ver != m_template_no) { std::unique_lock lk(m_template_lock); b = m_template; local_diff = m_diffic; lk.unlock(); local_template_ver = m_template_no; nonce = m_starter_nonce + th_local_index; } if(!local_template_ver)//no any set_block_template call { logger(TRACE) << "Block template not set yet"; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); continue; } b.nonce = nonce; crypto::hash h; if (!m_stop && !get_block_longhash(context, b, h)) { logger(ERROR) << "Failed to get block long hash"; m_stop = true; } if (!m_stop && check_hash(h, local_diff)) { //we lucky! ++m_config.current_extra_message_index; logger(INFO, GREEN) << "Found block for difficulty: " << local_diff; if(!m_handler.handle_block_found(b)) { --m_config.current_extra_message_index; } else { //success update, lets update config epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + CryptoNote::parameters::MINER_CONFIG_FILE_NAME); } } nonce += m_threads_total; ++m_hashes; } logger(INFO) << "Miner thread stopped ["<< th_local_index << "]"; return true; } //----------------------------------------------------------------------------------------------------- }