Skip to content

Commit 93b35b1

Browse files
committed
tx_memory_pool: make get_pool_info() handle skipped txs from get_transactions_info() correctly
1 parent 4ee0299 commit 93b35b1

File tree

3 files changed

+64
-29
lines changed

3 files changed

+64
-29
lines changed

src/cryptonote_core/cryptonote_core.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1462,7 +1462,7 @@ namespace cryptonote
14621462
//-----------------------------------------------------------------------------------------------
14631463
bool core::get_pool_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>>& txs, bool include_sensitive_txes, size_t limit_size) const
14641464
{
1465-
return m_mempool.get_transactions_info(txids, txs, include_sensitive_txes, limit_size);
1465+
return m_mempool.get_transactions_info(epee::to_span(txids), txs, include_sensitive_txes, limit_size);
14661466
}
14671467
//-----------------------------------------------------------------------------------------------
14681468
bool core::get_pool_transactions(std::vector<transaction>& txs, bool include_sensitive_data) const

src/cryptonote_core/tx_pool.cpp

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -644,16 +644,18 @@ namespace cryptonote
644644
return true;
645645
}
646646
//------------------------------------------------------------------
647-
bool tx_memory_pool::get_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_details>>& txs, bool include_sensitive, size_t limit_size) const
647+
bool tx_memory_pool::get_transactions_info(const epee::span<const crypto::hash> txids, std::vector<std::pair<crypto::hash, tx_details>>& txs, bool include_sensitive, size_t limit_size) const
648648
{
649649
CRITICAL_REGION_LOCAL(m_transactions_lock);
650650
CRITICAL_REGION_LOCAL1(m_blockchain);
651651

652652
txs.clear();
653+
txs.reserve(std::min<size_t>(txids.size(), 1000000)); // reserve limited to 1 million
653654
size_t size = 0;
654655

655-
for (const auto &it: txids)
656+
for (size_t i = 0; i < txids.size(); ++i)
656657
{
658+
const crypto::hash &it{txids[i]};
657659
tx_details details;
658660
bool success = get_transaction_info(it, details, include_sensitive, true/*include_blob*/);
659661
if (success && (!limit_size || (size + details.blob_size < limit_size)))
@@ -964,47 +966,80 @@ namespace cryptonote
964966
remaining_added_txids.clear();
965967
removed_txs.clear();
966968

967-
std::vector<crypto::hash> txids;
968969
if (!incremental)
969-
{
970970
LOG_PRINT_L2("Giving back the whole pool");
971-
// Give back the whole pool in 'added_txs'; because calling 'get_transaction_info' right inside the
972-
// anonymous method somehow results in an LMDB error with transactions we have to build a list of
973-
// ids first and get the full info afterwards
974-
get_transaction_hashes(txids, include_sensitive);
975-
if (txids.size() > max_tx_count)
971+
972+
// If incremental, handle removed TXIDs first since it's important that txs are removed
973+
// from synchronizers' pools, and we need need to estimate how much space we have left to
974+
// request full-bodied txs
975+
if (incremental)
976+
{
977+
std::multimap<time_t, removed_tx_info>::const_iterator rit = m_removed_txs_by_time.lower_bound(start_time);
978+
while (rit != m_removed_txs_by_time.end())
976979
{
977-
remaining_added_txids = std::vector<crypto::hash>(txids.begin() + max_tx_count, txids.end());
978-
txids.erase(txids.begin() + max_tx_count, txids.end());
980+
if (include_sensitive || !rit->second.sensitive)
981+
{
982+
removed_txs.push_back(rit->second.txid);
983+
}
984+
++rit;
979985
}
980-
get_transactions_info(txids, added_txs, include_sensitive, limit_size);
981-
return true;
982986
}
983987

984-
// Give back incrementally, based on time of entry into the map
988+
// Build a list of TXIDs that we want information for eventually. When `!incremental`, we might
989+
// collect TXIDs that have already left the pool which will cause a followup RPC call that
990+
// fetches non-existent txs, but that's *okay* since a non-incremental caller probably doesn't
991+
// care about being incredibly efficient. This problem is also unavoidable anyways if we need to
992+
// ever return anything inside `remaining_added_txids`, since those txs might have left the mempool
993+
// by the time the followup RPC call reaches the daemon
994+
std::vector<crypto::hash> txids;
995+
txids.reserve(m_added_txs_by_id.size());
985996
for (const auto &pit : m_added_txs_by_id)
986997
{
987-
if (pit.second >= start_time)
998+
const bool relevant_txid{!incremental || pit.second >= start_time};
999+
if (relevant_txid)
9881000
txids.push_back(pit.first);
9891001
}
990-
get_transactions_info(txids, added_txs, include_sensitive, limit_size);
991-
if (added_txs.size() > max_tx_count)
1002+
1003+
// Estimate max cumulative size left for full tx blobs
1004+
const size_t removed_txids_clawback{32 * removed_txs.size()};
1005+
const size_t remaining_txids_clawback{32 * txids.size()};
1006+
const size_t added_tx_txid_clawback(32 * txids.size());
1007+
const size_t total_clawback{removed_txids_clawback + remaining_txids_clawback + added_tx_txid_clawback};
1008+
const size_t cumulative_txblob_size_limit{limit_size > total_clawback ? limit_size - total_clawback : 0};
1009+
1010+
// Perform TX info fetch, limited to max_tx_count and cumulative_txblob_size_limit
1011+
const size_t max_full_tx_fetch_count{std::min(txids.size(), max_tx_count)};
1012+
if (cumulative_txblob_size_limit && max_full_tx_fetch_count)
9921013
{
993-
remaining_added_txids.reserve(added_txs.size() - max_tx_count);
994-
for (size_t i = max_tx_count; i < added_txs.size(); ++i)
995-
remaining_added_txids.push_back(added_txs[i].first);
996-
added_txs.erase(added_txs.begin() + max_tx_count, added_txs.end());
1014+
const epee::span<const crypto::hash> txid_fetch_span{&txids[0], max_full_tx_fetch_count};
1015+
if (!get_transactions_info(txid_fetch_span, added_txs, include_sensitive, cumulative_txblob_size_limit))
1016+
return false;
9971017
}
9981018

999-
std::multimap<time_t, removed_tx_info>::const_iterator rit = m_removed_txs_by_time.lower_bound(start_time);
1000-
while (rit != m_removed_txs_by_time.end())
1019+
// Populate `remaining_added_txids` with all TXIDs in `txids` and not in `added_txs`
1020+
if (added_txs.size() < txids.size())
10011021
{
1002-
if (include_sensitive || !rit->second.sensitive)
1022+
const size_t num_remaining{added_txs.size() - max_tx_count};
1023+
remaining_added_txids.reserve(num_remaining);
1024+
1025+
// This iteration code assumes that the get_transactions_info() method A) returns elements in
1026+
// the same order as they are passed in (skipping failures) and B) does not return TXIDs which
1027+
// don't exist in the input. If these assumptions don't hold then remaining_added_txids will
1028+
// be wrong, but it shouldn't cause any undefined behavior outside of that and should terminate
1029+
// in a short finite time period O(N) with N = txids.size()
1030+
auto txid_it = txids.cbegin();
1031+
auto added_it = added_txs.cbegin();
1032+
while (txid_it != txids.cend())
10031033
{
1004-
removed_txs.push_back(rit->second.txid);
1034+
if (added_it != added_txs.cend() && added_it->first == *txid_it)
1035+
++added_it;
1036+
else
1037+
remaining_added_txids.push_back(*txid_it);
1038+
1039+
++txid_it;
10051040
}
1006-
++rit;
10071041
}
1042+
10081043
return true;
10091044
}
10101045
//------------------------------------------------------------------

src/cryptonote_core/tx_pool.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,14 +480,14 @@ namespace cryptonote
480480
};
481481

482482
/**
483-
* @brief get infornation about a single transaction
483+
* @brief get information about a single transaction
484484
*/
485485
bool get_transaction_info(const crypto::hash &txid, tx_details &td, bool include_sensitive_data, bool include_blob = false) const;
486486

487487
/**
488488
* @brief get information about multiple transactions
489489
*/
490-
bool get_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_details>>& txs, bool include_sensitive_data = false, size_t limit_size = 0) const;
490+
bool get_transactions_info(const epee::span<const crypto::hash> txids, std::vector<std::pair<crypto::hash, tx_details>>& txs, bool include_sensitive_data = false, size_t limit_size = 0) const;
491491

492492
/**
493493
* @brief get transactions not in the passed set

0 commit comments

Comments
 (0)