New RPC and daemon command to get output histogram
This is a list of existing output amounts along with the number of outputs of that amount in the blockchain. The daemon command takes: - no parameters: all outputs with at least 3 instances - one parameter: all outputs with at least that many instances - two parameters: all outputs within that many instances The default starts at 3 to avoid massive spamming of all dust outputs in the blockchain, and is the current minimum mixin requirement. An optional vector of amounts may be passed, to request histogram only for those outputs.
This commit is contained in:
parent
f9a2fd2ff5
commit
600a3cf0c0
16 changed files with 244 additions and 0 deletions
|
@ -2180,6 +2180,12 @@ void BlockchainBDB::get_output_tx_and_index(const uint64_t& amount, const std::v
|
|||
LOG_PRINT_L3("db3: " << db3);
|
||||
}
|
||||
|
||||
std::map<uint64_t, uint64_t>::BlockchainBDB::get_output_histogram(const std::vector<uint64_t> &amounts) const
|
||||
{
|
||||
LOG_PRINT_L3("BlockchainBDB::" << __func__);
|
||||
throw1(DB_ERROR("Not implemented."));
|
||||
}
|
||||
|
||||
void BlockchainBDB::set_hard_fork_starting_height(uint8_t version, uint64_t height)
|
||||
{
|
||||
LOG_PRINT_L3("BlockchainBDB::" << __func__);
|
||||
|
|
|
@ -341,6 +341,15 @@ public:
|
|||
virtual bool can_thread_bulk_indices() const { return false; }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief return a histogram of outputs on the blockchain
|
||||
*
|
||||
* @param amounts optional set of amounts to lookup
|
||||
*
|
||||
* @return a set of amount/instances
|
||||
*/
|
||||
std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const;
|
||||
|
||||
private:
|
||||
virtual void add_block( const block& blk
|
||||
, const size_t& block_size
|
||||
|
|
|
@ -1288,6 +1288,15 @@ public:
|
|||
*/
|
||||
virtual void drop_hard_fork_info() = 0;
|
||||
|
||||
/**
|
||||
* @brief return a histogram of outputs on the blockchain
|
||||
*
|
||||
* @param amounts optional set of amounts to lookup
|
||||
*
|
||||
* @return a set of amount/instances
|
||||
*/
|
||||
virtual std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const = 0;
|
||||
|
||||
/**
|
||||
* @brief is BlockchainDB in read-only mode?
|
||||
*
|
||||
|
|
|
@ -2692,6 +2692,63 @@ void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std::
|
|||
LOG_PRINT_L3("db3: " << db3);
|
||||
}
|
||||
|
||||
std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vector<uint64_t> &amounts) const
|
||||
{
|
||||
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
|
||||
check_open();
|
||||
|
||||
TXN_PREFIX_RDONLY();
|
||||
RCURSOR(output_amounts);
|
||||
|
||||
std::map<uint64_t, uint64_t> histogram;
|
||||
MDB_val k;
|
||||
MDB_val v;
|
||||
|
||||
if (amounts.empty())
|
||||
{
|
||||
MDB_cursor_op op = MDB_FIRST;
|
||||
while (1)
|
||||
{
|
||||
int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, op);
|
||||
op = MDB_NEXT_NODUP;
|
||||
if (ret == MDB_NOTFOUND)
|
||||
break;
|
||||
if (ret)
|
||||
throw0(DB_ERROR(lmdb_error("Failed to enumerate outputs: ", ret).c_str()));
|
||||
mdb_size_t num_elems = 0;
|
||||
mdb_cursor_count(m_cur_output_amounts, &num_elems);
|
||||
uint64_t amount = *(const uint64_t*)k.mv_data;
|
||||
histogram[amount] = num_elems;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &amount: amounts)
|
||||
{
|
||||
MDB_val_copy<uint64_t> k(amount);
|
||||
int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET);
|
||||
if (ret == MDB_NOTFOUND)
|
||||
{
|
||||
histogram[amount] = 0;
|
||||
}
|
||||
else if (ret == MDB_SUCCESS)
|
||||
{
|
||||
mdb_size_t num_elems = 0;
|
||||
mdb_cursor_count(m_cur_output_amounts, &num_elems);
|
||||
histogram[amount] = num_elems;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw0(DB_ERROR(lmdb_error("Failed to enumerate outputs: ", ret).c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TXN_POSTFIX_RDONLY();
|
||||
|
||||
return histogram;
|
||||
}
|
||||
|
||||
void BlockchainLMDB::check_hard_fork_info()
|
||||
{
|
||||
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
|
||||
|
|
|
@ -280,6 +280,16 @@ public:
|
|||
virtual void pop_block(block& blk, std::vector<transaction>& txs);
|
||||
|
||||
virtual bool can_thread_bulk_indices() const { return true; }
|
||||
|
||||
/**
|
||||
* @brief return a histogram of outputs on the blockchain
|
||||
*
|
||||
* @param amounts optional set of amounts to lookup
|
||||
*
|
||||
* @return a set of amount/instances
|
||||
*/
|
||||
std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const;
|
||||
|
||||
private:
|
||||
void do_resize(uint64_t size_increase=0);
|
||||
|
||||
|
|
|
@ -3313,6 +3313,11 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui
|
|||
return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting);
|
||||
}
|
||||
|
||||
std::map<uint64_t, uint64_t> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts) const
|
||||
{
|
||||
return m_db->get_output_histogram(amounts);
|
||||
}
|
||||
|
||||
void Blockchain::load_compiled_in_block_hashes()
|
||||
{
|
||||
if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr)
|
||||
|
|
|
@ -682,6 +682,15 @@ namespace cryptonote
|
|||
*/
|
||||
bool flush_txes_from_pool(const std::list<crypto::hash> &txids);
|
||||
|
||||
/**
|
||||
* @brief return a histogram of outputs on the blockchain
|
||||
*
|
||||
* @param amounts optional set of amounts to lookup
|
||||
*
|
||||
* @return a set of amount/instances
|
||||
*/
|
||||
std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const;
|
||||
|
||||
/**
|
||||
* @brief perform a check on all key images in the blockchain
|
||||
*
|
||||
|
|
|
@ -439,5 +439,23 @@ bool t_command_parser_executor::flush_txpool(const std::vector<std::string>& arg
|
|||
return m_executor.flush_txpool(txid);
|
||||
}
|
||||
|
||||
bool t_command_parser_executor::output_histogram(const std::vector<std::string>& args)
|
||||
{
|
||||
if (args.size() > 2) return false;
|
||||
|
||||
uint64_t min_count = 3;
|
||||
uint64_t max_count = 0;
|
||||
|
||||
if (args.size() >= 1)
|
||||
{
|
||||
min_count = boost::lexical_cast<uint64_t>(args[0]);
|
||||
}
|
||||
if (args.size() >= 2)
|
||||
{
|
||||
max_count = boost::lexical_cast<uint64_t>(args[1]);
|
||||
}
|
||||
return m_executor.output_histogram(min_count, max_count);
|
||||
}
|
||||
|
||||
|
||||
} // namespace daemonize
|
||||
|
|
|
@ -114,6 +114,8 @@ public:
|
|||
bool unban(const std::vector<std::string>& args);
|
||||
|
||||
bool flush_txpool(const std::vector<std::string>& args);
|
||||
|
||||
bool output_histogram(const std::vector<std::string>& args);
|
||||
};
|
||||
|
||||
} // namespace daemonize
|
||||
|
|
|
@ -214,6 +214,11 @@ t_command_server::t_command_server(
|
|||
, std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1)
|
||||
, "Flush a transaction from the tx pool by its txid, or the whole tx pool"
|
||||
);
|
||||
m_command_lookup.set_handler(
|
||||
"output_histogram"
|
||||
, std::bind(&t_command_parser_executor::output_histogram, &m_parser, p::_1)
|
||||
, "Print output histogram (amount, instances)"
|
||||
);
|
||||
}
|
||||
|
||||
bool t_command_server::process_command_str(const std::string& cmd)
|
||||
|
|
|
@ -1203,5 +1203,41 @@ bool t_rpc_command_executor::flush_txpool(const std::string &txid)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_count)
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req;
|
||||
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response res;
|
||||
std::string fail_message = "Unsuccessful";
|
||||
epee::json_rpc::error error_resp;
|
||||
|
||||
req.min_count = min_count;
|
||||
req.max_count = max_count;
|
||||
|
||||
if (m_is_rpc)
|
||||
{
|
||||
if (!m_rpc_client->json_rpc_request(req, res, "get_output_histogram", fail_message.c_str()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_rpc_server->on_get_output_histogram(req, res, error_resp))
|
||||
{
|
||||
tools::fail_msg_writer() << fail_message.c_str();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(res.histogram.begin(), res.histogram.end(),
|
||||
[](const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e1, const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e2)->bool { return e1.instances < e2.instances; });
|
||||
for (const auto &e: res.histogram)
|
||||
{
|
||||
tools::msg_writer() << e.instances << " " << cryptonote::print_money(e.amount);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}// namespace daemonize
|
||||
|
|
|
@ -132,6 +132,8 @@ public:
|
|||
bool unban(const std::string &ip);
|
||||
|
||||
bool flush_txpool(const std::string &txid);
|
||||
|
||||
bool output_histogram(uint64_t min_count, uint64_t max_count);
|
||||
};
|
||||
|
||||
} // namespace daemonize
|
||||
|
|
|
@ -1041,6 +1041,38 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool core_rpc_server::on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp)
|
||||
{
|
||||
if(!check_core_busy())
|
||||
{
|
||||
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
|
||||
error_resp.message = "Core is busy.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<uint64_t, uint64_t> histogram;
|
||||
try
|
||||
{
|
||||
histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
res.status = "Failed to get output histogram";
|
||||
return true;
|
||||
}
|
||||
|
||||
res.histogram.clear();
|
||||
res.histogram.reserve(histogram.size());
|
||||
for (const auto &i: histogram)
|
||||
{
|
||||
if (i.second >= req.min_count && (i.second <= req.max_count || req.max_count == 0))
|
||||
res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, i.second));
|
||||
}
|
||||
|
||||
res.status = CORE_RPC_STATUS_OK;
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool core_rpc_server::on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res)
|
||||
{
|
||||
cryptonote::core::set_fast_exit();
|
||||
|
|
|
@ -109,6 +109,7 @@ namespace cryptonote
|
|||
MAP_JON_RPC_WE("setbans", on_set_bans, COMMAND_RPC_SETBANS)
|
||||
MAP_JON_RPC_WE("getbans", on_get_bans, COMMAND_RPC_GETBANS)
|
||||
MAP_JON_RPC_WE("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL)
|
||||
MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM)
|
||||
END_JSON_RPC_MAP()
|
||||
END_URI_MAP2()
|
||||
|
||||
|
@ -149,6 +150,7 @@ namespace cryptonote
|
|||
bool on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp);
|
||||
bool on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp);
|
||||
bool on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp);
|
||||
bool on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp);
|
||||
//-----------------------
|
||||
|
||||
private:
|
||||
|
|
|
@ -986,5 +986,46 @@ namespace cryptonote
|
|||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_GET_OUTPUT_HISTOGRAM
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::vector<uint64_t> amounts;
|
||||
uint64_t min_count;
|
||||
uint64_t max_count;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amounts);
|
||||
KV_SERIALIZE(min_count);
|
||||
KV_SERIALIZE(max_count);
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct entry
|
||||
{
|
||||
uint64_t amount;
|
||||
uint64_t instances;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount);
|
||||
KV_SERIALIZE(instances);
|
||||
END_KV_SERIALIZE_MAP()
|
||||
|
||||
entry(uint64_t amount, uint64_t instances): amount(amount), instances(instances) {}
|
||||
entry() {}
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
std::string status;
|
||||
std::vector<entry> histogram;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(status)
|
||||
KV_SERIALIZE(histogram)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,7 @@ public:
|
|||
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 is_read_only() const { return false; }
|
||||
virtual std::map<uint64_t, uint64_t> get_output_histogram() const { return std::map<uint64_t, uint64_t>(); }
|
||||
|
||||
virtual void add_block( const block& blk
|
||||
, const size_t& block_size
|
||||
|
|
Loading…
Reference in a new issue