RPC: get latest quorum improvements/fixes

This changes the `get_quorum_state` RPC endpoint in two ways:

- If requesting a pulse quorum for a height that includes the current
height (i.e. top block + 1), this returns the current height, round-0
pulse quorum.

- When requesting the latest quorum state (i.e. the no-argument request)
you now get back the latest available quorum of each requested type,
instead of the quorum for the latest available block.  Thus requesting
all types will now give you:
    - the current top-of-the-chain round-0 pulse quorum
    - the top block obligations quorum (no change)
    - the top divisible-by-4 block for checkpoint quorums
    - the top divisible-by-5 block for blink quorums
Previously you would just get whatever quorums existing for the top
height, which often meant just the one-old pulse quorum + top block
obligations quorum, only only checkpoint 25% of the time and blink 20%
of the time.

- Also updated the RPC comments for GET_QUORUM_STATE, both to reflect
the above and to update some parts which were a bit stale.
This commit is contained in:
Jason Rhinelander 2020-10-10 21:41:59 -03:00
parent 3d063cd895
commit 26744cd73e
4 changed files with 72 additions and 21 deletions

View file

@ -1768,6 +1768,12 @@ namespace service_nodes
return get_pulse_entropy_for_next_block(db, top_block, pulse_round);
}
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db,
uint8_t pulse_round)
{
return get_pulse_entropy_for_next_block(db, db.get_top_block(), pulse_round);
}
service_nodes::quorum generate_pulse_quorum(cryptonote::network_type nettype,
crypto::public_key const &block_leader,
uint8_t hf_version,

View file

@ -687,6 +687,9 @@ namespace service_nodes
// The pulse entropy is generated for the next block after the top_block passed in.
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db, cryptonote::block const &top_block, uint8_t pulse_round);
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db, crypto::hash const &top_hash, uint8_t pulse_round);
// Same as above, but uses the current blockchain top block and defaults to round 0 if not
// specified.
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db, uint8_t pulse_round = 0);
payout service_node_info_to_payout(crypto::public_key const &key, service_node_info const &info);

View file

@ -39,6 +39,7 @@
#include <type_traits>
#include <variant>
#include <lokimq/base64.h>
#include "crypto/crypto.h"
#include "cryptonote_basic/tx_extra.h"
#include "cryptonote_core/loki_name_system.h"
#include "cryptonote_core/pulse.h"
@ -2644,12 +2645,33 @@ namespace cryptonote { namespace rpc {
throw rpc_error{ERROR_WRONG_PARAM,
"Quorum type specifies an invalid value: " + std::to_string(req.quorum_type)};
auto requested_type = [&req](service_nodes::quorum_type type) {
return req.quorum_type == GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE ||
req.quorum_type == static_cast<uint8_t>(type);
};
bool latest = false;
uint64_t latest_ob = 0, latest_cp = 0, latest_bl = 0;
uint64_t start = req.start_height, end = req.end_height;
uint64_t curr_height = m_core.get_blockchain_storage().get_current_blockchain_height();
if (start == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE &&
end == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE)
{
start = m_core.get_blockchain_storage().get_current_blockchain_height() - 1;
end = start + 1;
latest = true;
// Our start block for the latest quorum of each type depends on the type being requested:
// obligations: top block
// checkpoint: last block with height divisible by 4
// blink: last block with height divisible by 5
// pulse: current height (i.e. top block height + 1)
uint64_t top_height = curr_height - 1;
latest_ob = top_height;
latest_cp = std::min(start, top_height - top_height % 4);
latest_bl = std::min(start, top_height - top_height % 5);
if (requested_type(service_nodes::quorum_type::checkpointing))
start = std::min(start, latest_cp);
if (requested_type(service_nodes::quorum_type::blink))
start = std::min(start, latest_bl);
end = curr_height;
}
else if (start == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE)
{
@ -2663,16 +2685,14 @@ namespace cryptonote { namespace rpc {
else
{
if (end > start) end++;
else
{
if (end != 0)
end--;
}
else if (end != 0) end--;
}
uint64_t curr_height = m_core.get_blockchain_storage().get_current_blockchain_height();
start = std::min(curr_height, start);
end = std::min(curr_height, end);
// We can also provide the pulse quorum for the current block being produced, so if asked for
// that make a note.
bool add_curr_pulse = (latest || end > curr_height) && requested_type(service_nodes::quorum_type::pulse);
end = std::min(curr_height, end);
uint64_t count = (start > end) ? start - end : end - start;
if (!context.admin && count > GET_QUORUM_STATE::MAX_COUNT)
@ -2700,18 +2720,21 @@ namespace cryptonote { namespace rpc {
for (int quorum_int = (int)start_quorum_iterator; quorum_int <= (int)end_quorum_iterator; quorum_int++)
{
auto type = static_cast<service_nodes::quorum_type>(quorum_int);
if (latest)
{ // Latest quorum requested, so skip if this is isn't the latest height for *this* quorum type
if (type == service_nodes::quorum_type::obligations && height != latest_ob) continue;
if (type == service_nodes::quorum_type::checkpointing && height != latest_cp) continue;
if (type == service_nodes::quorum_type::blink && height != latest_bl) continue;
if (type == service_nodes::quorum_type::pulse) continue;
}
if (std::shared_ptr<const service_nodes::quorum> quorum = m_core.get_quorum(type, height, true /*include_old*/))
{
GET_QUORUM_STATE::quorum_for_height entry = {};
auto& entry = res.quorums.emplace_back();
entry.height = height;
entry.quorum_type = static_cast<uint8_t>(quorum_int);
entry.quorum.validators = hexify(quorum->validators);
entry.quorum.workers = hexify(quorum->workers);
entry.quorum.validators.reserve(quorum->validators.size());
entry.quorum.workers.reserve(quorum->workers.size());
for (crypto::public_key const &key : quorum->validators) entry.quorum.validators.push_back(epee::string_tools::pod_to_hex(key));
for (crypto::public_key const &key : quorum->workers) entry.quorum.workers.push_back(epee::string_tools::pod_to_hex(key));
res.quorums.push_back(entry);
at_least_one_succeeded = true;
}
}
@ -2721,6 +2744,25 @@ namespace cryptonote { namespace rpc {
else height--;
}
if (add_curr_pulse)
{
uint8_t hf_version = m_core.get_hard_fork_version(curr_height);
auto entropy = service_nodes::get_pulse_entropy_for_next_block(m_core.get_blockchain_storage().get_db());
auto& sn_list = m_core.get_service_node_list();
auto quorum = generate_pulse_quorum(m_core.get_nettype(), sn_list.get_block_leader().key, hf_version, sn_list.active_service_nodes_infos(), entropy, 0);
if (!quorum.validators.empty())
{
auto& entry = res.quorums.emplace_back();
entry.height = curr_height;
entry.quorum_type = static_cast<uint8_t>(service_nodes::quorum_type::pulse);
entry.quorum.validators = hexify(quorum.validators);
entry.quorum.workers = hexify(quorum.workers);
at_least_one_succeeded = true;
}
}
if (!at_least_one_succeeded)
throw rpc_error{ERROR_WRONG_PARAM, "Failed to query any quorums at all"};

View file

@ -1822,7 +1822,7 @@ namespace rpc {
LOKI_RPC_DOC_INTROSPECT
// Get the quorum state which is the list of public keys of the nodes who are voting, and the list of public keys of the nodes who are being tested.
// Accesses the list of public keys of the nodes who are participating or being tested in a quorum.
struct GET_QUORUM_STATE : PUBLIC
{
static constexpr auto names() { return NAMES("get_quorum_state"); }
@ -1832,17 +1832,17 @@ namespace rpc {
static constexpr uint8_t ALL_QUORUMS_SENTINEL_VALUE = 255;
struct request
{
uint64_t start_height; // (Optional): Start height, omit both start and end height to request the latest quorum
uint64_t start_height; // (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights.
uint64_t end_height; // (Optional): End height, omit both start and end height to request the latest quorum
uint8_t quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 255 = all quorums, default is all quorums;
uint8_t quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Blink, 3 = Pulse, 255 = all quorums, default is all quorums. For Pulse quorums, requesting the blockchain height (or latest) returns the primary pulse quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time.
KV_MAP_SERIALIZABLE
};
struct quorum_t
{
std::vector<std::string> validators; // Public key of the service node
std::vector<std::string> workers; // Public key of the service node
std::vector<std::string> validators; // List of service node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and blink these are the participating nodes (there are no workers); for Pulse blink quorums these are the block signers.
std::vector<std::string> workers; // Public key of the quorum workers. For obligations quorums these are the nodes being tested; for Pulse quorums this is the block producer. Checkpoint and Blink quorums do not populate this field.
KV_MAP_SERIALIZABLE