Inactive votes election hint (#2886)

* Add config field election_hint_weight_percent

* Add class inactive_cache_status to hold confirmed/bootstrap_started and new election_started(new), simplifying some code

* Start election on a threshold - missing check to not repeat

* Split out bootstrap start function

* Extra check for changes in inactive_cache_status

* FIx gap cache bootstrap start

* Inactive elections election start test with 5 representatives & genesis

* Additional check to ensure block_get is done twice at most; function doc; rename status_a to previously_a

* Formatting

* Add path to start elections from existing inactive votes on block processing

* Assert that the iterator is not invalidated, which would happen should we decide to erase from the inactive votes cache after inserting an election

* A more obvious condition

* Start impromptu elections passively

* Remove unecessary node in test (Wes review)

* Update test with checks on the assumptions

* Rename check -> trigger (Colin review)

* Revert "Split out bootstrap start function"

This reverts commit 9113784a1031f9f8f50342710402053e33ac0805 and bf6eb0b48e5e3abe9a95f48accf97d6e4132804f

* Remove election::transition_passive () calls as elections now start as passive implicitly

Co-authored-by: Sergey Kroshnin <sergiysw@gmail.com>
This commit is contained in:
Guilherme Lawless 2020-08-25 10:24:21 +01:00 committed by GitHub
commit 6783671f85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 320 additions and 68 deletions

View file

@ -646,6 +646,167 @@ TEST (active_transactions, inactive_votes_cache_multiple_votes)
ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
}
TEST (active_transactions, inactive_votes_cache_election_start)
{
nano::system system;
nano::node_config node_config (nano::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node = *system.add_node (node_config);
nano::block_hash latest (node.latest (nano::dev_genesis_key.pub));
nano::keypair key1, key2, key3, key4, key5;
nano::send_block_builder send_block_builder;
nano::state_block_builder state_block_builder;
auto send1 = send_block_builder.make_block ()
.previous (latest)
.destination (key1.pub)
.balance (nano::genesis_amount - 2000 * nano::Gxrb_ratio)
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
.work (*system.work.generate (latest))
.build_shared ();
auto send2 = send_block_builder.make_block ()
.previous (send1->hash ())
.destination (key2.pub)
.balance (nano::genesis_amount - 4000 * nano::Gxrb_ratio)
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build_shared ();
auto send3 = send_block_builder.make_block ()
.previous (send2->hash ())
.destination (key3.pub)
.balance (nano::genesis_amount - 6000 * nano::Gxrb_ratio)
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
.work (*system.work.generate (send2->hash ()))
.build_shared ();
auto send4 = send_block_builder.make_block ()
.previous (send3->hash ())
.destination (key4.pub)
.balance (nano::genesis_amount - 8000 * nano::Gxrb_ratio)
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
.work (*system.work.generate (send3->hash ()))
.build_shared ();
auto send5 = send_block_builder.make_block ()
.previous (send4->hash ())
.destination (key5.pub)
.balance (nano::genesis_amount - 10000 * nano::Gxrb_ratio)
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
.work (*system.work.generate (send4->hash ()))
.build_shared ();
auto open1 = state_block_builder.make_block ()
.account (key1.pub)
.previous (0)
.representative (key1.pub)
.balance (2000 * nano::Gxrb_ratio)
.link (send1->hash ())
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build_shared ();
auto open2 = state_block_builder.make_block ()
.account (key2.pub)
.previous (0)
.representative (key2.pub)
.balance (2000 * nano::Gxrb_ratio)
.link (send2->hash ())
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2.pub))
.build_shared ();
auto open3 = state_block_builder.make_block ()
.account (key3.pub)
.previous (0)
.representative (key3.pub)
.balance (2000 * nano::Gxrb_ratio)
.link (send3->hash ())
.sign (key3.prv, key3.pub)
.work (*system.work.generate (key3.pub))
.build_shared ();
auto open4 = state_block_builder.make_block ()
.account (key4.pub)
.previous (0)
.representative (key4.pub)
.balance (2000 * nano::Gxrb_ratio)
.link (send4->hash ())
.sign (key4.prv, key4.pub)
.work (*system.work.generate (key4.pub))
.build_shared ();
auto open5 = state_block_builder.make_block ()
.account (key5.pub)
.previous (0)
.representative (key5.pub)
.balance (2000 * nano::Gxrb_ratio)
.link (send5->hash ())
.sign (key5.prv, key5.pub)
.work (*system.work.generate (key5.pub))
.build_shared ();
node.block_processor.add (send1);
node.block_processor.add (send2);
node.block_processor.add (send3);
node.block_processor.add (send4);
node.block_processor.add (send5);
node.block_processor.add (open1);
node.block_processor.add (open2);
node.block_processor.add (open3);
node.block_processor.add (open4);
node.block_processor.add (open5);
node.block_processor.flush ();
ASSERT_TIMELY (5s, 11 == node.ledger.cache.block_count);
ASSERT_TRUE (node.active.empty ());
ASSERT_EQ (1, node.ledger.cache.cemented_count);
// These blocks will be processed later
auto send6 = send_block_builder.make_block ()
.previous (send5->hash ())
.destination (nano::keypair ().pub)
.balance (send5->balance ().number () - 1)
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
.work (*system.work.generate (send5->hash ()))
.build_shared ();
auto send7 = send_block_builder.make_block ()
.previous (send6->hash ())
.destination (nano::keypair ().pub)
.balance (send6->balance ().number () - 1)
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
.work (*system.work.generate (send6->hash ()))
.build_shared ();
// Inactive votes
std::vector<nano::block_hash> hashes{ open1->hash (), open2->hash (), open3->hash (), open4->hash (), open5->hash (), send7->hash () };
auto vote1 (std::make_shared<nano::vote> (key1.pub, key1.prv, 0, hashes));
node.vote_processor.vote (vote1, std::make_shared<nano::transport::channel_udp> (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version));
auto vote2 (std::make_shared<nano::vote> (key2.pub, key2.prv, 0, hashes));
node.vote_processor.vote (vote2, std::make_shared<nano::transport::channel_udp> (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version));
auto vote3 (std::make_shared<nano::vote> (key3.pub, key3.prv, 0, hashes));
node.vote_processor.vote (vote3, std::make_shared<nano::transport::channel_udp> (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version));
auto vote4 (std::make_shared<nano::vote> (key4.pub, key4.prv, 0, hashes));
node.vote_processor.vote (vote4, std::make_shared<nano::transport::channel_udp> (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version));
ASSERT_TIMELY (5s, node.active.inactive_votes_cache_size () == 6);
ASSERT_TRUE (node.active.empty ());
ASSERT_EQ (1, node.ledger.cache.cemented_count);
// 5 votes are required to start election
auto vote5 (std::make_shared<nano::vote> (key5.pub, key5.prv, 0, hashes));
node.vote_processor.vote (vote5, std::make_shared<nano::transport::channel_udp> (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version));
ASSERT_TIMELY (5s, 5 == node.active.size ());
// Confirm elections with weight quorum
auto vote0 (std::make_shared<nano::vote> (nano::dev_genesis_key.pub, nano::dev_genesis_key.prv, 0, hashes));
node.vote_processor.vote (vote0, std::make_shared<nano::transport::channel_udp> (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version));
ASSERT_TIMELY (5s, node.active.empty ());
ASSERT_TIMELY (5s, 11 == node.ledger.cache.cemented_count);
// A late block arrival also checks the inactive votes cache
ASSERT_TRUE (node.active.empty ());
auto send7_cache (node.active.find_inactive_votes_cache (send7->hash ()));
ASSERT_EQ (6, send7_cache.voters.size ());
ASSERT_TRUE (send7_cache.status.bootstrap_started);
ASSERT_TRUE (send7_cache.status.confirmed);
ASSERT_TRUE (send7_cache.status.election_started); // already marked even though the block does not exist
node.process_active (send6);
node.block_processor.flush ();
// An election is started for send6 but does not confirm
ASSERT_TIMELY (5s, 1 == node.active.size ());
node.vote_processor.flush ();
ASSERT_FALSE (node.block_confirmed_or_being_confirmed (node.store.tx_begin_read (), send6->hash ()));
// send7 cannot be voted on but an election should be started from inactive votes
ASSERT_FALSE (node.ledger.can_vote (node.store.tx_begin_read (), *send7));
node.process_active (send7);
node.block_processor.flush ();
ASSERT_TIMELY (5s, 13 == node.ledger.cache.cemented_count);
}
TEST (active_transactions, update_difficulty)
{
nano::system system (2);

View file

@ -169,6 +169,7 @@ TEST (toml, daemon_config_deserialize_defaults)
ASSERT_EQ (conf.node.work_watcher_period, defaults.node.work_watcher_period);
ASSERT_EQ (conf.node.online_weight_minimum, defaults.node.online_weight_minimum);
ASSERT_EQ (conf.node.online_weight_quorum, defaults.node.online_weight_quorum);
ASSERT_EQ (conf.node.election_hint_weight_percent, defaults.node.election_hint_weight_percent);
ASSERT_EQ (conf.node.password_fanout, defaults.node.password_fanout);
ASSERT_EQ (conf.node.peering_port, defaults.node.peering_port);
ASSERT_EQ (conf.node.pow_sleep_interval, defaults.node.pow_sleep_interval);
@ -404,6 +405,7 @@ TEST (toml, daemon_config_deserialize_no_defaults)
network_threads = 999
online_weight_minimum = "999"
online_weight_quorum = 99
election_hint_weight_percent = 19
password_fanout = 999
peering_port = 999
pow_sleep_interval= 999
@ -568,6 +570,7 @@ TEST (toml, daemon_config_deserialize_no_defaults)
ASSERT_NE (conf.node.work_watcher_period, defaults.node.work_watcher_period);
ASSERT_NE (conf.node.online_weight_minimum, defaults.node.online_weight_minimum);
ASSERT_NE (conf.node.online_weight_quorum, defaults.node.online_weight_quorum);
ASSERT_NE (conf.node.election_hint_weight_percent, defaults.node.election_hint_weight_percent);
ASSERT_NE (conf.node.password_fanout, defaults.node.password_fanout);
ASSERT_NE (conf.node.peering_port, defaults.node.peering_port);
ASSERT_NE (conf.node.pow_sleep_interval, defaults.node.pow_sleep_interval);
@ -757,32 +760,66 @@ TEST (toml, rpc_config_no_required)
/** Deserialize a node config with incorrect values */
TEST (toml, daemon_config_deserialize_errors)
{
std::stringstream ss_max_work_generate_multiplier;
ss_max_work_generate_multiplier << R"toml(
[node]
max_work_generate_multiplier = 0.9
)toml";
{
std::stringstream ss;
ss << R"toml(
[node]
max_work_generate_multiplier = 0.9
)toml";
nano::tomlconfig toml;
toml.read (ss_max_work_generate_multiplier);
nano::daemon_config conf;
conf.deserialize_toml (toml);
nano::tomlconfig toml;
toml.read (ss);
nano::daemon_config conf;
conf.deserialize_toml (toml);
ASSERT_EQ (toml.get_error ().get_message (), "max_work_generate_multiplier must be greater than or equal to 1");
ASSERT_EQ (toml.get_error ().get_message (), "max_work_generate_multiplier must be greater than or equal to 1");
}
std::stringstream ss_frontiers_confirmation;
ss_frontiers_confirmation << R"toml(
[node]
frontiers_confirmation = "randomstring"
)toml";
{
std::stringstream ss;
ss << R"toml(
[node]
frontiers_confirmation = "randomstring"
)toml";
nano::tomlconfig toml2;
toml2.read (ss_frontiers_confirmation);
nano::daemon_config conf2;
conf2.deserialize_toml (toml2);
nano::tomlconfig toml;
toml.read (ss);
nano::daemon_config conf;
conf.deserialize_toml (toml);
ASSERT_EQ (toml2.get_error ().get_message (), "frontiers_confirmation value is invalid (available: always, auto, disabled)");
ASSERT_EQ (conf2.node.frontiers_confirmation, nano::frontiers_confirmation_mode::invalid);
ASSERT_EQ (toml.get_error ().get_message (), "frontiers_confirmation value is invalid (available: always, auto, disabled)");
ASSERT_EQ (conf.node.frontiers_confirmation, nano::frontiers_confirmation_mode::invalid);
}
{
std::stringstream ss;
ss << R"toml(
[node]
election_hint_weight_percent = 4
)toml";
nano::tomlconfig toml;
toml.read (ss);
nano::daemon_config conf;
conf.deserialize_toml (toml);
ASSERT_EQ (toml.get_error ().get_message (), "election_hint_weight_percent must be a number between 5 and 50");
}
{
std::stringstream ss;
ss << R"toml(
[node]
election_hint_weight_percent = 51
)toml";
nano::tomlconfig toml;
toml.read (ss);
nano::daemon_config conf;
conf.deserialize_toml (toml);
ASSERT_EQ (toml.get_error ().get_message (), "election_hint_weight_percent must be a number between 5 and 50");
}
}
TEST (toml, daemon_read_config)

View file

@ -1234,7 +1234,7 @@ void nano::active_transactions::add_inactive_votes_cache (nano::block_hash const
auto existing (inactive_by_hash.find (hash_a));
if (existing != inactive_by_hash.end ())
{
if (!existing->confirmed || !existing->bootstrap_started)
if (existing->needs_eval ())
{
auto is_new (false);
inactive_by_hash.modify (existing, [representative_a, &is_new](nano::inactive_cache_information & info) {
@ -1249,17 +1249,13 @@ void nano::active_transactions::add_inactive_votes_cache (nano::block_hash const
if (is_new)
{
bool confirmed (false);
if (inactive_votes_bootstrap_check (existing->voters, hash_a, confirmed) && !existing->bootstrap_started)
auto const status = inactive_votes_bootstrap_check (existing->voters, hash_a, existing->status);
if (status != existing->status)
{
inactive_by_hash.modify (existing, [](nano::inactive_cache_information & info) {
info.bootstrap_started = true;
});
}
if (confirmed && !existing->confirmed)
{
inactive_by_hash.modify (existing, [](nano::inactive_cache_information & info) {
info.confirmed = true;
// The iterator is only valid if the container was unchanged, e.g., by erasing this item after inserting an election
debug_assert (inactive_by_hash.count (hash_a));
inactive_by_hash.modify (existing, [status](nano::inactive_cache_information & info) {
info.status = status;
});
}
}
@ -1267,11 +1263,10 @@ void nano::active_transactions::add_inactive_votes_cache (nano::block_hash const
}
else
{
std::vector<nano::account> representative_vector (1, representative_a);
bool confirmed (false);
bool start_bootstrap (inactive_votes_bootstrap_check (representative_vector, hash_a, confirmed));
std::vector<nano::account> representative_vector{ representative_a };
auto const status (inactive_votes_bootstrap_check (representative_vector, hash_a, {}));
auto & inactive_by_arrival (inactive_votes_cache.get<tag_arrival> ());
inactive_by_arrival.emplace (nano::inactive_cache_information{ std::chrono::steady_clock::now (), hash_a, representative_vector, start_bootstrap, confirmed });
inactive_by_arrival.emplace (nano::inactive_cache_information{ std::chrono::steady_clock::now (), hash_a, representative_vector, status });
if (inactive_votes_cache.size () > node.flags.inactive_votes_cache_size)
{
inactive_by_arrival.erase (inactive_by_arrival.begin ());
@ -1280,6 +1275,16 @@ void nano::active_transactions::add_inactive_votes_cache (nano::block_hash const
}
}
void nano::active_transactions::trigger_inactive_votes_cache_election (std::shared_ptr<nano::block> const & block_a)
{
nano::lock_guard<std::mutex> guard (mutex);
auto const status = find_inactive_votes_cache (block_a->hash ()).status;
if (status.election_started)
{
insert_impl (block_a);
}
}
nano::inactive_cache_information nano::active_transactions::find_inactive_votes_cache (nano::block_hash const & hash_a)
{
auto & inactive_by_hash (inactive_votes_cache.get<tag_hash> ());
@ -1299,46 +1304,66 @@ void nano::active_transactions::erase_inactive_votes_cache (nano::block_hash con
inactive_votes_cache.get<tag_hash> ().erase (hash_a);
}
bool nano::active_transactions::inactive_votes_bootstrap_check (std::vector<nano::account> const & voters_a, nano::block_hash const & hash_a, bool & confirmed_a)
nano::inactive_cache_status nano::active_transactions::inactive_votes_bootstrap_check (std::vector<nano::account> const & voters_a, nano::block_hash const & hash_a, nano::inactive_cache_status const & previously_a)
{
/** Perform checks on accumulated tally from inactive votes
* These votes are generally either for unconfirmed blocks or old confirmed blocks
* That check is made after hitting a tally threshold, and always as late and as few times as possible
*/
nano::inactive_cache_status status (previously_a);
constexpr unsigned election_start_voters_min{ 5 };
uint128_t tally;
for (auto const & voter : voters_a)
{
tally += node.ledger.weight (voter);
}
bool start_bootstrap (false);
if (tally >= node.config.online_weight_minimum.number ())
if (!previously_a.confirmed && tally >= node.config.online_weight_minimum.number ())
{
start_bootstrap = true;
confirmed_a = true;
status.bootstrap_started = true;
status.confirmed = true;
}
else if (!node.flags.disable_legacy_bootstrap && tally > node.gap_cache.bootstrap_threshold ())
else if (!previously_a.bootstrap_started && !node.flags.disable_legacy_bootstrap && node.flags.disable_lazy_bootstrap && tally > node.gap_cache.bootstrap_threshold ())
{
start_bootstrap = true;
status.bootstrap_started = true;
}
if (start_bootstrap && !node.ledger.block_exists (hash_a))
if (!previously_a.election_started && voters_a.size () >= election_start_voters_min && tally >= (node.online_reps.online_stake () / 100) * node.config.election_hint_weight_percent)
{
auto node_l (node.shared ());
node.alarm.add (std::chrono::steady_clock::now () + node.network_params.bootstrap.gap_cache_bootstrap_start_interval, [node_l, hash_a]() {
auto transaction (node_l->store.tx_begin_read ());
if (!node_l->store.block_exists (transaction, hash_a))
{
if (!node_l->bootstrap_initiator.in_progress ())
{
node_l->logger.try_log (boost::str (boost::format ("Missing block %1% which has enough votes to warrant lazy bootstrapping it") % hash_a.to_string ()));
}
if (!node_l->flags.disable_lazy_bootstrap)
{
node_l->bootstrap_initiator.bootstrap_lazy (hash_a);
}
else if (!node_l->flags.disable_legacy_bootstrap)
{
node_l->bootstrap_initiator.bootstrap ();
}
}
});
status.election_started = true;
}
return start_bootstrap;
if ((status.election_started && !previously_a.election_started) || (status.bootstrap_started && !previously_a.bootstrap_started))
{
auto transaction (node.store.tx_begin_read ());
auto block = node.store.block_get (transaction, hash_a);
if (block && status.election_started && !previously_a.election_started && !node.block_confirmed_or_being_confirmed (transaction, hash_a))
{
insert_impl (block);
}
else if (!block && status.bootstrap_started && !previously_a.bootstrap_started)
{
auto node_l (node.shared ());
node.alarm.add (std::chrono::steady_clock::now () + node.network_params.bootstrap.gap_cache_bootstrap_start_interval, [node_l, hash_a]() {
auto transaction (node_l->store.tx_begin_read ());
if (!node_l->store.block_exists (transaction, hash_a))
{
if (!node_l->bootstrap_initiator.in_progress ())
{
node_l->logger.try_log (boost::str (boost::format ("Missing block %1% which has enough votes to warrant lazy bootstrapping it") % hash_a.to_string ()));
}
if (!node_l->flags.disable_lazy_bootstrap)
{
node_l->bootstrap_initiator.bootstrap_lazy (hash_a);
}
else if (!node_l->flags.disable_legacy_bootstrap)
{
node_l->bootstrap_initiator.bootstrap ();
}
}
});
}
}
return status;
}
size_t nano::active_transactions::election_winner_details_size ()

View file

@ -48,14 +48,30 @@ public:
nano::qualified_root root;
};
class inactive_cache_status final
{
public:
bool bootstrap_started{ false };
bool election_started{ false }; // Did item reach config threshold to start an impromptu election?
bool confirmed{ false }; // Did item reach votes quorum? (minimum config value)
bool operator!= (inactive_cache_status const other) const
{
return bootstrap_started != other.bootstrap_started || election_started != other.election_started || confirmed != other.confirmed;
}
};
class inactive_cache_information final
{
public:
std::chrono::steady_clock::time_point arrival;
nano::block_hash hash;
std::vector<nano::account> voters;
bool bootstrap_started{ false };
bool confirmed{ false }; // Did item reach votes quorum? (minimum config value)
nano::inactive_cache_status status;
bool needs_eval () const
{
return !status.bootstrap_started || !status.election_started || !status.confirmed;
}
};
class dropped_elections final
@ -178,6 +194,8 @@ public:
void add_recently_cemented (nano::election_status const &);
void add_recently_confirmed (nano::qualified_root const &, nano::block_hash const &);
void add_inactive_votes_cache (nano::block_hash const &, nano::account const &);
// Inserts an election if conditions are met
void trigger_inactive_votes_cache_election (std::shared_ptr<nano::block> const &);
nano::inactive_cache_information find_inactive_votes_cache (nano::block_hash const &);
void erase_inactive_votes_cache (nano::block_hash const &);
nano::confirmation_height_processor & confirmation_height_processor;
@ -265,7 +283,7 @@ private:
mi::member<nano::inactive_cache_information, nano::block_hash, &nano::inactive_cache_information::hash>>>>;
ordered_cache inactive_votes_cache;
// clang-format on
bool inactive_votes_bootstrap_check (std::vector<nano::account> const &, nano::block_hash const &, bool &);
nano::inactive_cache_status inactive_votes_bootstrap_check (std::vector<nano::account> const &, nano::block_hash const &, nano::inactive_cache_status const &);
boost::thread thread;
friend class election;

View file

@ -299,6 +299,10 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std::
{
node.active.insert (block_a, process_return_a.previous_balance.number ());
}
else
{
node.active.trigger_inactive_votes_cache_election (block_a);
}
// Announce block contents to the network
if (origin_a == nano::block_origin::local)

View file

@ -450,7 +450,7 @@ bool nano::bootstrap_attempt_legacy::confirm_frontiers (nano::unique_lock<std::m
{
tally += node->ledger.weight (voter);
}
if (existing.confirmed || (tally > reps_weight / 8 && existing.voters.size () >= representatives.size () * 0.6)) // 12.5% of weight, 60% of reps
if (existing.status.confirmed || (tally > reps_weight / 8 && existing.voters.size () >= representatives.size () * 0.6)) // 12.5% of weight, 60% of reps
{
ii = frontiers.erase (ii);
}

View file

@ -78,6 +78,7 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const
toml.put ("receive_minimum", receive_minimum.to_string_dec (), "Minimum receive amount. Only affects node wallets. A large amount is recommended to avoid automatic work generation for tiny transactions.\ntype:string,amount,raw");
toml.put ("online_weight_minimum", online_weight_minimum.to_string_dec (), "Online weight minimum required to confirm a block.\ntype:string,amount,raw");
toml.put ("online_weight_quorum", online_weight_quorum, "Percentage of votes required to confirm blocks. A value below 50 is not recommended.\ntype:uint64");
toml.put ("election_hint_weight_percent", election_hint_weight_percent, "Percentage of online weight to hint at starting an election. Defaults to 10.\ntype:uint32,[5,50]");
toml.put ("password_fanout", password_fanout, "Password fanout factor.\ntype:uint64");
toml.put ("io_threads", io_threads, "Number of threads dedicated to I/O operations. Defaults to the number of CPU threads, and at least 4.\ntype:uint64");
toml.put ("network_threads", network_threads, "Number of threads dedicated to processing network messages. Defaults to the number of CPU threads, and at least 4.\ntype:uint64");
@ -308,6 +309,7 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml)
toml.get<uint16_t> ("peering_port", peering_port);
toml.get<unsigned> ("bootstrap_fraction_numerator", bootstrap_fraction_numerator);
toml.get<unsigned> ("online_weight_quorum", online_weight_quorum);
toml.get<unsigned> ("election_hint_weight_percent", election_hint_weight_percent);
toml.get<unsigned> ("password_fanout", password_fanout);
toml.get<unsigned> ("io_threads", io_threads);
toml.get<unsigned> ("work_threads", work_threads);
@ -395,6 +397,10 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml)
{
toml.get_error ().set ("online_weight_quorum must be less than 100");
}
if (election_hint_weight_percent < 5 || election_hint_weight_percent > 50)
{
toml.get_error ().set ("election_hint_weight_percent must be a number between 5 and 50");
}
if (password_fanout < 16 || password_fanout > 1024 * 1024)
{
toml.get_error ().set ("password_fanout must be a number between 16 and 1048576");

View file

@ -56,6 +56,7 @@ public:
unsigned vote_generator_threshold{ 3 };
nano::amount online_weight_minimum{ 60000 * nano::Gxrb_ratio };
unsigned online_weight_quorum{ 50 };
unsigned election_hint_weight_percent{ 10 };
unsigned password_fanout{ 1024 };
unsigned io_threads{ std::max<unsigned> (4, std::thread::hardware_concurrency ()) };
unsigned network_threads{ std::max<unsigned> (4, std::thread::hardware_concurrency ()) };