dncurrency/nano/node/vote_processor.cpp
Piotr Wójcik 6281105438
Use 'magic_enum' library for static reflection for enums (#4065)
This PR introduces magic_enum (https://github.com/Neargye/magic_enum) library as a submodule, which ergonomically handles enum to string conversions without requiring any adaptations to existing enum definitions.
2023-01-26 14:18:25 +00:00

300 lines
9.4 KiB
C++

#include <nano/lib/logger_mt.hpp>
#include <nano/lib/stats.hpp>
#include <nano/lib/threading.hpp>
#include <nano/lib/timer.hpp>
#include <nano/node/active_transactions.hpp>
#include <nano/node/node_observers.hpp>
#include <nano/node/nodeconfig.hpp>
#include <nano/node/online_reps.hpp>
#include <nano/node/repcrawler.hpp>
#include <nano/node/signatures.hpp>
#include <nano/node/vote_processor.hpp>
#include <nano/secure/common.hpp>
#include <nano/secure/ledger.hpp>
#include <boost/format.hpp>
#include <chrono>
using namespace std::chrono_literals;
nano::vote_processor::vote_processor (nano::signature_checker & checker_a, nano::active_transactions & active_a, nano::node_observers & observers_a, nano::stats & stats_a, nano::node_config & config_a, nano::node_flags & flags_a, nano::logger_mt & logger_a, nano::online_reps & online_reps_a, nano::rep_crawler & rep_crawler_a, nano::ledger & ledger_a, nano::network_params & network_params_a) :
checker (checker_a),
active (active_a),
observers (observers_a),
stats (stats_a),
config (config_a),
logger (logger_a),
online_reps (online_reps_a),
rep_crawler (rep_crawler_a),
ledger (ledger_a),
network_params (network_params_a),
max_votes (flags_a.vote_processor_capacity),
started (false),
stopped (false),
thread ([this] () {
nano::thread_role::set (nano::thread_role::name::vote_processing);
process_loop ();
nano::unique_lock<nano::mutex> lock{ mutex };
votes.clear ();
condition.notify_all ();
})
{
nano::unique_lock<nano::mutex> lock{ mutex };
condition.wait (lock, [&started = started] { return started; });
}
void nano::vote_processor::process_loop ()
{
nano::timer<std::chrono::milliseconds> elapsed;
bool log_this_iteration;
nano::unique_lock<nano::mutex> lock{ mutex };
started = true;
lock.unlock ();
condition.notify_all ();
lock.lock ();
while (!stopped)
{
if (!votes.empty ())
{
decltype (votes) votes_l;
votes_l.swap (votes);
lock.unlock ();
condition.notify_all ();
log_this_iteration = false;
if (config.logging.network_logging () && votes_l.size () > 50)
{
/*
* Only log the timing information for this iteration if
* there are a sufficient number of items for it to be relevant
*/
log_this_iteration = true;
elapsed.restart ();
}
verify_votes (votes_l);
total_processed += votes_l.size ();
if (log_this_iteration && elapsed.stop () > std::chrono::milliseconds (100))
{
logger.try_log (boost::str (boost::format ("Processed %1% votes in %2% milliseconds (rate of %3% votes per second)") % votes_l.size () % elapsed.value ().count () % ((votes_l.size () * 1000ULL) / elapsed.value ().count ())));
}
lock.lock ();
}
else
{
condition.wait (lock);
}
}
}
bool nano::vote_processor::vote (std::shared_ptr<nano::vote> const & vote_a, std::shared_ptr<nano::transport::channel> const & channel_a)
{
debug_assert (channel_a != nullptr);
bool process (false);
nano::unique_lock<nano::mutex> lock{ mutex };
if (!stopped)
{
// Level 0 (< 0.1%)
if (votes.size () < 6.0 / 9.0 * max_votes)
{
process = true;
}
// Level 1 (0.1-1%)
else if (votes.size () < 7.0 / 9.0 * max_votes)
{
process = (representatives_1.find (vote_a->account) != representatives_1.end ());
}
// Level 2 (1-5%)
else if (votes.size () < 8.0 / 9.0 * max_votes)
{
process = (representatives_2.find (vote_a->account) != representatives_2.end ());
}
// Level 3 (> 5%)
else if (votes.size () < max_votes)
{
process = (representatives_3.find (vote_a->account) != representatives_3.end ());
}
if (process)
{
votes.emplace_back (vote_a, channel_a);
lock.unlock ();
condition.notify_all ();
// Lock no longer required
}
else
{
stats.inc (nano::stat::type::vote, nano::stat::detail::vote_overflow);
}
}
return !process;
}
void nano::vote_processor::verify_votes (decltype (votes) const & votes_a)
{
auto size (votes_a.size ());
std::vector<unsigned char const *> messages;
messages.reserve (size);
std::vector<nano::block_hash> hashes;
hashes.reserve (size);
std::vector<std::size_t> lengths (size, sizeof (nano::block_hash));
std::vector<unsigned char const *> pub_keys;
pub_keys.reserve (size);
std::vector<unsigned char const *> signatures;
signatures.reserve (size);
std::vector<int> verifications;
verifications.resize (size);
for (auto const & vote : votes_a)
{
hashes.push_back (vote.first->hash ());
messages.push_back (hashes.back ().bytes.data ());
pub_keys.push_back (vote.first->account.bytes.data ());
signatures.push_back (vote.first->signature.bytes.data ());
}
nano::signature_check_set check = { size, messages.data (), lengths.data (), pub_keys.data (), signatures.data (), verifications.data () };
checker.verify (check);
auto i (0);
for (auto const & vote : votes_a)
{
debug_assert (verifications[i] == 1 || verifications[i] == 0);
if (verifications[i] == 1)
{
vote_blocking (vote.first, vote.second, true);
}
++i;
}
}
nano::vote_code nano::vote_processor::vote_blocking (std::shared_ptr<nano::vote> const & vote_a, std::shared_ptr<nano::transport::channel> const & channel_a, bool validated)
{
auto result (nano::vote_code::invalid);
if (validated || !vote_a->validate ())
{
result = active.vote (vote_a);
observers.vote.notify (vote_a, channel_a, result);
}
std::string status;
switch (result)
{
case nano::vote_code::invalid:
status = "Invalid";
stats.inc (nano::stat::type::vote, nano::stat::detail::vote_invalid);
break;
case nano::vote_code::replay:
status = "Replay";
stats.inc (nano::stat::type::vote, nano::stat::detail::vote_replay);
break;
case nano::vote_code::vote:
status = "Vote";
stats.inc (nano::stat::type::vote, nano::stat::detail::vote_valid);
break;
case nano::vote_code::indeterminate:
status = "Indeterminate";
stats.inc (nano::stat::type::vote, nano::stat::detail::vote_indeterminate);
break;
}
if (config.logging.vote_logging ())
{
logger.try_log (boost::str (boost::format ("Vote from: %1% timestamp: %2% duration %3%ms block(s): %4% status: %5%") % vote_a->account.to_account () % std::to_string (vote_a->timestamp ()) % std::to_string (vote_a->duration ().count ()) % vote_a->hashes_string () % status));
}
return result;
}
void nano::vote_processor::stop ()
{
{
nano::lock_guard<nano::mutex> lock{ mutex };
stopped = true;
}
condition.notify_all ();
if (thread.joinable ())
{
thread.join ();
}
}
void nano::vote_processor::flush ()
{
nano::unique_lock<nano::mutex> lock{ mutex };
auto const cutoff = total_processed.load (std::memory_order_relaxed) + votes.size ();
bool success = condition.wait_for (lock, 60s, [this, &cutoff] () {
return stopped || votes.empty () || total_processed.load (std::memory_order_relaxed) >= cutoff;
});
if (!success)
{
logger.always_log ("WARNING: vote_processor::flush timeout while waiting for flush");
debug_assert (false && "vote_processor::flush timeout while waiting for flush");
}
}
std::size_t nano::vote_processor::size ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
return votes.size ();
}
bool nano::vote_processor::empty ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
return votes.empty ();
}
bool nano::vote_processor::half_full ()
{
return size () >= max_votes / 2;
}
void nano::vote_processor::calculate_weights ()
{
nano::unique_lock<nano::mutex> lock{ mutex };
if (!stopped)
{
representatives_1.clear ();
representatives_2.clear ();
representatives_3.clear ();
auto supply (online_reps.trended ());
auto rep_amounts = ledger.cache.rep_weights.get_rep_amounts ();
for (auto const & rep_amount : rep_amounts)
{
nano::account const & representative (rep_amount.first);
auto weight (ledger.weight (representative));
if (weight > supply / 1000) // 0.1% or above (level 1)
{
representatives_1.insert (representative);
if (weight > supply / 100) // 1% or above (level 2)
{
representatives_2.insert (representative);
if (weight > supply / 20) // 5% or above (level 3)
{
representatives_3.insert (representative);
}
}
}
}
}
}
std::unique_ptr<nano::container_info_component> nano::collect_container_info (vote_processor & vote_processor, std::string const & name)
{
std::size_t votes_count;
std::size_t representatives_1_count;
std::size_t representatives_2_count;
std::size_t representatives_3_count;
{
nano::lock_guard<nano::mutex> guard{ vote_processor.mutex };
votes_count = vote_processor.votes.size ();
representatives_1_count = vote_processor.representatives_1.size ();
representatives_2_count = vote_processor.representatives_2.size ();
representatives_3_count = vote_processor.representatives_3.size ();
}
auto composite = std::make_unique<container_info_composite> (name);
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "votes", votes_count, sizeof (decltype (vote_processor.votes)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "representatives_1", representatives_1_count, sizeof (decltype (vote_processor.representatives_1)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "representatives_2", representatives_2_count, sizeof (decltype (vote_processor.representatives_2)::value_type) }));
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "representatives_3", representatives_3_count, sizeof (decltype (vote_processor.representatives_3)::value_type) }));
return composite;
}