New print_sn_state_changes command (#727)

* new `print_sn_state_changes` command

* Address reviews

* Fix fetching txs for each blocks

* sentinel value is set to current height - 1
This commit is contained in:
sachaaaaa 2019-07-15 13:45:09 +10:00 committed by Doyle
parent 2ccfca4322
commit 631e3f66fe
8 changed files with 222 additions and 2 deletions

View file

@ -95,6 +95,39 @@ bool t_command_parser_executor::print_checkpoints(const std::vector<std::string>
return m_executor.print_checkpoints(start_height, end_height, print_json);
}
bool t_command_parser_executor::print_sn_state_changes(const std::vector<std::string> &args)
{
uint64_t start_height;
uint64_t end_height = cryptonote::COMMAND_RPC_GET_SN_STATE_CHANGES::HEIGHT_SENTINEL_VALUE;
if (args.empty()) {
std::cout << "Missing first argument start_height" << std::endl;
return false;
}
std::forward_list<std::string> args_list(args.begin(), args.end());
if (!epee::string_tools::get_xtype_from_string(start_height, args_list.front()))
{
std::cout << "start_height should be a number" << std::endl;
return false;
}
args_list.pop_front();
if (!parse_if_present(args_list, end_height, "end height"))
return false;
if (!args_list.empty())
{
std::cout << "use: print_sn_state_changes <start_height> [end height]"
<< "(omit arguments to scan until the current block)"
<< std::endl;
return false;
}
return m_executor.print_sn_state_changes(start_height, end_height);
}
bool t_command_parser_executor::print_peer_list(const std::vector<std::string>& args)
{
if (args.size() > 3)
@ -301,7 +334,7 @@ bool t_command_parser_executor::set_log_level(const std::vector<std::string>& ar
}
}
bool t_command_parser_executor::print_height(const std::vector<std::string>& args)
bool t_command_parser_executor::print_height(const std::vector<std::string>& args)
{
if (!args.empty()) return false;

View file

@ -166,6 +166,8 @@ public:
bool check_blockchain_pruning(const std::vector<std::string>& args);
bool print_net_stats(const std::vector<std::string>& args);
bool print_sn_state_changes(const std::vector<std::string> &args);
};
} // namespace daemonize

View file

@ -362,7 +362,12 @@ t_command_server::t_command_server(
, "print_checkpoints [+json] [start height] [end height]"
, "Query the available checkpoints between the range, omit arguments to print the last 60 checkpoints"
);
m_command_lookup.set_handler(
"print_sn_state_changes"
, std::bind(&t_command_parser_executor::print_sn_state_changes, &m_parser, p::_1)
, "print_sn_state_changes <start_height> [end height]"
, "Query the state changes between the range, omit the last argument to scan until the current block"
);
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
m_command_lookup.set_handler(
"relay_votes_and_uptime", std::bind([rpc_server](std::vector<std::string> const &args) {

View file

@ -312,6 +312,42 @@ bool t_rpc_command_executor::print_checkpoints(uint64_t start_height, uint64_t e
return true;
}
bool t_rpc_command_executor::print_sn_state_changes(uint64_t start_height, uint64_t end_height)
{
cryptonote::COMMAND_RPC_GET_SN_STATE_CHANGES::request req;
cryptonote::COMMAND_RPC_GET_SN_STATE_CHANGES::response res;
epee::json_rpc::error error_resp;
req.start_height = start_height;
req.end_height = end_height;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "get_service_nodes_state_changes", "Failed to query service nodes state changes"))
return false;
}
else
{
if (!m_rpc_server->on_get_service_nodes_state_changes(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << "Failed to query sn state changes";
return false;
}
}
std::stringstream output;
output << "Service Node State Changes (blocks " << res.start_height << "-" << res.end_height << ")" << std::endl;
output << " Recommissions:\t\t" << res.total_recommission << std::endl;
output << " Unlocks:\t\t" << res.total_unlock << std::endl;
output << " Decommissions:\t\t" << res.total_decommission << std::endl;
output << " Deregistrations:\t" << res.total_deregister << std::endl;
output << " IP change penalties:\t" << res.total_ip_change_penalty << std::endl;
tools::success_msg_writer() << output.str();
return true;
}
bool t_rpc_command_executor::print_peer_list(bool white, bool gray, size_t limit) {
cryptonote::COMMAND_RPC_GET_PEER_LIST::request req;
cryptonote::COMMAND_RPC_GET_PEER_LIST::response res;

View file

@ -72,6 +72,8 @@ public:
bool print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json);
bool print_sn_state_changes(uint64_t start_height, uint64_t end_height);
bool print_peer_list(bool white = true, bool gray = true, size_t limit = 0);
bool print_peer_list_stats();

View file

@ -2965,5 +2965,100 @@ namespace cryptonote
res.checkpoints = db.get_checkpoints_range(req.start_height, req.end_height);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_service_nodes_state_changes(const COMMAND_RPC_GET_SN_STATE_CHANGES::request& req, COMMAND_RPC_GET_SN_STATE_CHANGES::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
using blob_t = cryptonote::blobdata;
using block_pair_t = std::pair<blob_t, block>;
std::vector<block_pair_t> blocks;
const auto& db = m_core.get_blockchain_storage();
const uint64_t current_height = db.get_current_blockchain_height();
uint64_t end_height;
if (req.end_height == COMMAND_RPC_GET_SN_STATE_CHANGES::HEIGHT_SENTINEL_VALUE) {
// current height is the block being mined, so exclude it from the results
end_height = current_height - 1;
} else {
end_height = req.end_height;
}
if (end_height < req.start_height){
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "The provided end_height needs to be higher than start_height";
return false;
}
if (!db.get_blocks(req.start_height, end_height - req.start_height + 1, blocks)) {
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Could not query block at requested height: " + std::to_string(req.start_height);
return false;
}
res.start_height = req.start_height;
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))
{
MERROR("Could not query block at requested height: " << cryptonote::get_block_height(block.second));
continue;
}
const uint8_t hard_fork_version = block.second.major_version;
for (const auto& blob : blobs)
{
cryptonote::transaction tx;
if (!cryptonote::parse_and_validate_tx_from_blob(blob, tx))
{
MERROR("tx could not be validated from blob, possibly corrupt blockchain");
continue;
}
if (tx.type == cryptonote::txtype::state_change)
{
cryptonote::tx_extra_service_node_state_change state_change;
if (!cryptonote::get_service_node_state_change_from_tx_extra(tx.extra, state_change, hard_fork_version))
{
LOG_ERROR("Could not get state change from tx, possibly corrupt tx, hf_version "<< std::to_string(hard_fork_version));
continue;
}
switch(state_change.state) {
case service_nodes::new_state::deregister:
res.total_deregister++;
break;
case service_nodes::new_state::decommission:
res.total_decommission++;
break;
case service_nodes::new_state::recommission:
res.total_recommission++;
break;
case service_nodes::new_state::ip_change_penalty:
res.total_ip_change_penalty++;
break;
default:
MERROR("Unhandled state in on_get_service_nodes_state_changes");
break;
}
}
if (tx.type == cryptonote::txtype::key_image_unlock)
{
res.total_unlock++;
}
}
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
} // namespace cryptonote

View file

@ -188,6 +188,7 @@ namespace cryptonote
MAP_JON_RPC_WE("get_checkpoints", on_get_checkpoints, COMMAND_RPC_GET_CHECKPOINTS)
MAP_JON_RPC_WE_IF("perform_blockchain_test", on_perform_blockchain_test, COMMAND_RPC_PERFORM_BLOCKCHAIN_TEST, !m_restricted)
MAP_JON_RPC_WE_IF("storage_server_ping", on_storage_server_ping, COMMAND_RPC_STORAGE_SERVER_PING, !m_restricted)
MAP_JON_RPC_WE("get_service_nodes_state_changes", on_get_service_nodes_state_changes, COMMAND_RPC_GET_SN_STATE_CHANGES)
END_JSON_RPC_MAP()
END_URI_MAP2()
@ -277,6 +278,7 @@ namespace cryptonote
bool on_perform_blockchain_test(const COMMAND_RPC_PERFORM_BLOCKCHAIN_TEST::request& req, COMMAND_RPC_PERFORM_BLOCKCHAIN_TEST::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_storage_server_ping(const COMMAND_RPC_STORAGE_SERVER_PING::request& req, COMMAND_RPC_STORAGE_SERVER_PING::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_get_checkpoints(const COMMAND_RPC_GET_CHECKPOINTS::request& req, COMMAND_RPC_GET_CHECKPOINTS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_get_service_nodes_state_changes(const COMMAND_RPC_GET_SN_STATE_CHANGES::request& req, COMMAND_RPC_GET_SN_STATE_CHANGES::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
//-----------------------
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)

View file

@ -3132,4 +3132,49 @@ namespace cryptonote
};
typedef epee::misc_utils::struct_init<response_t> response;
};
LOKI_RPC_DOC_INTROSPECT
// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints.
struct COMMAND_RPC_GET_SN_STATE_CHANGES
{
constexpr static uint32_t NUM_BLOCKS_TO_SCAN_BY_DEFAULT = 720;
constexpr static uint64_t HEIGHT_SENTINEL_VALUE = (UINT64_MAX - 1);
struct request_t
{
uint64_t start_height;
uint64_t end_height; // Optional: If omitted, the tally runs until the current block
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(start_height)
KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
struct response_t
{
std::string status; // Generic RPC error code. "OK" is the success value.
bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`.
uint32_t total_deregister;
uint32_t total_ip_change_penalty;
uint32_t total_decommission;
uint32_t total_recommission;
uint32_t total_unlock;
uint64_t start_height;
uint64_t end_height;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(untrusted)
KV_SERIALIZE(total_deregister)
KV_SERIALIZE(total_ip_change_penalty)
KV_SERIALIZE(total_decommission)
KV_SERIALIZE(total_recommission)
KV_SERIALIZE(start_height)
KV_SERIALIZE(end_height)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
};
}