dncurrency/nano/node/scheduler/optimistic.cpp
Piotr Wójcik ab093d58d6
Rework collect_container_info (..) functions (#4736)
* Move container info classes to separate file

* Introduce better `container_info` class

* Rename legacy to `container_info_entry`

* Conversion

* Test

* Fixes
2024-10-03 15:36:34 +02:00

200 lines
5.2 KiB
C++

#include <nano/lib/blocks.hpp>
#include <nano/lib/stats.hpp>
#include <nano/lib/tomlconfig.hpp>
#include <nano/node/active_elections.hpp>
#include <nano/node/election_behavior.hpp>
#include <nano/node/node.hpp>
#include <nano/node/scheduler/optimistic.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/secure/ledger_set_any.hpp>
#include <nano/secure/ledger_set_confirmed.hpp>
nano::scheduler::optimistic::optimistic (optimistic_config const & config_a, nano::node & node_a, nano::ledger & ledger_a, nano::active_elections & active_a, nano::network_constants const & network_constants_a, nano::stats & stats_a) :
config{ config_a },
node{ node_a },
ledger{ ledger_a },
active{ active_a },
network_constants{ network_constants_a },
stats{ stats_a }
{
}
nano::scheduler::optimistic::~optimistic ()
{
// Thread must be stopped before destruction
debug_assert (!thread.joinable ());
}
void nano::scheduler::optimistic::start ()
{
debug_assert (!thread.joinable ());
if (!config.enabled)
{
return;
}
thread = std::thread{ [this] () {
nano::thread_role::set (nano::thread_role::name::scheduler_optimistic);
run ();
} };
}
void nano::scheduler::optimistic::stop ()
{
{
nano::lock_guard<nano::mutex> guard{ mutex };
stopped = true;
}
notify ();
nano::join_or_pass (thread);
}
void nano::scheduler::optimistic::notify ()
{
condition.notify_all ();
}
bool nano::scheduler::optimistic::activate_predicate (const nano::account_info & account_info, const nano::confirmation_height_info & conf_info) const
{
// Chain with a big enough gap between account frontier and confirmation frontier
if (account_info.block_count - conf_info.height > config.gap_threshold)
{
return true;
}
// Account with nothing confirmed yet
if (conf_info.height == 0)
{
return true;
}
return false;
}
bool nano::scheduler::optimistic::activate (const nano::account & account, const nano::account_info & account_info, const nano::confirmation_height_info & conf_info)
{
if (!config.enabled)
{
return false;
}
debug_assert (account_info.block_count >= conf_info.height);
if (activate_predicate (account_info, conf_info))
{
{
nano::lock_guard<nano::mutex> lock{ mutex };
// Prevent duplicate candidate accounts
if (candidates.get<tag_account> ().contains (account))
{
return false; // Not activated
}
// Limit candidates container size
if (candidates.size () >= config.max_size)
{
return false; // Not activated
}
stats.inc (nano::stat::type::optimistic_scheduler, nano::stat::detail::activated);
candidates.push_back ({ account, nano::clock::now () });
}
return true; // Activated
}
return false; // Not activated
}
bool nano::scheduler::optimistic::predicate () const
{
debug_assert (!mutex.try_lock ());
if (active.vacancy (nano::election_behavior::optimistic) <= 0)
{
return false;
}
if (candidates.empty ())
{
return false;
}
auto candidate = candidates.front ();
bool result = nano::elapsed (candidate.timestamp, network_constants.optimistic_activation_delay);
return result;
}
void nano::scheduler::optimistic::run ()
{
nano::unique_lock<nano::mutex> lock{ mutex };
while (!stopped)
{
stats.inc (nano::stat::type::optimistic_scheduler, nano::stat::detail::loop);
if (predicate ())
{
auto transaction = ledger.tx_begin_read ();
while (predicate ())
{
debug_assert (!candidates.empty ());
auto candidate = candidates.front ();
candidates.pop_front ();
lock.unlock ();
run_one (transaction, candidate);
lock.lock ();
}
}
condition.wait_for (lock, network_constants.optimistic_activation_delay / 2, [this] () {
return stopped || predicate ();
});
}
}
void nano::scheduler::optimistic::run_one (secure::transaction const & transaction, entry const & candidate)
{
auto block = ledger.any.block_get (transaction, ledger.any.account_head (transaction, candidate.account));
if (block)
{
// Ensure block is not already confirmed
if (!node.block_confirmed_or_being_confirmed (transaction, block->hash ()))
{
// Try to insert it into AEC
// We check for AEC vacancy inside our predicate
auto result = node.active.insert (block, nano::election_behavior::optimistic);
stats.inc (nano::stat::type::optimistic_scheduler, result.inserted ? nano::stat::detail::insert : nano::stat::detail::insert_failed);
}
}
}
nano::container_info nano::scheduler::optimistic::container_info () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
nano::container_info info;
info.put ("candidates", candidates);
return info;
}
/*
* optimistic_scheduler_config
*/
nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig & toml)
{
toml.get ("enable", enabled);
toml.get ("gap_threshold", gap_threshold);
toml.get ("max_size", max_size);
return toml.get_error ();
}
nano::error nano::scheduler::optimistic_config::serialize (nano::tomlconfig & toml) const
{
toml.put ("enable", enabled, "Enable or disable optimistic elections\ntype:bool");
toml.put ("gap_threshold", gap_threshold, "Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation\ntype:uint64");
toml.put ("max_size", max_size, "Maximum number of candidates stored in memory\ntype:uint64");
return toml.get_error ();
}