add RPC to get a histogram of outputs of a given amount

This commit is contained in:
moneromooo-monero 2018-02-19 11:15:15 +00:00
parent 51bb7fafac
commit 41f727ce42
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
14 changed files with 284 additions and 8 deletions

View file

@ -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;
//

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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"

View file

@ -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:

View file

@ -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()
};
};
}

View file

@ -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);

View file

@ -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;

View file

@ -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)

View 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>>(); }