Changes the semantics of election::confirmed to return if the block is durably confirmed on disk, rather than simply being confirmed in memory. This was causing subtle race conditions or unnecessary extra checking to ensure the confirmation has been committed to disk. (#4200)
Changes the semantics of election::confirmed to return if the block is durably confirmed on disk, rather than simply being confirmed in memory. This was causing subtle race conditions or unnecessary extra checking to ensure the confirmation has been committed to disk. Modifies tests: active_transactions.dropped_cleanup active_transactions.inactive_votes_cache_election_start active_transactions.republish_winner confirmation_height.conflict_rollback_cemented election.quorum_minimum_confirm_success election.quorum_minimum_update_weight_before_quorum_checks node.rollback_gap_source rpc.confirmation_active vote_processor.invalid_signature Rewriting test to use a block that is not confirmed on disk. Cleaning up confirmation_height.conflict_rollback_cemented and reduce complexity. Cleaning up election.quorum_minimum_confirm_success and fixing race condition where confirmation happens asynchronously. Fixing race condition in election.quorum_minimum_update_weight_before_quorum_checks when checking asynchronous election confirmation. Cleaning up and simplifying node.rollback_gap_source Removing some test calls to block_processor::flush which is being phased out.
This commit is contained in:
parent
c288bb1327
commit
fde815f3ad
9 changed files with 105 additions and 162 deletions
|
@ -513,15 +513,11 @@ TEST (active_transactions, inactive_votes_cache_election_start)
|
|||
ASSERT_TRUE (send4_cache);
|
||||
ASSERT_EQ (3, send4_cache->voters.size ());
|
||||
node.process_active (send3);
|
||||
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 ();
|
||||
// An election is started for send6 but does not
|
||||
ASSERT_FALSE (node.block_confirmed_or_being_confirmed (send3->hash ()));
|
||||
// send7 cannot be voted on but an election should be started from inactive votes
|
||||
ASSERT_FALSE (node.ledger.dependents_confirmed (node.store.tx_begin_read (), *send4));
|
||||
node.process_active (send4);
|
||||
node.block_processor.flush ();
|
||||
ASSERT_TIMELY (5s, 7 == node.ledger.cache.cemented_count);
|
||||
}
|
||||
|
||||
|
@ -622,26 +618,28 @@ TEST (active_transactions, dropped_cleanup)
|
|||
nano::node_flags flags;
|
||||
flags.disable_request_loop = true;
|
||||
auto & node (*system.add_node (flags));
|
||||
auto chain = nano::test::setup_chain (system, node, 1, nano::dev::genesis_key, false);
|
||||
auto hash = chain[0]->hash ();
|
||||
|
||||
// Add to network filter to ensure proper cleanup after the election is dropped
|
||||
std::vector<uint8_t> block_bytes;
|
||||
{
|
||||
nano::vectorstream stream (block_bytes);
|
||||
nano::dev::genesis->serialize (stream);
|
||||
chain[0]->serialize (stream);
|
||||
}
|
||||
ASSERT_FALSE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
||||
ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
||||
|
||||
auto election = nano::test::start_election (system, node, nano::dev::genesis->hash ());
|
||||
auto election = nano::test::start_election (system, node, hash);
|
||||
ASSERT_NE (nullptr, election);
|
||||
|
||||
// Not yet removed
|
||||
ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
||||
ASSERT_TRUE (node.active.active (nano::dev::genesis->hash ()));
|
||||
ASSERT_TRUE (node.active.active (hash));
|
||||
|
||||
// Now simulate dropping the election
|
||||
ASSERT_FALSE (election->confirmed ());
|
||||
node.active.erase (*nano::dev::genesis);
|
||||
node.active.erase (*chain[0]);
|
||||
|
||||
// The filter must have been cleared
|
||||
ASSERT_FALSE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
||||
|
@ -650,16 +648,16 @@ TEST (active_transactions, dropped_cleanup)
|
|||
ASSERT_EQ (1, node.stats.count (nano::stat::type::active_dropped, nano::stat::detail::normal));
|
||||
|
||||
// Block cleared from active
|
||||
ASSERT_FALSE (node.active.active (nano::dev::genesis->hash ()));
|
||||
ASSERT_FALSE (node.active.active (hash));
|
||||
|
||||
// Repeat test for a confirmed election
|
||||
ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
||||
|
||||
election = nano::test::start_election (system, node, nano::dev::genesis->hash ());
|
||||
election = nano::test::start_election (system, node, hash);
|
||||
ASSERT_NE (nullptr, election);
|
||||
election->force_confirm ();
|
||||
ASSERT_TRUE (election->confirmed ());
|
||||
node.active.erase (*nano::dev::genesis);
|
||||
ASSERT_TIMELY (5s, election->confirmed ());
|
||||
node.active.erase (*chain[0]);
|
||||
|
||||
// The filter should not have been cleared
|
||||
ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
||||
|
@ -668,7 +666,7 @@ TEST (active_transactions, dropped_cleanup)
|
|||
ASSERT_EQ (1, node.stats.count (nano::stat::type::active_dropped, nano::stat::detail::normal));
|
||||
|
||||
// Block cleared from active
|
||||
ASSERT_FALSE (node.active.active (nano::dev::genesis->hash ()));
|
||||
ASSERT_FALSE (node.active.active (hash));
|
||||
}
|
||||
|
||||
TEST (active_transactions, republish_winner)
|
||||
|
|
|
@ -1092,98 +1092,53 @@ TEST (confirmation_height, all_block_types)
|
|||
test_mode (nano::confirmation_height_mode::unbounded);
|
||||
}
|
||||
|
||||
// this test cements a block on one node and another block on another node
|
||||
// it therefore tests that once a block is confirmed it cannot be rolled back
|
||||
// and if both nodes have different branches of the fork cemented then it is a permanent fork
|
||||
// This test ensures a block that's cemented cannot be rolled back by the node
|
||||
// A block is inserted and confirmed then later a different block is force inserted with a rollback attempt
|
||||
TEST (confirmation_height, conflict_rollback_cemented)
|
||||
{
|
||||
// functor to perform the conflict_rollback_cemented test using a certain mode
|
||||
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
|
||||
nano::state_block_builder builder{};
|
||||
nano::state_block_builder builder;
|
||||
auto const genesis_hash = nano::dev::genesis->hash ();
|
||||
|
||||
nano::test::system system{};
|
||||
nano::node_flags node_flags{};
|
||||
nano::test::system system;
|
||||
nano::node_flags node_flags;
|
||||
node_flags.confirmation_height_processor_mode = mode_a;
|
||||
|
||||
// create node 1 and account key1 (no voting key yet)
|
||||
auto node1 = system.add_node (node_flags);
|
||||
nano::keypair key1{};
|
||||
|
||||
nano::keypair key1;
|
||||
// create one side of a forked transaction on node1
|
||||
auto send1 = builder.make_block ()
|
||||
.previous (genesis_hash)
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.link (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - 100)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (genesis_hash))
|
||||
.build_shared ();
|
||||
node1->process_active (send1);
|
||||
ASSERT_TIMELY (5s, node1->active.election (send1->qualified_root ()) != nullptr);
|
||||
auto fork1a = builder.make_block ()
|
||||
.previous (genesis_hash)
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.link (key1.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - 100)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (genesis_hash))
|
||||
.build_shared ();
|
||||
ASSERT_EQ (nano::process_result::progress, node1->process (*fork1a).code);
|
||||
ASSERT_TRUE (nano::test::confirm (*node1, { fork1a }));
|
||||
ASSERT_TIMELY (5s, nano::test::confirmed (*node1, { fork1a }));
|
||||
|
||||
// create the other side of the fork on node2
|
||||
nano::keypair key2;
|
||||
auto send2 = builder.make_block ()
|
||||
.previous (genesis_hash)
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.link (key2.pub)
|
||||
.balance (nano::dev::constants.genesis_amount - 100)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (genesis_hash))
|
||||
.build_shared ();
|
||||
|
||||
// create node2, with send2 pre-initialised in the ledger so that block send1 cannot possibly get in the ledger first
|
||||
system.initialization_blocks.push_back (send2);
|
||||
auto node2 = system.add_node (node_flags);
|
||||
system.initialization_blocks.clear ();
|
||||
auto wallet1 = system.wallet (0);
|
||||
node2->process_active (send2);
|
||||
ASSERT_TIMELY (5s, node2->active.election (send2->qualified_root ()) != nullptr);
|
||||
|
||||
// force confirm send2 on node2
|
||||
ASSERT_TIMELY (5s, node2->ledger.store.block.get (node2->ledger.store.tx_begin_read (), send2->hash ()));
|
||||
node2->process_confirmed (nano::election_status{ send2 });
|
||||
ASSERT_TIMELY (5s, node2->block_confirmed (send2->hash ()));
|
||||
|
||||
// make node1 a voting node (it has all the voting weight)
|
||||
// from now on, node1 can vote for send1 at any time
|
||||
wallet1->insert_adhoc (nano::dev::genesis_key.prv);
|
||||
|
||||
// we expect node1 to vote for one side of the fork only, whichever side
|
||||
std::shared_ptr<nano::election> election_send1_node1{};
|
||||
ASSERT_EQ (send1->qualified_root (), send2->qualified_root ());
|
||||
ASSERT_TIMELY (5s, (election_send1_node1 = node1->active.election (send1->qualified_root ())) != nullptr);
|
||||
ASSERT_TIMELY (5s, 2 == election_send1_node1->votes ().size ());
|
||||
|
||||
// check that the send1 on node1 won the election and got confirmed
|
||||
// this happens because send1 is seen first by node1, and therefore it already winning and it cannot replaced by send2
|
||||
ASSERT_TIMELY (5s, election_send1_node1->confirmed ());
|
||||
auto const winner = election_send1_node1->winner ();
|
||||
ASSERT_NE (nullptr, winner);
|
||||
ASSERT_EQ (*winner, *send1);
|
||||
auto fork1b = builder.make_block ()
|
||||
.previous (genesis_hash)
|
||||
.account (nano::dev::genesis_key.pub)
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.link (key2.pub) // Different destination same 'previous'
|
||||
.balance (nano::dev::constants.genesis_amount - 100)
|
||||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (genesis_hash))
|
||||
.build_shared ();
|
||||
|
||||
node1->block_processor.force (fork1b);
|
||||
// node2 already has send2 forced confirmed whilst node1 should have confirmed send1 and therefore we have a cemented fork on node2
|
||||
// and node2 should print an error message on the log that it cannot rollback send2 because it is already cemented
|
||||
ASSERT_TIMELY (5s, 1 == node2->stats.count (nano::stat::type::ledger, nano::stat::detail::rollback_failed));
|
||||
|
||||
// get the tally for election the election on node1
|
||||
// we expect the winner to be send1 and we expect send1 to have "genesis balance" vote weight
|
||||
auto const tally = election_send1_node1->tally ();
|
||||
ASSERT_FALSE (tally.empty ());
|
||||
auto const & [amount, winner_alias] = *tally.begin ();
|
||||
ASSERT_EQ (*winner_alias, *send1);
|
||||
ASSERT_EQ (amount, nano::dev::constants.genesis_amount - 100);
|
||||
|
||||
// we expect send1 to exist on node1, is that because send2 is rolled back?
|
||||
ASSERT_TRUE (node1->ledger.block_or_pruned_exists (send1->hash ()));
|
||||
ASSERT_FALSE (node1->ledger.block_or_pruned_exists (send2->hash ()));
|
||||
|
||||
// we expect only send2 to be existing on node2
|
||||
ASSERT_TRUE (node2->ledger.block_or_pruned_exists (send2->hash ()));
|
||||
ASSERT_FALSE (node2->ledger.block_or_pruned_exists (send1->hash ()));
|
||||
[[maybe_unused]] size_t count = 0;
|
||||
ASSERT_TIMELY (5s, 1 == (count = node1->stats.count (nano::stat::type::ledger, nano::stat::detail::rollback_failed)));
|
||||
ASSERT_TRUE (nano::test::confirmed (*node1, { fork1a->hash () })); // fork1a should still remain after the rollback failed event
|
||||
};
|
||||
|
||||
test_mode (nano::confirmation_height_mode::bounded);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <nano/node/election.hpp>
|
||||
#include <nano/test_common/chains.hpp>
|
||||
#include <nano/test_common/system.hpp>
|
||||
#include <nano/test_common/testutil.hpp>
|
||||
|
||||
|
@ -19,7 +20,8 @@ TEST (election, construction)
|
|||
TEST (election, behavior)
|
||||
{
|
||||
nano::test::system system (1);
|
||||
auto election = nano::test::start_election (system, *system.nodes[0], nano::dev::genesis->hash ());
|
||||
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 ());
|
||||
}
|
||||
|
@ -125,10 +127,11 @@ TEST (election, quorum_minimum_flip_fail)
|
|||
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 (nano::test::get_available_port (), system.logging);
|
||||
nano::node_config node_config{ nano::test::get_available_port (), system.logging };
|
||||
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);
|
||||
|
@ -138,14 +141,13 @@ TEST (election, quorum_minimum_confirm_success)
|
|||
.account (nano::dev::genesis_key.pub)
|
||||
.previous (nano::dev::genesis->hash ())
|
||||
.representative (nano::dev::genesis_key.pub)
|
||||
.balance (node1.online_reps.delta ())
|
||||
.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_shared ();
|
||||
node1.work_generate_blocking (*send1);
|
||||
node1.process_active (send1);
|
||||
node1.block_processor.flush ();
|
||||
node1.scheduler.activate (nano::dev::genesis_key.pub, node1.store.tx_begin_read ());
|
||||
ASSERT_TIMELY (5s, node1.active.election (send1->qualified_root ()));
|
||||
auto election = node1.active.election (send1->qualified_root ());
|
||||
|
@ -153,9 +155,8 @@ TEST (election, quorum_minimum_confirm_success)
|
|||
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));
|
||||
node1.block_processor.flush ();
|
||||
ASSERT_NE (nullptr, node1.block (send1->hash ()));
|
||||
ASSERT_TRUE (election->confirmed ());
|
||||
ASSERT_TIMELY (5s, election->confirmed ());
|
||||
}
|
||||
|
||||
// checks that block cannot be confirmed if there is no enough votes to reach quorum
|
||||
|
@ -199,7 +200,7 @@ 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::test::system system;
|
||||
|
||||
nano::node_config node_config{ nano::test::get_available_port (), system.logging };
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
|
@ -207,8 +208,8 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks)
|
|||
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{};
|
||||
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);
|
||||
|
@ -225,7 +226,7 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks)
|
|||
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_shared ();
|
||||
ASSERT_EQ (nano::process_result::progress, node1.process (*open1).code);
|
||||
|
||||
nano::keypair key2{};
|
||||
nano::keypair key2;
|
||||
auto const send2 = builder.make_block ()
|
||||
.previous (open1->hash ())
|
||||
.destination (key2.pub)
|
||||
|
@ -242,7 +243,7 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks)
|
|||
system.wallet (1)->insert_adhoc (key1.prv);
|
||||
ASSERT_TIMELY (10s, node2.ledger.cache.block_count == 4);
|
||||
|
||||
std::shared_ptr<nano::election> election{};
|
||||
std::shared_ptr<nano::election> election;
|
||||
ASSERT_TIMELY (5s, (election = node1.active.election (send1->qualified_root ())) != nullptr);
|
||||
ASSERT_EQ (1, election->blocks ().size ());
|
||||
|
||||
|
@ -262,7 +263,7 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks)
|
|||
node1.online_reps.online_m = node_config.online_weight_minimum.number () + 20;
|
||||
}
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2));
|
||||
ASSERT_TRUE (election->confirmed ());
|
||||
ASSERT_TIMELY (5s, election->confirmed ());
|
||||
ASSERT_NE (nullptr, node1.block (send1->hash ()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3670,15 +3670,17 @@ TEST (node, rollback_gap_source)
|
|||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
||||
.build_shared ();
|
||||
auto fork = builder.make_block ()
|
||||
.account (key.pub)
|
||||
.previous (0)
|
||||
.representative (key.pub)
|
||||
.link (send1->hash ())
|
||||
.balance (1)
|
||||
.sign (key.prv, key.pub)
|
||||
.work (*system.work.generate (key.pub))
|
||||
.build_shared ();
|
||||
// Side a of a forked open block receiving from send1
|
||||
// This is a losing block
|
||||
auto fork1a = builder.make_block ()
|
||||
.account (key.pub)
|
||||
.previous (0)
|
||||
.representative (key.pub)
|
||||
.link (send1->hash ())
|
||||
.balance (1)
|
||||
.sign (key.prv, key.pub)
|
||||
.work (*system.work.generate (key.pub))
|
||||
.build_shared ();
|
||||
auto send2 = builder.make_block ()
|
||||
.from (*send1)
|
||||
.previous (send1->hash ())
|
||||
|
@ -3687,63 +3689,35 @@ TEST (node, rollback_gap_source)
|
|||
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
||||
.work (*system.work.generate (send1->hash ()))
|
||||
.build_shared ();
|
||||
auto open = builder.make_block ()
|
||||
.from (*fork)
|
||||
.link (send2->hash ())
|
||||
.sign (key.prv, key.pub)
|
||||
.build_shared ();
|
||||
// Side b of a forked open block receiving from send2.
|
||||
// This is the winning block
|
||||
auto fork1b = builder.make_block ()
|
||||
.from (*fork1a)
|
||||
.link (send2->hash ())
|
||||
.sign (key.prv, key.pub)
|
||||
.build_shared ();
|
||||
// Set 'node' up with losing block 'fork1a'
|
||||
ASSERT_EQ (nano::process_result::progress, node.process (*send1).code);
|
||||
ASSERT_EQ (nano::process_result::progress, node.process (*fork).code);
|
||||
// Node has fork & doesn't have source for correct block open (send2)
|
||||
ASSERT_EQ (nano::process_result::progress, node.process (*fork1a).code);
|
||||
// Node has 'fork1a' & doesn't have source 'send2' for winning 'fork1b' block
|
||||
ASSERT_EQ (nullptr, node.block (send2->hash ()));
|
||||
// Start election for fork
|
||||
nano::test::start_elections (system, node, { fork });
|
||||
{
|
||||
auto election = node.active.election (fork->qualified_root ());
|
||||
ASSERT_NE (nullptr, election);
|
||||
// Process conflicting block for election
|
||||
node.process_active (open);
|
||||
node.block_processor.flush ();
|
||||
ASSERT_EQ (2, election->blocks ().size ());
|
||||
ASSERT_EQ (1, election->votes ().size ());
|
||||
// Confirm open
|
||||
auto vote1 (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash> (1, open->hash ())));
|
||||
node.vote_processor.vote (vote1, std::make_shared<nano::transport::fake::channel> (node));
|
||||
ASSERT_TIMELY (5s, election->votes ().size () == 2);
|
||||
ASSERT_TIMELY (3s, election->confirmed ());
|
||||
}
|
||||
node.block_processor.force (fork1b);
|
||||
ASSERT_TIMELY (5s, node.block (fork1a->hash ()) == nullptr);
|
||||
// Wait for the rollback (attempt to replace fork with open)
|
||||
ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::rollback, nano::stat::detail::open) == 1);
|
||||
ASSERT_TIMELY (5s, node.active.empty ());
|
||||
// But replacing is not possible (missing source block - send2)
|
||||
node.block_processor.flush ();
|
||||
ASSERT_EQ (nullptr, node.block (open->hash ()));
|
||||
ASSERT_EQ (nullptr, node.block (fork->hash ()));
|
||||
// Fork can be returned by some other forked node or attacker
|
||||
node.process_active (fork);
|
||||
node.block_processor.flush ();
|
||||
ASSERT_NE (nullptr, node.block (fork->hash ()));
|
||||
ASSERT_EQ (nullptr, node.block (fork1b->hash ()));
|
||||
// Fork can be returned by some other forked node
|
||||
node.process_active (fork1a);
|
||||
ASSERT_TIMELY (5s, node.block (fork1a->hash ()) != nullptr);
|
||||
// With send2 block in ledger election can start again to remove fork block
|
||||
ASSERT_EQ (nano::process_result::progress, node.process (*send2).code);
|
||||
nano::test::start_elections (system, node, { fork });
|
||||
{
|
||||
auto election = node.active.election (fork->qualified_root ());
|
||||
ASSERT_NE (nullptr, election);
|
||||
// Process conflicting block for election
|
||||
node.process_active (open);
|
||||
node.block_processor.flush ();
|
||||
ASSERT_EQ (2, election->blocks ().size ());
|
||||
// Confirm open (again)
|
||||
auto vote1 (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash> (1, open->hash ())));
|
||||
node.vote_processor.vote (vote1, std::make_shared<nano::transport::fake::channel> (node));
|
||||
ASSERT_TIMELY (5s, election->votes ().size () == 2);
|
||||
}
|
||||
node.block_processor.force (fork1b);
|
||||
// Wait for new rollback
|
||||
ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::rollback, nano::stat::detail::open) == 2);
|
||||
// Now fork block should be replaced with open
|
||||
node.block_processor.flush ();
|
||||
ASSERT_NE (nullptr, node.block (open->hash ()));
|
||||
ASSERT_EQ (nullptr, node.block (fork->hash ()));
|
||||
ASSERT_TIMELY (5s, node.block (fork1b->hash ()) != nullptr);
|
||||
ASSERT_EQ (nullptr, node.block (fork1a->hash ()));
|
||||
}
|
||||
|
||||
// Confirm a complex dependency graph starting from the first block
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#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>
|
||||
|
||||
|
@ -61,13 +62,14 @@ 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>{ nano::dev::genesis->hash () });
|
||||
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, nano::dev::genesis->hash ());
|
||||
auto election = nano::test::start_election (system, node, chain[0]->hash ());
|
||||
ASSERT_NE (election, nullptr);
|
||||
ASSERT_EQ (1, election->votes ().size ());
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ void nano::active_transactions::request_confirm (nano::unique_lock<nano::mutex>
|
|||
bool const confirmed_l (election_l->confirmed ());
|
||||
unconfirmed_count_l += !confirmed_l;
|
||||
|
||||
if (election_l->transition_time (solicitor))
|
||||
if (confirmed_l || election_l->transition_time (solicitor))
|
||||
{
|
||||
erase (election_l->qualified_root);
|
||||
}
|
||||
|
@ -620,7 +620,8 @@ boost::optional<nano::election_status_type> nano::active_transactions::confirm_b
|
|||
nano::unique_lock<nano::mutex> election_lock{ existing->second->mutex };
|
||||
if (existing->second->status.winner && existing->second->status.winner->hash () == hash)
|
||||
{
|
||||
if (!existing->second->confirmed ())
|
||||
// Determine if the block was confirmed explicitly via election confirmation or implicitly via confirmation height
|
||||
if (!existing->second->status_confirmed ())
|
||||
{
|
||||
existing->second->confirm_once (election_lock, nano::election_status_type::active_confirmation_height);
|
||||
status_type = nano::election_status_type::active_confirmation_height;
|
||||
|
|
|
@ -157,6 +157,11 @@ void nano::election::transition_active ()
|
|||
}
|
||||
|
||||
bool nano::election::confirmed () const
|
||||
{
|
||||
return node.block_confirmed (status.winner->hash ());
|
||||
}
|
||||
|
||||
bool nano::election::status_confirmed () const
|
||||
{
|
||||
return state_m == nano::election::state_t::confirmed || state_m == nano::election::state_t::expired_confirmed;
|
||||
}
|
||||
|
|
|
@ -116,6 +116,12 @@ public: // State transitions
|
|||
void transition_active ();
|
||||
|
||||
public: // Status
|
||||
// Returns true when the election is confirmed in memory
|
||||
// Elections will first confirm in memory once sufficient votes have been received
|
||||
bool status_confirmed () const;
|
||||
// Returns true when the winning block is durably confirmed in the ledger.
|
||||
// Later once the confirmation height processor has updated the confirmation height it will be confirmed on disk
|
||||
// It is possible for an election to be confirmed on disk but not in memory, for instance if implicitly confirmed via confirmation height
|
||||
bool confirmed () const;
|
||||
bool failed () const;
|
||||
nano::election_extended_status current_status () const;
|
||||
|
|
|
@ -6829,6 +6829,7 @@ TEST (rpc, confirmation_active)
|
|||
auto election (node1->active.election (send1->qualified_root ()));
|
||||
ASSERT_NE (nullptr, election);
|
||||
election->force_confirm ();
|
||||
ASSERT_TIMELY (5s, election->confirmed ());
|
||||
|
||||
boost::property_tree::ptree request;
|
||||
request.put ("action", "confirmation_active");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue