Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IF: Missing vote logging and Prometheus logging of votes #63

Merged
merged 9 commits into from
Apr 24, 2024
34 changes: 32 additions & 2 deletions libraries/chain/block.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <eosio/chain/block.hpp>

namespace eosio { namespace chain {
namespace eosio::chain {
void additional_block_signatures_extension::reflector_init() {
static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types,
"additional_block_signatures_extension expects FC to support reflector_init" );
Expand Down Expand Up @@ -63,7 +63,37 @@ namespace eosio { namespace chain {
}

return results;
}

std::optional<block_extension> signed_block::extract_extension(uint16_t extension_id)const {
using decompose_t = block_extension_types::decompose_t;

for( size_t i = 0; i < block_extensions.size(); ++i ) {
const auto& e = block_extensions[i];
auto id = e.first;

if (id != extension_id)
continue;

std::optional<block_extension> ext;
ext.emplace();

auto match = decompose_t::extract<block_extension>( id, e.second, *ext );
EOS_ASSERT( match, invalid_block_extension,
"Block extension with id type ${id} is not supported",
("id", id)
);

return ext;
}

return {};
}

bool signed_block::contains_extension(uint16_t extension_id)const {
return std::any_of(block_extensions.cbegin(), block_extensions.cend(), [&](const auto& p) {
return p.first == extension_id;
});
}

} } /// namespace eosio::chain
} /// namespace eosio::chain
8 changes: 7 additions & 1 deletion libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4052,7 +4052,7 @@ struct controller_impl {
if( switch_fork ) {
auto head_fork_comp_str = apply<std::string>(chain_head, [](auto& head) -> std::string { return log_fork_comparison(*head); });
ilog("switching forks from ${chid} (block number ${chn}) ${c} to ${nhid} (block number ${nhn}) ${n}",
("chid", chain_head.id())("chn}", chain_head.block_num())("nhid", new_head->id())("nhn", new_head->block_num())
("chid", chain_head.id())("chn", chain_head.block_num())("nhid", new_head->id())("nhn", new_head->block_num())
("c", head_fork_comp_str)("n", log_fork_comparison(*new_head)));

// not possible to log transaction specific info when switching forks
Expand Down Expand Up @@ -5311,6 +5311,12 @@ const producer_authority_schedule* controller::next_producers()const {
return my->pending->next_producers();
}

finalizer_policy_ptr controller::head_active_finalizer_policy()const {
return apply_s<finalizer_policy_ptr>(my->chain_head, [](const auto& head) {
return head->active_finalizer_policy;
});
}

bool controller::light_validation_allowed() const {
return my->light_validation_allowed();
}
Expand Down
6 changes: 6 additions & 0 deletions libraries/chain/include/eosio/chain/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ namespace eosio { namespace chain {
extensions_type block_extensions;

flat_multimap<uint16_t, block_extension> validate_and_extract_extensions()const;
std::optional<block_extension> extract_extension(uint16_t extension_id)const;
template<typename Ext> Ext extract_extension()const {
assert(contains_extension(Ext::extension_id()));
return std::get<Ext>(*extract_extension(Ext::extension_id()));
}
bool contains_extension(uint16_t extension_id)const;
};
using signed_block_ptr = std::shared_ptr<signed_block>;

Expand Down
3 changes: 3 additions & 0 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ namespace eosio::chain {
// post-instant-finality this always returns nullptr
const producer_authority_schedule* pending_producers_legacy()const;

// returns nullptr pre-savanna
finalizer_policy_ptr head_active_finalizer_policy()const;

void set_if_irreversible_block_id(const block_id_type& id);
uint32_t if_irreversible_block_num() const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ class producer_plugin : public appbase::plugin<producer_plugin> {

static void set_test_mode(bool m) { test_mode_ = m; }

struct vote_block_metrics {
uint32_t block_num = 0;
std::vector<std::string> strong_votes;
std::vector<std::string> weak_votes;
std::vector<std::string> no_votes;
};

struct speculative_block_metrics {
account_name block_producer{};
uint32_t block_num = 0;
Expand Down Expand Up @@ -188,6 +195,7 @@ class producer_plugin : public appbase::plugin<producer_plugin> {
void register_update_produced_block_metrics(std::function<void(produced_block_metrics)>&&);
void register_update_speculative_block_metrics(std::function<void(speculative_block_metrics)>&&);
void register_update_incoming_block_metrics(std::function<void(incoming_block_metrics)>&&);
void register_update_vote_block_metrics(std::function<void(vote_block_metrics&&)>&&);

inline static bool test_mode_{false}; // to be moved into appbase (application_base)

Expand Down
92 changes: 89 additions & 3 deletions plugins/producer_plugin/producer_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
using signature_provider_type = signature_provider_plugin::signature_provider_type;
std::map<chain::public_key_type, signature_provider_type> _signature_providers;
chain::bls_pub_priv_key_map_t _finalizer_keys; // public, private
std::set<bls_public_key> _finalizers;
std::set<chain::account_name> _producers;
boost::asio::deadline_timer _timer;
block_timing_util::producer_watermarks _producer_watermarks;
Expand Down Expand Up @@ -550,6 +551,7 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
std::function<void(producer_plugin::produced_block_metrics)> _update_produced_block_metrics;
std::function<void(producer_plugin::speculative_block_metrics)> _update_speculative_block_metrics;
std::function<void(producer_plugin::incoming_block_metrics)> _update_incoming_block_metrics;
std::function<void(producer_plugin::vote_block_metrics&&)> _update_vote_block_metrics;

// ro for read-only
struct ro_trx_t {
Expand Down Expand Up @@ -626,14 +628,93 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
((_produce_block_cpu_effort.count() / 1000) * config::producer_repetitions) );
}

void on_block(const signed_block_ptr& block) {
void log_missing_votes(const signed_block_ptr& block, const block_id_type& id,
const finalizer_policy_ptr& active_finalizer_policy,
const valid_quorum_certificate& qc) {
if (vote_logger.is_enabled(fc::log_level::info)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of those two nested ifs, you can return is vote_logger is not enabled or minutes not passed, to reduce two nesting levels. But it is just a style. No need to change.

if (fc::time_point::now() - block->timestamp < fc::minutes(5) || (block->block_num() % 1000 == 0)) {
std::vector<std::string> not_voted;

auto check_weak = [](const auto& weak_votes, size_t i) {
return weak_votes && (*weak_votes)[i];
};

if (qc._strong_votes) {
const auto& votes = *qc._strong_votes;
auto& finalizers = active_finalizer_policy->finalizers;
assert(votes.size() == finalizers.size());
for (size_t i = 0; i < votes.size(); ++i) {
if (!votes[i] && !check_weak(qc._weak_votes, i)) {
not_voted.push_back(finalizers[i].description);
if (_finalizers.contains(finalizers[i].public_key)) {
fc_wlog(vote_logger, "Block ${n}:${id} has no votes from our finalizer: ${v}",
("n", block->block_num())("id", id.str().substr(8,16))("v", finalizers[i].description));
}
}
}
}
if (!not_voted.empty()) {
fc_ilog(vote_logger, "Block ${n}:${id} has no votes for: ${v}",
("n", block->block_num())("id", id.str().substr(8,16))("v", not_voted));
}
}
}
}

void update_vote_block_metrics(block_num_type block_num,
const finalizer_policy_ptr& active_finalizer_policy,
const valid_quorum_certificate& qc) {
if (_update_vote_block_metrics) {
producer_plugin::vote_block_metrics m;
m.block_num = block_num;
auto add_votes = [&](const auto& votes, std::vector<string>& desc, std::set<size_t>& not_voted) {
assert(votes.size() == active_finalizer_policy->finalizers.size());
for (size_t i = 0; i < votes.size(); ++i) {
if (votes[i]) {
desc.push_back(active_finalizer_policy->finalizers[i].description);
} else {
not_voted.insert(i);
}
}
};
std::set<size_t> not_voted;
if (qc._strong_votes) {
add_votes(*qc._strong_votes, m.strong_votes, not_voted);
}
if (qc._weak_votes) {
std::set<size_t> not_voted_weak;
add_votes(*qc._weak_votes, m.weak_votes, not_voted_weak);
std::set<size_t> no_votes;
std::ranges::set_intersection(not_voted, not_voted_weak, std::inserter(no_votes, no_votes.end()));
not_voted.swap(no_votes);
}
if (!not_voted.empty()) {
for (auto i : not_voted) {
m.no_votes.push_back(active_finalizer_policy->finalizers.at(i).description);
}
}
_update_vote_block_metrics(std::move(m));
}
}

void on_block(const signed_block_ptr& block, const block_id_type& id) {
auto& chain = chain_plug->chain();
auto before = _unapplied_transactions.size();
_unapplied_transactions.clear_applied(block);
chain.get_mutable_subjective_billing().on_block(_log, block, fc::time_point::now());
if (before > 0) {
fc_dlog(_log, "Removed applied transactions before: ${before}, after: ${after}", ("before", before)("after", _unapplied_transactions.size()));
}
if (vote_logger.is_enabled(fc::log_level::info) || _update_vote_block_metrics) {
if (block->contains_extension(quorum_certificate_extension::extension_id())) {
if (const auto& active_finalizers = chain.head_active_finalizer_policy()) {
const auto& qc_ext = block->extract_extension<quorum_certificate_extension>();
const auto& qc = qc_ext.qc.qc;
log_missing_votes(block, id, active_finalizers, qc);
update_vote_block_metrics(block->block_num(), active_finalizers, qc);
}
}
}
}

void on_block_header(const signed_block_ptr& block) {
Expand Down Expand Up @@ -1128,6 +1209,7 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia
if (bls) {
const auto& [pubkey, privkey] = *bls;
_finalizer_keys[pubkey.to_string()] = privkey.to_string();
_finalizers.insert(pubkey);
}
} catch(secure_enclave_exception& e) {
elog("Error with Secure Enclave signature provider: ${e}; ignoring ${val}", ("e", e.top_message())("val", key_spec_pair));
Expand Down Expand Up @@ -1340,8 +1422,8 @@ void producer_plugin_impl::plugin_startup() {
chain.set_node_finalizer_keys(_finalizer_keys);

_accepted_block_connection.emplace(chain.accepted_block().connect([this](const block_signal_params& t) {
const auto& [ block, _ ] = t;
on_block(block);
const auto& [ block, id ] = t;
on_block(block, id);
}));
_accepted_block_header_connection.emplace(chain.accepted_block_header().connect([this](const block_signal_params& t) {
const auto& [ block, _ ] = t;
Expand Down Expand Up @@ -2906,4 +2988,8 @@ void producer_plugin::register_update_incoming_block_metrics(std::function<void(
my->_update_incoming_block_metrics = std::move(fun);
}

void producer_plugin::register_update_vote_block_metrics(std::function<void(producer_plugin::vote_block_metrics&&)>&& fun) {
my->_update_vote_block_metrics = std::move(fun);
}

} // namespace eosio
33 changes: 33 additions & 0 deletions plugins/prometheus_plugin/metrics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ struct catalog_type {
p2p_connection_metrics p2p_metrics;

// producer plugin
struct vote_metrics {
Gauge& block_num;
prometheus::Family<Gauge>& voted;
};
vote_metrics block_votes;

prometheus::Family<Counter>& cpu_usage_us;
prometheus::Family<Counter>& net_usage_us;

Expand Down Expand Up @@ -140,6 +146,10 @@ struct catalog_type {
, .connection_start_time{family<Gauge>("nodeos_p2p_connection_start_time", "time of last connection to peer")}
, .peer_addr{family<Gauge>("nodeos_p2p_peer_addr", "peer address")}
}
, block_votes{
.block_num{build<Gauge>("nodeos_block_num", "current block number")}
, .voted{family<Gauge>("nodeos_block_votes", "votes incorporated into a block, -1 weak, 1 strong, 0 no vote")}
}
, cpu_usage_us(family<Counter>("nodeos_cpu_usage_us_total", "total cpu usage in microseconds for blocks"))
, net_usage_us(family<Counter>("nodeos_net_usage_us_total", "total net usage in microseconds for blocks"))
, last_irreversible(build<Gauge>("nodeos_last_irreversible", "last irreversible block number"))
Expand Down Expand Up @@ -234,6 +244,25 @@ struct catalog_type {
}
}

void update(const producer_plugin::vote_block_metrics&& metrics) {
block_votes.block_num.Set(metrics.block_num);

auto add_and_set_gauge = [&](auto& fam, const auto& prod, const auto& value) {
auto& gauge = fam.Add({{"producer", prod}});
gauge.Set(value);
};

for (const auto& v : metrics.strong_votes) {
add_and_set_gauge(block_votes.voted, v, 1);
}
for (const auto& v : metrics.weak_votes) {
add_and_set_gauge(block_votes.voted, v, -1);
}
for (const auto& v : metrics.no_votes) {
add_and_set_gauge(block_votes.voted, v, 0);
}
}

void update(block_metrics& blk_metrics, const producer_plugin::speculative_block_metrics& metrics) {
blk_metrics.num_blocks_created.Increment(1);
blk_metrics.current_block_num.Set(metrics.block_num);
Expand Down Expand Up @@ -323,6 +352,10 @@ struct catalog_type {
[&strand, this](const producer_plugin::incoming_block_metrics& metrics) {
strand.post([metrics, this]() { update(metrics); });
});
producer.register_update_vote_block_metrics(
[&strand, this](const producer_plugin::vote_block_metrics&& metrics) {
strand.post([metrics{std::move(metrics)}, this]() mutable { update(std::move(metrics)); });
});
}
};

Expand Down
Loading