RPC: add support for fetching transaction pool

This drops the dedicated GET_TRANSACTION_POOL endpoint entirely and
folds the txpool fetching into GET_TRANSACTIONS via a new `memory_pool`
parameter.  (This is backwards incompatible, of course, but the results
may not be too harmful: the wallet doesn't actually use this endpoint,
so it mainly affects internal oxend commands (fixed here) and the block
explorer (which is fairly easily fixed).

This also removes some of the useless GET_TRANSACTIONS bits such as
decode_as_json.

`fee` and `burned` are now always provided in the return (for both
specific requested transactions and mempool transactions).

As a side effect, this code ventured deep into core/blockchain in terms
of dealing with missed transactions: previously they were always
returned (via lvalue ref) as a vector, and always had to be specified.
This changes to take via pointer to an unordered_set, which can be null
if you don't care about the missed ones.  This reduces several calls
that indeed didn't care about collecting the missed values, and improved
virtually all the places that *did* care which need to query which ones
were missed rather than needing a specific order.
This commit is contained in:
Jason Rhinelander 2021-08-19 00:43:26 -03:00 committed by Thomas Winget
parent 27683554e4
commit fe376e184d
17 changed files with 350 additions and 448 deletions

View file

@ -312,15 +312,11 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
using clock = std::chrono::steady_clock;
using work_time = std::chrono::duration<float>;
int64_t constexpr BLOCK_COUNT = 1000;
constexpr int64_t BLOCK_COUNT = 1000;
auto work_start = clock::now();
auto scan_start = work_start;
work_time ons_duration{}, snl_duration{}, ons_iteration_duration{}, snl_iteration_duration{};
std::vector<cryptonote::block> blocks;
std::vector<cryptonote::transaction> txs;
std::vector<crypto::hash> missed_txs;
for (int64_t block_count = total_blocks,
index = 0;
block_count > 0;
@ -343,7 +339,7 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
ons_iteration_duration = snl_iteration_duration = {};
}
blocks.clear();
std::vector<cryptonote::block> blocks;
uint64_t height = start_height + (index * BLOCK_COUNT);
if (!get_blocks_only(height, static_cast<uint64_t>(BLOCK_COUNT), blocks))
{
@ -355,9 +351,8 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
{
uint64_t block_height = get_block_height(blk);
txs.clear();
missed_txs.clear();
if (!get_transactions(blk.tx_hashes, txs, missed_txs))
std::vector<cryptonote::transaction> txs;
if (!get_transactions(blk.tx_hashes, txs))
{
MERROR("Unable to get transactions for block for updating ONS DB: " << cryptonote::get_block_hash(blk));
return false;
@ -1952,8 +1947,8 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
// NOTE: Execute Alt Block Hooks
{
std::vector<transaction> txs;
std::vector<crypto::hash> missed;
if (!get_transactions(b.tx_hashes, txs, missed))
std::unordered_set<crypto::hash> missed;
if (!get_transactions(b.tx_hashes, txs, &missed))
{
bvc.m_verifivation_failed = true;
return false;
@ -2141,8 +2136,8 @@ bool Blockchain::get_blocks_only(uint64_t start_offset, size_t count, std::vecto
{
for(const auto& blk : blocks)
{
std::vector<crypto::hash> missed_ids;
get_transactions_blobs(blk.tx_hashes, *txs, missed_ids);
std::unordered_set<crypto::hash> missed_ids;
get_transactions_blobs(blk.tx_hashes, *txs, &missed_ids);
CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "has missed transactions in own block in main blockchain");
}
}
@ -2164,8 +2159,8 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector<std
for(const auto& blk : blocks)
{
std::vector<crypto::hash> missed_ids;
get_transactions_blobs(blk.second.tx_hashes, txs, missed_ids);
std::unordered_set<crypto::hash> missed_ids;
get_transactions_blobs(blk.second.tx_hashes, txs, &missed_ids);
CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "has missed transactions in own block in main blockchain");
}
@ -2211,7 +2206,11 @@ bool Blockchain::handle_get_blocks(NOTIFY_REQUEST_GET_BLOCKS::request& arg, NOTI
db_rtxn_guard rtxn_guard (m_db);
rsp.current_blockchain_height = get_current_blockchain_height();
std::vector<std::pair<cryptonote::blobdata,block>> blocks;
get_blocks(arg.blocks, blocks, rsp.missed_ids);
{
std::unordered_set<crypto::hash> missed_ids;
get_blocks(arg.blocks, blocks, &missed_ids);
rsp.missed_ids.insert(rsp.missed_ids.end(), missed_ids.begin(), missed_ids.end());
}
uint64_t const top_height = (m_db->height() - 1);
uint64_t const earliest_height_to_sync_checkpoints_granularly =
@ -2249,8 +2248,8 @@ bool Blockchain::handle_get_blocks(NOTIFY_REQUEST_GET_BLOCKS::request& arg, NOTI
// FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids
// is for missed blocks, not missed transactions as well.
std::vector<crypto::hash> missed_tx_ids;
get_transactions_blobs(block.tx_hashes, block_entry.txs, missed_tx_ids);
std::unordered_set<crypto::hash> missed_tx_ids;
get_transactions_blobs(block.tx_hashes, block_entry.txs, &missed_tx_ids);
for (auto &h : block.tx_hashes)
{
@ -2292,10 +2291,10 @@ bool Blockchain::handle_get_txs(NOTIFY_REQUEST_GET_TXS::request& arg, NOTIFY_NEW
std::lock(blockchain_lock, blink_lock);
db_rtxn_guard rtxn_guard (m_db);
std::vector<crypto::hash> missed;
std::unordered_set<crypto::hash> missed;
// First check the blockchain for any txs:
get_transactions_blobs(arg.txs, rsp.txs, missed);
get_transactions_blobs(arg.txs, rsp.txs, &missed);
// Look for any missed txes in the mempool:
m_tx_pool.find_transactions(missed, rsp.txs);
@ -2543,7 +2542,7 @@ uint64_t Blockchain::block_difficulty(uint64_t i) const
//------------------------------------------------------------------
//TODO: return type should be void, throw on exception
// alternatively, return true only if no blocks missed
bool Blockchain::get_blocks(const std::vector<crypto::hash>& block_ids, std::vector<std::pair<cryptonote::blobdata,block>>& blocks, std::vector<crypto::hash>& missed_bs) const
bool Blockchain::get_blocks(const std::vector<crypto::hash>& block_ids, std::vector<std::pair<cryptonote::blobdata,block>>& blocks, std::unordered_set<crypto::hash>* missed_bs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
std::unique_lock lock{*this};
@ -2561,11 +2560,11 @@ bool Blockchain::get_blocks(const std::vector<crypto::hash>& block_ids, std::vec
{
LOG_ERROR("Invalid block: " << block_hash);
blocks.pop_back();
missed_bs.push_back(block_hash);
if (missed_bs) missed_bs->insert(block_hash);
}
}
else
missed_bs.push_back(block_hash);
if (missed_bs) missed_bs->insert(block_hash);
}
catch (const std::exception& e)
{
@ -2577,7 +2576,7 @@ bool Blockchain::get_blocks(const std::vector<crypto::hash>& block_ids, std::vec
//------------------------------------------------------------------
//TODO: return type should be void, throw on exception
// alternatively, return true only if no transactions missed
bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs, bool pruned) const
bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::unordered_set<crypto::hash>* missed_txs, bool pruned) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
std::unique_lock lock{*this};
@ -2592,8 +2591,8 @@ bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids
txs.push_back(std::move(tx));
else if (!pruned && m_db->get_tx_blob(tx_hash, tx))
txs.push_back(std::move(tx));
else
missed_txs.push_back(tx_hash);
else if (missed_txs)
missed_txs->insert(tx_hash);
}
catch (const std::exception& e)
{
@ -2627,7 +2626,7 @@ size_t get_transaction_version(const cryptonote::blobdata &bd)
return version;
}
//------------------------------------------------------------------
bool Blockchain::get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::vector<crypto::hash>& missed_txs) const
bool Blockchain::get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::unordered_set<crypto::hash>* missed_txs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
std::unique_lock lock{*this};
@ -2649,8 +2648,8 @@ bool Blockchain::get_split_transactions_blobs(const std::vector<crypto::hash>& t
if (!m_db->get_prunable_tx_blob(tx_hash, prunable))
prunable.clear();
}
else
missed_txs.push_back(tx_hash);
else if (missed_txs)
missed_txs->insert(tx_hash);
}
catch (const std::exception& e)
{
@ -2660,7 +2659,7 @@ bool Blockchain::get_split_transactions_blobs(const std::vector<crypto::hash>& t
return true;
}
//------------------------------------------------------------------
bool Blockchain::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::vector<crypto::hash>& missed_txs) const
bool Blockchain::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::unordered_set<crypto::hash>* missed_txs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
std::unique_lock lock{*this};
@ -2681,8 +2680,8 @@ bool Blockchain::get_transactions(const std::vector<crypto::hash>& txs_ids, std:
return false;
}
}
else
missed_txs.push_back(tx_hash);
else if (missed_txs)
missed_txs->insert(tx_hash);
}
catch (const std::exception& e)
{
@ -2782,9 +2781,9 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons
}
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");
std::unordered_set<crypto::hash> mis;
get_transactions_blobs(b.tx_hashes, txs, &mis, pruned);
CHECK_AND_ASSERT_MES(mis.empty(), false, "internal error, transaction from block not found");
}
size += blocks.back().first.first.size();
for (const auto &t: txs)

View file

@ -716,25 +716,25 @@ namespace cryptonote
*
* @param block_ids a vector of block hashes for which to get the corresponding blocks
* @param blocks return-by-reference a vector to store result blocks in
* @param missed_bs return-by-reference a vector to store missed blocks in
* @param missed_bs optional pointer to an unordered_set to add missed blocks ids to
*
* @return false if an unexpected exception occurs, else true
*/
bool get_blocks(const std::vector<crypto::hash>& block_ids, std::vector<std::pair<cryptonote::blobdata,block>>& blocks, std::vector<crypto::hash>& missed_bs) const;
bool get_blocks(const std::vector<crypto::hash>& block_ids, std::vector<std::pair<cryptonote::blobdata,block>>& blocks, std::unordered_set<crypto::hash>* missed_bs = nullptr) const;
/**
* @brief gets transactions based on a list of transaction hashes
*
* @param txs_ids a vector of hashes for which to get the corresponding transactions
* @param txs return-by-reference a vector to store result transactions in
* @param missed_txs return-by-reference a vector to store missed transactions in
* @param missed_txs optional pointer to an unordered set to add missed transactions ids to
* @param pruned whether to return full or pruned blobs
*
* @return false if an unexpected exception occurs, else true
*/
bool get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<blobdata>& txs, std::vector<crypto::hash>& missed_txs, bool pruned = false) const;
bool get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::vector<crypto::hash>& missed_txs) const;
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::vector<crypto::hash>& missed_txs) const;
bool get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<blobdata>& txs, std::unordered_set<crypto::hash>* missed_txs = nullptr, bool pruned = false) const;
bool get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::unordered_set<crypto::hash>* missed_txs = nullptr) const;
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::unordered_set<crypto::hash>* missed_txs = nullptr) const;
/**
* @brief looks up transactions based on a list of transaction hashes and returns the block

View file

@ -457,17 +457,22 @@ namespace cryptonote
return m_blockchain_storage.get_blocks_only(start_offset, count, blocks);
}
//-----------------------------------------------------------------------------------------------
bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs) const
bool core::get_blocks(const std::vector<crypto::hash>& block_ids, std::vector<std::pair<cryptonote::blobdata, block>> blocks, std::unordered_set<crypto::hash>* missed_bs) const
{
return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs);
}
//-----------------------------------------------------------------------------------------------
bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::unordered_set<crypto::hash>* missed_txs) const
{
return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs);
}
//-----------------------------------------------------------------------------------------------
bool core::get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::vector<crypto::hash>& missed_txs) const
bool core::get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::unordered_set<crypto::hash>* missed_txs) const
{
return m_blockchain_storage.get_split_transactions_blobs(txs_ids, txs, missed_txs);
}
//-----------------------------------------------------------------------------------------------
bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::vector<crypto::hash>& missed_txs) const
bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::unordered_set<crypto::hash>* missed_txs) const
{
return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs);
}
@ -1777,9 +1782,8 @@ namespace cryptonote
[this, &cache_to, &result, &cache_build_started](uint64_t height, const crypto::hash& hash, const block& b){
auto& [emission_amount, total_fee_amount, burnt_oxen] = result;
std::vector<transaction> txs;
std::vector<crypto::hash> missed_txs;
uint64_t coinbase_amount = get_outs_money_amount(b.miner_tx);
get_transactions(b.tx_hashes, txs, missed_txs);
get_transactions(b.tx_hashes, txs);
uint64_t tx_fee_amount = 0;
for(const auto& tx: txs)
{
@ -2079,9 +2083,9 @@ namespace cryptonote
}
else if(bvc.m_added_to_main_chain)
{
std::vector<crypto::hash> missed_txs;
std::unordered_set<crypto::hash> missed_txs;
std::vector<cryptonote::blobdata> txs;
m_blockchain_storage.get_transactions_blobs(b.tx_hashes, txs, missed_txs);
m_blockchain_storage.get_transactions_blobs(b.tx_hashes, txs, &missed_txs);
if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b))
{
LOG_PRINT_L1("Block found but, seems that reorganize just happened after that, do not relay this block");

View file

@ -510,11 +510,7 @@ namespace cryptonote
*
* @note see Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const
*/
template<class t_ids_container, class t_blocks_container, class t_missed_container>
bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const
{
return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs);
}
bool get_blocks(const std::vector<crypto::hash>& block_ids, std::vector<std::pair<cryptonote::blobdata, block>> blocks, std::unordered_set<crypto::hash>* missed_bs = nullptr) const;
/**
* @copydoc Blockchain::get_block_id_by_height
@ -528,21 +524,21 @@ namespace cryptonote
*
* @note see Blockchain::get_transactions
*/
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs) const;
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::unordered_set<crypto::hash>* missed_txs = nullptr) const;
/**
* @copydoc Blockchain::get_transactions
*
* @note see Blockchain::get_transactions
*/
bool get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::vector<crypto::hash>& missed_txs) const;
bool get_split_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>& txs, std::unordered_set<crypto::hash>* missed_txs = nullptr) const;
/**
* @copydoc Blockchain::get_transactions
*
* @note see Blockchain::get_transactions
*/
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::vector<crypto::hash>& missed_txs) const;
bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<transaction>& txs, std::unordered_set<crypto::hash>* missed_txs = nullptr) const;
/**
* @copydoc Blockchain::get_block_by_hash

View file

@ -1280,7 +1280,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
int tx_memory_pool::find_transactions(const std::vector<crypto::hash> &tx_hashes, std::vector<cryptonote::blobdata> &txblobs) const
int tx_memory_pool::find_transactions(const std::unordered_set<crypto::hash>& tx_hashes, std::vector<cryptonote::blobdata>& txblobs) const
{
if (tx_hashes.empty())
return 0;
@ -1493,16 +1493,13 @@ namespace cryptonote
if (m_blockchain.get_blocks_only(immutable + 1, height, blocks))
{
std::vector<cryptonote::transaction> txs;
std::vector<crypto::hash> missed_txs;
uint64_t earliest = height;
for (auto it = blocks.rbegin(); it != blocks.rend(); it++)
{
const auto& block = *it;
auto block_height = cryptonote::get_block_height(block);
txs.clear();
missed_txs.clear();
if (!m_blockchain.get_transactions(block.tx_hashes, txs, missed_txs))
if (!m_blockchain.get_transactions(block.tx_hashes, txs))
{
MERROR("Unable to get transactions for block " << block.hash);
can_fix_with_a_rollback = false;

View file

@ -475,7 +475,7 @@ namespace cryptonote
*
* @return number of transactions added to txblobs
*/
int find_transactions(const std::vector<crypto::hash> &tx_hashes, std::vector<cryptonote::blobdata> &txblobs) const;
int find_transactions(const std::unordered_set<crypto::hash>& tx_hashes, std::vector<cryptonote::blobdata>& txblobs) const;
/**
* @brief get a list of all relayable transactions and their hashes

View file

@ -41,7 +41,9 @@
#include <list>
#include <ctime>
#include <chrono>
#include <fmt/core.h>
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "common/string_util.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/hardfork.h"
@ -729,9 +731,9 @@ namespace cryptonote
{
std::vector<crypto::hash> tx_ids;
std::vector<transaction> txes;
std::vector<crypto::hash> missing;
std::unordered_set<crypto::hash> missing;
tx_ids.push_back(tx_hash);
if (m_core.get_transactions(tx_ids, txes, missing) && missing.empty())
if (m_core.get_transactions(tx_ids, txes, &missing) && missing.empty())
{
if (txes.size() == 1)
{
@ -1021,8 +1023,8 @@ namespace cryptonote
}
std::vector<cryptonote::transaction> txs;
std::vector<crypto::hash> missed;
if (!m_core.get_transactions(txids, txs, missed))
std::unordered_set<crypto::hash> missed;
if (!m_core.get_transactions(txids, txs, &missed))
{
LOG_ERROR_CCONTEXT("Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX, "
<< "failed to get requested transactions");
@ -2363,18 +2365,20 @@ skip:
<< "Use the \"help\" command to see the list of available commands.\n"
<< "**********************************************************************");
m_sync_timer.pause();
if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info"))
if (CLOG_ENABLED(Info, "sync-info"))
{
const auto sync_time = m_sync_timer.value();
const auto add_time = m_add_timer.value();
if (sync_time > 0ns && add_time > 0ns)
{
MCLOG_YELLOW(el::Level::Info, "sync-info", "Sync time: " << tools::friendly_duration(sync_time) << " min, idle time " <<
(100.f * (1.0f - add_time / sync_time)) << "%" << ", " <<
(10 * m_sync_download_objects_size / 1024 / 1024) / 10.f << " + " <<
(10 * m_sync_download_chain_size / 1024 / 1024) / 10.f << " MB downloaded, " <<
100.0f * m_sync_old_spans_downloaded / m_sync_spans_downloaded << "% old spans, " <<
100.0f * m_sync_bad_spans_downloaded / m_sync_spans_downloaded << "% bad spans");
MCLOG_YELLOW(el::Level::Info, "sync-info",
fmt::format("Sync time: {}, idle time {:.2f}%, {:.1f} + {:.1f} MB downloaded, {:.2f}% old spans, {:2f}% bad spans",
tools::friendly_duration(sync_time),
((sync_time - add_time) / sync_time) * 100.0,
m_sync_download_objects_size / 1'000'000.0,
m_sync_download_chain_size / 1'000'000.0,
100.0 * m_sync_old_spans_downloaded / m_sync_spans_downloaded,
100.0 * m_sync_bad_spans_downloaded / m_sync_spans_downloaded));
}
}
m_core.on_synchronized();

View file

@ -442,14 +442,14 @@ bool command_parser_executor::print_transaction_pool_long(const std::vector<std:
{
if (!args.empty()) return false;
return m_executor.print_transaction_pool_long();
return m_executor.print_transaction_pool(true);
}
bool command_parser_executor::print_transaction_pool_short(const std::vector<std::string>& args)
{
if (!args.empty()) return false;
return m_executor.print_transaction_pool_short();
return m_executor.print_transaction_pool(false);
}
bool command_parser_executor::print_transaction_pool_stats(const std::vector<std::string>& args)

View file

@ -44,6 +44,7 @@
#include "rpc/rpc_args.h"
#include "rpc/http_server.h"
#include "rpc/lmq_server.h"
#include "rpc/bootstrap_daemon.h"
#include "cryptonote_protocol/quorumnet.h"
#include "cryptonote_core/uptime_proof.h"

View file

@ -915,99 +915,87 @@ bool rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) {
return false;
}
static void print_pool(const std::vector<cryptonote::rpc::tx_info> &transactions, bool include_json) {
if (transactions.empty())
static void print_pool(const json& txs) {
if (txs.empty())
{
tools::msg_writer() << "Pool is empty" << std::endl;
tools::msg_writer() << "Pool is empty\n";
return;
}
const time_t now = time(NULL);
tools::msg_writer() << "Transactions:";
for (auto &tx_info : transactions)
const time_t now = time(nullptr);
tools::msg_writer() << txs.size() << " Transactions:\n";
std::vector<std::string> lines;
for (auto &tx : txs)
{
auto w = tools::msg_writer();
w << "id: " << tx_info.id_hash << "\n";
if (include_json) w << tx_info.tx_json << "\n";
w << "blob_size: " << tx_info.blob_size << "\n"
<< "weight: " << tx_info.weight << "\n"
<< "fee: " << cryptonote::print_money(tx_info.fee) << "\n"
/// NB(Oxen): in v13 we have min_fee = per_out*outs + per_byte*bytes, only the total fee/byte matters for
/// the purpose of building a block template from the pool, so we still print the overall fee / byte here.
/// (we can't back out the individual per_out and per_byte that got used anyway).
<< "fee/byte: " << cryptonote::print_money(tx_info.fee / (double)tx_info.weight) << "\n"
<< "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")\n"
<< "relayed: " << (tx_info.relayed ? std::to_string(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")" : "no") << "\n"
<< std::boolalpha
<< "do_not_relay: " << tx_info.do_not_relay << "\n"
<< "blink: " << tx_info.blink << "\n"
<< "kept_by_block: " << tx_info.kept_by_block << "\n"
<< "double_spend_seen: " << tx_info.double_spend_seen << "\n"
<< std::noboolalpha
<< "max_used_block_height: " << tx_info.max_used_block_height << "\n"
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << "\n"
<< "last_failed_height: " << tx_info.last_failed_height << "\n"
<< "last_failed_id: " << tx_info.last_failed_id_hash << "\n";
std::vector<std::string_view> status;
if (tx.value("blink", false)) status.push_back("blink"sv);
status.push_back(tx["relayed"].get<bool>() ? "relayed"sv : "not relayed"sv);
if (tx.value("do_not_relay", false)) status.push_back("do not relay"sv);
if (tx.value("double_spend_seen", false)) status.push_back("double spend"sv);
if (tx.value("kept_by_block", false)) status.push_back("from popped block"sv);
lines.clear();
lines.push_back(tx["tx_hash"].get_ref<const std::string&>() + ":"s);
lines.push_back(fmt::format("size/weight: {}/{}", tx["size"].get<int>(), tx["weight"].get<int>()));
lines.push_back(fmt::format("fee: {} ({}/byte)",
cryptonote::print_money(tx["fee"].get<uint64_t>()), cryptonote::print_money(tx["fee"].get<double>() / tx["weight"].get<double>())));
lines.push_back(fmt::format("received: {} ({})", tx["received_timestamp"].get<std::time_t>(), get_human_time_ago(tx["received_timestamp"].get<std::time_t>(), now)));
lines.push_back("status: " + tools::join(", ", status));
lines.push_back(fmt::format("top required block: {} ({})", tx["max_used_height"].get<uint64_t>(), tx["max_used_block"]));
if (tx.count("last_failed_height"))
lines.push_back(fmt::format("last failed block: {} ({})", tx["last_failed_height"].get<uint64_t>(), tx["last_failed_block"].get<std::string_view>()));
if (auto extra = tx.find("extra"); extra != tx.end()) {
lines.push_back("transaction extra: ");
for (auto c : extra->dump(2)) {
if (c == '\n')
lines.back() += "\n "sv;
else
lines.back() += c;
}
}
tools::msg_writer() << tools::join("\n ", lines) << "\n";
}
}
bool rpc_command_executor::print_transaction_pool_long() {
GET_TRANSACTION_POOL::response res{};
if (!invoke<GET_TRANSACTION_POOL>({}, res, "Failed to retrieve transaction pool details"))
bool rpc_command_executor::print_transaction_pool(bool long_format) {
json args{{"memory_pool", true}};
if (long_format) args["tx_extra"] = true;
auto maybe_pool = try_running([this, &args] { return invoke<GET_TRANSACTIONS>(args); },
"Failed to retrieve transaction pool details");
if (!maybe_pool)
return false;
auto& pool = *maybe_pool;
print_pool(res.transactions, true);
print_pool(pool["txs"]);
if (res.spent_key_images.empty())
{
if (! res.transactions.empty())
tools::msg_writer() << "WARNING: Inconsistent pool state - no spent key images";
}
else
{
tools::msg_writer() << ""; // one newline
tools::msg_writer() << "Spent key images: ";
for (const auto& kinfo : res.spent_key_images)
if (long_format) {
// We used to have a warning here when we had transactions but no key_images; but that can
// happen on Oxen with 0-output tx state change transactions.
if (!pool["mempool_key_images"].empty())
{
tools::msg_writer() << "key image: " << kinfo.id_hash;
if (kinfo.txs_hashes.size() == 1)
tools::msg_writer() << "\nSpent key images: ";
for (const auto& [key, tx_hashes] : pool["mempool_key_images"].items())
{
tools::msg_writer() << " tx: " << kinfo.txs_hashes[0];
}
else if (kinfo.txs_hashes.size() == 0)
{
tools::msg_writer() << " WARNING: spent key image has no txs associated";
}
else
{
tools::msg_writer() << " NOTE: key image for multiple txs: " << kinfo.txs_hashes.size();
for (const std::string& tx_id : kinfo.txs_hashes)
tools::msg_writer() << "key image: " << key;
if (tx_hashes.size() == 1)
tools::msg_writer() << " tx: " << tx_hashes.front().get<std::string_view>();
else if (tx_hashes.empty())
tools::msg_writer() << " WARNING: spent key image has no txs associated!";
else
{
tools::msg_writer() << " tx: " << tx_id;
tools::msg_writer() << fmt::format(" NOTE: key image for multiple transactions ({}):", tx_hashes.size());
for (const auto& txid : tx_hashes)
tools::msg_writer() << " - " << txid.get<std::string_view>();
}
}
}
if (res.transactions.empty())
{
tools::msg_writer() << "WARNING: Inconsistent pool state - no transactions";
if (pool["txs"].empty())
tools::msg_writer() << "WARNING: Inconsistent pool state - key images but no no transactions";
}
}
return true;
}
bool rpc_command_executor::print_transaction_pool_short() {
GET_TRANSACTION_POOL::request req{};
GET_TRANSACTION_POOL::response res{};
if (!invoke<GET_TRANSACTION_POOL>({}, res, "Failed to retrieve transaction pool details"))
return false;
print_pool(res.transactions, false);
return true;
}
bool rpc_command_executor::print_transaction_pool_stats() {
auto full_reward_zone = try_running([this] {

View file

@ -176,9 +176,7 @@ public:
bool is_key_image_spent(const crypto::key_image &ki);
bool print_transaction_pool_long();
bool print_transaction_pool_short();
bool print_transaction_pool(bool long_format);
bool print_transaction_pool_stats();

View file

@ -50,6 +50,7 @@
#include "epee/string_tools.h"
#include "core_rpc_server.h"
#include "core_rpc_server_command_parser.h"
#include "bootstrap_daemon.h"
#include "rpc_args.h"
#include "core_rpc_server_error_codes.h"
#include "common/command_line.h"
@ -605,8 +606,7 @@ namespace cryptonote::rpc {
return res;
}
std::vector<transaction> txs;
std::vector<crypto::hash> missed_txs;
m_core.get_transactions(blk.tx_hashes, txs, missed_txs);
m_core.get_transactions(blk.tx_hashes, txs);
res.blocks.resize(res.blocks.size() + 1);
res.blocks.back().block = block_to_blob(blk);
for (auto& tx : txs)
@ -752,13 +752,13 @@ namespace cryptonote::rpc {
// a single one we want just the value itself; this does that. Returns a reference to the
// assigned value (whether as a top-level value or array element).
template <typename T>
json& set(const std::string& key, T&& value, bool binary = is_binary_parameter<T> || is_binary_vector<T>) {
json& set(const std::string& key, T&& value, bool binary = is_binary_parameter<T> || is_binary_container<T>) {
auto* x = &entry[key];
if (!x->is_null() && !x->is_array())
x = &(entry[key] = json::array({std::move(*x)}));
if (x->is_array())
x = &x->emplace_back();
if constexpr (is_binary_parameter<T> || is_binary_vector<T> || std::is_convertible_v<T, std::string_view>) {
if constexpr (is_binary_parameter<T> || is_binary_container<T> || std::is_convertible_v<T, std::string_view>) {
if (binary)
return json_binary_proxy{*x, format} = std::forward<T>(value);
}
@ -894,23 +894,20 @@ namespace cryptonote::rpc {
}
struct tx_info {
crypto::hash id; // txid hash
txpool_tx_meta_t meta;
std::string tx_blob; // Blob containing the transaction data.
bool blink; // True if this is a signed blink transaction
//std::optional<GET_TRANSACTIONS::extra_entry> extra; // Parsed tx_extra information (only if requested)
//std::optional<uint64_t> stake_amount; // Will be set to the staked amount if the transaction is a staking transaction *and* stake amounts were requested.
};
static std::vector<tx_info> get_pool_txs_impl(cryptonote::core& core, std::function<void(const transaction&, tx_info&)> post_process) {
static std::unordered_map<crypto::hash, tx_info> get_pool_txs_impl(cryptonote::core& core) {
auto& bc = core.get_blockchain_storage();
auto& pool = core.get_pool();
std::vector<tx_info> tx_infos;
std::unordered_map<crypto::hash, tx_info> tx_infos;
tx_infos.reserve(bc.get_txpool_tx_count());
bc.for_all_txpool_txes(
[&tx_infos, &pool, post_process=std::move(post_process)]
[&tx_infos, &pool]
(const crypto::hash& txid, const txpool_tx_meta_t& meta, const cryptonote::blobdata* bd) {
transaction tx;
if (!parse_and_validate_tx_from_blob(*bd, tx))
@ -919,22 +916,18 @@ namespace cryptonote::rpc {
// continue
return true;
}
auto& txi = tx_infos.emplace_back();
txi.id = txid;
auto& txi = tx_infos[txid];
txi.meta = meta;
txi.tx_blob = *bd;
tx.set_hash(txid);
//txi.tx_json = obj_to_json_str(tx);
txi.blink = pool.has_blink(txid);
if (post_process)
post_process(tx, txi);
return true;
}, true);
return tx_infos;
}
auto pool_locks(cryptonote::core& core) {
static auto pool_locks(cryptonote::core& core) {
auto& pool = core.get_pool();
std::unique_lock tx_lock{pool, std::defer_lock};
std::unique_lock bc_lock{core.get_blockchain_storage(), std::defer_lock};
@ -943,17 +936,18 @@ namespace cryptonote::rpc {
return std::make_tuple(std::move(tx_lock), std::move(bc_lock), std::move(blink_lock));
}
static std::pair<std::vector<tx_info>, tx_memory_pool::key_images_container> get_pool_txs_kis(
cryptonote::core& core, std::function<void(const transaction&, tx_info&)> post_process = {}) {
static std::pair<std::unordered_map<crypto::hash, tx_info>, tx_memory_pool::key_images_container> get_pool_txs_kis(cryptonote::core& core) {
auto locks = pool_locks(core);
return {get_pool_txs_impl(core, std::move(post_process)), core.get_pool().get_spent_key_images(true)};
return {get_pool_txs_impl(core), core.get_pool().get_spent_key_images(true)};
}
static std::vector<tx_info> get_pool_txs(
/*
static std::unordered_map<crypto::hash, tx_info> get_pool_txs(
cryptonote::core& core, std::function<void(const transaction&, tx_info&)> post_process = {}) {
auto locks = pool_locks(core);
return get_pool_txs_impl(core, std::move(post_process));
return get_pool_txs_impl(core);
}
*/
static tx_memory_pool::key_images_container get_pool_kis(
cryptonote::core& core, std::function<void(const transaction&, tx_info&)> post_process = {}) {
@ -970,81 +964,84 @@ namespace cryptonote::rpc {
return res;
*/
std::vector<crypto::hash> missed_txs;
std::unordered_set<crypto::hash> missed_txs;
using split_tx = std::tuple<crypto::hash, std::string, crypto::hash, std::string>;
std::vector<split_tx> txs;
if (!m_core.get_split_transactions_blobs(get.request.tx_hashes, txs, missed_txs))
{
get.response["status"] = STATUS_FAILED;
return;
if (!get.request.tx_hashes.empty()) {
if (!m_core.get_split_transactions_blobs(get.request.tx_hashes, txs, &missed_txs))
{
get.response["status"] = STATUS_FAILED;
return;
}
LOG_PRINT_L2("Found " << txs.size() << "/" << get.request.tx_hashes.size() << " transactions on the blockchain");
}
LOG_PRINT_L2("Found " << txs.size() << "/" << get.request.tx_hashes.size() << " transactions on the blockchain");
// try the pool for any missing txes
auto& pool = m_core.get_pool();
size_t found_in_pool = 0;
std::unordered_map<crypto::hash, tx_info> per_tx_pool_tx_info;
if (!missed_txs.empty())
std::unordered_map<crypto::hash, tx_info> found_in_pool;
if (!missed_txs.empty() || get.request.memory_pool)
{
try {
auto pool_tx_info = get_pool_txs(m_core);
// sort to match original request
std::vector<split_tx> sorted_txs;
unsigned txs_processed = 0;
for (const auto& h: get.request.tx_hashes)
{
auto missed_it = std::find(missed_txs.begin(), missed_txs.end(), h);
if (missed_it == missed_txs.end())
{
if (txs.size() == txs_processed)
{
get.response["status"] = "Failed: internal error - txs is empty";
return;
}
// core returns the ones it finds in the right order
if (std::get<0>(txs[txs_processed]) != h)
{
get.response["status"] = "Failed: tx hash mismatch";
return;
}
sorted_txs.push_back(std::move(txs[txs_processed]));
++txs_processed;
continue;
}
auto [pool_txs, pool_kis] = get_pool_txs_kis(m_core);
auto ptx_it = std::find_if(pool_tx_info.begin(), pool_tx_info.end(),
[&h](const auto& txi) { return h == txi.id; });
if (ptx_it != pool_tx_info.end())
{
cryptonote::transaction tx;
if (!cryptonote::parse_and_validate_tx_from_blob(ptx_it->tx_blob, tx))
{
get.response["status"] = "Failed to parse and validate tx from blob";
return;
auto split_mempool_tx = [](std::pair<const crypto::hash, tx_info>& info) {
cryptonote::transaction tx;
if (!cryptonote::parse_and_validate_tx_from_blob(info.second.tx_blob, tx))
throw std::runtime_error{"Unable to parse and validate tx from blob"};
serialization::binary_string_archiver ba;
try {
tx.serialize_base(ba);
} catch (const std::exception& e) {
throw std::runtime_error{"Failed to serialize transaction base: "s + e.what()};
}
std::string pruned = ba.str();
std::string pruned2{info.second.tx_blob, pruned.size()};
return split_tx{info.first, std::move(pruned), get_transaction_prunable_hash(tx), std::move(pruned2)};
};
if (!get.request.tx_hashes.empty()) {
// sort to match original request
std::vector<split_tx> sorted_txs;
unsigned txs_processed = 0;
for (const auto& h: get.request.tx_hashes) {
if (auto missed_it = missed_txs.find(h); missed_it == missed_txs.end()) {
if (txs.size() == txs_processed) {
get.response["status"] = "Failed: internal error - txs is empty";
return;
}
// core returns the ones it finds in the right order
if (std::get<0>(txs[txs_processed]) != h) {
get.response["status"] = "Failed: internal error - tx hash mismatch";
return;
}
sorted_txs.push_back(std::move(txs[txs_processed]));
++txs_processed;
} else if (auto ptx_it = pool_txs.find(h); ptx_it != pool_txs.end()) {
sorted_txs.push_back(split_mempool_tx(*ptx_it));
missed_txs.erase(missed_it);
found_in_pool.emplace(h, std::move(ptx_it->second));
}
serialization::binary_string_archiver ba;
try {
tx.serialize_base(ba);
} catch (const std::exception& e) {
get.response["status"] = "Failed to serialize transaction base: "s + e.what();
return;
}
std::string pruned = ba.str();
std::string pruned2{ptx_it->tx_blob, pruned.size()};
sorted_txs.emplace_back(h, std::move(pruned), get_transaction_prunable_hash(tx), std::move(pruned2));
missed_txs.erase(missed_it);
per_tx_pool_tx_info.emplace(h, *ptx_it);
++found_in_pool;
}
txs = std::move(sorted_txs);
get.response_hex["missed_tx"] = missed_txs; // non-plural here intentional to not break existing clients
LOG_PRINT_L2("Found " << found_in_pool.size() << "/" << get.request.tx_hashes.size() << " transactions in the pool");
} else if (get.request.memory_pool) {
txs.reserve(pool_txs.size());
std::transform(pool_txs.begin(), pool_txs.end(), std::back_inserter(txs), split_mempool_tx);
found_in_pool = std::move(pool_txs);
auto mki = get.response_hex["mempool_key_images"];
for (auto& [ki, txids] : pool_kis) {
// The *key* is also binary (hex for json):
std::string key{get.is_bt() ? tools::view_guts(ki) : tools::type_to_hex(ki)};
mki[key] = txids;
}
}
txs = std::move(sorted_txs);
} catch (const std::exception& e) {
// Log error but continue
MERROR(e.what());
get.response["status"] = "Failed: "s + e.what();
return;
}
get.response_hex["missed_tx"] = missed_txs; // non-plural here intentional to not break existing clients
LOG_PRINT_L2("Found " << found_in_pool << "/" << get.request.tx_hashes.size() << " transactions in the pool");
}
uint64_t immutable_height = m_core.get_blockchain_storage().get_immutable_height();
@ -1053,7 +1050,6 @@ namespace cryptonote::rpc {
auto& txs_out = get.response["txs"];
txs_out = json::array();
cryptonote::blobdata tx_data;
for (const auto& [tx_hash, unprunable_data, prunable_hash, prunable_data]: txs)
{
auto& e = txs_out.emplace_back();
@ -1070,93 +1066,92 @@ namespace cryptonote::rpc {
if (pruned || (prunable && (get.request.split || get.request.prune)))
e_bin["prunable_hash"] = prunable_hash;
if (get.request.split || get.request.prune || pruned)
std::string tx_data = unprunable_data;
if (!get.request.prune)
tx_data += prunable_data;
if (get.request.split || get.request.prune)
{
if (get.request.decode_as_json)
{
tx_data = unprunable_data;
if (!get.request.prune)
tx_data += prunable_data;
e_bin["pruned"] = unprunable_data;
if (get.request.split)
e_bin["prunable"] = prunable_data;
}
if (get.request.data) {
if (pruned || get.request.prune) {
if (!e.count("pruned"))
e_bin["pruned"] = unprunable_data;
} else {
e_bin["data"] = tx_data;
}
else
}
cryptonote::transaction tx;
if (get.request.prune || pruned)
{
if (!cryptonote::parse_and_validate_tx_base_from_blob(tx_data, tx))
{
e_bin["pruned"] = unprunable_data;
if (!get.request.prune && prunable && !pruned)
e_bin["prunable"] = prunable_data;
get.response["status"] = "Failed to parse and validate base tx data";
return;
}
}
else
{
// use non-splitted form, leaving pruned_as_hex and prunable_as_hex as empty
tx_data = unprunable_data;
tx_data += prunable_data;
if (!get.request.decode_as_json)
e_bin["data"] = tx_data;
if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx))
{
get.response["status"] = "Failed to parse and validate tx data";
return;
}
}
cryptonote::transaction t;
if (get.request.decode_as_json || get.request.tx_extra || get.request.stake_info)
{
if (get.request.prune || pruned)
{
if (!cryptonote::parse_and_validate_tx_base_from_blob(tx_data, t))
{
get.response["status"] = "Failed to parse and validate base tx data";
return;
}
// I hate this because it goes deep into epee:
if (get.request.decode_as_json)
e["as_json"] = obj_to_json_str(pruned_transaction{t});
}
else
{
if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, t))
{
get.response["status"] = "Failed to parse and validate tx data";
return;
}
if (get.request.decode_as_json)
e["as_json"] = obj_to_json_str(t);
}
if (get.request.tx_extra)
load_tx_extra_data(e["extra"], tx, nettype(), get.is_bt());
if (get.request.tx_extra)
load_tx_extra_data(e["extra"], t, nettype(), get.is_bt());
}
auto ptx_it = per_tx_pool_tx_info.find(tx_hash);
bool in_pool = ptx_it != per_tx_pool_tx_info.end();
auto ptx_it = found_in_pool.find(tx_hash);
bool in_pool = ptx_it != found_in_pool.end();
e["in_pool"] = in_pool;
bool might_be_blink = true;
auto height = std::numeric_limits<uint64_t>::max();
auto hf_version = get_network_version(nettype(), in_pool ? m_core.get_current_blockchain_height() : height);
if (uint64_t fee, burned; get_tx_miner_fee(tx, fee, hf_version >= HF_VERSION_FEE_BURNING, &burned)) {
e["fee"] = fee;
e["burned"] = burned;
}
if (in_pool)
{
if (ptx_it->second.meta.double_spend_seen)
e["double_spend_seen"] = true;
e["relayed"] = ptx_it->second.meta.relayed;
const auto& meta = ptx_it->second.meta;
e["weight"] = meta.weight;
e["relayed"] = (bool) ptx_it->second.meta.relayed;
e["received_timestamp"] = ptx_it->second.meta.receive_time;
e["blink"] = ptx_it->second.blink;
if (meta.double_spend_seen) e["double_spend_seen"] = true;
if (meta.do_not_relay) e["do_not_relay"] = true;
if (meta.last_relayed_time) e["last_relayed_time"] = meta.last_relayed_time;
if (meta.kept_by_block) e["kept_by_block"] = (bool) meta.kept_by_block;
if (meta.last_failed_id) e_bin["last_failed_block"] = meta.last_failed_id;
if (meta.last_failed_height) e["last_failed_height"] = meta.last_failed_height;
if (meta.max_used_block_id) e_bin["max_used_block"] = meta.max_used_block_id;
if (meta.max_used_block_height) e["max_used_height"] = meta.max_used_block_height;
}
else
{
height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash);
e["block_height"] = height;
e["block_timestamp"] = m_core.get_blockchain_storage().get_db().get_block_timestamp(height);
if (height <= immutable_height)
might_be_blink = false;
if (height > immutable_height) {
if (!blink_lock) blink_lock.lock();
e["blink"] = pool.has_blink(tx_hash);
}
}
if (get.request.stake_info) {
auto hf_version = get_network_version(nettype(), in_pool ? m_core.get_current_blockchain_height() : height);
{
service_nodes::staking_components sc;
if (service_nodes::tx_get_staking_components_and_amounts(nettype(), hf_version, t, height, &sc)
if (service_nodes::tx_get_staking_components_and_amounts(nettype(), hf_version, tx, height, &sc)
&& sc.transferred > 0)
e["stake_amount"] = sc.transferred;
}
if (might_be_blink)
{
if (!blink_lock) blink_lock.lock();
e["blink"] = pool.has_blink(tx_hash);
}
// output indices too if not in pool
if (!in_pool)
{
@ -1533,36 +1528,6 @@ namespace cryptonote::rpc {
return res;
}
//------------------------------------------------------------------------------------------------------------------------------
GET_TRANSACTION_POOL::response core_rpc_server::invoke(GET_TRANSACTION_POOL::request&& req, rpc_context context)
{
GET_TRANSACTION_POOL::response res{};
PERF_TIMER(on_get_transaction_pool);
if (use_bootstrap_daemon_if_necessary<GET_TRANSACTION_POOL>(req, res))
return res;
std::function<void(const transaction& tx, tx_info& txi)> load_extra;
if (req.tx_extra || req.stake_info)
load_extra = [this, &req, net=nettype()](const transaction& tx, tx_info& txi) {
if (req.tx_extra)
load_tx_extra_data(txi.extra.emplace(), tx, net);
if (req.stake_info) {
auto height = m_core.get_current_blockchain_height();
auto hf_version = get_network_version(net, height);
service_nodes::staking_components sc;
if (service_nodes::tx_get_staking_components_and_amounts(net, hf_version, tx, height, &sc)
&& sc.transferred > 0)
txi.stake_amount = sc.transferred;
}
};
m_core.get_pool().get_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, load_extra, context.admin);
for (tx_info& txi : res.transactions)
txi.tx_blob = oxenmq::to_hex(txi.tx_blob);
res.status = STATUS_OK;
return res;
}
//------------------------------------------------------------------------------------------------------------------------------
GET_TRANSACTION_POOL_HASHES_BIN::response core_rpc_server::invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context)
{
GET_TRANSACTION_POOL_HASHES_BIN::response res{};
@ -3535,11 +3500,10 @@ namespace cryptonote::rpc {
res.end_height = end_height;
std::vector<blob_t> blobs;
std::vector<crypto::hash> missed_ids;
for (const auto& block : blocks)
{
blobs.clear();
if (!db.get_transactions_blobs(block.second.tx_hashes, blobs, missed_ids))
if (!db.get_transactions_blobs(block.second.tx_hashes, blobs))
{
MERROR("Could not query block at requested height: " << cryptonote::get_block_height(block.second));
continue;

View file

@ -37,7 +37,6 @@
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include "bootstrap_daemon.h"
#include "core_rpc_server_commands_defs.h"
#include "cryptonote_core/cryptonote_core.h"
#include "p2p/net_node.h"
@ -47,8 +46,12 @@
#define OXEN_DEFAULT_LOG_CATEGORY "daemon.rpc"
namespace boost::program_options {
class options_description;
class variables_map;
class options_description;
class variables_map;
}
namespace cryptonote {
class bootstrap_daemon;
}
namespace cryptonote::rpc {
@ -236,7 +239,6 @@ namespace cryptonote::rpc {
GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context);
SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context);
SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context);
GET_TRANSACTION_POOL::response invoke(GET_TRANSACTION_POOL::request&& req, rpc_context context);
SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context);
GET_LIMIT::response invoke(GET_LIMIT::request&& req, rpc_context context);
SET_LIMIT::response invoke(SET_LIMIT::request&& req, rpc_context context);

View file

@ -39,6 +39,11 @@ namespace cryptonote::rpc {
template <typename T>
constexpr bool is_required_wrapper<required<T>> = true;
template <typename T>
constexpr bool is_std_optional = false;
template <typename T>
constexpr bool is_std_optional<std::optional<T>> = true;
using oxenmq::bt_dict_consumer;
using json_range = std::pair<json::const_iterator, json::const_iterator>;
@ -167,6 +172,8 @@ namespace cryptonote::rpc {
else if (skip_until(in, name)) {
if constexpr (is_required_wrapper<T>)
return load_value(in, val.value);
else if constexpr (is_std_optional<T>)
return load_value(in, val.emplace());
else
return load_value(in, val);
}
@ -294,13 +301,22 @@ namespace cryptonote::rpc {
if (auto it = json_in->find("txs_hashes"); it != json_in->end())
(*json_in)["tx_hashes"] = std::move(*it);
std::optional<bool> data;
get_values(in,
"decode_as_json", get.request.decode_as_json,
"data", data,
"memory_pool", get.request.memory_pool,
"prune", get.request.prune,
"split", get.request.split,
"stake_info", get.request.stake_info,
"tx_extra", get.request.tx_extra,
"tx_hashes", get.request.tx_hashes);
if (data)
get.request.data = *data;
else
get.request.data = !(get.request.prune || get.request.split);
if (get.request.memory_pool && !get.request.tx_hashes.empty())
throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"};
}
}

View file

@ -349,49 +349,6 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_CATEGORIES::response)
KV_SERIALIZE_MAP_CODE_END()
// FIXME: delete
KV_SERIALIZE_MAP_CODE_BEGIN(old_tx_info)
KV_SERIALIZE(id_hash)
KV_SERIALIZE(tx_json)
KV_SERIALIZE(blob_size)
KV_SERIALIZE_OPT(weight, (uint64_t)0)
KV_SERIALIZE(fee)
KV_SERIALIZE(max_used_block_id_hash)
KV_SERIALIZE(max_used_block_height)
KV_SERIALIZE(kept_by_block)
KV_SERIALIZE(last_failed_height)
KV_SERIALIZE(last_failed_id_hash)
KV_SERIALIZE(receive_time)
KV_SERIALIZE(relayed)
KV_SERIALIZE(last_relayed_time)
KV_SERIALIZE(do_not_relay)
KV_SERIALIZE(double_spend_seen)
KV_SERIALIZE(tx_blob)
// KV_SERIALIZE(extra)
KV_SERIALIZE(stake_amount)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(spent_key_image_info)
KV_SERIALIZE(id_hash)
KV_SERIALIZE(txs_hashes)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL::request)
KV_SERIALIZE(tx_extra)
KV_SERIALIZE(stake_info)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL::response)
KV_SERIALIZE(status)
KV_SERIALIZE(transactions)
KV_SERIALIZE(spent_key_images)
KV_SERIALIZE(untrusted)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES_BIN::request)
KV_SERIALIZE_OPT(blinked_txs_only, false)
KV_SERIALIZE_OPT(long_poll, false)

View file

@ -344,8 +344,8 @@ namespace cryptonote::rpc {
/// - /p status -- Generic RPC error code. "OK" is the success value.
/// - /p untrusted -- If the result is obtained using bootstrap mode then this will be set to
/// true, otherwise will be omitted.
/// - \p missed_tx -- list of transaction hashes that were not found. If all were found then this
/// field is omitted.
/// - \p missed_tx -- set of transaction hashes that were not found. If all were found then this
/// field is omitted. There is no particular ordering of hashes in this list.
/// - \p txs -- list of transaction details; each element is a dict containing:
/// - \p tx_hash -- Transaction hash.
/// - \p size -- Size of the transaction, in bytes. Note that if the transaction has been pruned
@ -354,6 +354,9 @@ namespace cryptonote::rpc {
/// and omitted if mined into a block.
/// - \p blink -- True if this is an approved, blink transaction; this information is generally
/// only available for approved in-pool transactions and txes in very recent blocks.
/// - \p fee -- the transaction fee (in atomic OXEN) incurred in this transaction (not including
/// any burned amount).
/// - \p burned -- the amount of OXEN (in atomic units) burned by this transaction.
/// - \p block_height -- Block height including the transaction. Omitted for tx pool
/// transactions.
/// - \p block_timestamp -- Unix time at which the block has been added to the blockchain.
@ -366,6 +369,25 @@ namespace cryptonote::rpc {
/// transactions.
/// - \p received_timestamp -- Timestamp transaction was received in the pool. Omitted for
/// mined blocks.
/// - \p max_used_block -- the hash of the highest block referenced by this transaction; only
/// for mempool transactions.
/// - \p max_used_height -- the height of the highest block referenced by this transaction; only
/// for mempool transactions.
/// - \p last_failed_block -- the hash of the last block where this transaction was attempted to
/// be mined (but failed).
/// - \p max_used_height -- the height of the last block where this transaction failed to be
/// acceptable for a block.
/// - \p weight -- the transaction "weight" which is the size of the transaction with padding
/// removed. Only included for mempool transactions (for mined transactions the size and
/// weight at the same and so only `size` is included).
/// - \p kept_by_block will be present and true if this is a mempool transaction that was added
/// to the mempool after being popped off a block (e.g. because of a blockchain
/// reorganization).
/// - \p last_relayed_time indicates the last time this block was relayed to the network; only
/// for mempool transactions.
/// - \p do_not_relay -- set to true for mempool blocks that are marked "do not relay"
/// - \p double_spend_seen -- set to true if one or more outputs in this mempool transaction
/// have already been spent (and thus the tx cannot currently be added to the blockchain).
/// - \p data -- Full, unpruned transaction data. For a json request this is hex-encoded; for a
/// bt-encoded request this is raw bytes. This field is omitted if any of `decode_as_json`,
/// `split`, or `prune` is requested; or if the transaction has been pruned in the database.
@ -377,12 +399,11 @@ namespace cryptonote::rpc {
/// - \p prunable_hash -- The hash of the prunable part of the transaction. Will be provided if
/// either: the tx has been pruned; or the tx is prunable and either of `prune` or `split` are
/// specified.
/// FIXME: drop this crap:
/// - \p as_json -- Transaction information parsed into json. Requires decode_as_json in request.
/// - \p extra -- Parsed "extra" transaction information; omitted unless specifically requested.
/// This is a dict containing one or more of the following keys.
/// - \p extra -- Parsed "extra" transaction information; omitted unless specifically requested
/// (via the `tx_extra` request parameter). This is a dict containing one or more of the
/// following keys.
/// - \p pubkey -- The tx extra public key
/// - \p burn_amount -- The amount of OXEN that this transaction burns
/// - \p burn_amount -- The amount of OXEN that this transaction burns, if any.
/// - \p extra_nonce -- Optional extra nonce value (in hex); will be empty if nonce is
/// recognized as a payment id
/// - \p payment_id -- The payment ID, if present. This is either a 16 hex character (8-byte)
@ -455,9 +476,12 @@ namespace cryptonote::rpc {
/// be a primary wallet address, wallet subaddress, or a plain public key.
/// - \p backup_owner -- an optional backup owner who also has permission to edit the
/// record.
/// - \p stake_amount -- If `stake_info` is explicitly requested then this field will be set to
/// the calculated transaction stake amount (only applicable if the transaction is a service
/// node registration or stake).
/// - \p stake_amount -- Set to the calculated transaction stake amount (only applicable if the
/// transaction is a service node registration or stake).
/// - \p mempool_key_images -- dict of spent key images of mempool transactions. Only included
/// when `memory_pool` is set to true. Each key is the key image (in hex, for json requests)
/// and each value is a list of transaction hashes that spend that key image (typically just
/// one, but in the case of conflicting transactions there can be multiple).
struct GET_TRANSACTIONS : PUBLIC, LEGACY
{
static constexpr auto names() { return NAMES("get_transactions", "gettransactions"); }
@ -465,20 +489,22 @@ namespace cryptonote::rpc {
struct request_parameters
{
/// List of transaction hashes to look up. (Will also be accepted as json input key
/// "txs_hashes" for backwards compatibility).
/// "txs_hashes" for backwards compatibility). Exclusive of `memory_pool`.
std::vector<crypto::hash> tx_hashes;
/// If set to true, the returned transaction information will be decoded.
bool decode_as_json = false;
/// If true then return all transactions and spent key images currently in the memory pool.
/// This field is exclusive of `tx_hashes`.
bool memory_pool = false;
/// If set to true then parse and return tx-extra information
bool tx_extra = false;
/// Controls whether the `data` (or `pruned`, if pruned) field containing raw tx data is
/// included: if explicitly specified then the raw data will be included if true. Otherwise
/// the raw data is included only when neither of `split` nor `prune` are set to true.
bool data = true;
/// If set to true then always split transactions into non-prunable and prunable parts in the
/// response.
bool split = false;
/// Like `split`, but also omits the prunable part (or details, for decode_as_json) of
/// transactions from the response.
/// Like `split`, but also omits the prunable part of transactions from the response details.
bool prune = false;
/// If true then calculate staking amount for staking/registration transactions
bool stake_info = false;
} request;
};
@ -1202,32 +1228,6 @@ namespace cryptonote::rpc {
};
};
OXEN_RPC_DOC_INTROSPECT
struct old_tx_info
{
std::string id_hash; // The transaction ID hash.
std::string tx_json; // JSON structure of all information in the transaction
uint64_t blob_size; // The size of the full transaction blob.
uint64_t weight; // The weight of the transaction.
uint64_t fee; // The amount of the mining fee included in the transaction, in atomic units.
std::string max_used_block_id_hash; // Tells the hash of the most recent block with an output used in this transaction.
uint64_t max_used_block_height; // Tells the height of the most recent block with an output used in this transaction.
bool kept_by_block; // States if the tx was included in a block at least once (`true`) or not (`false`).
uint64_t last_failed_height; // If the transaction validation has previously failed, this tells at what height that occured.
std::string last_failed_id_hash; // Like the previous, this tells the previous transaction ID hash.
uint64_t receive_time; // The Unix time that the transaction was first seen on the network by the node.
bool relayed; // States if this transaction has been relayed
uint64_t last_relayed_time; // Last unix time at which the transaction has been relayed.
bool do_not_relay; // States if this transaction should not be relayed.
bool double_spend_seen; // States if this transaction has been seen as double spend.
std::string tx_blob; // Hexadecimal blob represnting the transaction.
bool blink; // True if this is a signed blink transaction
//std::optional<GET_TRANSACTIONS::extra_entry> extra; // Parsed tx_extra information (only if requested)
std::optional<uint64_t> stake_amount; // Will be set to the staked amount if the transaction is a staking transaction *and* stake amounts were requested.
KV_MAP_SERIALIZABLE
};
OXEN_RPC_DOC_INTROSPECT
struct spent_key_image_info
{
@ -1237,32 +1237,6 @@ namespace cryptonote::rpc {
KV_MAP_SERIALIZABLE
};
OXEN_RPC_DOC_INTROSPECT
// Show information about valid transactions seen by the node but not yet mined into a block,
// as well as spent key image information for the txpool in the node's memory.
struct GET_TRANSACTION_POOL : PUBLIC, LEGACY
{
static constexpr auto names() { return NAMES("get_transaction_pool"); }
struct request
{
bool tx_extra; // Parse tx-extra information and adds it to the `extra` field.
bool stake_info; // Calculate and include staking contribution amount for registration/staking transactions
KV_MAP_SERIALIZABLE
};
struct response
{
std::string status; // General RPC error code. "OK" means everything looks good.
std::vector<old_tx_info> transactions; // List of transactions in the mempool are not in a block on the main chain at the moment:
std::vector<spent_key_image_info> spent_key_images; // List of spent output key images:
bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`).
KV_MAP_SERIALIZABLE
};
};
OXEN_RPC_DOC_INTROSPECT
// Get hashes from transaction pool. Binary request.
struct GET_TRANSACTION_POOL_HASHES_BIN : PUBLIC, BINARY
@ -2804,7 +2778,6 @@ namespace cryptonote::rpc {
GET_PUBLIC_NODES,
SET_LOG_LEVEL,
SET_LOG_CATEGORIES,
GET_TRANSACTION_POOL,
GET_BLOCK_HEADERS_RANGE,
SET_BOOTSTRAP_DAEMON,
GET_LIMIT,

View file

@ -4,6 +4,7 @@
#include "crypto/crypto.h"
#include <string_view>
#include <nlohmann/json.hpp>
#include <unordered_set>
using namespace std::literals;
@ -21,15 +22,17 @@ namespace cryptonote::rpc {
template <> inline constexpr bool is_binary_parameter<rct::key> = true;
template <typename T>
inline constexpr bool is_binary_vector = false;
inline constexpr bool is_binary_container = false;
template <typename T>
inline constexpr bool is_binary_vector<std::vector<T>> = is_binary_parameter<T>;
inline constexpr bool is_binary_container<std::vector<T>> = is_binary_parameter<T>;
template <typename T>
inline constexpr bool is_binary_container<std::unordered_set<T>> = is_binary_parameter<T>;
// De-referencing wrappers around the above:
template <typename T> inline constexpr bool is_binary_parameter<const T&> = is_binary_parameter<T>;
template <typename T> inline constexpr bool is_binary_parameter<T&&> = is_binary_parameter<T>;
template <typename T> inline constexpr bool is_binary_vector<const T&> = is_binary_vector<T>;
template <typename T> inline constexpr bool is_binary_vector<T&&> = is_binary_vector<T>;
template <typename T> inline constexpr bool is_binary_container<const T&> = is_binary_container<T>;
template <typename T> inline constexpr bool is_binary_container<T&&> = is_binary_container<T>;
void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data);
@ -87,8 +90,8 @@ namespace cryptonote::rpc {
/// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning
/// each one into a new array of binary values.
template <typename T>
nlohmann::json& operator=(const std::vector<T>& vals) {
template <typename T, std::enable_if_t<is_binary_container<T>, int> = 0>
nlohmann::json& operator=(const T& vals) {
auto a = nlohmann::json::array();
for (auto& val : vals)
json_binary_proxy{a.emplace_back(), format} = val;