diff --git a/nano/core_test/bootstrap_ascending.cpp b/nano/core_test/bootstrap_ascending.cpp index 015abe203a..0255106469 100644 --- a/nano/core_test/bootstrap_ascending.cpp +++ b/nano/core_test/bootstrap_ascending.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -266,3 +267,291 @@ TEST (bootstrap_ascending, trace_base) // std::cerr << "node1: " << node1.network.endpoint () << std::endl; ASSERT_TIMELY (10s, node1.block (receive1->hash ()) != nullptr); } + +/* + * Tests that bootstrap will prioritize existing accounts with outdated frontiers + */ +TEST (bootstrap_ascending, frontier_scan) +{ + nano::test::system system; + + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap_ascending.enable_scan = false; + config.bootstrap_ascending.enable_dependency_walker = false; + // Disable election activation + config.backlog_population.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens -> 10 updates) + std::vector> sends; + std::vector> opens; + std::vector> updates; + { + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); + + size_t const count = 10; + + for (int n = 0; n < count; ++n) + { + nano::keypair key; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + auto update = builder + .state () + .account (key.pub) + .previous (open->hash ()) + .representative (0) + .balance (1) + .link (0) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + + sends.push_back (send); + opens.push_back (open); + updates.push_back (update); + } + } + + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + blocks.insert (blocks.end (), opens.begin (), opens.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); + + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, updates)); + + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); + + // Frontier scan should detect all the accounts with missing blocks + ASSERT_TIMELY (10s, std::all_of (updates.begin (), updates.end (), [&node1] (auto const & block) { + return node1.ascendboot.prioritized (block->account ()); + })); +} + +/* + * Tests that bootstrap will prioritize not yet existing accounts with pending blocks + */ +TEST (bootstrap_ascending, frontier_scan_pending) +{ + nano::test::system system; + + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap_ascending.enable_scan = false; + config.bootstrap_ascending.enable_dependency_walker = false; + // Disable election activation + config.backlog_population.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens) + std::vector> sends; + std::vector> opens; + { + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); + + size_t const count = 10; + + for (int n = 0; n < count; ++n) + { + nano::keypair key; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + sends.push_back (send); + opens.push_back (open); + } + } + + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); + + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, opens)); + + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); + + // Frontier scan should detect all the accounts with missing blocks + ASSERT_TIMELY (10s, std::all_of (opens.begin (), opens.end (), [&node1] (auto const & block) { + return node1.ascendboot.prioritized (block->account ()); + })); +} + +/* + * Bootstrap should not attempt to prioritize accounts that can't be immediately connected to the ledger (no pending blocks, no existing frontier) + */ +TEST (bootstrap_ascending, frontier_scan_cannot_prioritize) +{ + nano::test::system system; + + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap_ascending.enable_scan = false; + config.bootstrap_ascending.enable_dependency_walker = false; + // Disable election activation + config.backlog_population.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens -> 10 sends -> 10 opens) + std::vector> sends; + std::vector> opens; + std::vector> sends2; + std::vector> opens2; + { + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); + + size_t const count = 10; + + for (int n = 0; n < count; ++n) + { + nano::keypair key, key2; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + auto send2 = builder + .state () + .account (key.pub) + .previous (open->hash ()) + .representative (key.pub) + .balance (0) + .link (key2.pub) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + + auto open2 = builder + .state () + .account (key2.pub) + .previous (0) + .representative (key2.pub) + .balance (1) + .link (send2->hash ()) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2.pub)) + .build (); + + sends.push_back (send); + opens.push_back (open); + sends2.push_back (send2); + opens2.push_back (open2); + } + } + + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + blocks.insert (blocks.end (), opens.begin (), opens.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); + + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, sends2)); + ASSERT_TRUE (nano::test::process (node0, opens2)); + + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); + + // Frontier scan should not detect the accounts + ASSERT_ALWAYS (1s, std::none_of (opens2.begin (), opens2.end (), [&node1] (auto const & block) { + return node1.ascendboot.prioritized (block->account ()); + })); +} \ No newline at end of file diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index ff35244d31..52c1d1eee4 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -217,6 +217,18 @@ std::size_t nano::bootstrap_ascending::service::score_size () const return scoring.size (); } +bool nano::bootstrap_ascending::service::prioritized (nano::account const & account) const +{ + nano::lock_guard lock{ mutex }; + return accounts.prioritized (account); +} + +bool nano::bootstrap_ascending::service::blocked (nano::account const & account) const +{ + nano::lock_guard lock{ mutex }; + return accounts.blocked (account); +} + /** Inspects a block that has been processed by the block processor - Marks an account as blocked if the result code is gap source as there is no reason request additional blocks for this account until the dependency is resolved - Marks an account as forwarded if it has been recently referenced by a block that has been inserted. diff --git a/nano/node/bootstrap_ascending/service.hpp b/nano/node/bootstrap_ascending/service.hpp index ba49ea6c42..e602a26732 100644 --- a/nano/node/bootstrap_ascending/service.hpp +++ b/nano/node/bootstrap_ascending/service.hpp @@ -47,6 +47,8 @@ namespace bootstrap_ascending std::size_t blocked_size () const; std::size_t priority_size () const; std::size_t score_size () const; + bool prioritized (nano::account const &) const; + bool blocked (nano::account const &) const; nano::bootstrap_ascending::account_sets::info_t info () const; private: // Dependencies