Rename `testing_quorum` class to just `quorum`

testing_quorum is a bit of a misnomer: it only does testing when used
for an obligations quorum, but it is also used for checkpointing and
soon Blink.
This commit is contained in:
Jason Rhinelander 2019-10-15 14:52:49 -03:00
parent 4706f86d84
commit 1b5234e6a6
14 changed files with 190 additions and 51 deletions

View File

@ -3211,7 +3211,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
auto const quorum_type = service_nodes::quorum_type::obligations;
auto const quorum = m_service_node_list.get_testing_quorum(quorum_type, state_change.block_height);
auto const quorum = m_service_node_list.get_quorum(quorum_type, state_change.block_height);
{
if (!quorum)
{

View File

@ -2254,9 +2254,9 @@ namespace cryptonote
return si.available;
}
//-----------------------------------------------------------------------------------------------
std::shared_ptr<const service_nodes::testing_quorum> core::get_testing_quorum(service_nodes::quorum_type type, uint64_t height, bool include_old, std::vector<std::shared_ptr<const service_nodes::testing_quorum>> *alt_states) const
std::shared_ptr<const service_nodes::quorum> core::get_quorum(service_nodes::quorum_type type, uint64_t height, bool include_old, std::vector<std::shared_ptr<const service_nodes::quorum>> *alt_states) const
{
return m_service_node_list.get_testing_quorum(type, height, include_old, alt_states);
return m_service_node_list.get_quorum(type, height, include_old, alt_states);
}
//-----------------------------------------------------------------------------------------------
bool core::is_service_node(const crypto::public_key& pubkey, bool require_active) const

View File

@ -798,7 +798,7 @@ namespace cryptonote
* @param include_old whether to look in the old quorum states (does nothing unless running with --store-full-quorum-history)
* @return Null shared ptr if quorum has not been determined yet or is not defined for height
*/
std::shared_ptr<const service_nodes::testing_quorum> get_testing_quorum(service_nodes::quorum_type type, uint64_t height, bool include_old = false, std::vector<std::shared_ptr<const service_nodes::testing_quorum>> *alt_states = nullptr) const;
std::shared_ptr<const service_nodes::quorum> get_quorum(service_nodes::quorum_type type, uint64_t height, bool include_old = false, std::vector<std::shared_ptr<const service_nodes::quorum>> *alt_states = nullptr) const;
/**
* @brief Get a non owning reference to the list of blacklisted key images

View File

@ -209,7 +209,7 @@ namespace service_nodes
return sort_and_filter(service_nodes_infos, [](const service_node_info &info) { return info.is_decommissioned() && info.is_fully_funded(); }, /*reserve=*/ false);
}
std::shared_ptr<const testing_quorum> service_node_list::get_testing_quorum(quorum_type type, uint64_t height, bool include_old, std::vector<std::shared_ptr<const testing_quorum>> *alt_quorums) const
std::shared_ptr<const quorum> service_node_list::get_quorum(quorum_type type, uint64_t height, bool include_old, std::vector<std::shared_ptr<const quorum>> *alt_quorums) const
{
height = offset_testing_quorum_height(type, height);
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
@ -251,17 +251,17 @@ namespace service_nodes
state_t const &alt_state = hash_to_state.second;
if (alt_state.height == height)
{
std::shared_ptr<const testing_quorum> alt_result = alt_state.quorums.get(type);
std::shared_ptr<const quorum> alt_result = alt_state.quorums.get(type);
if (alt_result) alt_quorums->push_back(alt_result);
}
}
}
std::shared_ptr<const testing_quorum> result = quorums->get(type);
std::shared_ptr<const quorum> result = quorums->get(type);
return result;
}
static bool get_pubkey_from_quorum(testing_quorum const &quorum, quorum_group group, size_t quorum_index, crypto::public_key &key)
static bool get_pubkey_from_quorum(quorum const &quorum, quorum_group group, size_t quorum_index, crypto::public_key &key)
{
std::vector<crypto::public_key> const *array = nullptr;
if (group == quorum_group::validator) array = &quorum.validators;
@ -284,7 +284,7 @@ namespace service_nodes
bool service_node_list::get_quorum_pubkey(quorum_type type, quorum_group group, uint64_t height, size_t quorum_index, crypto::public_key &key) const
{
std::shared_ptr<const testing_quorum> quorum = get_testing_quorum(type, height);
std::shared_ptr<const quorum> quorum = get_quorum(type, height);
if (!quorum)
{
LOG_PRINT_L1("Quorum for height: " << height << ", was not stored by the daemon");
@ -1132,7 +1132,7 @@ namespace service_nodes
if (block.major_version >= cryptonote::network_version_13_enforce_checkpoints && checkpoint)
{
std::shared_ptr<const testing_quorum> quorum = get_testing_quorum(quorum_type::checkpointing, checkpoint->height);
std::shared_ptr<const quorum> quorum = get_quorum(quorum_type::checkpointing, checkpoint->height);
if (!quorum)
{
LOG_PRINT_L1("Failed to get testing quorum checkpoint for block: " << cryptonote::get_block_hash(block));
@ -1211,7 +1211,7 @@ namespace service_nodes
{
auto type = static_cast<quorum_type>(type_int);
size_t num_validators = 0, num_workers = 0;
auto quorum = std::make_shared<testing_quorum>();
auto quorum = std::make_shared<service_nodes::quorum>();
std::vector<size_t> pub_keys_indexes;
if (type == quorum_type::obligations)
@ -1734,15 +1734,15 @@ namespace service_nodes
if (checkpoint)
{
std::vector<std::shared_ptr<const service_nodes::testing_quorum>> alt_quorums;
std::shared_ptr<const testing_quorum> quorum = get_testing_quorum(quorum_type::checkpointing, checkpoint->height, false, &alt_quorums);
std::vector<std::shared_ptr<const service_nodes::quorum>> alt_quorums;
std::shared_ptr<const quorum> quorum = get_quorum(quorum_type::checkpointing, checkpoint->height, false, &alt_quorums);
if (!quorum)
return false;
if (!service_nodes::verify_checkpoint(block.major_version, *checkpoint, *quorum))
{
bool verified_on_alt_quorum = false;
for (std::shared_ptr<const service_nodes::testing_quorum> alt_quorum : alt_quorums)
for (std::shared_ptr<const service_nodes::quorum> alt_quorum : alt_quorums)
{
if (service_nodes::verify_checkpoint(block.major_version, *checkpoint, *alt_quorum))
{
@ -2092,17 +2092,11 @@ namespace service_nodes
static quorum_manager quorum_for_serialization_to_quorum_manager(service_node_list::quorum_for_serialization const &source)
{
quorum_manager result = {};
{
auto quorum = std::make_shared<testing_quorum>(source.quorums[static_cast<uint8_t>(quorum_type::obligations)]);
result.obligations = quorum;
}
result.obligations = std::make_shared<quorum>(source.quorums[static_cast<uint8_t>(quorum_type::obligations)]);
// Don't load any checkpoints that shouldn't exist (see the comment in generate_quorums as to why the `+BUFFER` term is here).
if ((source.height + REORG_SAFETY_BUFFER_BLOCKS_POST_HF12) % CHECKPOINT_INTERVAL == 0)
{
auto quorum = std::make_shared<testing_quorum>(source.quorums[static_cast<uint8_t>(quorum_type::checkpointing)]);
result.checkpointing = quorum;
}
result.checkpointing = std::make_shared<quorum>(source.quorums[static_cast<uint8_t>(quorum_type::checkpointing)]);
return result;
}

View File

@ -324,8 +324,8 @@ namespace service_nodes
/// the actual internal quorum used is for `height - REORG_SAFETY_BUFFER_BLOCKS_POST_HF12`, i.e.
/// do no subtract off the buffer in advance)
/// return: nullptr if the quorum is not cached in memory (pruned from memory).
std::shared_ptr<const testing_quorum> get_testing_quorum(quorum_type type, uint64_t height, bool include_old = false, std::vector<std::shared_ptr<const testing_quorum>> *alt_states = nullptr) const;
bool get_quorum_pubkey(quorum_type type, quorum_group group, uint64_t height, size_t quorum_index, crypto::public_key &key) const;
std::shared_ptr<const quorum> get_quorum(quorum_type type, uint64_t height, bool include_old = false, std::vector<std::shared_ptr<const quorum>> *alt_states = nullptr) const;
bool get_quorum_pubkey(quorum_type type, quorum_group group, uint64_t height, size_t quorum_index, crypto::public_key &key) const;
std::vector<service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const;
const std::vector<key_image_blacklist_entry> &get_blacklisted_key_images() const { return m_state.key_image_blacklist; }
@ -363,7 +363,7 @@ namespace service_nodes
{
uint8_t version;
uint64_t height;
testing_quorum quorums[static_cast<uint8_t>(quorum_type::count)];
quorum quorums[static_cast<uint8_t>(quorum_type::count)];
BEGIN_SERIALIZE()
FIELD(version)

View File

@ -253,15 +253,14 @@ namespace service_nodes
// don't vote for the first 2 hours so this is purely cosmetic
if (obligations_height_hf_version >= cryptonote::network_version_12_checkpointing)
{
service_nodes::quorum_type checkpoint_type = quorum_type::checkpointing;
std::shared_ptr<const testing_quorum> quorum = m_core.get_testing_quorum(checkpoint_type, m_obligations_height);
auto quorum = m_core.get_quorum(quorum_type::checkpointing, m_obligations_height);
std::vector<cryptonote::block> blocks;
if (quorum && m_core.get_blocks(m_obligations_height, 1, blocks))
{
cryptonote::block const &block = blocks[0];
if (start_time < static_cast<ptrdiff_t>(block.timestamp)) // NOTE: If we started up before receiving the block, we likely have the voting information, if not we probably don't.
{
uint64_t quorum_height = offset_testing_quorum_height(checkpoint_type, m_obligations_height);
uint64_t quorum_height = offset_testing_quorum_height(quorum_type::checkpointing, m_obligations_height);
for (size_t index_in_quorum = 0; index_in_quorum < quorum->validators.size(); index_in_quorum++)
{
crypto::public_key const &key = quorum->validators[index_in_quorum];
@ -282,7 +281,7 @@ namespace service_nodes
if (!my_keys)
continue;
std::shared_ptr<const testing_quorum> quorum = m_core.get_testing_quorum(quorum_type::obligations, m_obligations_height);
auto quorum = m_core.get_quorum(quorum_type::obligations, m_obligations_height);
if (!quorum)
{
// TODO(loki): Fatal error
@ -437,7 +436,7 @@ namespace service_nodes
if (m_last_checkpointed_height < REORG_SAFETY_BUFFER_BLOCKS)
continue;
const std::shared_ptr<const testing_quorum> quorum = m_core.get_testing_quorum(quorum_type::checkpointing, m_last_checkpointed_height);
auto quorum = m_core.get_quorum(quorum_type::checkpointing, m_last_checkpointed_height);
if (!quorum)
{
// TODO(loki): Fatal error
@ -479,7 +478,7 @@ namespace service_nodes
if (!verify_vote_age(vote, m_core.get_current_blockchain_height(), vvc))
return false;
std::shared_ptr<const testing_quorum> quorum = m_core.get_testing_quorum(vote.type, vote.block_height);
std::shared_ptr<const quorum> quorum = m_core.get_quorum(vote.type, vote.block_height);
if (!quorum)
{
vvc.m_invalid_block_height = true;

View File

@ -46,10 +46,10 @@ namespace service_nodes
{
struct service_node_info;
struct testing_quorum
struct quorum
{
std::vector<crypto::public_key> validators; // Array of public keys identifying service nodes which are being tested for the queried height.
std::vector<crypto::public_key> workers; // Array of public keys identifying service nodes which are responsible for voting on the queried height.
std::vector<crypto::public_key> validators; // Array of public keys identifying service nodes who validate and sign.
std::vector<crypto::public_key> workers; // Array of public keys of tested service nodes (if applicable).
BEGIN_SERIALIZE()
FIELD(validators)
@ -59,12 +59,12 @@ namespace service_nodes
struct quorum_manager
{
std::shared_ptr<const testing_quorum> obligations;
// TODO(doyle): Validators aren't used, but I kept this as a testing_quorum
std::shared_ptr<const quorum> obligations;
// TODO(doyle): Workers aren't used, but I kept this as a quorum
// to avoid drastic changes for now to a lot of the service node API
std::shared_ptr<const testing_quorum> checkpointing;
std::shared_ptr<const quorum> checkpointing;
std::shared_ptr<const testing_quorum> get(quorum_type type) const
std::shared_ptr<const quorum> get(quorum_type type) const
{
if (type == quorum_type::obligations) return obligations;
else if (type == quorum_type::checkpointing) return checkpointing;

View File

@ -59,7 +59,7 @@ namespace service_nodes {
#endif
static_assert(STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE <= STATE_CHANGE_QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
static_assert(CHECKPOINT_MIN_VOTES <= CHECKPOINT_QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
static_assert(CHECKPOINT_MIN_VOTES <= CHECKPOINT_QUORUM_SIZE, "The number of votes required to add a checkpoint can't exceed the actual quorum size, otherwise we never add checkpoints.");
// NOTE: We can reorg up to last 2 checkpoints + the number of extra blocks before the next checkpoint is set
constexpr uint64_t REORG_SAFETY_BUFFER_BLOCKS_POST_HF12 = (CHECKPOINT_INTERVAL * CHECKPOINT_NUM_CHECKPOINTS_FOR_CHAIN_FINALITY) + (CHECKPOINT_INTERVAL - 1);

View File

@ -103,7 +103,7 @@ namespace service_nodes
return result;
}
static bool bounds_check_worker_index(service_nodes::testing_quorum const &quorum, uint32_t worker_index, cryptonote::vote_verification_context *vvc)
static bool bounds_check_worker_index(service_nodes::quorum const &quorum, uint32_t worker_index, cryptonote::vote_verification_context *vvc)
{
if (worker_index >= quorum.workers.size())
{
@ -114,7 +114,7 @@ namespace service_nodes
return true;
}
static bool bounds_check_validator_index(service_nodes::testing_quorum const &quorum, uint32_t validator_index, cryptonote::vote_verification_context *vvc)
static bool bounds_check_validator_index(service_nodes::quorum const &quorum, uint32_t validator_index, cryptonote::vote_verification_context *vvc)
{
if (validator_index >= quorum.validators.size())
{
@ -133,7 +133,7 @@ namespace service_nodes
bool verify_tx_state_change(const cryptonote::tx_extra_service_node_state_change &state_change,
uint64_t latest_height,
cryptonote::tx_verification_context &tvc,
const service_nodes::testing_quorum &quorum,
const service_nodes::quorum &quorum,
const uint8_t hf_version)
{
auto &vvc = tvc.m_vote_ctx;
@ -232,7 +232,7 @@ namespace service_nodes
return true;
}
bool verify_checkpoint(uint8_t hf_version, cryptonote::checkpoint_t const &checkpoint, service_nodes::testing_quorum const &quorum)
bool verify_checkpoint(uint8_t hf_version, cryptonote::checkpoint_t const &checkpoint, service_nodes::quorum const &quorum)
{
if (checkpoint.type == cryptonote::checkpoint_type::service_node)
{
@ -359,7 +359,7 @@ namespace service_nodes
return result;
}
bool verify_vote_signature(uint8_t hf_version, const quorum_vote_t &vote, cryptonote::vote_verification_context &vvc, const service_nodes::testing_quorum &quorum)
bool verify_vote_signature(uint8_t hf_version, const quorum_vote_t &vote, cryptonote::vote_verification_context &vvc, const service_nodes::quorum &quorum)
{
bool result = true;
if (vote.type >= quorum_type::count)

View File

@ -52,7 +52,7 @@ namespace cryptonote
namespace service_nodes
{
struct testing_quorum;
struct quorum;
struct checkpoint_vote { crypto::hash block_hash; };
struct state_change_vote { uint16_t worker_index; new_state state; };
@ -116,10 +116,10 @@ namespace service_nodes
quorum_vote_t make_checkpointing_vote(uint8_t hf_version, crypto::hash const &block_hash, uint64_t block_height, uint16_t index_in_quorum, const service_node_keys &keys);
cryptonote::checkpoint_t make_empty_service_node_checkpoint(crypto::hash const &block_hash, uint64_t height);
bool verify_checkpoint (uint8_t hf_version, cryptonote::checkpoint_t const &checkpoint, service_nodes::testing_quorum const &quorum);
bool verify_tx_state_change (const cryptonote::tx_extra_service_node_state_change& state_change, uint64_t latest_height, cryptonote::tx_verification_context& vvc, const service_nodes::testing_quorum &quorum, uint8_t hf_version);
bool verify_checkpoint (uint8_t hf_version, cryptonote::checkpoint_t const &checkpoint, service_nodes::quorum const &quorum);
bool verify_tx_state_change (const cryptonote::tx_extra_service_node_state_change& state_change, uint64_t latest_height, cryptonote::tx_verification_context& vvc, const service_nodes::quorum &quorum, uint8_t hf_version);
bool verify_vote_age (const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc);
bool verify_vote_signature (uint8_t hf_version, const quorum_vote_t& vote, cryptonote::vote_verification_context &vvc, const service_nodes::testing_quorum &quorum);
bool verify_vote_signature (uint8_t hf_version, const quorum_vote_t& vote, cryptonote::vote_verification_context &vvc, const service_nodes::quorum &quorum);
crypto::signature make_signature_from_vote (quorum_vote_t const &vote, const service_node_keys &keys);
crypto::signature make_signature_from_tx_state_change(cryptonote::tx_extra_service_node_state_change const &state_change, const service_node_keys &keys);

View File

@ -0,0 +1,53 @@
#include "tx_blink.h"
#include "common/util.h"
#include <algorithm>
namespace cryptonote {
static void check_args(blink_tx::quorum q, unsigned position, const char *func_name) {
if (q >= blink_tx::quorum::_count)
throw std::domain_error("Invalid sub-quorum value passed to " + std::string(func_name));
if (position >= BLINK_QUORUM_SIZE)
throw std::domain_error("Invalid voter position passed to " + std::string(func_name));
}
crypto::public_key get_sn_pubkey(blink_tx::quorum q, unsigned position) {
check_args(q, position, __func__);
// TODO
return crypto::null_pkey;
};
crypto::hash blink_tx::hash() const {
auto buf = tools::memcpy_le(height_, tx_.hash);
crypto::hash hash;
crypto::cn_fast_hash(buf.data(), buf.size(), hash);
return hash;
}
bool blink_tx::add_signature(quorum q, unsigned position, const crypto::signature &sig) {
check_args(q, position, __func__);
auto &sig_slot = signatures_[static_cast<uint8_t>(q)][position];
if (sig_slot && sig_slot == sig)
return false;
if (!crypto::check_signature(hash(), get_sn_pubkey(q, position), sig))
throw signature_verification_error("Given blink quorum signature verification failed!");
sig_slot = sig;
}
bool blink_tx::has_signature(quorum q, unsigned position) {
check_args(q, position, __func__);
return signatures_[static_cast<uint8_t>(q)][position];
}
bool blink_tx::valid() const {
// Signatures are verified when added, so here we can just test that they are non-null
return std::all_of(signatures_.begin(), signatures_.end(), [](const auto &sigs) {
return BLOCK_QUORUM_VOTES_REQUIRED <= std::count_if(sigs.begin(), sigs.end(),
[](const auto &s) -> bool { return s; });
});
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) 2019, The Loki Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "../cryptonote_basic/cryptonote_basic.h"
#include <iostream>
namespace cryptonote {
constexpr unsigned int BLINK_QUORUM_SIZE = 10;
constexpr unsigned int BLOCK_QUORUM_VOTES_REQUIRED = 7;
class blink_tx {
public:
enum class quorum : uint8_t { base, future, _count };
class signature_verification_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
/**
* Construct a new blink_tx wrapper given the tx and a blink authorization height.
*/
blink_tx(transaction tx, uint64_t height)
: tx_{std::move(tx)}, height_{height} {}
/**
* Adds a signature for the given quorum and position. Returns true if the signature was accepted
* (i.e. is valid, and existing signature is empty), false if the signature was already present,
* and throws a `blink_tx::signature_verification_error` if the signature fails validation.
*/
bool add_signature(quorum q, unsigned int position, const crypto::signature &sig);
/**
* Remove the signature at the given quorum and position by setting it to null. Returns true if
* removed, false if it was already null.
*/
bool clear_signature(quorum q, unsigned int position);
/**
* Returns true if there is a verified signature at the given quorum and position.
*/
bool has_signature(quorum q, unsigned int position);
/**
* Returns true if this blink tx is valid for inclusion in the blockchain, that is, has the
* required number of valid signatures in each quorum.
*/
bool valid() const;
/// Returns a reference to the transaction.
const transaction &tx() const { return tx_; }
/// Returns the blink authorization height of this blink tx
uint64_t height() const { return height_; }
/// Returns the hashed signing value for this blink TX (a fast hash of the height + tx hash)
crypto::hash hash() const;
private:
transaction tx_;
uint64_t height_;
std::array<std::array<crypto::signature, BLINK_QUORUM_SIZE>, static_cast<uint8_t>(quorum::_count)> signatures_;
};
}

View File

@ -183,7 +183,7 @@ static quorum_vote_t deserialize_vote(const bt_dict &d) {
return vote;
}
static const std::vector<crypto::public_key> &quorum_voter_list(quorum_type t, const service_nodes::testing_quorum &q) {
static const std::vector<crypto::public_key> &quorum_voter_list(quorum_type t, const service_nodes::quorum &q) {
return t == quorum_type::checkpointing ? q.workers : q.validators;
}
@ -203,7 +203,7 @@ static void relay_votes(void *obj, const std::vector<service_nodes::quorum_vote_
std::vector<std::tuple<int, const service_nodes::quorum_vote_t *, const std::vector<crypto::public_key> *>> valid_votes;
valid_votes.reserve(votes.size());
for (auto &v : votes) {
auto quorum = snw.sn_list.get_testing_quorum(v.type, v.block_height);
auto quorum = snw.sn_list.get_quorum(v.type, v.block_height);
if (!quorum) {
MWARNING("Unable to relay vote: no testing quorum vote for type " << v.type << " @ height " << v.block_height);

View File

@ -2585,7 +2585,7 @@ namespace cryptonote
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 (std::shared_ptr<const service_nodes::testing_quorum> quorum = m_core.get_testing_quorum(type, height, true /*include_old*/))
if (std::shared_ptr<const service_nodes::quorum> quorum = m_core.get_quorum(type, height, true /*include_old*/))
{
COMMAND_RPC_GET_QUORUM_STATE::quorum_for_height entry = {};
entry.height = height;