diff options
Diffstat (limited to 'src/cryptonote_core')
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 60 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.h | 6 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.cpp | 15 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.h | 6 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_tx_utils.cpp | 2 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.cpp | 164 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.h | 6 |
7 files changed, 147 insertions, 112 deletions
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 5578f519e..2571e4203 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -928,7 +928,7 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain, for (auto& bl : original_chain) { block_verification_context bvc = {}; - bool r = handle_block_to_main_chain(bl, bvc); + bool r = handle_block_to_main_chain(bl, bvc, false); CHECK_AND_ASSERT_MES(r && bvc.m_added_to_main_chain, false, "PANIC! failed to add (again) block while chain switching during the rollback!"); } @@ -979,7 +979,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info> block_verification_context bvc = {}; // add block to main chain - bool r = handle_block_to_main_chain(bei.bl, bvc); + bool r = handle_block_to_main_chain(bei.bl, bvc, false); // if adding block to main chain failed, rollback to previous state and // return false @@ -1044,6 +1044,11 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info> reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(), "%n", std::to_string(m_db->height() - split_height).c_str(), "%d", std::to_string(discarded_blocks).c_str(), NULL); + std::shared_ptr<tools::Notify> block_notify = m_block_notify; + if (block_notify) + for (const auto &bei: alt_chain) + block_notify->notify("%s", epee::string_tools::pod_to_hex(get_block_hash(bei.bl)).c_str(), NULL); + MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height()); return true; } @@ -2488,38 +2493,10 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons } db_rtxn_guard rtxn_guard(m_db); - total_height = get_current_blockchain_height(); - size_t count = 0, size = 0; blocks.reserve(std::min(std::min(max_count, (size_t)10000), (size_t)(total_height - start_height))); - for(uint64_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.first = m_db->get_block_blob_from_height(i); - block b; - CHECK_AND_ASSERT_MES(parse_and_validate_block_from_blob(blocks.back().first.first, b), false, "internal error, invalid block"); - blocks.back().first.second = get_miner_tx_hash ? cryptonote::get_transaction_hash(b.miner_tx) : crypto::null_hash; - std::vector<cryptonote::blobdata> txs; - if (pruned) - { - CHECK_AND_ASSERT_MES(m_db->get_pruned_tx_blobs_from(b.tx_hashes.front(), b.tx_hashes.size(), txs), false, "Failed to retrieve all transactions needed"); - } - else - { - std::vector<crypto::hash> mis; - get_transactions_blobs(b.tx_hashes, txs, mis, pruned); - CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); - } - size += blocks.back().first.first.size(); - for (const auto &t: txs) - size += t.size(); + CHECK_AND_ASSERT_MES(m_db->get_blocks_from(start_height, 3, max_count, FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE, blocks, pruned, true, get_miner_tx_hash), + false, "Error getting blocks"); - CHECK_AND_ASSERT_MES(txs.size() == b.tx_hashes.size(), false, "mismatched sizes of b.tx_hashes and txs"); - blocks.back().second.reserve(txs.size()); - for (size_t i = 0; i < txs.size(); ++i) - { - blocks.back().second.push_back(std::make_pair(b.tx_hashes[i], std::move(txs[i]))); - } - } return true; } //------------------------------------------------------------------ @@ -2574,11 +2551,11 @@ bool Blockchain::have_block(const crypto::hash& id) const return false; } //------------------------------------------------------------------ -bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc) +bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc, bool notify/* = true*/) { LOG_PRINT_L3("Blockchain::" << __func__); crypto::hash id = get_block_hash(bl); - return handle_block_to_main_chain(bl, id, bvc); + return handle_block_to_main_chain(bl, id, bvc, notify); } //------------------------------------------------------------------ size_t Blockchain::get_total_transactions() const @@ -3687,7 +3664,7 @@ bool Blockchain::flush_txes_from_pool(const std::vector<crypto::hash> &txids) // Needs to validate the block and acquire each transaction from the // transaction mem_pool, then pass the block and transactions to // m_db->add_block() -bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc) +bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc, bool notify/* = true*/) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -4067,9 +4044,12 @@ leave: get_difficulty_for_next_block(); // just to cache it invalidate_block_template_cache(); - std::shared_ptr<tools::Notify> block_notify = m_block_notify; - if (block_notify) - block_notify->notify("%s", epee::string_tools::pod_to_hex(id).c_str(), NULL); + if (notify) + { + std::shared_ptr<tools::Notify> block_notify = m_block_notify; + if (block_notify) + block_notify->notify("%s", epee::string_tools::pod_to_hex(id).c_str(), NULL); + } return true; } @@ -5176,12 +5156,12 @@ bool Blockchain::for_all_transactions(std::function<bool(const crypto::hash&, co bool Blockchain::for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const { - return m_db->for_all_outputs(f);; + return m_db->for_all_outputs(f); } bool Blockchain::for_all_outputs(uint64_t amount, std::function<bool(uint64_t height)> f) const { - return m_db->for_all_outputs(amount, f);; + return m_db->for_all_outputs(amount, f); } void Blockchain::invalidate_block_template_cache() diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 4ed7a2f02..3a89cc5df 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1212,10 +1212,11 @@ namespace cryptonote * * @param bl the block to be added * @param bvc metadata concerning the block's validity + * @param notify if set to true, sends new block notification on success * * @return true if the block was added successfully, otherwise false */ - bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); + bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc, bool notify = true); /** * @brief validate and add a new block to the end of the blockchain @@ -1227,10 +1228,11 @@ namespace cryptonote * @param bl the block to be added * @param id the hash of the block * @param bvc metadata concerning the block's validity + * @param notify if set to true, sends new block notification on success * * @return true if the block was added successfully, otherwise false */ - bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); + bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc, bool notify = true); /** * @brief validate and add a new block to an alternate blockchain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 212616af8..3ff3c77e2 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -428,9 +428,9 @@ namespace cryptonote return m_blockchain_storage.get_split_transactions_blobs(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- - bool core::get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const + bool core::get_txpool_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive_txes) const { - m_mempool.get_transaction_backlog(backlog); + m_mempool.get_transaction_backlog(backlog, include_sensitive_txes); return true; } //----------------------------------------------------------------------------------------------- @@ -1284,6 +1284,7 @@ namespace cryptonote break; case relay_method::block: case relay_method::fluff: + case relay_method::stem: public_req.txs.push_back(std::move(std::get<1>(tx))); break; } @@ -1295,9 +1296,9 @@ namespace cryptonote re-relaying public and private _should_ be acceptable here. */ const boost::uuids::uuid source = boost::uuids::nil_uuid(); if (!public_req.txs.empty()) - get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_); + get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_, relay_method::fluff); if (!private_req.txs.empty()) - get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid); + get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid, relay_method::local); } return true; } @@ -1543,9 +1544,9 @@ namespace cryptonote return m_blockchain_storage.get_db().get_block_cumulative_difficulty(height); } //----------------------------------------------------------------------------------------------- - size_t core::get_pool_transactions_count() const + size_t core::get_pool_transactions_count(bool include_sensitive_txes) const { - return m_mempool.get_transactions_count(); + return m_mempool.get_transactions_count(include_sensitive_txes); } //----------------------------------------------------------------------------------------------- bool core::have_block(const crypto::hash& id) const @@ -1907,7 +1908,7 @@ namespace cryptonote MDEBUG("blocks in the last " << seconds[n] / 60 << " minutes: " << b << " (probability " << p << ")"); if (p < threshold) { - MWARNING("There were " << b << (b == max_blocks_checked ? " or more" : "") << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack. Or it could be just sheer bad luck."); + MWARNING("There were " << b << (b == max_blocks_checked ? " or more" : "") << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack, or your computer's time is off. Or it could be just sheer bad luck."); std::shared_ptr<tools::Notify> block_rate_notify = m_block_rate_notify; if (block_rate_notify) diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 79a846de1..255645efc 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -469,10 +469,11 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_txpool_backlog + * @param include_sensitive_txes include private transactions * * @note see tx_memory_pool::get_txpool_backlog */ - bool get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const; + bool get_txpool_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive_txes = false) const; /** * @copydoc tx_memory_pool::get_transactions @@ -514,10 +515,11 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_transactions_count + * @param include_sensitive_txes include private transactions * * @note see tx_memory_pool::get_transactions_count */ - size_t get_pool_transactions_count() const; + size_t get_pool_transactions_count(bool include_sensitive_txes = false) const; /** * @copydoc Blockchain::get_total_transactions diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index eb16fb297..3dd29dd1b 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -142,7 +142,7 @@ namespace cryptonote uint64_t summary_amounts = 0; for (size_t no = 0; no < out_amounts.size(); no++) { - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);; + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 53a6ce579..469319824 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -46,6 +46,7 @@ #include "warnings.h" #include "common/perf_timer.h" #include "crypto/hash.h" +#include "crypto/duration.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "txpool" @@ -58,6 +59,29 @@ namespace cryptonote { namespace { + /*! The Dandelion++ has formula for calculating the average embargo timeout: + (-k*(k-1)*hop)/(2*log(1-ep)) + where k is the number of hops before this node and ep is the probability + that one of the k hops hits their embargo timer, and hop is the average + time taken between hops. So decreasing ep will make it more probable + that "this" node is the first to expire the embargo timer. Increasing k + will increase the number of nodes that will be "hidden" as a prior + recipient of the tx. + + As example, k=5 and ep=0.1 means "this" embargo timer has a 90% + probability of being the first to expire amongst 5 nodes that saw the + tx before "this" one. These values are independent to the fluff + probability, but setting a low k with a low p (fluff probability) is + not ideal since a blackhole is more likely to reveal earlier nodes in + the chain. + + This value was calculated with k=10, ep=0.10, and hop = 175 ms. A + testrun from a recent Intel laptop took ~80ms to + receive+parse+proces+send transaction. At least 50ms will be added to + the latency if crossing an ocean. So 175ms is the fudge factor for + a single hop with 173s being the embargo timer. */ + constexpr const std::chrono::seconds dandelionpp_embargo_average{CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE}; + //TODO: constants such as these should at least be in the header, // but probably somewhere more accessible to the rest of the // codebase. As it stands, it is at best nontrivial to test @@ -186,7 +210,7 @@ namespace cryptonote // TODO: Investigate why not? if(!kept_by_block) { - if(have_tx_keyimges_as_spent(tx)) + if(have_tx_keyimges_as_spent(tx, id)) { mark_double_spend(tx); LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images"); @@ -229,7 +253,7 @@ namespace cryptonote meta.last_relayed_time = time(NULL); meta.relayed = relayed; meta.set_relay_method(tx_relay); - meta.double_spend_seen = have_tx_keyimges_as_spent(tx); + meta.double_spend_seen = have_tx_keyimges_as_spent(tx, id); meta.pruned = tx.pruned; meta.bf_padding = 0; memset(meta.padding, 0, sizeof(meta.padding)); @@ -262,34 +286,51 @@ namespace cryptonote } }else { - //update transactions container - meta.weight = tx_weight; - meta.fee = fee; - meta.max_used_block_id = max_used_block_id; - meta.max_used_block_height = max_used_block_height; - meta.last_failed_height = 0; - meta.last_failed_id = null_hash; - meta.receive_time = receive_time; - meta.last_relayed_time = time(NULL); - meta.relayed = relayed; - meta.set_relay_method(tx_relay); - meta.double_spend_seen = false; - meta.pruned = tx.pruned; - meta.bf_padding = 0; - memset(meta.padding, 0, sizeof(meta.padding)); - try { if (kept_by_block) m_parsed_tx_cache.insert(std::make_pair(id, tx)); CRITICAL_REGION_LOCAL1(m_blockchain); LockedTXN lock(m_blockchain.get_db()); - m_blockchain.remove_txpool_tx(id); - if (!insert_key_images(tx, id, tx_relay)) - return false; - m_blockchain.add_txpool_tx(id, blob, meta); - m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); + const bool existing_tx = m_blockchain.get_txpool_tx_meta(id, meta); + if (existing_tx) + { + /* If Dandelion++ loop. Do not use txes in the `local` state in the + loop detection - txes in that state should be outgoing over i2p/tor + then routed back via public dandelion++ stem. Pretend to be + another stem node in that situation, a loop over the public + network hasn't been hit yet. */ + if (tx_relay == relay_method::stem && meta.dandelionpp_stem) + tx_relay = relay_method::fluff; + } + else + meta.set_relay_method(relay_method::none); + + if (meta.upgrade_relay_method(tx_relay) || !existing_tx) // synchronize with embargo timer or stem/fluff out-of-order messages + { + //update transactions container + meta.last_relayed_time = std::numeric_limits<decltype(meta.last_relayed_time)>::max(); + meta.receive_time = receive_time; + meta.weight = tx_weight; + meta.fee = fee; + meta.max_used_block_id = max_used_block_id; + meta.max_used_block_height = max_used_block_height; + meta.last_failed_height = 0; + meta.last_failed_id = null_hash; + meta.relayed = relayed; + meta.double_spend_seen = false; + meta.pruned = tx.pruned; + meta.bf_padding = 0; + memset(meta.padding, 0, sizeof(meta.padding)); + + if (!insert_key_images(tx, id, tx_relay)) + return false; + + m_blockchain.remove_txpool_tx(id); + m_blockchain.add_txpool_tx(id, blob, meta); + m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); + } lock.commit(); } catch (const std::exception &e) @@ -299,8 +340,9 @@ namespace cryptonote } tvc.m_added_to_pool = true; - if(meta.fee > 0 && tx_relay != relay_method::none) - tvc.m_should_be_relayed = true; + static_assert(unsigned(relay_method::none) == 0, "expected relay_method::none value to be zero"); + if(meta.fee > 0) + tvc.m_relay = tx_relay; } tvc.m_verifivation_failed = false; @@ -404,27 +446,18 @@ namespace cryptonote CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image]; - /* If any existing key-image in the set is publicly visible AND this is - not forcibly "kept_by_block", then fail (duplicate key image). If all - existing key images are supposed to be hidden, we silently allow so - that the node doesn't leak knowledge of a local/stem tx. */ - bool visible = false; + // Only allow multiple txes per key-image if kept-by-block. Only allow + // the same txid if going from local/stem->fluff. + if (tx_relay != relay_method::block) { - for (const crypto::hash& other_id : kei_image_set) - visible |= m_blockchain.txpool_tx_matches_category(other_id, relay_category::legacy); - } - - CHECK_AND_ASSERT_MES(!visible, false, "internal error: tx_relay=" << unsigned(tx_relay) + const bool one_txid = + (kei_image_set.empty() || (kei_image_set.size() == 1 && *(kei_image_set.cbegin()) == id)); + CHECK_AND_ASSERT_MES(one_txid, false, "internal error: tx_relay=" << unsigned(tx_relay) << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL << "tx_id=" << id); + } - /* If adding a tx (hash) that already exists, fail only if the tx has - been publicly "broadcast" previously. This way, when a private tx is - received for the first time from a remote node, "this" node will - respond as-if it were seen for the first time. LMDB does the - "hard-check" on key-images, so the effect is overwriting the existing - tx_pool metadata and "first seen" time. */ const bool new_or_previously_private = kei_image_set.insert(id).second || !m_blockchain.txpool_tx_matches_category(id, relay_category::legacy); @@ -562,7 +595,7 @@ namespace cryptonote td.last_failed_height = meta.last_failed_height; td.last_failed_id = meta.last_failed_id; td.receive_time = meta.receive_time; - td.last_relayed_time = meta.last_relayed_time; + td.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time; td.relayed = meta.relayed; td.do_not_relay = meta.do_not_relay; td.double_spend_seen = meta.double_spend_seen; @@ -582,8 +615,8 @@ namespace cryptonote CRITICAL_REGION_LOCAL1(m_blockchain); m_blockchain.for_all_txpool_txes([this, &hashes, &txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata*) { - const auto relay_method = meta.get_relay_method(); - if (relay_method != relay_method::block && relay_method != relay_method::fluff) + const auto tx_relay_method = meta.get_relay_method(); + if (tx_relay_method != relay_method::block && tx_relay_method != relay_method::fluff) return true; const auto i = std::find(hashes.begin(), hashes.end(), txid); if (i == hashes.end()) @@ -695,8 +728,13 @@ namespace cryptonote txs.reserve(m_blockchain.get_txpool_tx_count()); m_blockchain.for_all_txpool_txes([this, now, &txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *){ // 0 fee transactions are never relayed - if(!meta.pruned && meta.fee > 0 && !meta.do_not_relay && now - meta.last_relayed_time > get_relay_delay(now, meta.receive_time)) + if(!meta.pruned && meta.fee > 0 && !meta.do_not_relay) { + if (!meta.dandelionpp_stem && now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time)) + return true; + if (meta.dandelionpp_stem && meta.last_relayed_time < now) // for dandelion++ stem, this value is the embargo timeout + return true; + // if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem // mentioned by smooth where nodes would flush txes at slightly different times, causing // flushed txes to be re-added when received from a node which was just about to flush it @@ -721,9 +759,11 @@ namespace cryptonote //--------------------------------------------------------------------------------- void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method) { + crypto::random_poisson_seconds embargo_duration{dandelionpp_embargo_average}; + const auto now = std::chrono::system_clock::now(); + CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - const time_t now = time(NULL); LockedTXN lock(m_blockchain.get_db()); for (const auto& hash : hashes) { @@ -732,9 +772,15 @@ namespace cryptonote txpool_tx_meta_t meta; if (m_blockchain.get_txpool_tx_meta(hash, meta)) { + // txes can be received as "stem" or "fluff" in either order + meta.upgrade_relay_method(method); meta.relayed = true; - meta.last_relayed_time = now; - meta.set_relay_method(method); + + if (meta.dandelionpp_stem) + meta.last_relayed_time = std::chrono::system_clock::to_time_t(now + embargo_duration()); + else + meta.last_relayed_time = std::chrono::system_clock::to_time_t(now); + m_blockchain.update_txpool_tx(hash, meta); } } @@ -919,7 +965,7 @@ namespace cryptonote txi.receive_time = include_sensitive_data ? meta.receive_time : 0; txi.relayed = meta.relayed; // In restricted mode we do not include this data: - txi.last_relayed_time = include_sensitive_data ? meta.last_relayed_time : 0; + txi.last_relayed_time = (include_sensitive_data && !meta.dandelionpp_stem) ? meta.last_relayed_time : 0; txi.do_not_relay = meta.do_not_relay; txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(std::move(txi)); @@ -971,7 +1017,7 @@ namespace cryptonote 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.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time; txi.do_not_relay = meta.do_not_relay; txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(txi); @@ -1052,30 +1098,32 @@ namespace cryptonote return m_blockchain.get_db().txpool_has_tx(id, tx_category); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const + bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail - if(have_tx_keyimg_as_spent(tokey_in.k_image)) + if(have_tx_keyimg_as_spent(tokey_in.k_image, txid)) return true; } return false; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) const + bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const { CRITICAL_REGION_LOCAL(m_transactions_lock); - bool spent = false; const auto found = m_spent_key_images.find(key_im); - if (found != m_spent_key_images.end()) + if (found != m_spent_key_images.end() && !found->second.empty()) { - for (const crypto::hash& tx_hash : found->second) - spent |= m_blockchain.txpool_tx_matches_category(tx_hash, relay_category::broadcasted); + // If another tx is using the key image, always return as spent. + // See `insert_key_images`. + if (1 < found->second.size() || *(found->second.cbegin()) != txid) + return true; + return m_blockchain.txpool_tx_matches_category(txid, relay_category::broadcasted); } - return spent; + return false; } //--------------------------------------------------------------------------------- void tx_memory_pool::lock() const diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index ca0e50415..292d427e2 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -470,10 +470,11 @@ namespace cryptonote * @brief check if a transaction in the pool has a given spent key image * * @param key_im the spent key image to look for + * @param txid hash of the new transaction where `key_im` was seen. * * @return true if the spent key image is present, otherwise false */ - bool have_tx_keyimg_as_spent(const crypto::key_image& key_im) const; + bool have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const; /** * @brief check if any spent key image in a transaction is in the pool @@ -484,10 +485,11 @@ namespace cryptonote * @note see tx_pool::have_tx_keyimg_as_spent * * @param tx the transaction to check spent key images of + * @param txid hash of `tx`. * * @return true if any spent key images are present in the pool, otherwise false */ - bool have_tx_keyimges_as_spent(const transaction& tx) const; + bool have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const; /** * @brief forget a transaction's spent key images |