diff options
Diffstat (limited to 'src/cryptonote_core')
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 336 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.h | 98 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.cpp | 264 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.h | 103 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_tx_utils.cpp | 4 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_tx_utils.h | 1 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.cpp | 103 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.h | 31 |
8 files changed, 759 insertions, 181 deletions
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 745608b9f..2330b6c42 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -59,6 +59,8 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "blockchain" +#define FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE (100*1024*1024) // 100 MB + //#include "serialization/json_archive.h" /* TODO: @@ -99,6 +101,9 @@ static const struct { // version 5 starts from block 1288616, which is on or around the 15th of April, 2017. Fork time finalised on 2017-03-14. { 5, 1288616, 0, 1489520158 }, + + // version 6 starts from block 1400000, which is on or around the 16th of September, 2017. Fork time finalised on 2017-08-18. + { 6, 1400000, 0, 1503046577 }, }; static const uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -118,13 +123,15 @@ static const struct { { 3, 800500, 0, 1472415034 }, { 4, 801219, 0, 1472415035 }, { 5, 802660, 0, 1472415036 + 86400*180 }, // add 5 months on testnet to shut the update warning up since there's a large gap to v6 + + { 6, 971400, 0, 1501709789 }, }; static const uint64_t testnet_hard_fork_version_1_till = 624633; //------------------------------------------------------------------ Blockchain::Blockchain(tx_memory_pool& tx_pool) : - m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_sz_limit(0), m_is_in_checkpoint_zone(false), - m_is_blockchain_storing(false), m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(1), m_db_sync_mode(db_async), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_cancel(false) + m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_sz_limit(0), + m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_cancel(false) { LOG_PRINT_L3("Blockchain::" << __func__); } @@ -279,7 +286,8 @@ uint64_t Blockchain::get_current_blockchain_height() const bool Blockchain::init(BlockchainDB* db, const bool testnet, const cryptonote::test_options *test_options) { LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); + CRITICAL_REGION_LOCAL(m_tx_pool); + CRITICAL_REGION_LOCAL1(m_blockchain_lock); bool fakechain = test_options != NULL; @@ -743,7 +751,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block() m_timestamps = timestamps; m_difficulties = difficulties; } - size_t target = get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + size_t target = get_difficulty_target(); return next_difficulty(timestamps, difficulties, target); } //------------------------------------------------------------------ @@ -1350,7 +1358,6 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id } // Check the block's hash against the difficulty target for its alt chain - m_is_in_checkpoint_zone = false; difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei); CHECK_AND_ASSERT_MES(current_diff, false, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"); crypto::hash proof_of_work = null_hash; @@ -1425,7 +1432,9 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id { //block orphaned bvc.m_marked_as_orphaned = true; - MERROR_VER("Block recognized as orphaned and rejected, id = " << id); + MERROR_VER("Block recognized as orphaned and rejected, id = " << id << ", height " << block_height + << ", parent in alt " << (it_prev != m_alternative_chains.end()) << ", parent in main " << parent_in_main + << " (parent " << b.prev_id << ", current top " << get_tail_id() << ", chain height " << get_current_blockchain_height() << ")"); } return true; @@ -1562,6 +1571,98 @@ void Blockchain::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_A output_data_t data = m_db->get_output_key(amount, i); oen.out_key = data.pubkey; } + +uint64_t Blockchain::get_num_mature_outputs(uint64_t amount) const +{ + uint64_t num_outs = m_db->get_num_outputs(amount); + // ensure we don't include outputs that aren't yet eligible to be used + // outpouts are sorted by height + while (num_outs > 0) + { + const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1); + const uint64_t height = m_db->get_tx_block_height(toi.first); + if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height()) + break; + --num_outs; + } + + return num_outs; +} + +std::vector<uint64_t> Blockchain::get_random_outputs(uint64_t amount, uint64_t count) const +{ + uint64_t num_outs = get_num_mature_outputs(amount); + + std::vector<uint64_t> indices; + + std::unordered_set<uint64_t> seen_indices; + + // if there aren't enough outputs to mix with (or just enough), + // use all of them. Eventually this should become impossible. + if (num_outs <= count) + { + for (uint64_t i = 0; i < num_outs; i++) + { + // get tx_hash, tx_out_index from DB + tx_out_index toi = m_db->get_output_tx_and_index(amount, i); + + // if tx is unlocked, add output to indices + if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) + { + indices.push_back(i); + } + } + } + else + { + // while we still need more mixins + while (indices.size() < count) + { + // if we've gone through every possible output, we've gotten all we can + if (seen_indices.size() == num_outs) + { + break; + } + + // get a random output index from the DB. If we've already seen it, + // return to the top of the loop and try again, otherwise add it to the + // list of output indices we've seen. + + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + uint64_t i = (uint64_t)(frac*num_outs); + // just in case rounding up to 1 occurs after sqrt + if (i == num_outs) + --i; + + if (seen_indices.count(i)) + { + continue; + } + seen_indices.emplace(i); + + // get tx_hash, tx_out_index from DB + tx_out_index toi = m_db->get_output_tx_and_index(amount, i); + + // if the output's transaction is unlocked, add the output's index to + // our list. + if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) + { + indices.push_back(i); + } + } + } + + return indices; +} + +crypto::public_key Blockchain::get_output_key(uint64_t amount, uint64_t global_index) const +{ + output_data_t data = m_db->get_output_key(amount, global_index); + return data.pubkey; +} + //------------------------------------------------------------------ // This function takes an RPC request for mixins and creates an RPC response // with the requested mixins. @@ -1576,80 +1677,18 @@ bool Blockchain::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUT // from BlockchainDB where <n> is req.outs_count (number of mixins). for (uint64_t amount : req.amounts) { - auto num_outs = m_db->get_num_outputs(amount); - // ensure we don't include outputs that aren't yet eligible to be used - // outpouts are sorted by height - while (num_outs > 0) - { - const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1); - const uint64_t height = m_db->get_tx_block_height(toi.first); - if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height()) - break; - --num_outs; - } - // create outs_for_amount struct and populate amount field COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs = *res.outs.insert(res.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount()); result_outs.amount = amount; - std::unordered_set<uint64_t> seen_indices; - - // if there aren't enough outputs to mix with (or just enough), - // use all of them. Eventually this should become impossible. - if (num_outs <= req.outs_count) - { - for (uint64_t i = 0; i < num_outs; i++) - { - // get tx_hash, tx_out_index from DB - tx_out_index toi = m_db->get_output_tx_and_index(amount, i); - - // if tx is unlocked, add output to result_outs - if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) - { - add_out_to_get_random_outs(result_outs, amount, i); - } + std::vector<uint64_t> indices = get_random_outputs(amount, req.outs_count); - } - } - else + for (auto i : indices) { - // while we still need more mixins - while (result_outs.outs.size() < req.outs_count) - { - // if we've gone through every possible output, we've gotten all we can - if (seen_indices.size() == num_outs) - { - break; - } - - // get a random output index from the DB. If we've already seen it, - // return to the top of the loop and try again, otherwise add it to the - // list of output indices we've seen. - - // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit - uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); - double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); - uint64_t i = (uint64_t)(frac*num_outs); - // just in case rounding up to 1 occurs after sqrt - if (i == num_outs) - --i; - - if (seen_indices.count(i)) - { - continue; - } - seen_indices.emplace(i); - - // get tx_hash, tx_out_index from DB - tx_out_index toi = m_db->get_output_tx_and_index(amount, i); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oe = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); - // if the output's transaction is unlocked, add the output's index to - // our list. - if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) - { - add_out_to_get_random_outs(result_outs, amount, i); - } - } + oe.global_amount_index = i; + oe.out_key = get_output_key(amount, i); } } return true; @@ -1807,6 +1846,15 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA return true; } //------------------------------------------------------------------ +void Blockchain::get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const +{ + const auto o_data = m_db->get_output_key(amount, index); + key = o_data.pubkey; + mask = o_data.commitment; + tx_out_index toi = m_db->get_output_tx_and_index(amount, index); + unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); +} +//------------------------------------------------------------------ // This function takes a list of block hashes from another node // on the network to find where the split point is between us and them. // This is used to see what to send another node that needs to sync. @@ -2016,27 +2064,39 @@ void Blockchain::print_blockchain_outs(const std::string& file) const // Find the split point between us and foreign blockchain and return // (by reference) the most recent common block hash along with up to // BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. -bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); // if we can't find the split point, return false - if(!find_blockchain_supplement(qblock_ids, resp.start_height)) + if(!find_blockchain_supplement(qblock_ids, start_height)) { return false; } m_db->block_txn_start(true); - resp.total_height = get_current_blockchain_height(); + current_height = get_current_blockchain_height(); size_t count = 0; - for(size_t i = resp.start_height; i < resp.total_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) + for(size_t i = start_height; i < current_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) { - resp.m_block_ids.push_back(m_db->get_block_hash_from_height(i)); + hashes.push_back(m_db->get_block_hash_from_height(i)); } + m_db->block_txn_stop(); return true; } + +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height); + resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); + + return result; +} //------------------------------------------------------------------ //FIXME: change argument to std::vector, low priority // find split point between ours and foreign blockchain (or start at @@ -2067,8 +2127,8 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons m_db->block_txn_start(true); total_height = get_current_blockchain_height(); - size_t count = 0; - for(size_t i = start_height; i < total_height && count < max_count; i++, count++) + size_t count = 0, size = 0; + for(size_t i = start_height; i < total_height && count < max_count && (size < FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE || count < 3); i++, count++) { blocks.resize(blocks.size()+1); blocks.back().first = m_db->get_block_blob_from_height(i); @@ -2077,6 +2137,9 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons std::list<crypto::hash> mis; get_transactions_blobs(b.tx_hashes, blocks.back().second, mis); CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); + size += blocks.back().first.size(); + for (const auto &t: blocks.back().second) + size += t.size(); } m_db->block_txn_stop(); return true; @@ -2254,8 +2317,8 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh TIME_MEASURE_FINISH(a); if(m_show_time_stats) { - size_t mixin = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() - 1 : 0; - MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << mixin << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a); + size_t ring_size = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0; + MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a); } return true; } @@ -2266,8 +2329,8 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh TIME_MEASURE_FINISH(a); if(m_show_time_stats) { - size_t mixin = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() - 1 : 0; - MINFO("HASH: " << get_transaction_hash(tx) << " I/M/O: " << tx.vin.size() << "/" << mixin << "/" << tx.vout.size() << " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time << " B: " << get_object_blobsize(tx)); + size_t ring_size = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0; + MINFO("HASH: " << get_transaction_hash(tx) << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time << " B: " << get_object_blobsize(tx)); } if (!res) return false; @@ -2459,13 +2522,13 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { if (n_unmixable == 0) { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and no unmixable inputs"); + MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low ring size (" << (mixin + 1) << "), and no unmixable inputs"); tvc.m_low_mixin = true; return false; } if (n_mixable > 1) { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and more than one mixable input with unmixable inputs"); + MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low ring size (" << (mixin + 1) << "), and more than one mixable input with unmixable inputs"); tvc.m_low_mixin = true; return false; } @@ -2496,7 +2559,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, assert(it != m_check_txin_table.end()); } - uint64_t t_t1 = 0; std::vector<std::vector<rct::ctkey>> pubkeys(tx.vin.size()); std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); @@ -2630,7 +2692,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (failed) { - MERROR_VER("Failed to check ring signatures!, t_loop: " << t_t1); + MERROR_VER("Failed to check ring signatures!"); return false; } } @@ -2779,12 +2841,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, //------------------------------------------------------------------ void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature>& sig, uint64_t &result) { - if (m_is_in_checkpoint_zone) - { - result = true; - return; - } - std::vector<const crypto::public_key *> p_output_keys; for (auto &key : pubkeys) { @@ -3097,6 +3153,8 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& CRITICAL_REGION_LOCAL(m_blockchain_lock); TIME_MEASURE_START(t1); + static bool seen_future_version = false; + m_db->block_txn_start(true); if(bl.prev_id != get_tail_id()) { @@ -3106,6 +3164,18 @@ leave: return false; } + // warn users if they're running an old version + if (!seen_future_version && bl.major_version > m_hardfork->get_ideal_version()) + { + seen_future_version = true; + const el::Level level = el::Level::Warning; + MCLOG_RED(level, "global", "**********************************************************************"); + MCLOG_RED(level, "global", "A block was seen on the network with a version higher than the last"); + MCLOG_RED(level, "global", "known one. This may be an old version of the daemon, and a software"); + MCLOG_RED(level, "global", "update may be required to sync further. Try running: update check"); + MCLOG_RED(level, "global", "**********************************************************************"); + } + // this is a cheap test if (!m_hardfork->check(bl)) { @@ -3541,19 +3611,17 @@ void Blockchain::set_enforce_dns_checkpoints(bool enforce_checkpoints) } //------------------------------------------------------------------ -void Blockchain::block_longhash_worker(const uint64_t height, const std::vector<block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const +void Blockchain::block_longhash_worker(uint64_t height, const std::vector<block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const { TIME_MEASURE_START(t); slow_hash_allocate_state(); - //FIXME: height should be changing here, as get_block_longhash expects - // the height of the block passed to it for (const auto & block : blocks) { if (m_cancel) - return; + break; crypto::hash id = get_block_hash(block); - crypto::hash pow = get_block_longhash(block, height); + crypto::hash pow = get_block_longhash(block, height++); map.emplace(id, pow); } @@ -3564,12 +3632,23 @@ void Blockchain::block_longhash_worker(const uint64_t height, const std::vector< //------------------------------------------------------------------ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) { + bool success = false; + MTRACE("Blockchain::" << __func__); CRITICAL_REGION_BEGIN(m_blockchain_lock); TIME_MEASURE_START(t1); - m_db->batch_stop(); - if (m_sync_counter > 0) + try + { + m_db->batch_stop(); + success = true; + } + catch (const std::exception &e) + { + MERROR("Exception in cleanup_handle_incoming_blocks: " << e.what()); + } + + if (success && m_sync_counter > 0) { if (force_sync) { @@ -3604,7 +3683,7 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) CRITICAL_REGION_END(); m_tx_pool.unlock(); - return true; + return success; } //------------------------------------------------------------------ @@ -3745,9 +3824,11 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e if (!blocks_exist) { m_blocks_longhash_table.clear(); + uint64_t thread_height = height; for (uint64_t i = 0; i < threads; i++) { - thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, height + (i * batches), std::cref(blocks[i]), std::ref(maps[i])))); + thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i])))); + thread_height += blocks[i].size(); } for (size_t j = 0; j < thread_list.size(); j++) @@ -3866,17 +3947,17 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e offset_map[in_to_key.amount].push_back(offset); } - - // sort and remove duplicate absolute_offsets in offset_map - for (auto &offsets : offset_map) - { - std::sort(offsets.second.begin(), offsets.second.end()); - auto last = std::unique(offsets.second.begin(), offsets.second.end()); - offsets.second.erase(last, offsets.second.end()); - } } } + // sort and remove duplicate absolute_offsets in offset_map + for (auto &offsets : offset_map) + { + std::sort(offsets.second.begin(), offsets.second.end()); + auto last = std::unique(offsets.second.begin(), offsets.second.end()); + offsets.second.erase(last, offsets.second.end()); + } + // [output] stores all transactions for each tx_out_index::hash found std::vector<std::unordered_map<crypto::hash, cryptonote::transaction>> transactions(amounts.size()); @@ -4022,12 +4103,29 @@ bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, con void Blockchain::set_user_options(uint64_t maxthreads, uint64_t blocks_per_sync, blockchain_db_sync_mode sync_mode, bool fast_sync) { + if (sync_mode == db_defaultsync) + { + m_db_default_sync = true; + sync_mode = db_async; + } m_db_sync_mode = sync_mode; m_fast_sync = fast_sync; m_db_blocks_per_sync = blocks_per_sync; m_max_prepare_blocks_threads = maxthreads; } +void Blockchain::safesyncmode(const bool onoff) +{ + /* all of this is no-op'd if the user set a specific + * --db-sync-mode at startup. + */ + if (m_db_default_sync) + { + m_db->safesyncmode(onoff); + m_db_sync_mode = onoff ? db_nosync : db_async; + } +} + HardFork::State Blockchain::get_hard_fork_state() const { return m_hardfork->get_state(); @@ -4038,6 +4136,11 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting); } +uint64_t Blockchain::get_difficulty_target() const +{ + return get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; +} + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { return m_db->get_output_histogram(amounts, unlocked, recent_cutoff); @@ -4081,7 +4184,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "23d8a8c73de7b2383c72a016d9a6034e69d62dd48077d1c414e064ceab6daa94"; +static const char expected_block_hashes_hash[] = "d3ca80d50661684cde0e715d46d7c19704d2e216b21ed088af9fd4ef37ed4d65"; void Blockchain::load_compiled_in_block_hashes() { if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr && get_blocks_dat_size(m_testnet) > 0) @@ -4154,6 +4257,15 @@ void Blockchain::load_compiled_in_block_hashes() } #endif +bool Blockchain::is_within_compiled_block_hash_area(uint64_t height) const +{ +#if defined(PER_BLOCK_CHECKPOINT) + return height < m_blocks_hash_check.size(); +#else + return false; +#endif +} + void Blockchain::lock() { m_blockchain_lock.lock(); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 4f2e4f0d3..e2da535cd 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -65,6 +65,7 @@ namespace cryptonote */ enum blockchain_db_sync_mode { + db_defaultsync, //!< user didn't specify, use db_async db_sync, //!< handle syncing calls instead of the backing db, synchronously db_async, //!< handle syncing calls instead of the backing db, asynchronously db_nosync //!< Leave syncing up to the backing db (safest, but slowest because of disk I/O) @@ -373,6 +374,22 @@ namespace cryptonote * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. * * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param hashes the hashes to be returned, return-by-reference + * @param start_height the start height, return-by-reference + * @param current_height the current blockchain height, return-by-reference + * + * @return true if a block found in common, else false + */ + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const; + + /** + * @brief get recent block hashes for a foreign chain + * + * Find the split point between us and foreign blockchain and return + * (by reference) the most recent common block hash along with up to + * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. + * + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) * @param resp return-by-reference the split height and subsequent blocks' hashes * * @return true if a block found in common, else false @@ -426,6 +443,35 @@ namespace cryptonote bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); /** + * @brief get number of outputs of an amount past the minimum spendable age + * + * @param amount the output amount + * + * @return the number of mature outputs + */ + uint64_t get_num_mature_outputs(uint64_t amount) const; + + /** + * @brief get random outputs (indices) for an amount + * + * @param amount the amount + * @param count the number of random outputs to choose + * + * @return the outputs' amount-global indices + */ + std::vector<uint64_t> get_random_outputs(uint64_t amount, uint64_t count) const; + + /** + * @brief get the public key for an output + * + * @param amount the output amount + * @param global_index the output amount-global index + * + * @return the public key + */ + crypto::public_key get_output_key(uint64_t amount, uint64_t global_index) const; + + /** * @brief gets random outputs to mix with * * This function takes an RPC request for outputs to mix with @@ -457,6 +503,17 @@ namespace cryptonote bool get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const; /** + * @brief gets an output's key and unlocked state + * + * @param amount in - the output amount + * @param index in - the output global amount index + * @param mask out - the output's RingCT mask + * @param key out - the output's key + * @param unlocked out - the output's unlocked state + */ + void get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const; + + /** * @brief gets random ringct outputs to mix with * * This function takes an RPC request for outputs to mix with @@ -585,7 +642,7 @@ namespace cryptonote * * @return true if Blockchain is having the chain stored currently, else false */ - bool is_storing_blockchain()const{return m_is_blockchain_storing;} + bool is_storing_blockchain()const{return false;} /** * @brief gets the difficulty of the block with a given height @@ -701,6 +758,11 @@ namespace cryptonote blockchain_db_sync_mode sync_mode, bool fast_sync); /** + * @brief Put DB in safe sync mode + */ + void safesyncmode(const bool onoff); + + /** * @brief set whether or not to show/print time statistics * * @param stats the new time stats setting @@ -746,6 +808,15 @@ namespace cryptonote uint8_t get_ideal_hard_fork_version(uint64_t height) const { return m_hardfork->get_ideal_version(height); } /** + * @brief returns the actual hardfork version for a given block height + * + * @param height the height for which to check version info + * + * @return the version + */ + uint8_t get_hard_fork_version(uint64_t height) const { return m_hardfork->get(height); } + + /** * @brief get information about hardfork voting for a version * * @param version the version in question @@ -760,6 +831,13 @@ namespace cryptonote bool get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; /** + * @brief get difficulty target based on chain and hardfork version + * + * @return difficulty target + */ + uint64_t get_difficulty_target() const; + + /** * @brief remove transactions from the transaction pool (if present) * * @param txids a list of hashes of transactions to be removed @@ -822,6 +900,16 @@ namespace cryptonote * * @return a reference to the BlockchainDB instance */ + const BlockchainDB& get_db() const + { + return *m_db; + } + + /** + * @brief get a reference to the BlockchainDB in use by Blockchain + * + * @return a reference to the BlockchainDB instance + */ BlockchainDB& get_db() { return *m_db; @@ -846,7 +934,7 @@ namespace cryptonote * @param blocks the blocks to be hashed * @param map return-by-reference the hashes for each block */ - void block_longhash_worker(const uint64_t height, const std::vector<block> &blocks, + void block_longhash_worker(uint64_t height, const std::vector<block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const; /** @@ -865,6 +953,9 @@ namespace cryptonote cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const; + bool is_within_compiled_block_hash_area(uint64_t height) const; + bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); } + void lock(); void unlock(); @@ -910,6 +1001,7 @@ namespace cryptonote blockchain_db_sync_mode m_db_sync_mode; bool m_fast_sync; bool m_show_time_stats; + bool m_db_default_sync; uint64_t m_db_blocks_per_sync; uint64_t m_max_prepare_blocks_threads; uint64_t m_fake_pow_calc_time; @@ -931,8 +1023,6 @@ namespace cryptonote checkpoints m_checkpoints; - std::atomic<bool> m_is_in_checkpoint_zone; - std::atomic<bool> m_is_blockchain_storing; bool m_enforce_dns_checkpoints; HardFork *m_hardfork; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index c601c066c..01ee64b78 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -37,6 +37,7 @@ using namespace epee; #include "common/util.h" #include "common/updates.h" #include "common/download.h" +#include "common/task_region.h" #include "warnings.h" #include "crypto/crypto.h" #include "cryptonote_config.h" @@ -47,10 +48,6 @@ using namespace epee; #include "cryptonote_basic/checkpoints.h" #include "ringct/rctTypes.h" #include "blockchain_db/blockchain_db.h" -#include "blockchain_db/lmdb/db_lmdb.h" -#if defined(BERKELEY_DB) -#include "blockchain_db/berkeleydb/db_bdb.h" -#endif #include "ringct/rctSigs.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -76,8 +73,11 @@ namespace cryptonote m_checkpoints_path(""), m_last_dns_checkpoints_update(0), m_last_json_checkpoints_update(0), + m_disable_dns_checkpoints(false), + m_threadpool(tools::thread_group::optimal()), m_update_download(0) { + m_checkpoints_updating.clear(); set_cryptonote_protocol(pprotocol); } void core::set_cryptonote_protocol(i_cryptonote_protocol* pprotocol) @@ -105,7 +105,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::update_checkpoints() { - if (m_testnet || m_fakechain) return true; + if (m_testnet || m_fakechain || m_disable_dns_checkpoints) return true; if (m_checkpoints_updating.test_and_set()) return true; @@ -156,19 +156,19 @@ namespace cryptonote command_line::add_arg(desc, command_line::arg_testnet_on); command_line::add_arg(desc, command_line::arg_dns_checkpoints); - command_line::add_arg(desc, command_line::arg_db_type); command_line::add_arg(desc, command_line::arg_prep_blocks_threads); command_line::add_arg(desc, command_line::arg_fast_block_sync); - command_line::add_arg(desc, command_line::arg_db_sync_mode); command_line::add_arg(desc, command_line::arg_show_time_stats); command_line::add_arg(desc, command_line::arg_block_sync_size); command_line::add_arg(desc, command_line::arg_check_updates); + command_line::add_arg(desc, command_line::arg_fluffy_blocks); // we now also need some of net_node's options (p2p bind arg, for separate data dir) command_line::add_arg(desc, nodetool::arg_testnet_p2p_bind_port, false); command_line::add_arg(desc, nodetool::arg_p2p_bind_port, false); miner::init_options(desc); + BlockchainDB::init_options(desc); } //----------------------------------------------------------------------------------------------- bool core::handle_command_line(const boost::program_options::variables_map& vm) @@ -198,6 +198,7 @@ namespace cryptonote set_enforce_dns_checkpoints(command_line::get_arg(vm, command_line::arg_dns_checkpoints)); test_drop_download_height(command_line::get_arg(vm, command_line::arg_test_drop_download_height)); + m_fluffy_blocks_enabled = m_testnet || get_arg(vm, command_line::arg_fluffy_blocks); if (command_line::get_arg(vm, command_line::arg_test_drop_download) == true) test_drop_download(); @@ -241,6 +242,12 @@ namespace cryptonote return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- + bool core::get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const + { + m_mempool.get_transaction_backlog(backlog); + return true; + } + //----------------------------------------------------------------------------------------------- bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<transaction>& txs, std::list<crypto::hash>& missed_txs) const { return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); @@ -272,8 +279,9 @@ namespace cryptonote m_config_folder_mempool = m_config_folder_mempool + "/" + m_port; } - std::string db_type = command_line::get_arg(vm, command_line::arg_db_type); - std::string db_sync_mode = command_line::get_arg(vm, command_line::arg_db_sync_mode); + std::string db_type = command_line::get_arg(vm, cryptonote::arg_db_type); + std::string db_sync_mode = command_line::get_arg(vm, cryptonote::arg_db_sync_mode); + bool db_salvage = command_line::get_arg(vm, cryptonote::arg_db_salvage) != 0; bool fast_sync = command_line::get_arg(vm, command_line::arg_fast_block_sync) != 0; uint64_t blocks_threads = command_line::get_arg(vm, command_line::arg_prep_blocks_threads); std::string check_updates_string = command_line::get_arg(vm, command_line::arg_check_updates); @@ -302,18 +310,8 @@ namespace cryptonote // folder might not be a directory, etc, etc catch (...) { } - BlockchainDB* db = nullptr; - uint64_t DBS_FAST_MODE = 0; - uint64_t DBS_FASTEST_MODE = 0; - uint64_t DBS_SAFE_MODE = 0; - if (db_type == "lmdb") - { - db = new BlockchainLMDB(); - DBS_SAFE_MODE = MDB_NORDAHEAD; - DBS_FAST_MODE = MDB_NORDAHEAD | MDB_NOSYNC; - DBS_FASTEST_MODE = MDB_NORDAHEAD | MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC; - } - else + BlockchainDB* db = new_db(db_type); + if (db == NULL) { LOG_ERROR("Attempted to use non-existent database type"); return false; @@ -324,7 +322,7 @@ namespace cryptonote const std::string filename = folder.string(); // default to fast:async:1 - blockchain_db_sync_mode sync_mode = db_async; + blockchain_db_sync_mode sync_mode = db_defaultsync; uint64_t blocks_per_sync = 1; try @@ -339,7 +337,7 @@ namespace cryptonote MDEBUG("option: " << option); // default to fast:async:1 - uint64_t DEFAULT_FLAGS = DBS_FAST_MODE; + uint64_t DEFAULT_FLAGS = DBF_FAST; if(options.size() == 0) { @@ -353,15 +351,19 @@ namespace cryptonote if(options[0] == "safe") { safemode = true; - db_flags = DBS_SAFE_MODE; + db_flags = DBF_SAFE; sync_mode = db_nosync; } else if(options[0] == "fast") - db_flags = DBS_FAST_MODE; + { + db_flags = DBF_FAST; + sync_mode = db_async; + } else if(options[0] == "fastest") { - db_flags = DBS_FASTEST_MODE; + db_flags = DBF_FASTEST; blocks_per_sync = 1000; // default to fastest:async:1000 + sync_mode = db_async; } else db_flags = DEFAULT_FLAGS; @@ -383,6 +385,9 @@ namespace cryptonote blocks_per_sync = bps; } + if (db_salvage) + db_flags |= DBF_SALVAGE; + db->open(filename, db_flags); if(!db->m_open) return false; @@ -410,8 +415,8 @@ namespace cryptonote CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); block_sync_size = command_line::get_arg(vm, command_line::arg_block_sync_size); - if (block_sync_size == 0) - block_sync_size = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; + + MGINFO("Loading checkpoints"); // load json & DNS checkpoints, and verify them // with respect to what blocks we already have @@ -482,11 +487,9 @@ namespace cryptonote return false; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx_pre(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay) { tvc = boost::value_initialized<tx_verification_context>(); - //want to process all transactions sequentially - CRITICAL_REGION_LOCAL(m_incoming_tx_lock); if(tx_blob.size() > get_max_tx_size()) { @@ -496,9 +499,8 @@ namespace cryptonote return false; } - crypto::hash tx_hash = null_hash; - crypto::hash tx_prefixt_hash = null_hash; - transaction tx; + tx_hash = null_hash; + tx_prefixt_hash = null_hash; if(!parse_tx_from_blob(tx, tx_hash, tx_prefixt_hash, tx_blob)) { @@ -508,15 +510,18 @@ namespace cryptonote } //std::cout << "!"<< tx.vin.size() << std::endl; + bad_semantics_txes_lock.lock(); for (int idx = 0; idx < 2; ++idx) { if (bad_semantics_txes[idx].find(tx_hash) != bad_semantics_txes[idx].end()) { + bad_semantics_txes_lock.unlock(); LOG_PRINT_L1("Transaction already seen with bad semantics, rejected"); tvc.m_verifivation_failed = true; return false; } } + bad_semantics_txes_lock.unlock(); uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); const size_t max_tx_version = version == 1 ? 1 : 2; @@ -527,18 +532,11 @@ namespace cryptonote return false; } - if(m_mempool.have_tx(tx_hash)) - { - LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool"); - return true; - } - - if(m_blockchain_storage.have_tx(tx_hash)) - { - LOG_PRINT_L2("tx " << tx_hash << " already have transaction in blockchain"); - return true; - } - + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx_post(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay) + { if(!check_tx_syntax(tx)) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); @@ -563,27 +561,112 @@ namespace cryptonote rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key); } - if(!check_tx_semantic(tx, keeped_by_block)) + if (keeped_by_block && get_blockchain_storage().is_within_compiled_block_hash_area()) + { + MTRACE("Skipping semantics check for tx kept by block in embedded hash area"); + } + else if(!check_tx_semantic(tx, keeped_by_block)) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected"); tvc.m_verifivation_failed = true; + bad_semantics_txes_lock.lock(); bad_semantics_txes[0].insert(tx_hash); if (bad_semantics_txes[0].size() >= BAD_SEMANTICS_TXES_MAX_SIZE) { std::swap(bad_semantics_txes[0], bad_semantics_txes[1]); bad_semantics_txes[0].clear(); } + bad_semantics_txes_lock.unlock(); return false; } - bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block, relayed, do_not_relay); - if(tvc.m_verifivation_failed) - {MERROR_VER("Transaction verification failed: " << tx_hash);} - else if(tvc.m_verifivation_impossible) - {MERROR_VER("Transaction verification impossible: " << tx_hash);} + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_txs(const std::list<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + { + TRY_ENTRY(); + + struct result { bool res; cryptonote::transaction tx; crypto::hash hash; crypto::hash prefix_hash; bool in_txpool; bool in_blockchain; }; + std::vector<result> results(tx_blobs.size()); + + tvc.resize(tx_blobs.size()); + tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + region.run([&, i, it] { + try + { + results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); + } + catch (const std::exception &e) + { + MERROR_VER("Exception in handle_incoming_tx_pre: " << e.what()); + results[i].res = false; + } + }); + } + }); + tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + continue; + if(m_mempool.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); + } + else if(m_blockchain_storage.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); + } + else + { + region.run([&, i, it] { + try + { + results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); + } + catch (const std::exception &e) + { + MERROR_VER("Exception in handle_incoming_tx_post: " << e.what()); + results[i].res = false; + } + }); + } + } + }); + + bool ok = true; + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + { + ok = false; + continue; + } + + ok &= add_new_tx(results[i].tx, results[i].hash, results[i].prefix_hash, it->size(), tvc[i], keeped_by_block, relayed, do_not_relay); + if(tvc[i].m_verifivation_failed) + {MERROR_VER("Transaction verification failed: " << results[i].hash);} + else if(tvc[i].m_verifivation_impossible) + {MERROR_VER("Transaction verification impossible: " << results[i].hash);} - if(tvc.m_added_to_pool) - MDEBUG("tx added: " << tx_hash); + if(tvc[i].m_added_to_pool) + MDEBUG("tx added: " << results[i].hash); + } + return ok; + + CATCH_ENTRY_L0("core::handle_incoming_txs()", false); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + { + std::list<cryptonote::blobdata> tx_blobs; + tx_blobs.push_back(tx_blob); + std::vector<tx_verification_context> tvcv(1); + bool r = handle_incoming_txs(tx_blobs, tvcv, keeped_by_block, relayed, do_not_relay); + tvc = tvcv[0]; return r; } //----------------------------------------------------------------------------------------------- @@ -659,6 +742,12 @@ namespace cryptonote return false; } + if (!check_tx_inputs_ring_members_diff(tx)) + { + MERROR_VER("tx uses duplicate ring members"); + return false; + } + if (!check_tx_inputs_keyimages_domain(tx)) { MERROR_VER("tx uses key image not in the valid domain"); @@ -711,6 +800,23 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + size_t core::get_block_sync_size(uint64_t height) const + { + static const uint64_t quick_height = m_testnet ? 801219 : 1220516; + if (block_sync_size > 0) + return block_sync_size; + if (height >= quick_height) + return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; + return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT_PRE_V4; + } + //----------------------------------------------------------------------------------------------- + bool core::are_key_images_spent_in_pool(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const + { + spent.clear(); + + return m_mempool.check_for_key_images(key_im, spent); + } + //----------------------------------------------------------------------------------------------- std::pair<uint64_t, uint64_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) { uint64_t emission_amount = 0; @@ -751,6 +857,22 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const + { + const uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); + if (version >= 6) + { + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + for (size_t n = 1; n < tokey_in.key_offsets.size(); ++n) + if (tokey_in.key_offsets[n] == 0) + return false; + } + } + return true; + } + //----------------------------------------------------------------------------------------------- bool core::check_tx_inputs_keyimages_domain(const transaction& tx) const { std::unordered_set<crypto::key_image> ki; @@ -948,6 +1070,11 @@ namespace cryptonote m_miner.on_synchronized(); } //----------------------------------------------------------------------------------------------- + void core::safesyncmode(const bool onoff) + { + m_blockchain_storage.safesyncmode(onoff); + } + //----------------------------------------------------------------------------------------------- bool core::add_new_block(const block& b, block_verification_context& bvc) { return m_blockchain_storage.add_new_block(b, bvc); @@ -964,17 +1091,20 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::cleanup_handle_incoming_blocks(bool force_sync) { + bool success = false; try { - m_blockchain_storage.cleanup_handle_incoming_blocks(force_sync); + success = m_blockchain_storage.cleanup_handle_incoming_blocks(force_sync); } catch (...) {} m_incoming_tx_lock.unlock(); - return true; + return success; } //----------------------------------------------------------------------------------------------- bool core::handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate) { + TRY_ENTRY(); + // load json & DNS checkpoints every 10min/hour respectively, // and verify them with respect to what blocks we already have CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); @@ -998,6 +1128,8 @@ namespace cryptonote if(update_miner_blocktemplate && bvc.m_added_to_main_chain) update_miner_block_template(); return true; + + CATCH_ENTRY_L0("core::handle_incoming_block()", false); } //----------------------------------------------------------------------------------------------- // Used by the RPC server to check the size of an incoming @@ -1017,6 +1149,11 @@ namespace cryptonote return m_blockchain_storage.get_tail_id(); } //----------------------------------------------------------------------------------------------- + difficulty_type core::get_block_cumulative_difficulty(uint64_t height) const + { + return m_blockchain_storage.get_db().get_block_cumulative_difficulty(height); + } + //----------------------------------------------------------------------------------------------- size_t core::get_pool_transactions_count() const { return m_mempool.get_transactions_count(); @@ -1070,6 +1207,11 @@ namespace cryptonote return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos); } //----------------------------------------------------------------------------------------------- + bool core::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + return m_mempool.get_pool_for_rpc(tx_infos, key_image_infos); + } + //----------------------------------------------------------------------------------------------- bool core::get_short_chain_history(std::list<crypto::hash>& ids) const { return m_blockchain_storage.get_short_chain_history(ids); @@ -1146,6 +1288,16 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + uint8_t core::get_ideal_hard_fork_version(uint64_t height) const + { + return get_blockchain_storage().get_ideal_hard_fork_version(height); + } + //----------------------------------------------------------------------------------------------- + uint8_t core::get_hard_fork_version(uint64_t height) const + { + return get_blockchain_storage().get_hard_fork_version(height); + } + //----------------------------------------------------------------------------------------------- bool core::check_updates() { static const char software[] = "monero"; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index e5fbf7f91..a9ee9e9d0 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -40,6 +40,7 @@ #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" #include "common/download.h" +#include "common/thread_group.h" #include "tx_pool.h" #include "blockchain.h" #include "cryptonote_basic/miner.h" @@ -115,6 +116,22 @@ namespace cryptonote bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); /** + * @brief handles a list of incoming transactions + * + * Parses incoming transactions and, if nothing is obviously wrong, + * passes them along to the transaction pool + * + * @param tx_blobs the txs to handle + * @param tvc metadata about the transactions' validity + * @param keeped_by_block if the transactions have been in a block + * @param relayed whether or not the transactions were relayed to us + * @param do_not_relay whether to prevent the transactions from being relayed + * + * @return true if the transactions made it to the transaction pool, otherwise false + */ + bool handle_incoming_txs(const std::list<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + + /** * @brief handles an incoming block * * periodic update to checkpoints is triggered here @@ -390,6 +407,13 @@ namespace cryptonote void set_enforce_dns_checkpoints(bool enforce_dns); /** + * @brief set whether or not to enable or disable DNS checkpoints + * + * @param disble whether to disable DNS checkpoints + */ + void disable_dns_checkpoints(bool disable = true) { m_disable_dns_checkpoints = disable; } + + /** * @copydoc tx_memory_pool::have_tx * * @note see tx_memory_pool::have_tx @@ -404,6 +428,13 @@ namespace cryptonote bool get_pool_transactions(std::list<transaction>& txs) const; /** + * @copydoc tx_memory_pool::get_txpool_backlog + * + * @note see tx_memory_pool::get_txpool_backlog + */ + bool get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const; + + /** * @copydoc tx_memory_pool::get_transactions * * @note see tx_memory_pool::get_transactions @@ -432,6 +463,13 @@ namespace cryptonote bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; /** + * @copydoc tx_memory_pool::get_pool_for_rpc + * + * @note see tx_memory_pool::get_pool_for_rpc + */ + bool get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** * @copydoc tx_memory_pool::get_transactions_count * * @note see tx_memory_pool::get_transactions_count @@ -497,6 +535,13 @@ namespace cryptonote crypto::hash get_tail_id() const; /** + * @copydoc Blockchain::get_block_cumulative_difficulty + * + * @note see Blockchain::get_block_cumulative_difficulty + */ + difficulty_type get_block_cumulative_difficulty(uint64_t height) const; + + /** * @copydoc Blockchain::get_random_outs_for_amounts * * @note see Blockchain::get_random_outs_for_amounts @@ -583,6 +628,13 @@ namespace cryptonote void on_synchronized(); /** + * @copydoc Blockchain::safesyncmode + * + * 2note see Blockchain::safesyncmode + */ + void safesyncmode(const bool onoff); + + /** * @brief sets the target blockchain height * * @param target_blockchain_height the height to set @@ -597,6 +649,20 @@ namespace cryptonote uint64_t get_target_blockchain_height() const; /** + * @brief return the ideal hard fork version for a given block height + * + * @return what it says above + */ + uint8_t get_ideal_hard_fork_version(uint64_t height) const; + + /** + * @brief return the hard fork version for a given block height + * + * @return what it says above + */ + uint8_t get_hard_fork_version(uint64_t height) const; + + /** * @brief gets start_time * */ @@ -649,11 +715,21 @@ namespace cryptonote bool are_key_images_spent(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const; /** + * @brief check if multiple key images are spent in the transaction pool + * + * @param key_im list of key images to check + * @param spent return-by-reference result for each image checked + * + * @return true + */ + bool are_key_images_spent_in_pool(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const; + + /** * @brief get the number of blocks to sync in one go * * @return the number of blocks to sync in one go */ - size_t get_block_sync_size() const { return block_sync_size; } + size_t get_block_sync_size(uint64_t height) const; /** * @brief get the sum of coinbase tx amounts between blocks @@ -669,6 +745,13 @@ namespace cryptonote */ bool get_testnet() const { return m_testnet; }; + /** + * @brief get whether fluffy blocks are enabled + * + * @return whether fluffy blocks are enabled + */ + bool fluffy_blocks_enabled() const { return m_fluffy_blocks_enabled; } + private: /** @@ -753,6 +836,9 @@ namespace cryptonote */ bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; + bool handle_incoming_tx_pre(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_tx_post(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay); + /** * @copydoc miner::on_block_chain_update * @@ -781,6 +867,15 @@ namespace cryptonote bool check_tx_inputs_keyimages_diff(const transaction& tx) const; /** + * @brief verify that each ring uses distinct members + * + * @param tx the transaction to check + * + * @return false if any ring uses duplicate members, true otherwise + */ + bool check_tx_inputs_ring_members_diff(const transaction& tx) const; + + /** * @brief verify that each input key image in a transaction is in * the valid domain * @@ -853,12 +948,16 @@ namespace cryptonote time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once + bool m_disable_dns_checkpoints; size_t block_sync_size; time_t start_time; std::unordered_set<crypto::hash> bad_semantics_txes[2]; + boost::mutex bad_semantics_txes_lock; + + tools::thread_group m_threadpool; enum { UPDATES_DISABLED, @@ -870,6 +969,8 @@ namespace cryptonote tools::download_async_handle m_update_download; size_t m_last_update_length; boost::mutex m_update_mutex; + + bool m_fluffy_blocks_enabled; }; } diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 26d5fb767..94f069827 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -265,7 +265,7 @@ namespace cryptonote // "Shuffle" outs std::vector<tx_destination_entry> shuffled_dsts(destinations); - std::sort(shuffled_dsts.begin(), shuffled_dsts.end(), [](const tx_destination_entry& de1, const tx_destination_entry& de2) { return de1.amount < de2.amount; } ); + std::random_shuffle(shuffled_dsts.begin(), shuffled_dsts.end(), [](unsigned int i) { return crypto::rand<unsigned int>() % i; }); uint64_t summary_outs_money = 0; //fill outputs @@ -371,7 +371,7 @@ namespace cryptonote // enforce same mixin for all outputs for (size_t i = 1; i < sources.size(); ++i) { if (n_total_outs != sources[i].outputs.size()) { - LOG_ERROR("Non-simple ringct transaction has varying mixin"); + LOG_ERROR("Non-simple ringct transaction has varying ring size"); return false; } } diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 933070e1e..7aa7c280d 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -32,6 +32,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include <boost/serialization/vector.hpp> #include <boost/serialization/utility.hpp> +#include "ringct/rctOps.h" namespace cryptonote { diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index ffb5b478b..7de392036 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -80,7 +80,7 @@ namespace cryptonote uint64_t get_transaction_size_limit(uint8_t version) { - return get_min_block_size(version) * 125 / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + return get_min_block_size(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } // This class is meant to create a batch when none currently exists. @@ -92,7 +92,7 @@ namespace cryptonote LockedTXN(Blockchain &b): m_blockchain(b), m_batch(false) { m_batch = m_blockchain.get_db().batch_start(); } - ~LockedTXN() { if (m_batch) { m_blockchain.get_db().batch_stop(); } } + ~LockedTXN() { try { if (m_batch) { m_blockchain.get_db().batch_stop(); } } catch (const std::exception &e) { MWARNING("LockedTXN dtor filtering exception: " << e.what()); } } private: Blockchain &m_blockchain; bool m_batch; @@ -225,6 +225,7 @@ namespace cryptonote meta.last_relayed_time = time(NULL); meta.relayed = relayed; meta.do_not_relay = do_not_relay; + memset(meta.padding, 0, sizeof(meta.padding)); try { CRITICAL_REGION_LOCAL1(m_blockchain); @@ -261,6 +262,7 @@ namespace cryptonote meta.last_relayed_time = time(NULL); meta.relayed = relayed; meta.do_not_relay = do_not_relay; + memset(meta.padding, 0, sizeof(meta.padding)); try { @@ -551,6 +553,17 @@ namespace cryptonote }); } //------------------------------------------------------------------ + void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const uint64_t now = time(NULL); + m_blockchain.for_all_txpool_txes([&backlog, now](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + backlog.push_back({meta.blob_size, meta.fee, meta.receive_time - now}); + return true; + }); + } + //------------------------------------------------------------------ void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats) const { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -558,7 +571,10 @@ namespace cryptonote const uint64_t now = time(NULL); std::map<uint64_t, txpool_histo> agebytes; stats.txs_total = m_blockchain.get_txpool_tx_count(); - m_blockchain.for_all_txpool_txes([&stats, now, &agebytes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + std::vector<uint32_t> sizes; + sizes.reserve(stats.txs_total); + m_blockchain.for_all_txpool_txes([&stats, &sizes, now, &agebytes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + sizes.push_back(meta.blob_size); stats.bytes_total += meta.blob_size; if (!stats.bytes_min || meta.blob_size < stats.bytes_min) stats.bytes_min = meta.blob_size; @@ -573,11 +589,12 @@ namespace cryptonote stats.num_10m++; if (meta.last_failed_height) stats.num_failing++; - uint64_t age = now - meta.receive_time; + uint64_t age = now - meta.receive_time + (now == meta.receive_time); agebytes[age].txs++; agebytes[age].bytes += meta.blob_size; return true; }); + stats.bytes_med = epee::misc_utils::median(sizes); if (stats.txs_total > 1) { /* looking for 98th percentile */ @@ -667,6 +684,65 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + cryptonote::rpc::tx_in_pool txi; + txi.tx_hash = txid; + transaction tx; + if (!parse_and_validate_tx_from_blob(*bd, tx)) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + txi.tx = tx; + txi.blob_size = meta.blob_size; + txi.fee = meta.fee; + txi.kept_by_block = meta.kept_by_block; + txi.max_used_block_height = meta.max_used_block_height; + txi.max_used_block_hash = meta.max_used_block_id; + txi.last_failed_block_height = meta.last_failed_height; + txi.last_failed_block_hash = meta.last_failed_id; + txi.receive_time = meta.receive_time; + txi.relayed = meta.relayed; + txi.last_relayed_time = meta.last_relayed_time; + txi.do_not_relay = meta.do_not_relay; + tx_infos.push_back(txi); + return true; + }, true); + + for (const key_images_container::value_type& kee : m_spent_key_images) { + std::vector<crypto::hash> tx_hashes; + const std::unordered_set<crypto::hash>& kei_image_set = kee.second; + for (const crypto::hash& tx_id_hash : kei_image_set) + { + tx_hashes.push_back(tx_id_hash); + } + + const crypto::key_image& k_image = kee.first; + key_image_infos[k_image] = tx_hashes; + } + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + spent.clear(); + + for (const auto& image : key_images) + { + spent.push_back(m_spent_key_images.find(image) == m_spent_key_images.end() ? false : true); + } + + return true; + } + //--------------------------------------------------------------------------------- bool tx_memory_pool::get_transaction(const crypto::hash& id, cryptonote::blobdata& txblob) const { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -846,6 +922,9 @@ namespace cryptonote std::unordered_set<crypto::key_image> k_images; LOG_PRINT_L2("Filling block template, median size " << median_size << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool"); + + LockedTXN lock(m_blockchain); + auto sorted_it = m_txs_by_fee_and_receive_time.begin(); while (sorted_it != m_txs_by_fee_and_receive_time.end()) { @@ -903,7 +982,21 @@ namespace cryptonote // Skip transactions that are not ready to be // included into the blockchain or that are // missing key images - if (!is_transaction_ready_to_go(meta, tx)) + const cryptonote::txpool_tx_meta_t original_meta = meta; + bool ready = is_transaction_ready_to_go(meta, tx); + if (memcmp(&original_meta, &meta, sizeof(meta))) + { + try + { + m_blockchain.update_txpool_tx(sorted_it->second, meta); + } + catch (const std::exception &e) + { + MERROR("Failed to update tx meta: " << e.what()); + // continue, not fatal + } + } + if (!ready) { LOG_PRINT_L2(" not ready to go"); sorted_it++; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 1858ccdd8..3e4ccb338 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -46,6 +46,7 @@ #include "blockchain_db/blockchain_db.h" #include "crypto/hash.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/message_data_structs.h" namespace cryptonote { @@ -243,6 +244,13 @@ namespace cryptonote void get_transaction_hashes(std::vector<crypto::hash>& txs) const; /** + * @brief get (size, fee, receive time) for all transaction in the pool + * + * @param txs return-by-reference that data + */ + void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog) const; + + /** * @brief get a summary statistics of all transaction hashes in the pool * * @param stats return-by-reference the pool statistics @@ -262,6 +270,28 @@ namespace cryptonote bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; /** + * @brief get information about all transactions and key images in the pool + * + * see documentation on tx_in_pool and key_images_with_tx_hashes for more details + * + * @param tx_infos [out] the transactions' information + * @param key_image_infos [out] the spent key images' information + * + * @return true + */ + bool get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** + * @brief check for presence of key images in the pool + * + * @param key_images [in] vector of key images to check + * @param spent [out] vector of bool to return + * + * @return true + */ + bool check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const; + + /** * @brief get a specific transaction from the pool * * @param h the hash of the transaction to get @@ -493,7 +523,6 @@ private: */ std::unordered_set<crypto::hash> m_timed_out_transactions; - std::string m_config_folder; //!< the folder to save state to Blockchain& m_blockchain; //!< reference to the Blockchain object }; } |