Service Nodes States Endpoint (#145)

* Add get_service_node_list_state command for analytics

* Update service_node_list_state to search particular pubkey

* Service node list state sorts display results by longest waiting

* Fix up leftover todos/unused data structures

* Change get_service_node_list_state to print_sn
This commit is contained in:
Doyle 2018-08-15 12:59:05 +10:00 committed by GitHub
parent 0891999d2b
commit f55b9b1b82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 375 additions and 35 deletions

View file

@ -1106,6 +1106,12 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
uint64_t core::get_uptime_proof(const crypto::public_key &key) const
{
uint64_t result = m_quorum_cop.get_uptime_proof(key);
return result;
}
//-----------------------------------------------------------------------------------------------
bool core::handle_uptime_proof(uint64_t timestamp, const crypto::public_key& pubkey, const crypto::signature& sig)
{
return m_quorum_cop.handle_uptime_proof(timestamp, pubkey, sig);
@ -1673,6 +1679,12 @@ namespace cryptonote
return result;
}
//-----------------------------------------------------------------------------------------------
std::vector<service_nodes::service_node_pubkey_info> core::get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const
{
std::vector<service_nodes::service_node_pubkey_info> result = m_service_node_list.get_service_node_list_state(service_node_pubkeys);
return result;
}
//-----------------------------------------------------------------------------------------------
bool core::add_deregister_vote(const loki::service_node_deregister::vote& vote, vote_verification_context &vvc)
{
{

View file

@ -798,6 +798,15 @@ namespace cryptonote
*/
const std::shared_ptr<service_nodes::quorum_state> get_quorum_state(uint64_t height) const;
/**
* @brief Get a snapshot of the service node list state at the time of the call.
*
* @param service_node_pubkeys pubkeys to search, if empty this indicates get all the pubkeys
*
* @return All the service nodes that can be matched from pubkeys in param
*/
std::vector<service_nodes::service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key>& service_node_pubkeys) const;
/**
* @brief Add a vote to deregister a service node from network
*
@ -836,6 +845,15 @@ namespace cryptonote
*/
bool submit_uptime_proof();
/**
* @brief Try find the uptime proof from the service node.
*
* @param key The public key of the service node
*
* @return 0 if no uptime proof found, otherwise the timestamp it last received in epoch time
*/
uint64_t get_uptime_proof(const crypto::public_key &key) const;
private:
/**

View file

@ -198,4 +198,15 @@ namespace service_nodes
return true;
}
uint64_t quorum_cop::get_uptime_proof(const crypto::public_key &pubkey) const
{
const auto& it = m_uptime_proof_seen.find(pubkey);
if (it == m_uptime_proof_seen.end())
{
return 0;
}
return (*it).second;
}
}

View file

@ -63,6 +63,8 @@ namespace service_nodes
"Safety buffer should always be less than the vote lifetime");
bool prune_uptime_proof();
uint64_t get_uptime_proof(const crypto::public_key &pubkey) const;
private:
cryptonote::core& m_core;

View file

@ -142,6 +142,41 @@ namespace service_nodes
return result;
}
std::vector<service_node_pubkey_info> service_node_list::get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const
{
std::vector<service_node_pubkey_info> result;
if (service_node_pubkeys.empty())
{
result.reserve(m_service_nodes_infos.size());
for (const auto &it : m_service_nodes_infos)
{
service_node_pubkey_info entry = {};
entry.pubkey = it.first;
entry.info = it.second;
result.push_back(entry);
}
}
else
{
result.reserve(service_node_pubkeys.size());
for (const auto &it : service_node_pubkeys)
{
const auto &find_it = m_service_nodes_infos.find(it);
if (find_it == m_service_nodes_infos.end())
continue;
service_node_pubkey_info entry = {};
entry.pubkey = (*find_it).first;
entry.info = (*find_it).second;
result.push_back(entry);
}
}
return result;
}
bool service_node_list::is_service_node(const crypto::public_key& pubkey) const
{
return m_service_nodes_infos.find(pubkey) != m_service_nodes_infos.end();

View file

@ -48,6 +48,39 @@ namespace service_nodes
std::vector<crypto::public_key> nodes_to_test;
};
struct service_node_info // registration information
{
struct contribution
{
uint64_t amount;
uint64_t reserved;
cryptonote::account_public_address address;
contribution(uint64_t _reserved, const cryptonote::account_public_address& _address)
: amount(0), reserved(_reserved), address(_address) { }
};
// block_height and transaction_index are to record when the service node is registered or when it last received a reward.
uint64_t last_reward_block_height;
uint32_t last_reward_transaction_index;
std::vector<contribution> contributors;
uint64_t total_contributed;
uint64_t total_reserved;
uint64_t staking_requirement;
uint32_t portions_for_operator;
cryptonote::account_public_address operator_address;
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
// the minimum contribution to start a new contributor
uint64_t get_min_contribution() const { return std::min(staking_requirement - total_reserved, staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS); }
};
struct service_node_pubkey_info
{
crypto::public_key pubkey;
service_node_info info;
};
class service_node_list
: public cryptonote::Blockchain::BlockAddedHook,
public cryptonote::Blockchain::BlockchainDetachedHook,
@ -69,42 +102,9 @@ namespace service_nodes
bool is_service_node(const crypto::public_key& pubkey) const;
const std::shared_ptr<quorum_state> get_quorum_state(uint64_t height) const;
std::vector<service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const;
private:
struct service_node_info
{
struct contribution
{
uint64_t amount;
uint64_t reserved;
cryptonote::account_public_address address;
contribution(uint64_t _reserved, const cryptonote::account_public_address& _address)
: amount(0), reserved(_reserved), address(_address) { }
};
// block_height and transaction_index are to record when the service node
// is registered or when it last received a reward.
//
// set the winning service node as though it was re-registering at the
// block height it wins on, with transaction index=-1
// (hence transaction_index is signed)
uint64_t last_reward_block_height;
uint32_t last_reward_transaction_index;
std::vector<contribution> contributors;
uint64_t total_contributed;
uint64_t total_reserved;
uint64_t staking_requirement;
uint32_t portions_for_operator;
cryptonote::account_public_address operator_address;
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
// the minimum contribution to start a new contributor
uint64_t get_min_contribution() const { return std::min(staking_requirement - total_reserved, staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS); }
};
bool is_registration_tx(const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info) const;
bool get_contribution(const cryptonote::transaction& tx, uint64_t block_height, cryptonote::account_public_address& address, uint64_t& transferred) const;

View file

@ -168,6 +168,12 @@ bool t_command_parser_executor::prepare_registration()
return result;
}
bool t_command_parser_executor::print_sn(const std::vector<std::string>& args)
{
bool result = m_executor.print_sn(args);
return result;
}
bool t_command_parser_executor::set_log_level(const std::vector<std::string>& args)
{
if(args.size() > 1)

View file

@ -83,6 +83,8 @@ public:
bool prepare_registration();
bool print_sn(const std::vector<std::string>& args);
bool set_log_level(const std::vector<std::string>& args);
bool set_log_categories(const std::vector<std::string>& args);

View file

@ -119,6 +119,12 @@ t_command_server::t_command_server(
, "prepare_registration"
, "Interactive prompt to prepare the registration. The resulting registration data is saved to disk."
);
m_command_lookup.set_handler(
"print_sn"
, std::bind(&t_command_parser_executor::print_sn, &m_parser, p::_1)
, "print_sn [<pubkey> [...]]"
, "Print service node registration info for the current height"
);
m_command_lookup.set_handler(
"is_key_image_spent"
, std::bind(&t_command_parser_executor::is_key_image_spent, &m_parser, p::_1)

View file

@ -1991,9 +1991,139 @@ bool t_rpc_command_executor::get_service_node_registration_cmd(const std::vector
tools::fail_msg_writer() << make_error(fail_message, error_resp.message);
return true;
}
tools::success_msg_writer() << res.registration_cmd;
}
return true;
}
static void print_service_node_list_state(std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry *> list)
{
const char indent1[] = " ";
const char indent2[] = " ";
const char indent3[] = " ";
for (size_t i = 0; i < list.size(); ++i)
{
const cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry &entry = *list[i];
bool is_registered = entry.total_contributed >= entry.staking_requirement;
epee::console_colors color = is_registered ? console_color_green : epee::console_color_yellow;
tools::msg_writer(color) << indent1 << "[" << i << "] Service Node: " << entry.service_node_pubkey;
tools::msg_writer(color) << indent2 << "Total Contributed / Staking Requirement: " << cryptonote::print_money(entry.total_contributed) << " / " << cryptonote::print_money(entry.staking_requirement);
tools::msg_writer() << indent2 << "Total Reserved / Staking Requirement: " << cryptonote::print_money(entry.total_reserved) << " / " << cryptonote::print_money(entry.staking_requirement);
if (is_registered)
{
tools::msg_writer() << indent2 << "Last Reward At (Block Height/TX Index): " << entry.last_reward_block_height << " / " << entry.last_reward_transaction_index;
}
tools::msg_writer() << indent2 << "Operator Cut (\% Of Reward): " << ((entry.portions_for_operator / (double)STAKING_PORTIONS) * 100.0) << "%";
tools::msg_writer() << indent2 << "Operator Address: " << entry.operator_address;
epee::console_colors uptime_proof_color = (entry.last_uptime_proof == 0) ? epee::console_color_red : epee::console_color_green;
if (is_registered)
{
if (entry.last_uptime_proof == 0)
tools::msg_writer(uptime_proof_color) << indent2 << "Last Uptime Proof Received: Not Received Yet";
else
tools::msg_writer(uptime_proof_color) << indent2 << "Last Uptime Proof Received: " << get_human_time_ago(entry.last_uptime_proof, time(nullptr));
}
tools::msg_writer() << "";
for (size_t j = 0; j < entry.contributors.size(); ++j)
{
const cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::contribution &contributor = entry.contributors[j];
tools::msg_writer() << indent2 << "[" << j << "] Contributor: " << contributor.address;
tools::msg_writer() << indent3 << "Amount / Reserved: " << cryptonote::print_money(contributor.amount) << " / " << cryptonote::print_money(contributor.reserved);
}
tools::msg_writer() << "";
}
}
bool t_rpc_command_executor::print_sn(const std::vector<std::string> &args)
{
cryptonote::COMMAND_RPC_GET_SERVICE_NODE::request req = {};
cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response res = {};
std::string fail_message = "Unsuccessful";
epee::json_rpc::error error_resp;
req.service_node_pubkeys = args;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "get_service_node", fail_message.c_str()))
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
}
else
{
if (!m_rpc_server->on_get_service_node(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, error_resp.message);
return true;
}
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry *> unregistered;
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry *> registered;
registered.reserve (res.service_node_states.size());
unregistered.reserve(res.service_node_states.size() * 0.5f);
for (auto &entry : res.service_node_states)
{
if (entry.total_contributed == entry.staking_requirement)
{
registered.push_back(&entry);
}
else
{
unregistered.push_back(&entry);
}
}
std::sort(unregistered.begin(), unregistered.end(),
[](const cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry *a, const cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry *b) {
uint64_t a_remaining = a->staking_requirement - a->total_reserved;
uint64_t b_remaining = b->staking_requirement - b->total_reserved;
if (b_remaining == a_remaining)
return b->portions_for_operator < a->portions_for_operator;
return b_remaining < a_remaining;
});
std::stable_sort(registered.begin(), registered.end(),
[](const cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry *a, const cryptonote::COMMAND_RPC_GET_SERVICE_NODE::response::entry *b) {
if (a->last_reward_block_height == b->last_reward_block_height)
return a->last_reward_transaction_index < b->last_reward_transaction_index;
return a->last_reward_block_height < b->last_reward_block_height;
});
if (unregistered.size() > 0)
{
tools::msg_writer() << "Service Node Unregistered State[" << unregistered.size()<< "]";
print_service_node_list_state(unregistered);
}
if (registered.size() > 0)
{
tools::msg_writer() << "Service Node Registration State[" << registered.size()<< "]";
print_service_node_list_state(registered);
}
if (unregistered.size() == 0 && registered.size() == 0)
{
tools::msg_writer() << "No service node is currently known on the network";
}
}
tools::success_msg_writer() << res.registration_cmd;
return true;
}

View file

@ -161,6 +161,8 @@ public:
bool get_service_node_key();
bool prepare_registration();
bool print_sn(const std::vector<std::string> &args);
};
} // namespace daemonize

View file

@ -2285,6 +2285,58 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_service_node(const COMMAND_RPC_GET_SERVICE_NODE::request& req, COMMAND_RPC_GET_SERVICE_NODE::response& res, epee::json_rpc::error& error_resp)
{
PERF_TIMER(on_get_sn);
std::vector<crypto::public_key> pubkeys(req.service_node_pubkeys.size());
for (size_t i = 0; i < req.service_node_pubkeys.size(); i++)
{
if (!string_tools::hex_to_pod(req.service_node_pubkeys[i], pubkeys[i]))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Could not convert to a public key, arg: ";
error_resp.message += std::to_string(i);
error_resp.message += " which is pubkey: ";
error_resp.message += req.service_node_pubkeys[i];
return false;
}
}
std::vector<service_nodes::service_node_pubkey_info> pubkey_info_list = m_core.get_service_node_list_state(pubkeys);
res.status = CORE_RPC_STATUS_OK;
res.service_node_states.reserve(pubkey_info_list.size());
for (const auto &pubkey_info : pubkey_info_list)
{
COMMAND_RPC_GET_SERVICE_NODE::response::entry entry = {};
entry.service_node_pubkey = string_tools::pod_to_hex(pubkey_info.pubkey);
entry.last_reward_block_height = pubkey_info.info.last_reward_block_height;
entry.last_reward_transaction_index = pubkey_info.info.last_reward_transaction_index;
entry.last_uptime_proof = m_core.get_uptime_proof(pubkey_info.pubkey);
entry.contributors.reserve(pubkey_info.info.contributors.size());
for (service_nodes::service_node_info::contribution const &contributor : pubkey_info.info.contributors)
{
COMMAND_RPC_GET_SERVICE_NODE::response::contribution new_contributor = {};
new_contributor.amount = contributor.amount;
new_contributor.reserved = contributor.reserved;
new_contributor.address = string_tools::pod_to_hex(contributor.address);
entry.contributors.push_back(new_contributor);
}
entry.total_contributed = pubkey_info.info.total_contributed;
entry.total_reserved = pubkey_info.info.total_reserved;
entry.staking_requirement = pubkey_info.info.staking_requirement;
entry.portions_for_operator = pubkey_info.info.portions_for_operator;
entry.operator_address = string_tools::pod_to_hex(pubkey_info.info.operator_address);
res.service_node_states.push_back(entry);
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = {

View file

@ -158,6 +158,7 @@ namespace cryptonote
MAP_JON_RPC_WE("get_quorum_state", on_get_quorum_state_json, COMMAND_RPC_GET_QUORUM_STATE)
MAP_JON_RPC_WE("get_service_node_registration_cmd", on_get_service_node_registration_cmd, COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD)
MAP_JON_RPC_WE("get_service_node_key", on_get_service_node_key, COMMAND_RPC_GET_SERVICE_NODE_KEY)
MAP_JON_RPC_WE("get_service_node", on_get_service_node, COMMAND_RPC_GET_SERVICE_NODE)
END_JSON_RPC_MAP()
END_URI_MAP2()
@ -224,6 +225,7 @@ namespace cryptonote
bool on_get_quorum_state_json(const COMMAND_RPC_GET_QUORUM_STATE::request& req, COMMAND_RPC_GET_QUORUM_STATE::response& res, epee::json_rpc::error& error_resp);
bool on_get_service_node_registration_cmd(const COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD::request& req, COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD::response& res, epee::json_rpc::error& error_resp);
bool on_get_service_node_key(const COMMAND_RPC_GET_SERVICE_NODE_KEY::request& req, COMMAND_RPC_GET_SERVICE_NODE_KEY::response& res, epee::json_rpc::error &error_resp);
bool on_get_service_node(const COMMAND_RPC_GET_SERVICE_NODE::request& req, COMMAND_RPC_GET_SERVICE_NODE::response& res, epee::json_rpc::error& error_resp);
//-----------------------
private:

View file

@ -2345,4 +2345,66 @@ namespace cryptonote
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_GET_SERVICE_NODE
{
struct request
{
std::vector<std::string> service_node_pubkeys; // pass empty vector to get all the service nodes
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(service_node_pubkeys);
END_KV_SERIALIZE_MAP()
};
struct response
{
struct contribution
{
uint64_t amount;
uint64_t reserved;
std::string address;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(amount)
KV_SERIALIZE(reserved)
KV_SERIALIZE(address)
END_KV_SERIALIZE_MAP()
};
struct entry
{
std::string service_node_pubkey;
uint64_t last_reward_block_height;
uint32_t last_reward_transaction_index;
uint64_t last_uptime_proof;
std::vector<contribution> contributors;
uint64_t total_contributed;
uint64_t total_reserved;
uint64_t staking_requirement;
uint32_t portions_for_operator;
std::string operator_address;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(service_node_pubkey)
KV_SERIALIZE(last_reward_block_height)
KV_SERIALIZE(last_reward_transaction_index)
KV_SERIALIZE(last_uptime_proof)
KV_SERIALIZE(contributors)
KV_SERIALIZE(total_contributed)
KV_SERIALIZE(total_reserved)
KV_SERIALIZE(staking_requirement)
KV_SERIALIZE(portions_for_operator)
KV_SERIALIZE(operator_address)
END_KV_SERIALIZE_MAP()
};
std::vector<entry> service_node_states;
std::string status;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(service_node_states)
KV_SERIALIZE(status)
END_KV_SERIALIZE_MAP()
};
};
}