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