diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index af9053bd..2153a5c3 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -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 hashes{ open1->hash (), open2->hash (), open3->hash (), open4->hash (), open5->hash (), send7->hash () }; + auto vote1 (std::make_shared (key1.pub, key1.prv, 0, hashes)); + node.vote_processor.vote (vote1, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + auto vote2 (std::make_shared (key2.pub, key2.prv, 0, hashes)); + node.vote_processor.vote (vote2, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + auto vote3 (std::make_shared (key3.pub, key3.prv, 0, hashes)); + node.vote_processor.vote (vote3, std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + auto vote4 (std::make_shared (key4.pub, key4.prv, 0, hashes)); + node.vote_processor.vote (vote4, std::make_shared (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 (key5.pub, key5.prv, 0, hashes)); + node.vote_processor.vote (vote5, std::make_shared (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::dev_genesis_key.pub, nano::dev_genesis_key.prv, 0, hashes)); + node.vote_processor.vote (vote0, std::make_shared (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); diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index 119bef4d..19b35893 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -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) diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 080193a5..6771a65f 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -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 representative_vector (1, representative_a); - bool confirmed (false); - bool start_bootstrap (inactive_votes_bootstrap_check (representative_vector, hash_a, confirmed)); + std::vector representative_vector{ representative_a }; + auto const status (inactive_votes_bootstrap_check (representative_vector, hash_a, {})); auto & inactive_by_arrival (inactive_votes_cache.get ()); - 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 const & block_a) +{ + nano::lock_guard 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 ()); @@ -1299,46 +1304,66 @@ void nano::active_transactions::erase_inactive_votes_cache (nano::block_hash con inactive_votes_cache.get ().erase (hash_a); } -bool nano::active_transactions::inactive_votes_bootstrap_check (std::vector 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 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 () diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index ef7c7520..2d377c9d 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -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 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 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>>>; ordered_cache inactive_votes_cache; // clang-format on - bool inactive_votes_bootstrap_check (std::vector const &, nano::block_hash const &, bool &); + nano::inactive_cache_status inactive_votes_bootstrap_check (std::vector const &, nano::block_hash const &, nano::inactive_cache_status const &); boost::thread thread; friend class election; diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index 4fc073f2..21fc63de 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -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) diff --git a/nano/node/bootstrap/bootstrap_attempt.cpp b/nano/node/bootstrap/bootstrap_attempt.cpp index faf3fa3d..78f858f1 100644 --- a/nano/node/bootstrap/bootstrap_attempt.cpp +++ b/nano/node/bootstrap/bootstrap_attempt.cpp @@ -450,7 +450,7 @@ bool nano::bootstrap_attempt_legacy::confirm_frontiers (nano::unique_lockledger.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); } diff --git a/nano/node/nodeconfig.cpp b/nano/node/nodeconfig.cpp index 05ee2813..4e58b060 100644 --- a/nano/node/nodeconfig.cpp +++ b/nano/node/nodeconfig.cpp @@ -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 ("peering_port", peering_port); toml.get ("bootstrap_fraction_numerator", bootstrap_fraction_numerator); toml.get ("online_weight_quorum", online_weight_quorum); + toml.get ("election_hint_weight_percent", election_hint_weight_percent); toml.get ("password_fanout", password_fanout); toml.get ("io_threads", io_threads); toml.get ("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"); diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index 70e7fee0..627f28f4 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -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 (4, std::thread::hardware_concurrency ()) }; unsigned network_threads{ std::max (4, std::thread::hardware_concurrency ()) };