Vote spacing (#2763)
* Currently there is no rate limiter on vote generation for a particular root and this can lead to increased, unnecessary vote traffic. This patch adds a delay between constructing non-cached votes. Co-authored-by: Russel <russel@nano.org>
This commit is contained in:
parent
053c4ffda7
commit
a74b506a70
5 changed files with 211 additions and 8 deletions
|
@ -119,3 +119,117 @@ TEST (vote_generator, session)
|
|||
thread.join ();
|
||||
ASSERT_TIMELY (2s, 1 == node->stats.count (nano::stat::type::vote, nano::stat::detail::vote_indeterminate));
|
||||
}
|
||||
|
||||
TEST (vote_spacing, basic)
|
||||
{
|
||||
nano::vote_spacing spacing{ std::chrono::milliseconds{ 100 } };
|
||||
nano::root root1{ 1 };
|
||||
nano::root root2{ 2 };
|
||||
nano::block_hash hash3{ 3 };
|
||||
nano::block_hash hash4{ 4 };
|
||||
nano::block_hash hash5{ 5 };
|
||||
ASSERT_EQ (0, spacing.size ());
|
||||
ASSERT_TRUE (spacing.votable (root1, hash3));
|
||||
spacing.flag (root1, hash3);
|
||||
ASSERT_EQ (1, spacing.size ());
|
||||
ASSERT_TRUE (spacing.votable (root1, hash3));
|
||||
ASSERT_FALSE (spacing.votable (root1, hash4));
|
||||
spacing.flag (root2, hash5);
|
||||
ASSERT_EQ (2, spacing.size ());
|
||||
}
|
||||
|
||||
TEST (vote_spacing, prune)
|
||||
{
|
||||
auto length = std::chrono::milliseconds{ 100 };
|
||||
nano::vote_spacing spacing{ length };
|
||||
nano::root root1{ 1 };
|
||||
nano::root root2{ 2 };
|
||||
nano::block_hash hash3{ 3 };
|
||||
nano::block_hash hash4{ 4 };
|
||||
spacing.flag (root1, hash3);
|
||||
ASSERT_EQ (1, spacing.size ());
|
||||
std::this_thread::sleep_for (length);
|
||||
spacing.flag (root2, hash4);
|
||||
ASSERT_EQ (1, spacing.size ());
|
||||
}
|
||||
|
||||
TEST (vote_spacing, vote_generator)
|
||||
{
|
||||
nano::node_config config;
|
||||
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
nano::system system;
|
||||
auto & node = *system.add_node (config);
|
||||
auto & wallet = *system.wallet (0);
|
||||
wallet.insert_adhoc (nano::dev_genesis_key.prv);
|
||||
nano::state_block_builder builder;
|
||||
auto send1 = builder.make_block ()
|
||||
.account (nano::dev_genesis_key.pub)
|
||||
.previous (nano::genesis_hash)
|
||||
.representative (nano::dev_genesis_key.pub)
|
||||
.balance (nano::genesis_amount - nano::Gxrb_ratio)
|
||||
.link (nano::dev_genesis_key.pub)
|
||||
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
|
||||
.work (*system.work.generate (nano::genesis_hash))
|
||||
.build_shared ();
|
||||
auto send2 = builder.make_block ()
|
||||
.account (nano::dev_genesis_key.pub)
|
||||
.previous (nano::genesis_hash)
|
||||
.representative (nano::dev_genesis_key.pub)
|
||||
.balance (nano::genesis_amount - nano::Gxrb_ratio - 1)
|
||||
.link (nano::dev_genesis_key.pub)
|
||||
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
|
||||
.work (*system.work.generate (nano::genesis_hash))
|
||||
.build_shared ();
|
||||
ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code);
|
||||
ASSERT_EQ (0, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts));
|
||||
node.active.generator.add (nano::genesis_hash, send1->hash ());
|
||||
ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts) == 1);
|
||||
ASSERT_FALSE (node.ledger.rollback (node.store.tx_begin_write (), send1->hash ()));
|
||||
ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send2).code);
|
||||
node.active.generator.add (nano::genesis_hash, send2->hash ());
|
||||
ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_spacing) == 1);
|
||||
ASSERT_EQ (1, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts));
|
||||
std::this_thread::sleep_for (config.network_params.voting.delay);
|
||||
node.active.generator.add (nano::genesis_hash, send2->hash ());
|
||||
ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts) == 2);
|
||||
}
|
||||
|
||||
TEST (vote_spacing, rapid)
|
||||
{
|
||||
nano::node_config config;
|
||||
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
nano::system system;
|
||||
auto & node = *system.add_node (config);
|
||||
auto & wallet = *system.wallet (0);
|
||||
wallet.insert_adhoc (nano::dev_genesis_key.prv);
|
||||
nano::state_block_builder builder;
|
||||
auto send1 = builder.make_block ()
|
||||
.account (nano::dev_genesis_key.pub)
|
||||
.previous (nano::genesis_hash)
|
||||
.representative (nano::dev_genesis_key.pub)
|
||||
.balance (nano::genesis_amount - nano::Gxrb_ratio)
|
||||
.link (nano::dev_genesis_key.pub)
|
||||
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
|
||||
.work (*system.work.generate (nano::genesis_hash))
|
||||
.build_shared ();
|
||||
auto send2 = builder.make_block ()
|
||||
.account (nano::dev_genesis_key.pub)
|
||||
.previous (nano::genesis_hash)
|
||||
.representative (nano::dev_genesis_key.pub)
|
||||
.balance (nano::genesis_amount - nano::Gxrb_ratio - 1)
|
||||
.link (nano::dev_genesis_key.pub)
|
||||
.sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub)
|
||||
.work (*system.work.generate (nano::genesis_hash))
|
||||
.build_shared ();
|
||||
ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send1).code);
|
||||
node.active.generator.add (nano::genesis_hash, send1->hash ());
|
||||
ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts) == 1);
|
||||
ASSERT_FALSE (node.ledger.rollback (node.store.tx_begin_write (), send1->hash ()));
|
||||
ASSERT_EQ (nano::process_result::progress, node.ledger.process (node.store.tx_begin_write (), *send2).code);
|
||||
node.active.generator.add (nano::genesis_hash, send2->hash ());
|
||||
ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_spacing) == 1);
|
||||
ASSERT_TIMELY (3s, 1 == node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts));
|
||||
std::this_thread::sleep_for (config.network_params.voting.delay);
|
||||
node.active.generator.add (nano::genesis_hash, send2->hash ());
|
||||
ASSERT_TIMELY (3s, node.stats.count (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts) == 2);
|
||||
}
|
||||
|
|
|
@ -760,6 +760,9 @@ std::string nano::stat::detail_to_string (uint32_t key)
|
|||
case nano::stat::detail::generator_replies_discarded:
|
||||
res = "generator_replies_discarded";
|
||||
break;
|
||||
case nano::stat::detail::generator_spacing:
|
||||
res = "generator_spacing";
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -342,7 +342,8 @@ public:
|
|||
// vote generator
|
||||
generator_broadcasts,
|
||||
generator_replies,
|
||||
generator_replies_discarded
|
||||
generator_replies_discarded,
|
||||
generator_spacing
|
||||
};
|
||||
|
||||
/** Direction of the stat. If the direction is irrelevant, use in */
|
||||
|
|
|
@ -11,6 +11,44 @@
|
|||
|
||||
#include <chrono>
|
||||
|
||||
void nano::vote_spacing::trim ()
|
||||
{
|
||||
recent.get<tag_time> ().erase (recent.get<tag_time> ().begin (), recent.get<tag_time> ().upper_bound (std::chrono::steady_clock::now () - delay));
|
||||
}
|
||||
|
||||
bool nano::vote_spacing::votable (nano::root const & root_a, nano::block_hash const & hash_a) const
|
||||
{
|
||||
bool result = true;
|
||||
for (auto range = recent.get<tag_root> ().equal_range (root_a); result && range.first != range.second; ++range.first)
|
||||
{
|
||||
auto & item = *range.first;
|
||||
result = hash_a == item.hash || item.time < std::chrono::steady_clock::now () - delay;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void nano::vote_spacing::flag (nano::root const & root_a, nano::block_hash const & hash_a)
|
||||
{
|
||||
trim ();
|
||||
auto now = std::chrono::steady_clock::now ();
|
||||
auto existing = recent.get<tag_root> ().find (root_a);
|
||||
if (existing != recent.end ())
|
||||
{
|
||||
recent.get<tag_root> ().modify (existing, [now] (entry & entry) {
|
||||
entry.time = now;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
recent.insert ({ root_a, now, hash_a });
|
||||
}
|
||||
}
|
||||
|
||||
size_t nano::vote_spacing::size () const
|
||||
{
|
||||
return recent.size ();
|
||||
}
|
||||
|
||||
bool nano::local_vote_history::consistency_check (nano::root const & root_a) const
|
||||
{
|
||||
auto & history_by_root (history.get<tag_root> ());
|
||||
|
@ -117,6 +155,7 @@ ledger (ledger_a),
|
|||
wallets (wallets_a),
|
||||
vote_processor (vote_processor_a),
|
||||
history (history_a),
|
||||
spacing{ config_a.network_params.voting.delay },
|
||||
network (network_a),
|
||||
stats (stats_a),
|
||||
thread ([this]() { run (); })
|
||||
|
@ -218,18 +257,27 @@ void nano::vote_generator::broadcast (nano::unique_lock<std::mutex> & lock_a)
|
|||
}
|
||||
if (cached_votes.empty () && std::find (roots.begin (), roots.end (), root) == roots.end ())
|
||||
{
|
||||
roots.push_back (root);
|
||||
hashes.push_back (hash);
|
||||
if (spacing.votable (root, hash))
|
||||
{
|
||||
roots.push_back (root);
|
||||
hashes.push_back (hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
stats.inc (nano::stat::type::vote_generator, nano::stat::detail::generator_spacing);
|
||||
}
|
||||
}
|
||||
candidates.pop_front ();
|
||||
}
|
||||
if (!hashes.empty ())
|
||||
{
|
||||
lock_a.unlock ();
|
||||
vote (hashes, roots, [this](auto const & vote_a) { this->broadcast_action (vote_a); });
|
||||
vote (hashes, roots, [this](auto const & vote_a) {
|
||||
this->broadcast_action (vote_a);
|
||||
this->stats.inc (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts);
|
||||
});
|
||||
lock_a.lock ();
|
||||
}
|
||||
stats.inc (nano::stat::type::vote_generator, nano::stat::detail::generator_broadcasts);
|
||||
}
|
||||
|
||||
void nano::vote_generator::reply (nano::unique_lock<std::mutex> & lock_a, request_t && request_a)
|
||||
|
@ -260,8 +308,15 @@ void nano::vote_generator::reply (nano::unique_lock<std::mutex> & lock_a, reques
|
|||
}
|
||||
if (cached_votes.empty () && std::find (roots.begin (), roots.end (), root) == roots.end ())
|
||||
{
|
||||
roots.push_back (i->first);
|
||||
hashes.push_back (i->second);
|
||||
if (spacing.votable (root, hash))
|
||||
{
|
||||
roots.push_back (root);
|
||||
hashes.push_back (hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
stats.inc (nano::stat::type::vote_generator, nano::stat::detail::generator_spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hashes.empty ())
|
||||
|
@ -289,6 +344,7 @@ void nano::vote_generator::vote (std::vector<nano::block_hash> const & hashes_a,
|
|||
for (size_t i (0), n (hashes_a.size ()); i != n; ++i)
|
||||
{
|
||||
history.add (roots_a[i], hashes_a[i], vote_l);
|
||||
spacing.flag (roots_a[i], hashes_a[i]);
|
||||
}
|
||||
action_a (vote_l);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <nano/secure/common.hpp>
|
||||
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/multi_index/sequenced_index.hpp>
|
||||
#include <boost/multi_index_container.hpp>
|
||||
|
@ -31,6 +32,34 @@ namespace transport
|
|||
class channel;
|
||||
}
|
||||
|
||||
class vote_spacing final
|
||||
{
|
||||
class entry
|
||||
{
|
||||
public:
|
||||
nano::root root;
|
||||
std::chrono::steady_clock::time_point time;
|
||||
nano::block_hash hash;
|
||||
};
|
||||
|
||||
boost::multi_index_container<entry,
|
||||
mi::indexed_by<
|
||||
mi::hashed_non_unique<mi::tag<class tag_root>,
|
||||
mi::member<entry, nano::root, &entry::root>>,
|
||||
mi::ordered_non_unique<mi::tag<class tag_time>,
|
||||
mi::member<entry, std::chrono::steady_clock::time_point, &entry::time>>
|
||||
>>
|
||||
recent;
|
||||
std::chrono::milliseconds const delay;
|
||||
void trim ();
|
||||
public:
|
||||
vote_spacing (std::chrono::milliseconds const & delay) :
|
||||
delay{ delay } {}
|
||||
bool votable (nano::root const & root_a, nano::block_hash const & hash_a) const;
|
||||
void flag (nano::root const & root_a, nano::block_hash const & hash_a);
|
||||
size_t size () const;
|
||||
};
|
||||
|
||||
class local_vote_history final
|
||||
{
|
||||
class local_vote final
|
||||
|
@ -77,7 +106,6 @@ private:
|
|||
mutable std::mutex mutex;
|
||||
|
||||
friend std::unique_ptr<container_info_component> collect_container_info (local_vote_history & history, const std::string & name);
|
||||
|
||||
friend class local_vote_history_basic_Test;
|
||||
};
|
||||
|
||||
|
@ -110,6 +138,7 @@ private:
|
|||
nano::wallets & wallets;
|
||||
nano::vote_processor & vote_processor;
|
||||
nano::local_vote_history & history;
|
||||
nano::vote_spacing spacing;
|
||||
nano::network & network;
|
||||
nano::stat & stats;
|
||||
mutable std::mutex mutex;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue