Bootstrap poison test
This commit is contained in:
		
					parent
					
						
							
								d682454665
							
						
					
				
			
			
				commit
				
					
						684c7f9041
					
				
			
		
					 3 changed files with 151 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -3232,8 +3232,6 @@ TEST (node, dependency_graph_frontier)
 | 
			
		|||
	ASSERT_TIMELY_EQ (15s, node2.ledger.cemented_count (), node2.ledger.block_count ());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace nano
 | 
			
		||||
{
 | 
			
		||||
TEST (node, deferred_dependent_elections)
 | 
			
		||||
{
 | 
			
		||||
	nano::test::system system;
 | 
			
		||||
| 
						 | 
				
			
			@ -3355,7 +3353,6 @@ TEST (node, deferred_dependent_elections)
 | 
			
		|||
	ASSERT_TIMELY (5s, node.block_confirmed (send2->hash ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, node.active.active (receive->qualified_root ()));
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test that a node configured with `enable_pruning` and `max_pruning_age = 1s` will automatically
 | 
			
		||||
// prune old confirmed blocks without explicitly saying `node.ledger_pruning` in the unit test
 | 
			
		||||
| 
						 | 
				
			
			@ -3668,3 +3665,143 @@ TEST (node, bounded_backlog)
 | 
			
		|||
 | 
			
		||||
	ASSERT_TIMELY_EQ (20s, node.ledger.block_count (), 11); // 10 + genesis
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This test checks that a bootstrapping node can resolve a fork when a "poisoned" node
 | 
			
		||||
// attempts to feed it the incorrect side of a fork.
 | 
			
		||||
// The scenario involves:
 | 
			
		||||
// 1. A bootstrapping node (node_boot) - the node being tested
 | 
			
		||||
// 2. A poisoned node (node_poison) that has bootstrap serving enabled with an incorrect block
 | 
			
		||||
// 3. A representative node (node_rep) with enough voting weight but bootstrap serving disabled
 | 
			
		||||
// 4. A non-representative node (node_correct) that serves the correct side of the fork
 | 
			
		||||
TEST (node, bootstrap_poison)
 | 
			
		||||
{
 | 
			
		||||
	nano::test::system system;
 | 
			
		||||
 | 
			
		||||
	// Create the representative node with bootstrap serving disabled
 | 
			
		||||
	nano::node_config rep_config = system.default_config ();
 | 
			
		||||
	rep_config.bootstrap_server.enable = false; // Disable bootstrap serving
 | 
			
		||||
	rep_config.bootstrap.enable = false; // Disable bootstrap from the network
 | 
			
		||||
	// Disable schedulers
 | 
			
		||||
	rep_config.priority_scheduler.enable = false;
 | 
			
		||||
	rep_config.hinted_scheduler.enable = false;
 | 
			
		||||
	rep_config.optimistic_scheduler.enable = false;
 | 
			
		||||
	rep_config.backlog_scan.enable = false;
 | 
			
		||||
	auto & node_rep = *system.add_node (rep_config);
 | 
			
		||||
 | 
			
		||||
	// Create the poisoned node with bootstrap serving enabled
 | 
			
		||||
	nano::node_config poison_config = system.default_config ();
 | 
			
		||||
	poison_config.bootstrap_server.enable = true; // Enable bootstrap serving
 | 
			
		||||
	poison_config.bootstrap.enable = false; // Disable bootstrap from the network
 | 
			
		||||
	// Disable schedulers
 | 
			
		||||
	poison_config.priority_scheduler.enable = false;
 | 
			
		||||
	poison_config.hinted_scheduler.enable = false;
 | 
			
		||||
	poison_config.optimistic_scheduler.enable = false;
 | 
			
		||||
	poison_config.backlog_scan.enable = false;
 | 
			
		||||
	auto & node_poison = *system.add_node (poison_config);
 | 
			
		||||
 | 
			
		||||
	// Representative node needs to hold the genesis key to have voting weight
 | 
			
		||||
	system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
 | 
			
		||||
 | 
			
		||||
	// Create keys for our test accounts
 | 
			
		||||
	nano::keypair key1;
 | 
			
		||||
 | 
			
		||||
	// Create and process blocks on representative node (the correct chain)
 | 
			
		||||
	nano::block_builder builder;
 | 
			
		||||
 | 
			
		||||
	// First send from genesis to key1
 | 
			
		||||
	auto send1 = builder.send ()
 | 
			
		||||
				 .previous (nano::dev::genesis->hash ())
 | 
			
		||||
				 .destination (key1.pub)
 | 
			
		||||
				 .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
 | 
			
		||||
				 .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
 | 
			
		||||
				 .work (*system.work.generate (nano::dev::genesis->hash ()))
 | 
			
		||||
				 .build ();
 | 
			
		||||
 | 
			
		||||
	ASSERT_EQ (nano::block_status::progress, node_rep.process (send1));
 | 
			
		||||
	ASSERT_EQ (nano::block_status::progress, node_poison.process (send1));
 | 
			
		||||
 | 
			
		||||
	// Valid open block for key1 (correct version)
 | 
			
		||||
	auto open_correct = builder.open ()
 | 
			
		||||
						.source (send1->hash ())
 | 
			
		||||
						.representative (key1.pub)
 | 
			
		||||
						.account (key1.pub)
 | 
			
		||||
						.sign (key1.prv, key1.pub)
 | 
			
		||||
						.work (*system.work.generate (key1.pub))
 | 
			
		||||
						.build ();
 | 
			
		||||
 | 
			
		||||
	// Fork of the open block (incorrect version) - using a different representative
 | 
			
		||||
	nano::keypair bad_rep;
 | 
			
		||||
	auto open_fork = builder.open ()
 | 
			
		||||
					 .source (send1->hash ())
 | 
			
		||||
					 .representative (bad_rep.pub) // Different representative
 | 
			
		||||
					 .account (key1.pub)
 | 
			
		||||
					 .sign (key1.prv, key1.pub)
 | 
			
		||||
					 .work (*system.work.generate (key1.pub))
 | 
			
		||||
					 .build ();
 | 
			
		||||
 | 
			
		||||
	// Process the correct open block on the representative node
 | 
			
		||||
	ASSERT_EQ (nano::block_status::progress, node_rep.process (open_correct));
 | 
			
		||||
 | 
			
		||||
	// Process the forked open block on the poisoned node
 | 
			
		||||
	ASSERT_EQ (nano::block_status::progress, node_poison.process (open_fork));
 | 
			
		||||
 | 
			
		||||
	// Confirm the correct block on the representative node
 | 
			
		||||
	nano::test::confirm (node_rep, { open_correct });
 | 
			
		||||
 | 
			
		||||
	// Now create a bootstrapping node that will try to sync from both nodes
 | 
			
		||||
	nano::node_config node_config = system.default_config ();
 | 
			
		||||
	node_config.bootstrap.account_sets.cooldown = 100ms; // Short cooldown between requests to speed up the test
 | 
			
		||||
	node_config.bootstrap.request_timeout = 250ms;
 | 
			
		||||
	node_config.bootstrap.frontier_rate_limit = 100;
 | 
			
		||||
	// Disable schedulers
 | 
			
		||||
	node_config.priority_scheduler.enable = false;
 | 
			
		||||
	node_config.hinted_scheduler.enable = false;
 | 
			
		||||
	node_config.optimistic_scheduler.enable = false;
 | 
			
		||||
	node_config.backlog_scan.enable = false;
 | 
			
		||||
	nano::node_flags node_flags;
 | 
			
		||||
	auto & node = *system.add_node (node_config, node_flags);
 | 
			
		||||
	ASSERT_EQ (node.network.size (), 2);
 | 
			
		||||
 | 
			
		||||
	std::cout << "Main node: " << node.identifier () << std::endl;
 | 
			
		||||
	std::cout << "Waiting for: " << open_fork->hash ().to_string () << std::endl;
 | 
			
		||||
 | 
			
		||||
	// The node should initially get the incorrect block from the poisoned node
 | 
			
		||||
	ASSERT_TIMELY (15s, node.block (open_fork->hash ()) != nullptr);
 | 
			
		||||
	ASSERT_NEVER (1s, node.stats.count (nano::stat::type::ledger, nano::stat::detail::fork) > 0);
 | 
			
		||||
 | 
			
		||||
	// Create another non-rep node that will serve the correct side of the fork
 | 
			
		||||
	nano::node_config correct_config = system.default_config ();
 | 
			
		||||
	correct_config.bootstrap_server.enable = true; // Enable bootstrap serving
 | 
			
		||||
	correct_config.bootstrap.enable = false; // Disable bootstrap from the network
 | 
			
		||||
	correct_config.priority_scheduler.enable = false;
 | 
			
		||||
	correct_config.hinted_scheduler.enable = false;
 | 
			
		||||
	correct_config.optimistic_scheduler.enable = false;
 | 
			
		||||
	auto & node_correct = *system.add_node (correct_config);
 | 
			
		||||
	ASSERT_EQ (node.network.size (), 3);
 | 
			
		||||
 | 
			
		||||
	// Process the correct open block on the non-representative node
 | 
			
		||||
	ASSERT_EQ (nano::block_status::progress, node_correct.process (send1));
 | 
			
		||||
	ASSERT_EQ (nano::block_status::progress, node_correct.process (open_correct));
 | 
			
		||||
 | 
			
		||||
	// The node should at some point notice that there is a forked block
 | 
			
		||||
	ASSERT_TIMELY (15s, node.stats.count (nano::stat::type::ledger, nano::stat::detail::fork) > 0);
 | 
			
		||||
 | 
			
		||||
	// Should no longer be needed, fork should be cached
 | 
			
		||||
	node_correct.stop ();
 | 
			
		||||
 | 
			
		||||
	// We need an election active to force the correct side of the fork
 | 
			
		||||
	ASSERT_TRUE (nano::test::start_election (system, node, open_fork->hash ()));
 | 
			
		||||
 | 
			
		||||
	// Wait for the node to resolve the fork conflict
 | 
			
		||||
	ASSERT_TIMELY (15s, node.block (open_correct->hash ()) != nullptr);
 | 
			
		||||
 | 
			
		||||
	// Verify that the node got the correct block and not the fork
 | 
			
		||||
	ASSERT_NE (nullptr, node.block (open_correct->hash ()));
 | 
			
		||||
	ASSERT_EQ (nullptr, node.block (open_fork->hash ()));
 | 
			
		||||
 | 
			
		||||
	// Verify the account information on the bootstrap node is correct
 | 
			
		||||
	nano::account_info account_info;
 | 
			
		||||
	ASSERT_FALSE (node.store.account.get (node.store.tx_begin_read (), key1.pub, account_info));
 | 
			
		||||
	ASSERT_EQ (account_info.head, open_correct->hash ());
 | 
			
		||||
	ASSERT_EQ (account_info.representative, key1.pub); // Correct representative
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +37,11 @@ void nano::bootstrap_server::start ()
 | 
			
		|||
{
 | 
			
		||||
	debug_assert (threads.empty ());
 | 
			
		||||
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (auto i = 0u; i < config.threads; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		threads.push_back (std::thread ([this] () {
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +112,11 @@ bool nano::bootstrap_server::verify (const nano::asc_pull_req & message) const
 | 
			
		|||
 | 
			
		||||
bool nano::bootstrap_server::request (nano::asc_pull_req const & message, std::shared_ptr<nano::transport::channel> const & channel)
 | 
			
		||||
{
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
	{
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!verify (message))
 | 
			
		||||
	{
 | 
			
		||||
		stats.inc (nano::stat::type::bootstrap_server, nano::stat::detail::invalid);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ public:
 | 
			
		|||
	nano::error serialize (nano::tomlconfig &) const;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	bool enable{ true };
 | 
			
		||||
	size_t max_queue{ 16 };
 | 
			
		||||
	size_t threads{ 1 };
 | 
			
		||||
	size_t batch_size{ 64 };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue