mirror of
https://github.com/monero-project/monero.git
synced 2024-11-17 16:27:39 +00:00
add RPC to get a histogram of outputs of a given amount
This commit is contained in:
parent
51bb7fafac
commit
41f727ce42
14 changed files with 284 additions and 8 deletions
|
@ -1448,7 +1448,9 @@ public:
|
|||
*
|
||||
* @return false if the function returns false for any output, otherwise true
|
||||
*/
|
||||
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const = 0;
|
||||
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const = 0;
|
||||
virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const = 0;
|
||||
|
||||
|
||||
|
||||
//
|
||||
|
|
|
@ -2564,7 +2564,7 @@ bool BlockchainLMDB::for_all_transactions(std::function<bool(const crypto::hash&
|
|||
return fret;
|
||||
}
|
||||
|
||||
bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const
|
||||
bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const
|
||||
{
|
||||
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
|
||||
check_open();
|
||||
|
@ -2588,7 +2588,47 @@ bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const c
|
|||
uint64_t amount = *(const uint64_t*)k.mv_data;
|
||||
outkey *ok = (outkey *)v.mv_data;
|
||||
tx_out_index toi = get_output_tx_and_index_from_global(ok->output_id);
|
||||
if (!f(amount, toi.first, toi.second)) {
|
||||
if (!f(amount, toi.first, ok->data.height, toi.second)) {
|
||||
fret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TXN_POSTFIX_RDONLY();
|
||||
|
||||
return fret;
|
||||
}
|
||||
|
||||
bool BlockchainLMDB::for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const
|
||||
{
|
||||
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
|
||||
check_open();
|
||||
|
||||
TXN_PREFIX_RDONLY();
|
||||
RCURSOR(output_amounts);
|
||||
|
||||
MDB_val_set(k, amount);
|
||||
MDB_val v;
|
||||
bool fret = true;
|
||||
|
||||
MDB_cursor_op op = MDB_SET;
|
||||
while (1)
|
||||
{
|
||||
int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, op);
|
||||
op = MDB_NEXT_DUP;
|
||||
if (ret == MDB_NOTFOUND)
|
||||
break;
|
||||
if (ret)
|
||||
throw0(DB_ERROR("Failed to enumerate outputs"));
|
||||
uint64_t out_amount = *(const uint64_t*)k.mv_data;
|
||||
if (amount != out_amount)
|
||||
{
|
||||
MERROR("Amount is not the expected amount");
|
||||
fret = false;
|
||||
break;
|
||||
}
|
||||
const outkey *ok = (const outkey *)v.mv_data;
|
||||
if (!f(ok->data.height)) {
|
||||
fret = false;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -255,7 +255,8 @@ public:
|
|||
virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const;
|
||||
virtual bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const;
|
||||
virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const;
|
||||
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const;
|
||||
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const;
|
||||
virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const;
|
||||
|
||||
virtual uint64_t add_block( const block& blk
|
||||
, const size_t& block_size
|
||||
|
|
|
@ -1903,6 +1903,45 @@ void Blockchain::get_output_key_mask_unlocked(const uint64_t& amount, const uint
|
|||
unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first));
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const
|
||||
{
|
||||
// rct outputs don't exist before v3
|
||||
if (amount == 0)
|
||||
{
|
||||
switch (m_nettype)
|
||||
{
|
||||
case STAGENET: start_height = stagenet_hard_forks[2].height; break;
|
||||
case TESTNET: start_height = testnet_hard_forks[2].height; break;
|
||||
case MAINNET: start_height = mainnet_hard_forks[2].height; break;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
start_height = 0;
|
||||
base = 0;
|
||||
|
||||
const uint64_t real_start_height = start_height;
|
||||
if (from_height > start_height)
|
||||
start_height = from_height;
|
||||
|
||||
distribution.clear();
|
||||
uint64_t db_height = m_db->height();
|
||||
if (start_height >= db_height)
|
||||
return false;
|
||||
distribution.resize(db_height - start_height, 0);
|
||||
bool r = for_all_outputs(amount, [&](uint64_t height) {
|
||||
CHECK_AND_ASSERT_MES(height >= real_start_height && height <= db_height, false, "Height not in expected range");
|
||||
if (height >= start_height)
|
||||
distribution[height - start_height]++;
|
||||
else
|
||||
base++;
|
||||
return true;
|
||||
});
|
||||
if (!r)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
// 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.
|
||||
|
@ -4441,11 +4480,16 @@ bool Blockchain::for_all_transactions(std::function<bool(const crypto::hash&, co
|
|||
return m_db->for_all_transactions(f);
|
||||
}
|
||||
|
||||
bool Blockchain::for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const
|
||||
bool Blockchain::for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const
|
||||
{
|
||||
return m_db->for_all_outputs(f);;
|
||||
}
|
||||
|
||||
bool Blockchain::for_all_outputs(uint64_t amount, std::function<bool(uint64_t height)> f) const
|
||||
{
|
||||
return m_db->for_all_outputs(amount, f);;
|
||||
}
|
||||
|
||||
namespace cryptonote {
|
||||
template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::list<transaction>&, std::list<crypto::hash>&) const;
|
||||
}
|
||||
|
|
|
@ -523,6 +523,17 @@ namespace cryptonote
|
|||
*/
|
||||
bool get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) const;
|
||||
|
||||
/**
|
||||
* @brief gets per block distribution of outputs of a given amount
|
||||
*
|
||||
* @param amount the amount to get a ditribution for
|
||||
* @param return-by-reference from_height the height before which we do not care about the data
|
||||
* @param return-by-reference start_height the height of the first rct output
|
||||
* @param return-by-reference distribution the start offset of the first rct output in this block (same as previous if none)
|
||||
* @param return-by-reference base how many outputs of that amount are before the stated distribution
|
||||
*/
|
||||
bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const;
|
||||
|
||||
/**
|
||||
* @brief gets the global indices for outputs from a given transaction
|
||||
*
|
||||
|
@ -857,7 +868,17 @@ namespace cryptonote
|
|||
*
|
||||
* @return false if any output fails the check, otherwise true
|
||||
*/
|
||||
bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)>) const;
|
||||
bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)>) const;
|
||||
|
||||
/**
|
||||
* @brief perform a check on all outputs of a given amount in the blockchain
|
||||
*
|
||||
* @param amount the amount to iterate through
|
||||
* @param std::function the check to perform, pass/fail
|
||||
*
|
||||
* @return false if any output fails the check, otherwise true
|
||||
*/
|
||||
bool for_all_outputs(uint64_t amount, std::function<bool(uint64_t height)>) const;
|
||||
|
||||
/**
|
||||
* @brief get a reference to the BlockchainDB in use by Blockchain
|
||||
|
|
|
@ -1105,6 +1105,11 @@ namespace cryptonote
|
|||
return m_blockchain_storage.get_random_rct_outs(req, res);
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const
|
||||
{
|
||||
return m_blockchain_storage.get_output_distribution(amount, from_height, start_height, distribution, base);
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) const
|
||||
{
|
||||
return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs);
|
||||
|
|
|
@ -571,6 +571,12 @@ namespace cryptonote
|
|||
*/
|
||||
bool get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) const;
|
||||
|
||||
/**
|
||||
* @copydoc Blockchain::get_output_distribution
|
||||
*
|
||||
* @brief get per block distribution of outputs of a given amount
|
||||
*/
|
||||
bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const;
|
||||
|
||||
/**
|
||||
* @copydoc miner::pause
|
||||
|
|
|
@ -2076,6 +2076,41 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool core_rpc_server::on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (uint64_t amount: req.amounts)
|
||||
{
|
||||
std::vector<uint64_t> distribution;
|
||||
uint64_t start_height, base;
|
||||
if (!m_core.get_output_distribution(amount, req.from_height, start_height, distribution, base))
|
||||
{
|
||||
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
|
||||
error_resp.message = "Failed to get rct distribution";
|
||||
return false;
|
||||
}
|
||||
if (req.cumulative)
|
||||
{
|
||||
distribution[0] += base;
|
||||
for (size_t n = 1; n < distribution.size(); ++n)
|
||||
distribution[n] += distribution[n-1];
|
||||
}
|
||||
res.distributions.push_back({amount, start_height, std::move(distribution), base});
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
|
||||
error_resp.message = "Failed to get output distribution";
|
||||
return false;
|
||||
}
|
||||
|
||||
res.status = CORE_RPC_STATUS_OK;
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = {
|
||||
"rpc-bind-port"
|
||||
|
|
|
@ -153,6 +153,7 @@ namespace cryptonote
|
|||
MAP_JON_RPC_WE_IF("relay_tx", on_relay_tx, COMMAND_RPC_RELAY_TX, !m_restricted)
|
||||
MAP_JON_RPC_WE_IF("sync_info", on_sync_info, COMMAND_RPC_SYNC_INFO, !m_restricted)
|
||||
MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG)
|
||||
MAP_JON_RPC_WE_IF("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION, !m_restricted)
|
||||
END_JSON_RPC_MAP()
|
||||
END_URI_MAP2()
|
||||
|
||||
|
@ -214,6 +215,7 @@ namespace cryptonote
|
|||
bool on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp);
|
||||
bool on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp);
|
||||
bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp);
|
||||
bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp);
|
||||
//-----------------------
|
||||
|
||||
private:
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace cryptonote
|
|||
// advance which version they will stop working with
|
||||
// Don't go over 32767 for any of these
|
||||
#define CORE_RPC_VERSION_MAJOR 1
|
||||
#define CORE_RPC_VERSION_MINOR 18
|
||||
#define CORE_RPC_VERSION_MINOR 19
|
||||
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
|
||||
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
|
||||
|
||||
|
@ -2203,4 +2203,47 @@ namespace cryptonote
|
|||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_GET_OUTPUT_DISTRIBUTION
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::vector<uint64_t> amounts;
|
||||
uint64_t from_height;
|
||||
bool cumulative;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amounts)
|
||||
KV_SERIALIZE_OPT(from_height, (uint64_t)0)
|
||||
KV_SERIALIZE_OPT(cumulative, false)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct distribution
|
||||
{
|
||||
uint64_t amount;
|
||||
uint64_t start_height;
|
||||
std::vector<uint64_t> distribution;
|
||||
uint64_t base;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(start_height)
|
||||
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(distribution)
|
||||
KV_SERIALIZE(base)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
std::string status;
|
||||
std::vector<distribution> distributions;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(status)
|
||||
KV_SERIALIZE(distributions)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -2266,6 +2266,71 @@ bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok)
|
|||
return ok;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::get_output_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution)
|
||||
{
|
||||
uint32_t rpc_version;
|
||||
boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version);
|
||||
// no error
|
||||
if (!!result)
|
||||
{
|
||||
// empty string -> not connection
|
||||
THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion");
|
||||
THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion");
|
||||
if (*result != CORE_RPC_STATUS_OK)
|
||||
{
|
||||
MDEBUG("Cannot determine daemon RPC version, not requesting rct distribution");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 19))
|
||||
{
|
||||
MDEBUG("Daemon is recent enough, requesting rct distribution");
|
||||
}
|
||||
else
|
||||
{
|
||||
MDEBUG("Daemon is too old, not requesting rct distribution");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req = AUTO_VAL_INIT(req);
|
||||
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res);
|
||||
req.amounts.push_back(0);
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req, res, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
if (!r)
|
||||
{
|
||||
MWARNING("Failed to request output distribution: no connection to daemon");
|
||||
return false;
|
||||
}
|
||||
if (res.status == CORE_RPC_STATUS_BUSY)
|
||||
{
|
||||
MWARNING("Failed to request output distribution: daemon is busy");
|
||||
return false;
|
||||
}
|
||||
if (res.status != CORE_RPC_STATUS_OK)
|
||||
{
|
||||
MWARNING("Failed to request output distribution: " << res.status);
|
||||
return false;
|
||||
}
|
||||
if (res.distributions.size() != 1)
|
||||
{
|
||||
MWARNING("Failed to request output distribution: not the expected single result");
|
||||
return false;
|
||||
}
|
||||
if (res.distributions[0].amount != 0)
|
||||
{
|
||||
MWARNING("Failed to request output distribution: results are not for amount 0");
|
||||
return false;
|
||||
}
|
||||
start_height = res.distributions[0].start_height;
|
||||
distribution = std::move(res.distributions[0].distribution);
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::detach_blockchain(uint64_t height)
|
||||
{
|
||||
LOG_PRINT_L0("Detaching blockchain on height " << height);
|
||||
|
|
|
@ -1103,6 +1103,8 @@ namespace tools
|
|||
rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const;
|
||||
void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n);
|
||||
|
||||
bool get_output_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
|
||||
|
||||
cryptonote::account_base m_account;
|
||||
boost::optional<epee::net_utils::http::login> m_daemon_login;
|
||||
std::string m_daemon_address;
|
||||
|
|
|
@ -85,6 +85,7 @@ namespace tools
|
|||
// no_connection_to_daemon
|
||||
// is_key_image_spent_error
|
||||
// get_histogram_error
|
||||
// get_output_distribution
|
||||
// wallet_files_doesnt_correspond
|
||||
//
|
||||
// * - class with protected ctor
|
||||
|
@ -757,6 +758,14 @@ namespace tools
|
|||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct get_output_distribution : public wallet_rpc_error
|
||||
{
|
||||
explicit get_output_distribution(std::string&& loc, const std::string& request)
|
||||
: wallet_rpc_error(std::move(loc), "failed to get output distribution", request)
|
||||
{
|
||||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct wallet_files_doesnt_correspond : public wallet_logic_error
|
||||
{
|
||||
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)
|
||||
|
|
|
@ -109,7 +109,8 @@ public:
|
|||
virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const { return true; }
|
||||
virtual bool for_blocks_range(const uint64_t&, const uint64_t&, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const { return true; }
|
||||
virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const { return true; }
|
||||
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const { return true; }
|
||||
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const { return true; }
|
||||
virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const { return true; }
|
||||
virtual bool is_read_only() const { return false; }
|
||||
virtual std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { return std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>(); }
|
||||
|
||||
|
|
Loading…
Reference in a new issue