dncurrency/nano/core_test/election.cpp

311 lines
12 KiB
C++

#include <nano/lib/blocks.hpp>
#include <nano/node/active_transactions.hpp>
#include <nano/node/election.hpp>
#include <nano/node/scheduler/component.hpp>
#include <nano/node/scheduler/priority.hpp>
#include <nano/secure/ledger.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 (election, construction)
{
nano::test::system system (1);
auto & node = *system.nodes[0];
auto election = std::make_shared<nano::election> (
node, nano::dev::genesis, [] (auto const &) {}, [] (auto const &) {}, nano::election_behavior::normal);
}
TEST (election, behavior)
{
nano::test::system system (1);
auto chain = nano::test::setup_chain (system, *system.nodes[0], 1, nano::dev::genesis_key, false);
auto election = nano::test::start_election (system, *system.nodes[0], chain[0]->hash ());
ASSERT_NE (nullptr, election);
ASSERT_EQ (nano::election_behavior::normal, election->behavior ());
}
TEST (election, quorum_minimum_flip_success)
{
nano::test::system system{};
nano::node_config node_config = system.default_config ();
node_config.online_weight_minimum = nano::dev::constants.genesis_amount;
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node1 = *system.add_node (node_config);
auto const latest_hash = nano::dev::genesis->hash ();
nano::state_block_builder builder{};
nano::keypair key1{};
auto send1 = builder.make_block ()
.previous (latest_hash)
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.balance (node1.online_reps.delta ())
.link (key1.pub)
.work (*system.work.generate (latest_hash))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
nano::keypair key2{};
auto send2 = builder.make_block ()
.previous (latest_hash)
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.balance (node1.online_reps.delta ())
.link (key2.pub)
.work (*system.work.generate (latest_hash))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
node1.process_active (send1);
ASSERT_TIMELY (5s, node1.active.election (send1->qualified_root ()) != nullptr)
node1.process_active (send2);
std::shared_ptr<nano::election> election{};
ASSERT_TIMELY (5s, (election = node1.active.election (send2->qualified_root ())) != nullptr)
ASSERT_TIMELY_EQ (5s, election->blocks ().size (), 2);
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send2->hash () });
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote).at (send2->hash ()));
ASSERT_TIMELY (5s, election->confirmed ());
auto const winner = election->winner ();
ASSERT_NE (nullptr, winner);
ASSERT_EQ (*winner, *send2);
}
TEST (election, quorum_minimum_flip_fail)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
node_config.online_weight_minimum = nano::dev::constants.genesis_amount;
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node = *system.add_node (node_config);
nano::state_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.balance (node.online_reps.delta () - 1)
.link (nano::keypair{}.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
auto send2 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.balance (node.online_reps.delta () - 1)
.link (nano::keypair{}.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
// process send1 and wait until its election appears
node.process_active (send1);
ASSERT_TIMELY (5s, node.active.election (send1->qualified_root ()))
// process send2 and wait until it is added to the existing election
node.process_active (send2);
std::shared_ptr<nano::election> election;
ASSERT_TIMELY (5s, election = node.active.election (send2->qualified_root ()))
ASSERT_TIMELY_EQ (5s, election->blocks ().size (), 2);
// genesis generates a final vote for send2 but it should not be enough to reach quorum due to the online_weight_minimum being so high
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send2->hash () });
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote).at (send2->hash ()));
// give the election some time before asserting it is not confirmed so that in case
// it would be wrongfully confirmed, have that immediately fail instead of race
WAIT (1s);
ASSERT_FALSE (election->confirmed ());
ASSERT_FALSE (node.block_confirmed (send2->hash ()));
}
// This test ensures blocks can be confirmed precisely at the quorum minimum
TEST (election, quorum_minimum_confirm_success)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
node_config.online_weight_minimum = nano::dev::constants.genesis_amount;
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node1 = *system.add_node (node_config);
nano::keypair key1;
nano::block_builder builder;
auto send1 = builder.state ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (node1.online_reps.delta ()) // Only minimum quorum remains
.link (key1.pub)
.work (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
node1.work_generate_blocking (*send1);
node1.process_active (send1);
node1.scheduler.priority.activate (nano::dev::genesis_key.pub, node1.ledger.tx_begin_read ());
ASSERT_TIMELY (5s, node1.active.election (send1->qualified_root ()));
auto election = node1.active.election (send1->qualified_root ());
ASSERT_NE (nullptr, election);
ASSERT_EQ (1, election->blocks ().size ());
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () });
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote).at (send1->hash ()));
ASSERT_NE (nullptr, node1.block (send1->hash ()));
ASSERT_TIMELY (5s, election->confirmed ());
}
// checks that block cannot be confirmed if there is no enough votes to reach quorum
TEST (election, quorum_minimum_confirm_fail)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
node_config.online_weight_minimum = nano::dev::constants.genesis_amount;
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node1 = *system.add_node (node_config);
nano::block_builder builder;
auto send1 = builder.state ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (node1.online_reps.delta () - 1)
.link (nano::keypair{}.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
node1.process_active (send1);
auto election = nano::test::start_election (system, node1, send1->hash ());
ASSERT_NE (nullptr, election);
ASSERT_EQ (1, election->blocks ().size ());
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () });
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote).at (send1->hash ()));
// give the election a chance to confirm
WAIT (1s);
// it should not confirm because there should not be enough quorum
ASSERT_TRUE (node1.block (send1->hash ()));
ASSERT_FALSE (election->confirmed ());
}
namespace nano
{
// FIXME: this test fails on rare occasions. It needs a review.
TEST (election, quorum_minimum_update_weight_before_quorum_checks)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node1 = *system.add_node (node_config);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key1;
nano::send_block_builder builder;
auto const amount = ((nano::uint256_t (node_config.online_weight_minimum.number ()) * nano::online_reps::online_weight_quorum) / 100).convert_to<nano::uint128_t> () - 1;
auto const latest = node1.latest (nano::dev::genesis_key.pub);
auto const send1 = builder.make_block ()
.previous (latest)
.destination (key1.pub)
.balance (amount)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest))
.build ();
node1.process_active (send1);
ASSERT_TIMELY (5s, node1.block (send1->hash ()) != nullptr);
auto const open1 = nano::open_block_builder{}.make_block ().account (key1.pub).source (send1->hash ()).representative (key1.pub).sign (key1.prv, key1.pub).work (*system.work.generate (key1.pub)).build ();
ASSERT_EQ (nano::block_status::progress, node1.process (open1));
nano::keypair key2;
auto const send2 = builder.make_block ()
.previous (open1->hash ())
.destination (key2.pub)
.balance (3)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (open1->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node1.process (send2));
ASSERT_TIMELY_EQ (5s, node1.ledger.block_count (), 4);
node_config.peering_port = system.get_available_port ();
auto & node2 = *system.add_node (node_config);
system.wallet (1)->insert_adhoc (key1.prv);
ASSERT_TIMELY_EQ (10s, node2.ledger.block_count (), 4);
std::shared_ptr<nano::election> election;
ASSERT_TIMELY (5s, (election = node1.active.election (send1->qualified_root ())) != nullptr);
ASSERT_EQ (1, election->blocks ().size ());
auto vote1 = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () });
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1).at (send1->hash ()));
auto channel = node1.network.find_node_id (node2.get_node_id ());
ASSERT_NE (channel, nullptr);
auto vote2 = nano::test::make_final_vote (key1, { send1->hash () });
node1.rep_crawler.force_process (vote2, channel);
ASSERT_FALSE (election->confirmed ());
{
nano::lock_guard<nano::mutex> guard (node1.online_reps.mutex);
// Modify online_m for online_reps to more than is available, this checks that voting below updates it to current online reps.
node1.online_reps.online_m = node_config.online_weight_minimum.number () + 20;
}
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2).at (send1->hash ()));
ASSERT_TIMELY (5s, election->confirmed ());
ASSERT_NE (nullptr, node1.block (send1->hash ()));
}
}
TEST (election, continuous_voting)
{
nano::test::system system{};
auto & node1 = *system.add_node ();
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// We want genesis to have just enough voting weight to be a principal rep, but not enough to confirm blocks on their own
nano::keypair key1{};
nano::send_block_builder builder{};
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.balance (node1.balance (nano::dev::genesis_key.pub) / 10 * 1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
ASSERT_TRUE (nano::test::process (node1, { send1 }));
ASSERT_TRUE (nano::test::start_elections (system, node1, { send1 }, true));
ASSERT_TIMELY (5s, nano::test::confirmed (node1, { send1 }));
node1.stats.clear ();
// Create a block that should be staying in AEC but not get confirmed
auto send2 = builder.make_block ()
.previous (send1->hash ())
.destination (key1.pub)
.balance (node1.balance (nano::dev::genesis_key.pub) - 1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
ASSERT_TRUE (nano::test::process (node1, { send2 }));
ASSERT_TIMELY (5s, node1.active.active (*send2));
// Ensure votes are broadcasted in continuous manner
ASSERT_TIMELY (5s, node1.stats.count (nano::stat::type::election, nano::stat::detail::broadcast_vote) >= 5);
}