// Copyright (c) 2014-2016, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include #include #include #include #include #include #include "misc_language.h" #include "include_base_utils.h" #include "cryptonote_basic_impl.h" #include "cryptonote_format_utils.h" #include "file_io_utils.h" #include "common/command_line.h" #include "string_coding.h" #include "storages/portable_storage_template_helper.h" using namespace epee; #include "miner.h" extern "C" void slow_hash_allocate_state(); extern "C" void slow_hash_free_state(); namespace cryptonote { namespace { const command_line::arg_descriptor arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; const command_line::arg_descriptor arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; const command_line::arg_descriptor arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; } miner::miner(i_miner_handler* phandler):m_stop(1), m_template(boost::value_initialized()), m_template_no(0), m_diffic(0), m_thread_index(0), m_phandler(phandler), m_height(0), 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) { } //----------------------------------------------------------------------------------------------------- miner::~miner() { stop(); } //----------------------------------------------------------------------------------------------------- bool miner::set_block_template(const block& bl, const difficulty_type& di, uint64_t height) { CRITICAL_REGION_LOCAL(m_template_lock); m_template = bl; m_diffic = di; m_height = height; ++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 = AUTO_VAL_INIT(bl); difficulty_type di = AUTO_VAL_INIT(di); uint64_t height = AUTO_VAL_INIT(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_phandler->get_block_template(bl, m_mine_address, di, height, extra_nonce)) { LOG_ERROR("Failed to get_block_template(), stopping mining"); return false; } set_block_template(bl, di, height); return true; } //----------------------------------------------------------------------------------------------------- bool miner::on_idle() { m_update_block_template_interval.do_call([&](){ if(is_mining())request_block_template(); return true; }); m_update_merge_hr_interval.do_call([&](){ merge_hr(); return true; }); return true; } //----------------------------------------------------------------------------------------------------- void miner::do_print_hashrate(bool do_hr) { m_do_print_hashrate = do_hr; } //----------------------------------------------------------------------------------------------------- void miner::merge_hr() { if(m_last_hr_merge_time && is_mining()) { m_current_hash_rate = m_hashes * 1000 / ((misc_utils::get_tick_count() - m_last_hr_merge_time + 1)); CRITICAL_REGION_LOCAL(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(), 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 = misc_utils::get_tick_count(); m_hashes = 0; } //----------------------------------------------------------------------------------------------------- void miner::init_options(boost::program_options::options_description& desc) { command_line::add_arg(desc, arg_extra_messages); command_line::add_arg(desc, arg_start_mining); command_line::add_arg(desc, arg_mining_threads); } //----------------------------------------------------------------------------------------------------- bool miner::init(const boost::program_options::variables_map& vm, bool testnet) { if(command_line::has_arg(vm, arg_extra_messages)) { std::string buff; bool r = file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_extra_messages), buff); CHECK_AND_ASSERT_MES(r, false, "Failed to load file with extra messages: " << command_line::get_arg(vm, arg_extra_messages)); 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++) { string_tools::trim(extra_vec[i]); if(!extra_vec[i].size()) continue; std::string buff = string_encoding::base64_decode(extra_vec[i]); if(buff != "0") m_extra_messages[i] = buff; } m_config_folder_path = boost::filesystem::path(command_line::get_arg(vm, arg_extra_messages)).parent_path().string(); m_config = AUTO_VAL_INIT(m_config); epee::serialization::load_t_from_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); LOG_PRINT_L0("Loaded " << m_extra_messages.size() << " extra messages, current index " << m_config.current_extra_message_index); } if(command_line::has_arg(vm, arg_start_mining)) { if(!cryptonote::get_account_address_from_str(m_mine_address, testnet, command_line::get_arg(vm, arg_start_mining))) { LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled"); return false; } m_threads_total = 1; m_do_mining = true; if(command_line::has_arg(vm, arg_mining_threads)) { m_threads_total = command_line::get_arg(vm, arg_mining_threads); } } return true; } //----------------------------------------------------------------------------------------------------- bool miner::is_mining() const { return !m_stop; } //----------------------------------------------------------------------------------------------------- const account_public_address& miner::get_mining_address() const { return m_mine_address; } //----------------------------------------------------------------------------------------------------- uint32_t miner::get_threads_count() const { return m_threads_total; } //----------------------------------------------------------------------------------------------------- bool miner::start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs) { m_mine_address = adr; m_threads_total = static_cast(threads_count); m_starter_nonce = crypto::rand(); CRITICAL_REGION_LOCAL(m_threads_lock); if(is_mining()) { LOG_ERROR("Starting miner but it's already started"); return false; } if(!m_threads.empty()) { LOG_ERROR("Unable to start miner because there are active mining threads"); return false; } if(!m_template_no) request_block_template();//lets update block template boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); for(size_t i = 0; i != threads_count; i++) { m_threads.push_back(boost::thread(attrs, boost::bind(&miner::worker_thread, this))); } LOG_PRINT_L0("Mining has started with " << threads_count << " threads, good luck!" ); return true; } //----------------------------------------------------------------------------------------------------- uint64_t miner::get_speed() const { if(is_mining()) { return m_current_hash_rate; } else { return 0; } } //----------------------------------------------------------------------------------------------------- void miner::send_stop_signal() { boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); } //----------------------------------------------------------------------------------------------------- bool miner::stop() { LOG_PRINT_L1("Miner has received stop signal"); if (!is_mining()) LOG_PRINT_L1("Not mining - nothing to stop" ); return true; send_stop_signal(); CRITICAL_REGION_LOCAL(m_threads_lock); BOOST_FOREACH(boost::thread& th, m_threads) th.join(); LOG_PRINT_L0("Mining has been stopped, " << m_threads.size() << " finished" ); m_threads.clear(); return true; } //----------------------------------------------------------------------------------------------------- bool miner::find_nonce_for_given_block(block& bl, const difficulty_type& diffic, uint64_t height) { for(; bl.nonce != std::numeric_limits::max(); bl.nonce++) { crypto::hash h; get_block_longhash(bl, h, height); 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() { CRITICAL_REGION_LOCAL(m_miners_count_lock); ++m_pausers_count; if(m_pausers_count == 1 && is_mining()) LOG_PRINT_L2("MINING PAUSED"); } //----------------------------------------------------------------------------------------------------- void miner::resume() { CRITICAL_REGION_LOCAL(m_miners_count_lock); --m_pausers_count; if(m_pausers_count < 0) { m_pausers_count = 0; LOG_PRINT_RED_L0("Unexpected miner::resume() called"); } if(!m_pausers_count && is_mining()) LOG_PRINT_L2("MINING RESUMED"); } //----------------------------------------------------------------------------------------------------- bool miner::worker_thread() { uint32_t th_local_index = boost::interprocess::ipcdetail::atomic_inc32(&m_thread_index); LOG_PRINT_L0("Miner thread was started ["<< th_local_index << "]"); log_space::log_singletone::set_thread_log_prefix(std::string("[miner ") + std::to_string(th_local_index) + "]"); uint32_t nonce = m_starter_nonce + th_local_index; uint64_t height = 0; difficulty_type local_diff = 0; uint32_t local_template_ver = 0; block b; slow_hash_allocate_state(); while(!m_stop) { if(m_pausers_count)//anti split workaround { misc_utils::sleep_no_w(100); continue; } if(local_template_ver != m_template_no) { CRITICAL_REGION_BEGIN(m_template_lock); b = m_template; local_diff = m_diffic; height = m_height; CRITICAL_REGION_END(); local_template_ver = m_template_no; nonce = m_starter_nonce + th_local_index; } if(!local_template_ver)//no any set_block_template call { LOG_PRINT_L2("Block template not set yet"); epee::misc_utils::sleep_no_w(1000); continue; } b.nonce = nonce; crypto::hash h; get_block_longhash(b, h, height); if(check_hash(h, local_diff)) { //we lucky! ++m_config.current_extra_message_index; LOG_PRINT_GREEN("Found block for difficulty: " << local_diff, LOG_LEVEL_0); if(!m_phandler->handle_block_found(b)) { --m_config.current_extra_message_index; }else { //success update, lets update config if (!m_config_folder_path.empty()) epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); } } nonce+=m_threads_total; ++m_hashes; } slow_hash_free_state(); LOG_PRINT_L0("Miner thread stopped ["<< th_local_index << "]"); return true; } //----------------------------------------------------------------------------------------------------- }