dncurrency/nano/core_test/vote_processor.cpp
Piotr Wójcik 2e9f5bd1f4
Enable running core test suite in parallel (#4246)
* Move `test::get_available_port` to `test::system` class

* Use ephemeral ports in tests

* Fix database directories for core tests

* Fix rpc tests

* Use `SO_REUSEADDR`

* Fix `ipv6_bind_send_ipv4`

* Use 0 to bind to any port by default

* Fix `network` tests

* Fix `socket` tests

* Fix `ipc` tests

* Fix remaining tests

* Raise the file limit soft-cap when running tests in parallel.

* Importing gtest-parallel as a submodule.

---------

Co-authored-by: Colin LeMahieu <clemahieu@gmail.com>
2023-06-13 17:27:58 +02:00

349 lines
17 KiB
C++

#include <nano/lib/jsonconfig.hpp>
#include <nano/node/transport/inproc.hpp>
#include <nano/node/vote_processor.hpp>
#include <nano/test_common/chains.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
using namespace std::chrono_literals;
TEST (vote_processor, codes)
{
nano::test::system system (1);
auto & node (*system.nodes[0]);
nano::keypair key;
auto vote (std::make_shared<nano::vote> (key.pub, key.prv, nano::vote::timestamp_min * 1, 0, std::vector<nano::block_hash>{ nano::dev::genesis->hash () }));
auto vote_invalid = std::make_shared<nano::vote> (*vote);
vote_invalid->signature.bytes[0] ^= 1;
auto channel (std::make_shared<nano::transport::inproc::channel> (node, node));
// Invalid signature
ASSERT_EQ (nano::vote_code::invalid, node.vote_processor.vote_blocking (vote_invalid, channel, false));
// Hint of pre-validation
ASSERT_NE (nano::vote_code::invalid, node.vote_processor.vote_blocking (vote_invalid, channel, true));
// No ongoing election
ASSERT_EQ (nano::vote_code::indeterminate, node.vote_processor.vote_blocking (vote, channel));
// First vote from an account for an ongoing election
node.block_confirm (nano::dev::genesis);
ASSERT_NE (nullptr, node.active.election (nano::dev::genesis->qualified_root ()));
ASSERT_EQ (nano::vote_code::vote, node.vote_processor.vote_blocking (vote, channel));
// Processing the same vote is a replay
ASSERT_EQ (nano::vote_code::replay, node.vote_processor.vote_blocking (vote, channel));
// Invalid takes precedence
ASSERT_EQ (nano::vote_code::invalid, node.vote_processor.vote_blocking (vote_invalid, channel));
// Once the election is removed (confirmed / dropped) the vote is again indeterminate
node.active.erase (*nano::dev::genesis);
ASSERT_EQ (nano::vote_code::indeterminate, node.vote_processor.vote_blocking (vote, channel));
}
TEST (vote_processor, flush)
{
nano::test::system system (1);
auto & node (*system.nodes[0]);
auto channel (std::make_shared<nano::transport::inproc::channel> (node, node));
for (unsigned i = 0; i < 2000; ++i)
{
auto vote = std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_min * (1 + i), 0, std::vector<nano::block_hash>{ nano::dev::genesis->hash () });
node.vote_processor.vote (vote, channel);
}
node.vote_processor.flush ();
ASSERT_TRUE (node.vote_processor.empty ());
}
TEST (vote_processor, invalid_signature)
{
nano::test::system system{ 1 };
auto & node = *system.nodes[0];
auto chain = nano::test::setup_chain (system, node, 1, nano::dev::genesis_key, false);
nano::keypair key;
auto vote = std::make_shared<nano::vote> (key.pub, key.prv, nano::vote::timestamp_min * 1, 0, std::vector<nano::block_hash>{ chain[0]->hash () });
auto vote_invalid = std::make_shared<nano::vote> (*vote);
vote_invalid->signature.bytes[0] ^= 1;
auto channel = std::make_shared<nano::transport::inproc::channel> (node, node);
auto election = nano::test::start_election (system, node, chain[0]->hash ());
ASSERT_NE (election, nullptr);
ASSERT_EQ (1, election->votes ().size ());
node.vote_processor.vote (vote_invalid, channel);
ASSERT_TIMELY (5s, 1 == election->votes ().size ());
node.vote_processor.vote (vote, channel);
ASSERT_TIMELY (5s, 2 == election->votes ().size ());
}
TEST (vote_processor, no_capacity)
{
nano::test::system system;
nano::node_flags node_flags;
node_flags.vote_processor_capacity = 0;
auto & node (*system.add_node (node_flags));
nano::keypair key;
auto vote (std::make_shared<nano::vote> (key.pub, key.prv, nano::vote::timestamp_min * 1, 0, std::vector<nano::block_hash>{ nano::dev::genesis->hash () }));
auto channel (std::make_shared<nano::transport::inproc::channel> (node, node));
ASSERT_TRUE (node.vote_processor.vote (vote, channel));
}
TEST (vote_processor, overflow)
{
nano::test::system system;
nano::node_flags node_flags;
node_flags.vote_processor_capacity = 1;
auto & node (*system.add_node (node_flags));
nano::keypair key;
auto vote (std::make_shared<nano::vote> (key.pub, key.prv, nano::vote::timestamp_min * 1, 0, std::vector<nano::block_hash>{ nano::dev::genesis->hash () }));
auto channel (std::make_shared<nano::transport::inproc::channel> (node, node));
auto start_time = std::chrono::system_clock::now ();
// No way to lock the processor, but queueing votes in quick succession must result in overflow
size_t not_processed{ 0 };
size_t const total{ 1000 };
for (unsigned i = 0; i < total; ++i)
{
if (node.vote_processor.vote (vote, channel))
{
++not_processed;
}
}
ASSERT_GT (not_processed, 0);
ASSERT_LT (not_processed, total);
ASSERT_EQ (not_processed, node.stats.count (nano::stat::type::vote, nano::stat::detail::vote_overflow));
// check that it did not timeout
ASSERT_LT (std::chrono::system_clock::now () - start_time, 10s);
}
namespace nano
{
TEST (vote_processor, weights)
{
nano::test::system system (4);
auto & node (*system.nodes[0]);
// Create representatives of different weight levels
// The online stake will be the minimum configurable due to online_reps sampling in tests
auto const online = node.config.online_weight_minimum.number ();
auto const level0 = online / 5000; // 0.02%
auto const level1 = online / 500; // 0.2%
auto const level2 = online / 50; // 2%
nano::keypair key0;
nano::keypair key1;
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (1)->insert_adhoc (key0.prv);
system.wallet (2)->insert_adhoc (key1.prv);
system.wallet (3)->insert_adhoc (key2.prv);
system.wallet (1)->store.representative_set (system.nodes[1]->wallets.tx_begin_write (), key0.pub);
system.wallet (2)->store.representative_set (system.nodes[2]->wallets.tx_begin_write (), key1.pub);
system.wallet (3)->store.representative_set (system.nodes[3]->wallets.tx_begin_write (), key2.pub);
system.wallet (0)->send_sync (nano::dev::genesis_key.pub, key0.pub, level0);
system.wallet (0)->send_sync (nano::dev::genesis_key.pub, key1.pub, level1);
system.wallet (0)->send_sync (nano::dev::genesis_key.pub, key2.pub, level2);
// Wait for representatives
ASSERT_TIMELY (10s, node.ledger.cache.rep_weights.get_rep_amounts ().size () == 4);
node.vote_processor.calculate_weights ();
ASSERT_EQ (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (key0.pub));
ASSERT_EQ (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (key0.pub));
ASSERT_EQ (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (key0.pub));
ASSERT_NE (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (key1.pub));
ASSERT_EQ (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (key1.pub));
ASSERT_EQ (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (key1.pub));
ASSERT_NE (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (key2.pub));
ASSERT_NE (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (key2.pub));
ASSERT_EQ (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (key2.pub));
ASSERT_NE (node.vote_processor.representatives_1.end (), node.vote_processor.representatives_1.find (nano::dev::genesis_key.pub));
ASSERT_NE (node.vote_processor.representatives_2.end (), node.vote_processor.representatives_2.find (nano::dev::genesis_key.pub));
ASSERT_NE (node.vote_processor.representatives_3.end (), node.vote_processor.representatives_3.find (nano::dev::genesis_key.pub));
}
}
// Issue that tracks last changes on this test: https://github.com/nanocurrency/nano-node/issues/3485
// Reopen in case the nondeterministic failure appears again.
// Checks local votes (a vote with a key that is in the node's wallet) are not re-broadcast when received.
// Nodes should not relay their own votes
TEST (vote_processor, no_broadcast_local)
{
nano::test::system system;
nano::node_flags flags;
flags.disable_request_loop = true;
nano::node_config config1, config2;
config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node (*system.add_node (config1, flags));
config2.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
config2.peering_port = system.get_available_port ();
system.add_node (config2, flags);
nano::block_builder builder;
std::error_code ec;
// Reduce the weight of genesis to 2x default min voting weight
nano::keypair key;
std::shared_ptr<nano::block> send = builder.state ()
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.balance (2 * node.config.vote_minimum.number ())
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build (ec);
ASSERT_FALSE (ec);
ASSERT_EQ (nano::process_result::progress, node.process_local (send).value ().code);
ASSERT_TIMELY (10s, !node.active.empty ());
ASSERT_EQ (2 * node.config.vote_minimum.number (), node.weight (nano::dev::genesis_key.pub));
// Insert account in wallet. Votes on node are not enabled.
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Ensure that the node knows the genesis key in its wallet.
node.wallets.compute_reps ();
ASSERT_TRUE (node.wallets.reps ().exists (nano::dev::genesis_key.pub));
ASSERT_FALSE (node.wallets.reps ().have_half_rep ()); // Genesis balance remaining after `send' is less than the half_rep threshold
// Process a vote with a key that is in the local wallet.
auto vote = std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector<nano::block_hash>{ send->hash () });
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote));
// Make sure the vote was processed.
auto election (node.active.election (send->qualified_root ()));
ASSERT_NE (nullptr, election);
auto votes (election->votes ());
auto existing (votes.find (nano::dev::genesis_key.pub));
ASSERT_NE (votes.end (), existing);
ASSERT_EQ (vote->timestamp (), existing->second.timestamp);
// Ensure the vote, from a local representative, was not broadcast on processing - it should be flooded on vote generation instead.
ASSERT_EQ (0, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out));
}
// Issue that tracks last changes on this test: https://github.com/nanocurrency/nano-node/issues/3485
// Reopen in case the nondeterministic failure appears again.
// Checks non-local votes (a vote with a key that is not in the node's wallet) are re-broadcast when received.
// Done without a representative.
TEST (vote_processor, local_broadcast_without_a_representative)
{
nano::test::system system;
nano::node_flags flags;
flags.disable_request_loop = true;
nano::node_config config1, config2;
config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node (*system.add_node (config1, flags));
config2.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
config2.peering_port = system.get_available_port ();
system.add_node (config2, flags);
nano::block_builder builder;
std::error_code ec;
// Reduce the weight of genesis to 2x default min voting weight
nano::keypair key;
std::shared_ptr<nano::block> send = builder.state ()
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.balance (node.config.vote_minimum.number ())
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build (ec);
ASSERT_FALSE (ec);
ASSERT_EQ (nano::process_result::progress, node.process_local (send).value ().code);
ASSERT_TIMELY (10s, !node.active.empty ());
ASSERT_EQ (node.config.vote_minimum, node.weight (nano::dev::genesis_key.pub));
node.block_confirm (send);
// Process a vote without a representative
auto vote = std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector<nano::block_hash>{ send->hash () });
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote));
// Make sure the vote was processed.
auto election (node.active.election (send->qualified_root ()));
ASSERT_NE (nullptr, election);
auto votes (election->votes ());
auto existing (votes.find (nano::dev::genesis_key.pub));
ASSERT_NE (votes.end (), existing);
ASSERT_EQ (vote->timestamp (), existing->second.timestamp);
// Ensure the vote was broadcast
ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out));
}
// Issue that tracks last changes on this test: https://github.com/nanocurrency/nano-node/issues/3485
// Reopen in case the nondeterministic failure appears again.
// Checks local votes (a vote with a key that is in the node's wallet) are not re-broadcast when received.
// Done with a principal representative.
TEST (vote_processor, no_broadcast_local_with_a_principal_representative)
{
nano::test::system system;
nano::node_flags flags;
flags.disable_request_loop = true;
nano::node_config config1, config2;
config1.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node (*system.add_node (config1, flags));
config2.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
config2.peering_port = system.get_available_port ();
system.add_node (config2, flags);
nano::block_builder builder;
std::error_code ec;
// Reduce the weight of genesis to 2x default min voting weight
nano::keypair key;
std::shared_ptr<nano::block> send = builder.state ()
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.balance (nano::dev::constants.genesis_amount - 2 * node.config.vote_minimum.number ())
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build (ec);
ASSERT_FALSE (ec);
ASSERT_EQ (nano::process_result::progress, node.process_local (send).value ().code);
ASSERT_TIMELY (10s, !node.active.empty ());
ASSERT_EQ (nano::dev::constants.genesis_amount - 2 * node.config.vote_minimum.number (), node.weight (nano::dev::genesis_key.pub));
// Insert account in wallet. Votes on node are not enabled.
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Ensure that the node knows the genesis key in its wallet.
node.wallets.compute_reps ();
ASSERT_TRUE (node.wallets.reps ().exists (nano::dev::genesis_key.pub));
ASSERT_TRUE (node.wallets.reps ().have_half_rep ()); // Genesis balance after `send' is over both half_rep and PR threshold.
// Process a vote with a key that is in the local wallet.
auto vote = std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector<nano::block_hash>{ send->hash () });
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote));
// Make sure the vote was processed.
auto election (node.active.election (send->qualified_root ()));
ASSERT_NE (nullptr, election);
auto votes (election->votes ());
auto existing (votes.find (nano::dev::genesis_key.pub));
ASSERT_NE (votes.end (), existing);
ASSERT_EQ (vote->timestamp (), existing->second.timestamp);
// Ensure the vote was not broadcast.
ASSERT_EQ (0, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out));
}
/**
* basic test to check that the timestamp mask is applied correctly on vote timestamp and duration fields
*/
TEST (vote, timestamp_and_duration_masking)
{
nano::test::system system;
nano::keypair key;
auto hash = std::vector<nano::block_hash>{ nano::dev::genesis->hash () };
auto vote = std::make_shared<nano::vote> (key.pub, key.prv, 0x123f, 0xf, hash);
ASSERT_EQ (vote->timestamp (), 0x1230);
ASSERT_EQ (vote->duration ().count (), 524288);
ASSERT_EQ (vote->duration_bits (), 0xf);
}
/**
* Test that a vote can encode an empty hash set
*/
TEST (vote, empty_hashes)
{
nano::keypair key;
auto vote = std::make_shared<nano::vote> (key.pub, key.prv, 0, 0, std::vector<nano::block_hash>{} /* empty */);
}