// Copyright (c) 2014-2018 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Copyright (c) 2012-2013 The Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "include_base_utils.h" using namespace epee; #include #include #include "currency_core.h" #include "common/command_line.h" #include "common/util.h" #include "warnings.h" #include "crypto/crypto.h" #include "currency_core/currency_config.h" #include "currency_format_utils.h" #include "misc_language.h" DISABLE_VS_WARNINGS(4355) #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "core" ENABLE_CHANNEL_BY_DEFAULT("core"); namespace currency { //----------------------------------------------------------------------------------------------- core::core(i_currency_protocol* pprotocol): m_mempool(m_blockchain_storage, pprotocol), m_blockchain_storage(m_mempool), m_miner(this, m_blockchain_storage), m_miner_address(boost::value_initialized()), m_starter_message_showed(false) { set_currency_protocol(pprotocol); } void core::set_currency_protocol(i_currency_protocol* pprotocol) { if(pprotocol) m_pprotocol = pprotocol; else m_pprotocol = &m_protocol_stub; m_mempool.set_protocol(m_pprotocol); } //----------------------------------------------------------------------------------- bool core::set_checkpoints(checkpoints&& chk_pts) { return m_blockchain_storage.set_checkpoints(std::move(chk_pts)); } //----------------------------------------------------------------------------------- void core::init_options(boost::program_options::options_description& desc) { blockchain_storage::init_options(desc); } //----------------------------------------------------------------------------------------------- std::string core::get_config_folder() { return m_config_folder; } //----------------------------------------------------------------------------------------------- bool core::handle_command_line(const boost::program_options::variables_map& vm) { m_config_folder = command_line::get_arg(vm, command_line::arg_data_dir); return true; } //----------------------------------------------------------------------------------------------- uint64_t core::get_current_blockchain_size() const { return m_blockchain_storage.get_current_blockchain_size(); } //----------------------------------------------------------------------------------------------- uint64_t core::get_top_block_height() const { return m_blockchain_storage.get_top_block_height(); } //----------------------------------------------------------------------------------------------- bool core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const { top_id = m_blockchain_storage.get_top_block_id(height); return true; } //----------------------------------------------------------------------------------------------- bool core::get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) { return m_blockchain_storage.get_blocks(start_offset, count, blocks, txs); } //----------------------------------------------------------------------------------------------- bool core::get_blocks(uint64_t start_offset, size_t count, std::list& blocks) { return m_blockchain_storage.get_blocks(start_offset, count, blocks); } //----------------------------------------------------------------------------------------------- bool core::get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs) { return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- bool core::get_transaction(const crypto::hash &h, transaction &tx) { std::vector ids; ids.push_back(h); std::list ltx; std::list missing; if (m_blockchain_storage.get_transactions(ids, ltx, missing)) { if (ltx.size() > 0) { tx = *ltx.begin(); return true; } } return false; } //----------------------------------------------------------------------------------------------- bool core::get_alternative_blocks(std::list& blocks) { return m_blockchain_storage.get_alternative_blocks(blocks); } //----------------------------------------------------------------------------------------------- size_t core::get_alternative_blocks_count() { return m_blockchain_storage.get_alternative_blocks_count(); } //----------------------------------------------------------------------------------------------- bool core::init(const boost::program_options::variables_map& vm) { bool r = handle_command_line(vm); r = m_mempool.init(m_config_folder); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); r = m_blockchain_storage.init(m_config_folder, vm); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); r = m_miner.init(vm); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner"); //check if tx_pool module synchronized with blockchaine storage // if (m_blockchain_storage.get_top_block_id() != m_mempool.get_last_core_hash()) // { // m_mempool.clear(); // LOG_PRINT_MAGENTA("Tx pool had been reset due to missmatch top block id", LOG_LEVEL_0); // } return load_state_data(); } //----------------------------------------------------------------------------------------------- bool core::set_genesis_block(const block& b) { return m_blockchain_storage.reset_and_set_genesis_block(b); } //----------------------------------------------------------------------------------------------- bool core::load_state_data() { // may be some code later return true; } //----------------------------------------------------------------------------------------------- bool core::deinit() { //m_mempool.set_last_core_hash(m_blockchain_storage.get_top_block_id()); m_miner.stop(); m_miner.deinit(); m_mempool.deinit(); m_blockchain_storage.deinit(); return true; } //----------------------------------------------------------------------------------------------- bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool kept_by_block) { tvc = boost::value_initialized(); //want to process all transactions sequentially TIME_MEASURE_START_MS(wait_lock_time); CRITICAL_REGION_LOCAL(m_incoming_tx_lock); TIME_MEASURE_FINISH_MS(wait_lock_time); if(tx_blob.size() > get_max_tx_size()) { LOG_PRINT_L0("WRONG TRANSACTION BLOB, too big size " << tx_blob.size() << ", rejected"); tvc.m_verification_failed = true; return false; } crypto::hash tx_hash = null_hash; transaction tx; TIME_MEASURE_START_MS(parse_tx_time); if(!parse_tx_from_blob(tx, tx_hash, tx_blob)) { LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to parse, rejected"); tvc.m_verification_failed = true; return false; } TIME_MEASURE_FINISH_MS(parse_tx_time); TIME_MEASURE_START_MS(check_tx_syntax_time); if(!check_tx_syntax(tx)) { LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); tvc.m_verification_failed = true; return false; } TIME_MEASURE_FINISH_MS(check_tx_syntax_time); TIME_MEASURE_START_MS(check_tx_semantic_time); if(!check_tx_semantic(tx, kept_by_block)) { LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected"); tvc.m_verification_failed = true; return false; } TIME_MEASURE_FINISH_MS(check_tx_semantic_time); TIME_MEASURE_START_MS(add_new_tx_time); bool r = add_new_tx(tx, tx_hash, get_object_blobsize(tx), tvc, kept_by_block); TIME_MEASURE_FINISH_MS(add_new_tx_time); if(tvc.m_verification_failed) {LOG_PRINT_RED_L0("Transaction verification failed: " << tx_hash);} else if(tvc.m_verification_impossible) {LOG_PRINT_RED_L0("Transaction verification impossible: " << tx_hash);} if (tvc.m_added_to_pool) { LOG_PRINT_L2("incoming tx " << tx_hash << " was added to the pool"); } LOG_PRINT_L2("[CORE HANDLE_INCOMING_TX]: timing " << wait_lock_time << "/" << parse_tx_time << "/" << check_tx_syntax_time << "/" << check_tx_semantic_time << "/" << add_new_tx_time); return r; } //----------------------------------------------------------------------------------------------- bool core::get_stat_info(const core_stat_info::params& pr, core_stat_info& st_inf) { st_inf.mining_speed = m_miner.get_speed(); st_inf.alternative_blocks = m_blockchain_storage.get_alternative_blocks_count(); st_inf.blockchain_height = m_blockchain_storage.get_current_blockchain_size(); st_inf.tx_pool_size = m_mempool.get_transactions_count(); st_inf.top_block_id_str = epee::string_tools::pod_to_hex(m_blockchain_storage.get_top_block_id()); std::list blocks; m_blockchain_storage.get_blocks(m_blockchain_storage.get_current_blockchain_size() - pr.chain_len, static_cast(pr.chain_len), blocks); for (auto& b : blocks) { st_inf.main_chain_blocks.push_back(chain_entry()); st_inf.main_chain_blocks.back().h = get_block_height(b); st_inf.main_chain_blocks.back().id = epee::string_tools::pod_to_hex(get_block_hash(b)); } blocks.clear(); m_blockchain_storage.get_alternative_blocks(blocks); for (auto& b : blocks) { st_inf.alt_chain_blocks.push_back(chain_entry()); st_inf.alt_chain_blocks.back().h = get_block_height(b); st_inf.alt_chain_blocks.back().id = epee::string_tools::pod_to_hex(get_block_hash(b)); } blocks.clear(); auto ch_locked_proxy_container = epee::log_space::get_channels_errors_stat_container(); epee::log_space::channels_err_stat_container_type& ch_container_std = *ch_locked_proxy_container; for (const auto& entry : ch_container_std) { st_inf.errors_stat.push_back(error_stat_entry()); st_inf.errors_stat.back().channel = entry.first; st_inf.errors_stat.back().err_count = entry.second; } st_inf.epic_failure_happend = m_blockchain_storage.get_performnce_data().epic_failure_happend; epee::log_space::log_singletone::get_journal_items(st_inf.errors_journal, pr.logs_journal_len); //check deadlocks auto dl_list = epee::deadlock_guard_singleton::get_deadlock_journal(); if (dl_list.size()) { st_inf.epic_failure_happend = true; st_inf.errors_journal.insert(st_inf.errors_journal.end(), dl_list.begin(), dl_list.end()); } return true; } //----------------------------------------------------------------------------------------------- bool core::check_tx_semantic(const transaction& tx, bool kept_by_block) { if(!tx.vin.size()) { LOG_PRINT_RED_L0("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); return false; } if(!check_inputs_types_supported(tx)) { LOG_PRINT_RED_L0("unsupported input types for tx id= " << get_transaction_hash(tx)); return false; } if(!check_outs_valid(tx)) { LOG_PRINT_RED_L0("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); return false; } if(!check_money_overflow(tx)) { LOG_PRINT_RED_L0("tx has money overflow, rejected for tx id= " << get_transaction_hash(tx)); return false; } uint64_t amount_in = 0; get_inputs_money_amount(tx, amount_in); uint64_t amount_out = get_outs_money_amount(tx); if(amount_in < amount_out) { LOG_PRINT_RED_L0("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); return false; } if(!kept_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_comulative_blocksize_limit() - CURRENCY_COINBASE_BLOB_RESERVED_SIZE) { LOG_PRINT_RED_L0("tx has too big size " << get_object_blobsize(tx) << ", expected no bigger than " << m_blockchain_storage.get_current_comulative_blocksize_limit() - CURRENCY_COINBASE_BLOB_RESERVED_SIZE); return false; } //check if tx use different key images if(!check_tx_inputs_keyimages_diff(tx)) { LOG_PRINT_RED_L0("tx inputs have the same key images"); return false; } if(!check_tx_extra(tx)) { LOG_PRINT_RED_L0("tx has wrong extra, rejected"); return false; } return true; } //----------------------------------------------------------------------------------------------- bool core::check_tx_extra(const transaction& tx) { tx_extra_info ei = AUTO_VAL_INIT(ei); bool r = parse_and_validate_tx_extra(tx, ei); if(!r) return false; return true; } //----------------------------------------------------------------------------------------------- bool core::check_tx_inputs_keyimages_diff(const transaction& tx) { std::unordered_set ki; BOOST_FOREACH(const auto& in, tx.vin) { if (in.type() == typeid(txin_to_key)) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); if (!ki.insert(tokey_in.k_image).second) return false; } } return true; } //----------------------------------------------------------------------------------------------- bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool kept_by_block) { crypto::hash tx_hash = null_hash; uint64_t blob_size = 0; get_transaction_hash(tx, tx_hash, blob_size); return add_new_tx(tx, tx_hash, blob_size, tvc, kept_by_block); } //----------------------------------------------------------------------------------------------- size_t core::get_blockchain_total_transactions() { return m_blockchain_storage.get_total_transactions(); } //----------------------------------------------------------------------------------------------- bool core::get_outs(uint64_t amount, std::list& pkeys) { return m_blockchain_storage.get_outs(amount, pkeys); } //----------------------------------------------------------------------------------------------- bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, size_t blob_size, tx_verification_context& tvc, bool kept_by_block) { if(m_mempool.have_tx(tx_hash)) { LOG_PRINT_L3("add_new_tx: already have tx " << tx_hash << " in the pool"); return true; } if(m_blockchain_storage.have_tx(tx_hash)) { LOG_PRINT_L3("add_new_tx: already have tx " << tx_hash << " in the blockchain"); return true; } return m_mempool.add_tx(tx, tx_hash, blob_size, tvc, kept_by_block); } //----------------------------------------------------------------------------------------------- bool core::get_block_template(block& b, const account_public_address& adr, const account_public_address& stakeholder_address, wide_difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce, bool pos, const pos_entry& pe) { return m_blockchain_storage.create_block_template(b, adr, stakeholder_address, diffic, height, ex_nonce, pos, pe); } //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const { return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); } //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const { return m_blockchain_storage.find_blockchain_supplement(qblock_ids, blocks, total_height, start_height, max_count); } //----------------------------------------------------------------------------------------------- void core::print_blockchain(uint64_t start_index, uint64_t end_index) { m_blockchain_storage.print_blockchain(start_index, end_index); } //----------------------------------------------------------------------------------------------- void core::print_blockchain_index() { m_blockchain_storage.print_blockchain_index(); } //----------------------------------------------------------------------------------------------- void core::print_blockchain_outs(const std::string& file) { m_blockchain_storage.print_blockchain_outs(file); } //----------------------------------------------------------------------------------------------- bool core::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) { return m_blockchain_storage.get_random_outs_for_amounts(req, res); } //----------------------------------------------------------------------------------------------- bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) { return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs); } //----------------------------------------------------------------------------------------------- void core::pause_mine() { m_miner.pause(); } //----------------------------------------------------------------------------------------------- void core::resume_mine() { m_miner.resume(); } //----------------------------------------------------------------------------------------------- bool core::handle_block_found(const block& b, block_verification_context* p_verification_result, bool need_update_miner_block_template) { TIME_MEASURE_START_MS(time_total_ms); block_verification_context bvc = boost::value_initialized(); if (!p_verification_result) p_verification_result = &bvc; m_miner.pause(); misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler([this]() { m_miner.resume(); }); TIME_MEASURE_START_MS(time_add_new_block_ms); m_blockchain_storage.add_new_block(b, *p_verification_result); TIME_MEASURE_FINISH_MS(time_add_new_block_ms); //anyway - update miner template TIME_MEASURE_START_MS(time_update_block_template_ms); if (need_update_miner_block_template) update_miner_block_template(); TIME_MEASURE_FINISH_MS(time_update_block_template_ms); uint64_t time_pack_txs_ms = 0, time_relay_ms = 0; if (p_verification_result->m_verification_failed || !p_verification_result->m_added_to_main_chain) { LOG_PRINT2("failed_mined_blocks.log", "verification_failed: " << p_verification_result->m_verification_failed << ", added_to_main_chain: " << p_verification_result->m_added_to_main_chain << ENDL << currency::obj_to_json_str(b), LOG_LEVEL_0); } CHECK_AND_ASSERT_MES(!p_verification_result->m_verification_failed, false, "mined block has failed to pass verification: id " << get_block_hash(b) << " @ height " << get_block_height(b) << " prev_id: " << b.prev_id); if(p_verification_result->m_added_to_main_chain) { time_pack_txs_ms = epee::misc_utils::get_tick_count(); currency_connection_context exclude_context = boost::value_initialized(); NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); arg.hop = 0; arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_size(); std::list missed_txs; std::list txs; m_blockchain_storage.get_transactions(b.tx_hashes, txs, missed_txs); if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) { LOG_PRINT_L0("Block found (id " << get_block_hash(b) << " @ height " << get_block_height(b) << ") but it seems that reorganize just happened after that, do not relay this block"); return true; } if (txs.size() != b.tx_hashes.size() || missed_txs.size() != 0) { std::stringstream ss; ss << "txs:" << ENDL; for (auto& t : txs) ss << get_transaction_hash(t) << ENDL; ss << "missed txs:" << ENDL; for (auto& tx_id : missed_txs) ss << tx_id << ENDL; LOG_ERROR("can't find some transactions in found block: " << get_block_hash(b) << ", txs.size()=" << txs.size() << ", b.tx_hashes.size()=" << b.tx_hashes.size() << ", missed_txs.size()=" << missed_txs.size() << ENDL << ss.str()); return false; } block_to_blob(b, arg.b.block); //pack transactions for(auto& tx : txs) arg.b.txs.push_back(t_serializable_object_to_blob(tx)); TIME_MEASURE_FINISH_MS(time_pack_txs_ms); time_relay_ms = epee::misc_utils::get_tick_count(); m_pprotocol->relay_block(arg, exclude_context); TIME_MEASURE_FINISH_MS(time_relay_ms); } TIME_MEASURE_FINISH_MS(time_total_ms); LOG_PRINT_L2("handle_block_found timings (ms): total: " << time_total_ms << ", add new block: " << time_add_new_block_ms << ", update template: " << time_update_block_template_ms << ", pack txs: " << time_pack_txs_ms << ", relay: " << time_relay_ms); return p_verification_result->m_added_to_main_chain; } //----------------------------------------------------------------------------------------------- bool core::handle_block_found(const block& b, block_verification_context* p_verification_result /* = nullptr */) { return handle_block_found(b, p_verification_result, true); } //----------------------------------------------------------------------------------------------- void core::on_synchronized() { m_miner.on_synchronized(); } bool core::get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count) { return m_blockchain_storage.get_backward_blocks_sizes(from_height, sizes, count); } //----------------------------------------------------------------------------------------------- bool core::add_new_block(const block& b, block_verification_context& bvc) { return m_blockchain_storage.add_new_block(b, bvc); } //----------------------------------------------------------------------------------------------- bool core::parse_block(const blobdata& block_blob, block& b, block_verification_context& bvc) { if (block_blob.size() > get_max_block_size()) { LOG_PRINT_L0("WRONG BLOCK BLOB, too big size " << block_blob.size() << ", rejected"); bvc.m_verification_failed = true; return false; } b = AUTO_VAL_INIT_T(block); if (!parse_and_validate_block_from_blob(block_blob, b)) { LOG_PRINT_L0("Failed to parse and validate new block"); bvc.m_verification_failed = true; return false; } return true; } //----------------------------------------------------------------------------------------------- bool core::pre_validate_block(block& b, block_verification_context& bvc, const crypto::hash& id) { return m_blockchain_storage.pre_validate_relayed_block(b, bvc, id); } //----------------------------------------------------------------------------------------------- bool core::handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate) { bvc = AUTO_VAL_INIT_T(block_verification_context); block b = AUTO_VAL_INIT(b); if (!parse_block(block_blob, b, bvc)) { LOG_PRINT_L0("Failed to parse and validate new block"); bvc.m_verification_failed = true; return false; } return handle_incoming_block(b, bvc, update_miner_blocktemplate); } //----------------------------------------------------------------------------------------------- bool core::handle_incoming_block(const block& b, block_verification_context& bvc, bool update_miner_blocktemplate) { bool r = add_new_block(b, bvc); if (update_miner_blocktemplate && bvc.m_added_to_main_chain) update_miner_block_template(); return r; } //----------------------------------------------------------------------------------------------- crypto::hash core::get_tail_id() { return m_blockchain_storage.get_top_block_id(); } //----------------------------------------------------------------------------------------------- size_t core::get_pool_transactions_count() { return m_mempool.get_transactions_count(); } //----------------------------------------------------------------------------------------------- bool core::have_block(const crypto::hash& id) { return m_blockchain_storage.have_block(id); } //----------------------------------------------------------------------------------------------- bool core::parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, const blobdata& blob) { return parse_and_validate_tx_from_blob(blob, tx, tx_hash); } //----------------------------------------------------------------------------------------------- bool core::check_tx_syntax(const transaction& tx) { return true; } //----------------------------------------------------------------------------------------------- bool core::get_pool_transactions(std::list& txs) { return m_mempool.get_transactions(txs); } //----------------------------------------------------------------------------------------------- bool core::get_short_chain_history(std::list& ids) { return m_blockchain_storage.get_short_chain_history(ids); } //----------------------------------------------------------------------------------------------- bool core::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, currency_connection_context& context)const { return m_blockchain_storage.handle_get_objects(arg, rsp); } //----------------------------------------------------------------------------------------------- crypto::hash core::get_block_id_by_height(uint64_t height)const { return m_blockchain_storage.get_block_id_by_height(height); } //----------------------------------------------------------------------------------------------- bool core::get_block_by_hash(const crypto::hash &h, block &blk) { return m_blockchain_storage.get_block_by_hash(h, blk); } //----------------------------------------------------------------------------------------------- // void core::get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) { // m_blockchain_storage.get_all_known_block_ids(main, alt, invalid); // } //----------------------------------------------------------------------------------------------- std::string core::print_pool(bool short_format) { return m_mempool.print_pool(short_format); } //----------------------------------------------------------------------------------------------- bool core::update_miner_block_template() { notify_blockchain_update_listeners(); m_miner.on_block_chain_update(); return true; } //----------------------------------------------------------------------------------------------- bool core::on_idle() { if(!m_starter_message_showed) { LOG_PRINT_L0(ENDL << "**********************************************************************" << ENDL << "The daemon will start synchronizing with the network. It may take up to several hours." << ENDL << ENDL << "You can adjust verbosity by using command: \"set_log \", where 0 is the least verbose and 3 is the most." << ENDL << ENDL << "Use \"help\" command to list all available commands." << ENDL << ENDL << "Note: in case you need to interrupt the process, use \"exit\" command. Otherwise, the current progress might not be saved." << ENDL << "**********************************************************************"); m_starter_message_showed = true; } m_prune_alt_blocks_interval.do_call([this](){return m_blockchain_storage.prune_aged_alt_blocks();}); m_miner.on_idle(); m_mempool.on_idle(); return true; } //----------------------------------------------------------------------------------------------- void core::add_blockchain_update_listener(i_blockchain_update_listener *l) { CRITICAL_REGION_LOCAL(m_blockchain_update_listeners_lock); m_blockchain_update_listeners.push_back(l); } //----------------------------------------------------------------------------------------------- void core::remove_blockchain_update_listener(i_blockchain_update_listener *l) { CRITICAL_REGION_LOCAL(m_blockchain_update_listeners_lock); m_blockchain_update_listeners.erase(std::remove(m_blockchain_update_listeners.begin(), m_blockchain_update_listeners.end(), l), m_blockchain_update_listeners.end()); } //----------------------------------------------------------------------------------------------- void core::notify_blockchain_update_listeners() { CRITICAL_REGION_LOCAL(m_blockchain_update_listeners_lock); for(auto l : m_blockchain_update_listeners) l->on_blockchain_update(); } //----------------------------------------------------------------------------------------------- } #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL NULL