aboutsummaryrefslogtreecommitdiff
path: root/src/cryptonote_core
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2017-09-18 13:08:16 +0200
committerRiccardo Spagni <ric@spagni.net>2017-09-18 13:08:16 +0200
commit591e53445b2643af1e9a5266ffa5324cad758255 (patch)
treeb10169969aab78bdf6868f9c48176a8804182ee9 /src/cryptonote_core
parentMerge pull request #2378 (diff)
parentFix various oversights/bugs in ZMQ RPC server code (diff)
downloadmonero-591e53445b2643af1e9a5266ffa5324cad758255.tar.xz
Merge pull request #2044
0299cb77 Fix various oversights/bugs in ZMQ RPC server code (Thomas Winget) 77986023 json serialization for rpc-relevant monero types (Thomas Winget) 5c1e08fe Refactor some things into more composable (smaller) functions (Thomas Winget) 9ac2ad07 DRY refactoring (Thomas Winget)
Diffstat (limited to 'src/cryptonote_core')
-rw-r--r--src/cryptonote_core/blockchain.cpp203
-rw-r--r--src/cryptonote_core/blockchain.h63
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp12
-rw-r--r--src/cryptonote_core/cryptonote_core.h17
-rw-r--r--src/cryptonote_core/tx_pool.cpp59
-rw-r--r--src/cryptonote_core/tx_pool.h23
6 files changed, 303 insertions, 74 deletions
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 69d2edf65..2330b6c42 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -751,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);
}
//------------------------------------------------------------------
@@ -1571,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.
@@ -1585,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;
+ std::vector<uint64_t> indices = get_random_outputs(amount, req.outs_count);
- // 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 (auto i : indices)
{
- 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);
+ 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 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);
- }
-
- }
- }
- else
- {
- // 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);
-
- // 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;
@@ -1816,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.
@@ -2025,28 +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));
}
- resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1);
+
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
@@ -4086,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);
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index b8ea657b4..e2da535cd 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -374,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
@@ -427,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
@@ -458,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
@@ -775,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
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index c3aeb15f0..01ee64b78 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -810,6 +810,13 @@ namespace cryptonote
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;
@@ -1200,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);
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 8e17569a9..a9ee9e9d0 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -463,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
@@ -708,6 +715,16 @@ 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
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index e61d95ac3..54d3ec0da 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -684,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);
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 6414620c9..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
{
@@ -269,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