Merge pull request #4739 from pwojcikdev/frontier-scan-5
Frontier scan client
This commit is contained in:
		
				commit
				
					
						e72a999e4c
					
				
			
		
					 28 changed files with 1144 additions and 333 deletions
				
			
		| 
						 | 
				
			
			@ -436,8 +436,8 @@ TEST (inactive_votes_cache, election_start)
 | 
			
		|||
	nano::test::system system;
 | 
			
		||||
	nano::node_config node_config = system.default_config ();
 | 
			
		||||
	node_config.backlog_population.enable = false;
 | 
			
		||||
	node_config.priority_scheduler.enabled = false;
 | 
			
		||||
	node_config.optimistic_scheduler.enabled = false;
 | 
			
		||||
	node_config.priority_scheduler.enable = false;
 | 
			
		||||
	node_config.optimistic_scheduler.enable = false;
 | 
			
		||||
	auto & node = *system.add_node (node_config);
 | 
			
		||||
	nano::block_hash latest (node.latest (nano::dev::genesis_key.pub));
 | 
			
		||||
	nano::keypair key1, key2;
 | 
			
		||||
| 
						 | 
				
			
			@ -1336,7 +1336,7 @@ TEST (active_elections, limit_vote_hinted_elections)
 | 
			
		|||
	nano::node_config config = system.default_config ();
 | 
			
		||||
	const int aec_limit = 10;
 | 
			
		||||
	config.backlog_population.enable = false;
 | 
			
		||||
	config.optimistic_scheduler.enabled = false;
 | 
			
		||||
	config.optimistic_scheduler.enable = false;
 | 
			
		||||
	config.active_elections.size = aec_limit;
 | 
			
		||||
	config.active_elections.hinted_limit_percentage = 10; // Should give us a limit of 1 hinted election
 | 
			
		||||
	auto & node = *system.add_node (config);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,12 +2,11 @@
 | 
			
		|||
#include <nano/lib/logging.hpp>
 | 
			
		||||
#include <nano/lib/stats.hpp>
 | 
			
		||||
#include <nano/lib/tomlconfig.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/database_scan.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/service.hpp>
 | 
			
		||||
#include <nano/node/make_store.hpp>
 | 
			
		||||
#include <nano/secure/ledger.hpp>
 | 
			
		||||
#include <nano/secure/ledger_set_any.hpp>
 | 
			
		||||
#include <nano/test_common/ledger_context.hpp>
 | 
			
		||||
#include <nano/test_common/chains.hpp>
 | 
			
		||||
#include <nano/test_common/system.hpp>
 | 
			
		||||
#include <nano/test_common/testutil.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -269,218 +268,290 @@ TEST (bootstrap_ascending, trace_base)
 | 
			
		|||
	ASSERT_TIMELY (10s, node1.block (receive1->hash ()) != nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST (bootstrap_ascending, pending_database_scanner)
 | 
			
		||||
/*
 | 
			
		||||
 * Tests that bootstrap will prioritize existing accounts with outdated frontiers
 | 
			
		||||
 */
 | 
			
		||||
TEST (bootstrap_ascending, frontier_scan)
 | 
			
		||||
{
 | 
			
		||||
	nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
 | 
			
		||||
	nano::test::system system;
 | 
			
		||||
 | 
			
		||||
	// Prepare pending sends from genesis
 | 
			
		||||
	// 1 account with 1 pending
 | 
			
		||||
	// 1 account with 21 pendings
 | 
			
		||||
	// 2 accounts with 1 pending each
 | 
			
		||||
	std::deque<std::shared_ptr<nano::block>> blocks;
 | 
			
		||||
	nano::keypair key1, key2, key3, key4;
 | 
			
		||||
	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<std::shared_ptr<nano::block>> sends;
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> opens;
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> updates;
 | 
			
		||||
	{
 | 
			
		||||
		nano::state_block_builder builder;
 | 
			
		||||
 | 
			
		||||
		auto source = nano::dev::genesis_key;
 | 
			
		||||
		auto latest = nano::dev::genesis->hash ();
 | 
			
		||||
		auto balance = nano::dev::genesis->balance ().number ();
 | 
			
		||||
 | 
			
		||||
		// 1 account with 1 pending
 | 
			
		||||
		{
 | 
			
		||||
			auto send = builder.make_block ()
 | 
			
		||||
						.account (source.pub)
 | 
			
		||||
						.previous (latest)
 | 
			
		||||
						.representative (source.pub)
 | 
			
		||||
						.link (key1.pub)
 | 
			
		||||
						.balance (balance - 1)
 | 
			
		||||
						.sign (source.prv, source.pub)
 | 
			
		||||
						.work (*pool.generate (latest))
 | 
			
		||||
						.build ();
 | 
			
		||||
			latest = send->hash ();
 | 
			
		||||
			balance = send->balance_field ().value ().number ();
 | 
			
		||||
			blocks.push_back (send);
 | 
			
		||||
		}
 | 
			
		||||
		// 1 account with 21 pendings
 | 
			
		||||
		for (int i = 0; i < 21; ++i)
 | 
			
		||||
		{
 | 
			
		||||
			auto send = builder.make_block ()
 | 
			
		||||
						.account (source.pub)
 | 
			
		||||
						.previous (latest)
 | 
			
		||||
						.representative (source.pub)
 | 
			
		||||
						.link (key2.pub)
 | 
			
		||||
						.balance (balance - 1)
 | 
			
		||||
						.sign (source.prv, source.pub)
 | 
			
		||||
						.work (*pool.generate (latest))
 | 
			
		||||
						.build ();
 | 
			
		||||
			latest = send->hash ();
 | 
			
		||||
			balance = send->balance_field ().value ().number ();
 | 
			
		||||
			blocks.push_back (send);
 | 
			
		||||
		}
 | 
			
		||||
		// 2 accounts with 1 pending each
 | 
			
		||||
		{
 | 
			
		||||
			auto send = builder.make_block ()
 | 
			
		||||
						.account (source.pub)
 | 
			
		||||
						.previous (latest)
 | 
			
		||||
						.representative (source.pub)
 | 
			
		||||
						.link (key3.pub)
 | 
			
		||||
						.balance (balance - 1)
 | 
			
		||||
						.sign (source.prv, source.pub)
 | 
			
		||||
						.work (*pool.generate (latest))
 | 
			
		||||
						.build ();
 | 
			
		||||
			latest = send->hash ();
 | 
			
		||||
			balance = send->balance_field ().value ().number ();
 | 
			
		||||
			blocks.push_back (send);
 | 
			
		||||
		}
 | 
			
		||||
		{
 | 
			
		||||
			auto send = builder.make_block ()
 | 
			
		||||
						.account (source.pub)
 | 
			
		||||
						.previous (latest)
 | 
			
		||||
						.representative (source.pub)
 | 
			
		||||
						.link (key4.pub)
 | 
			
		||||
						.balance (balance - 1)
 | 
			
		||||
						.sign (source.prv, source.pub)
 | 
			
		||||
						.work (*pool.generate (latest))
 | 
			
		||||
						.build ();
 | 
			
		||||
			latest = send->hash ();
 | 
			
		||||
			balance = send->balance_field ().value ().number ();
 | 
			
		||||
			blocks.push_back (send);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
		size_t const count = 10;
 | 
			
		||||
 | 
			
		||||
	nano::test::ledger_context ctx{ std::move (blocks) };
 | 
			
		||||
 | 
			
		||||
	// Single batch
 | 
			
		||||
	{
 | 
			
		||||
		nano::bootstrap_ascending::pending_database_iterator scanner{ ctx.ledger () };
 | 
			
		||||
		auto transaction = ctx.store ().tx_begin_read ();
 | 
			
		||||
		auto accounts = scanner.next_batch (transaction, 256);
 | 
			
		||||
 | 
			
		||||
		// Check that account set contains all keys
 | 
			
		||||
		ASSERT_EQ (accounts.size (), 4);
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key1.pub) != accounts.end ());
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key2.pub) != accounts.end ());
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key3.pub) != accounts.end ());
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key4.pub) != accounts.end ());
 | 
			
		||||
 | 
			
		||||
		ASSERT_EQ (scanner.completed, 1);
 | 
			
		||||
	}
 | 
			
		||||
	// Multi batch
 | 
			
		||||
	{
 | 
			
		||||
		nano::bootstrap_ascending::pending_database_iterator scanner{ ctx.ledger () };
 | 
			
		||||
		auto transaction = ctx.store ().tx_begin_read ();
 | 
			
		||||
 | 
			
		||||
		// Request accounts in multiple batches
 | 
			
		||||
		auto accounts1 = scanner.next_batch (transaction, 2);
 | 
			
		||||
		auto accounts2 = scanner.next_batch (transaction, 1);
 | 
			
		||||
		auto accounts3 = scanner.next_batch (transaction, 1);
 | 
			
		||||
 | 
			
		||||
		ASSERT_EQ (accounts1.size (), 2);
 | 
			
		||||
		ASSERT_EQ (accounts2.size (), 1);
 | 
			
		||||
		ASSERT_EQ (accounts3.size (), 1);
 | 
			
		||||
 | 
			
		||||
		std::deque<nano::account> accounts;
 | 
			
		||||
		accounts.insert (accounts.end (), accounts1.begin (), accounts1.end ());
 | 
			
		||||
		accounts.insert (accounts.end (), accounts2.begin (), accounts2.end ());
 | 
			
		||||
		accounts.insert (accounts.end (), accounts3.begin (), accounts3.end ());
 | 
			
		||||
 | 
			
		||||
		// Check that account set contains all keys
 | 
			
		||||
		ASSERT_EQ (accounts.size (), 4);
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key1.pub) != accounts.end ());
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key2.pub) != accounts.end ());
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key3.pub) != accounts.end ());
 | 
			
		||||
		ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key4.pub) != accounts.end ());
 | 
			
		||||
 | 
			
		||||
		ASSERT_EQ (scanner.completed, 1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST (bootstrap_ascending, account_database_scanner)
 | 
			
		||||
{
 | 
			
		||||
	nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
 | 
			
		||||
 | 
			
		||||
	size_t const count = 4;
 | 
			
		||||
 | 
			
		||||
	// Prepare some accounts
 | 
			
		||||
	std::deque<std::shared_ptr<nano::block>> blocks;
 | 
			
		||||
	std::deque<nano::keypair> keys;
 | 
			
		||||
	{
 | 
			
		||||
		nano::state_block_builder builder;
 | 
			
		||||
 | 
			
		||||
		auto source = nano::dev::genesis_key;
 | 
			
		||||
		auto latest = nano::dev::genesis->hash ();
 | 
			
		||||
		auto balance = nano::dev::genesis->balance ().number ();
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < count; ++i)
 | 
			
		||||
		for (int n = 0; n < count; ++n)
 | 
			
		||||
		{
 | 
			
		||||
			nano::keypair key;
 | 
			
		||||
			auto send = builder.make_block ()
 | 
			
		||||
			nano::block_builder builder;
 | 
			
		||||
 | 
			
		||||
			balance -= 1;
 | 
			
		||||
			auto send = builder
 | 
			
		||||
						.state ()
 | 
			
		||||
						.account (source.pub)
 | 
			
		||||
						.previous (latest)
 | 
			
		||||
						.representative (source.pub)
 | 
			
		||||
						.balance (balance)
 | 
			
		||||
						.link (key.pub)
 | 
			
		||||
						.balance (balance - 1)
 | 
			
		||||
						.sign (source.prv, source.pub)
 | 
			
		||||
						.work (*pool.generate (latest))
 | 
			
		||||
						.work (*system.work.generate (latest))
 | 
			
		||||
						.build ();
 | 
			
		||||
			auto open = builder.make_block ()
 | 
			
		||||
 | 
			
		||||
			latest = send->hash ();
 | 
			
		||||
 | 
			
		||||
			auto open = builder
 | 
			
		||||
						.state ()
 | 
			
		||||
						.account (key.pub)
 | 
			
		||||
						.previous (0)
 | 
			
		||||
						.representative (key.pub)
 | 
			
		||||
						.link (send->hash ())
 | 
			
		||||
						.balance (1)
 | 
			
		||||
						.link (send->hash ())
 | 
			
		||||
						.sign (key.prv, key.pub)
 | 
			
		||||
						.work (*pool.generate (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<std::shared_ptr<nano::block>> 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<std::shared_ptr<nano::block>> sends;
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> 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 ();
 | 
			
		||||
			balance = send->balance_field ().value ().number ();
 | 
			
		||||
			blocks.push_back (send);
 | 
			
		||||
			blocks.push_back (open);
 | 
			
		||||
			keys.push_back (key);
 | 
			
		||||
 | 
			
		||||
			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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nano::test::ledger_context ctx{ std::move (blocks) };
 | 
			
		||||
	// Initialize nodes with blocks without the `updates` frontiers
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> blocks;
 | 
			
		||||
	blocks.insert (blocks.end (), sends.begin (), sends.end ());
 | 
			
		||||
	system.set_initialization_blocks ({ blocks.begin (), blocks.end () });
 | 
			
		||||
 | 
			
		||||
	// Single batch
 | 
			
		||||
	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<std::shared_ptr<nano::block>> sends;
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> opens;
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> sends2;
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> opens2;
 | 
			
		||||
	{
 | 
			
		||||
		nano::bootstrap_ascending::account_database_iterator scanner{ ctx.ledger () };
 | 
			
		||||
		auto transaction = ctx.store ().tx_begin_read ();
 | 
			
		||||
		auto accounts = scanner.next_batch (transaction, 256);
 | 
			
		||||
		auto source = nano::dev::genesis_key;
 | 
			
		||||
		auto latest = nano::dev::genesis->hash ();
 | 
			
		||||
		auto balance = nano::dev::genesis->balance ().number ();
 | 
			
		||||
 | 
			
		||||
		// Check that account set contains all keys
 | 
			
		||||
		ASSERT_EQ (accounts.size (), keys.size () + 1); // +1 for genesis
 | 
			
		||||
		for (auto const & key : keys)
 | 
			
		||||
		size_t const count = 10;
 | 
			
		||||
 | 
			
		||||
		for (int n = 0; n < count; ++n)
 | 
			
		||||
		{
 | 
			
		||||
			ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key.pub) != accounts.end ());
 | 
			
		||||
			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);
 | 
			
		||||
		}
 | 
			
		||||
		ASSERT_EQ (scanner.completed, 1);
 | 
			
		||||
	}
 | 
			
		||||
	// Multi batch
 | 
			
		||||
	{
 | 
			
		||||
		nano::bootstrap_ascending::account_database_iterator scanner{ ctx.ledger () };
 | 
			
		||||
		auto transaction = ctx.store ().tx_begin_read ();
 | 
			
		||||
 | 
			
		||||
		// Request accounts in multiple batches
 | 
			
		||||
		auto accounts1 = scanner.next_batch (transaction, 2);
 | 
			
		||||
		auto accounts2 = scanner.next_batch (transaction, 2);
 | 
			
		||||
		auto accounts3 = scanner.next_batch (transaction, 1);
 | 
			
		||||
	// Initialize nodes with blocks without the `updates` frontiers
 | 
			
		||||
	std::vector<std::shared_ptr<nano::block>> 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 () });
 | 
			
		||||
 | 
			
		||||
		ASSERT_EQ (accounts1.size (), 2);
 | 
			
		||||
		ASSERT_EQ (accounts2.size (), 2);
 | 
			
		||||
		ASSERT_EQ (accounts3.size (), 1);
 | 
			
		||||
	auto & node0 = *system.add_node (config, flags);
 | 
			
		||||
	ASSERT_TRUE (nano::test::process (node0, sends2));
 | 
			
		||||
	ASSERT_TRUE (nano::test::process (node0, opens2));
 | 
			
		||||
 | 
			
		||||
		std::deque<nano::account> accounts;
 | 
			
		||||
		accounts.insert (accounts.end (), accounts1.begin (), accounts1.end ());
 | 
			
		||||
		accounts.insert (accounts.end (), accounts2.begin (), accounts2.end ());
 | 
			
		||||
		accounts.insert (accounts.end (), accounts3.begin (), accounts3.end ());
 | 
			
		||||
	// 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);
 | 
			
		||||
 | 
			
		||||
		// Check that account set contains all keys
 | 
			
		||||
		ASSERT_EQ (accounts.size (), keys.size () + 1); // +1 for genesis
 | 
			
		||||
		for (auto const & key : keys)
 | 
			
		||||
		{
 | 
			
		||||
			ASSERT_TRUE (std::find (accounts.begin (), accounts.end (), key.pub) != accounts.end ());
 | 
			
		||||
		}
 | 
			
		||||
		ASSERT_EQ (scanner.completed, 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 ());
 | 
			
		||||
	}));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3762,9 +3762,9 @@ TEST (node, local_block_broadcast)
 | 
			
		|||
 | 
			
		||||
	// Disable active elections to prevent the block from being broadcasted by the election
 | 
			
		||||
	auto node_config = system.default_config ();
 | 
			
		||||
	node_config.priority_scheduler.enabled = false;
 | 
			
		||||
	node_config.hinted_scheduler.enabled = false;
 | 
			
		||||
	node_config.optimistic_scheduler.enabled = false;
 | 
			
		||||
	node_config.priority_scheduler.enable = false;
 | 
			
		||||
	node_config.hinted_scheduler.enable = false;
 | 
			
		||||
	node_config.optimistic_scheduler.enable = false;
 | 
			
		||||
	node_config.local_block_broadcaster.rebroadcast_interval = 1s;
 | 
			
		||||
	auto & node1 = *system.add_node (node_config);
 | 
			
		||||
	auto & node2 = *system.make_disconnected_node ();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -244,11 +244,11 @@ TEST (toml, daemon_config_deserialize_defaults)
 | 
			
		|||
	ASSERT_EQ (conf.node.rocksdb_config.read_cache, defaults.node.rocksdb_config.read_cache);
 | 
			
		||||
	ASSERT_EQ (conf.node.rocksdb_config.write_cache, defaults.node.rocksdb_config.write_cache);
 | 
			
		||||
 | 
			
		||||
	ASSERT_EQ (conf.node.optimistic_scheduler.enabled, defaults.node.optimistic_scheduler.enabled);
 | 
			
		||||
	ASSERT_EQ (conf.node.optimistic_scheduler.enable, defaults.node.optimistic_scheduler.enable);
 | 
			
		||||
	ASSERT_EQ (conf.node.optimistic_scheduler.gap_threshold, defaults.node.optimistic_scheduler.gap_threshold);
 | 
			
		||||
	ASSERT_EQ (conf.node.optimistic_scheduler.max_size, defaults.node.optimistic_scheduler.max_size);
 | 
			
		||||
 | 
			
		||||
	ASSERT_EQ (conf.node.hinted_scheduler.enabled, defaults.node.hinted_scheduler.enabled);
 | 
			
		||||
	ASSERT_EQ (conf.node.hinted_scheduler.enable, defaults.node.hinted_scheduler.enable);
 | 
			
		||||
	ASSERT_EQ (conf.node.hinted_scheduler.hinting_threshold_percent, defaults.node.hinted_scheduler.hinting_threshold_percent);
 | 
			
		||||
	ASSERT_EQ (conf.node.hinted_scheduler.check_interval.count (), defaults.node.hinted_scheduler.check_interval.count ());
 | 
			
		||||
	ASSERT_EQ (conf.node.hinted_scheduler.block_cooldown.count (), defaults.node.hinted_scheduler.block_cooldown.count ());
 | 
			
		||||
| 
						 | 
				
			
			@ -599,7 +599,8 @@ TEST (toml, daemon_config_deserialize_no_defaults)
 | 
			
		|||
 | 
			
		||||
	[node.bootstrap_ascending]
 | 
			
		||||
	enable = false
 | 
			
		||||
	enable_database_scan = false
 | 
			
		||||
	enable_frontier_scan = false
 | 
			
		||||
	enable_database_scan = true
 | 
			
		||||
	enable_dependency_walker = false
 | 
			
		||||
	channel_limit = 999
 | 
			
		||||
	database_rate_limit = 999
 | 
			
		||||
| 
						 | 
				
			
			@ -751,11 +752,11 @@ TEST (toml, daemon_config_deserialize_no_defaults)
 | 
			
		|||
	ASSERT_NE (conf.node.rocksdb_config.read_cache, defaults.node.rocksdb_config.read_cache);
 | 
			
		||||
	ASSERT_NE (conf.node.rocksdb_config.write_cache, defaults.node.rocksdb_config.write_cache);
 | 
			
		||||
 | 
			
		||||
	ASSERT_NE (conf.node.optimistic_scheduler.enabled, defaults.node.optimistic_scheduler.enabled);
 | 
			
		||||
	ASSERT_NE (conf.node.optimistic_scheduler.enable, defaults.node.optimistic_scheduler.enable);
 | 
			
		||||
	ASSERT_NE (conf.node.optimistic_scheduler.gap_threshold, defaults.node.optimistic_scheduler.gap_threshold);
 | 
			
		||||
	ASSERT_NE (conf.node.optimistic_scheduler.max_size, defaults.node.optimistic_scheduler.max_size);
 | 
			
		||||
 | 
			
		||||
	ASSERT_NE (conf.node.hinted_scheduler.enabled, defaults.node.hinted_scheduler.enabled);
 | 
			
		||||
	ASSERT_NE (conf.node.hinted_scheduler.enable, defaults.node.hinted_scheduler.enable);
 | 
			
		||||
	ASSERT_NE (conf.node.hinted_scheduler.hinting_threshold_percent, defaults.node.hinted_scheduler.hinting_threshold_percent);
 | 
			
		||||
	ASSERT_NE (conf.node.hinted_scheduler.check_interval.count (), defaults.node.hinted_scheduler.check_interval.count ());
 | 
			
		||||
	ASSERT_NE (conf.node.hinted_scheduler.block_cooldown.count (), defaults.node.hinted_scheduler.block_cooldown.count ());
 | 
			
		||||
| 
						 | 
				
			
			@ -777,6 +778,7 @@ TEST (toml, daemon_config_deserialize_no_defaults)
 | 
			
		|||
	ASSERT_NE (conf.node.vote_processor.batch_size, defaults.node.vote_processor.batch_size);
 | 
			
		||||
 | 
			
		||||
	ASSERT_NE (conf.node.bootstrap_ascending.enable, defaults.node.bootstrap_ascending.enable);
 | 
			
		||||
	ASSERT_NE (conf.node.bootstrap_ascending.enable_frontier_scan, defaults.node.bootstrap_ascending.enable_frontier_scan);
 | 
			
		||||
	ASSERT_NE (conf.node.bootstrap_ascending.enable_database_scan, defaults.node.bootstrap_ascending.enable_database_scan);
 | 
			
		||||
	ASSERT_NE (conf.node.bootstrap_ascending.enable_dependency_walker, defaults.node.bootstrap_ascending.enable_dependency_walker);
 | 
			
		||||
	ASSERT_NE (conf.node.bootstrap_ascending.channel_limit, defaults.node.bootstrap_ascending.channel_limit);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,8 +22,8 @@ TEST (vote_processor, codes)
 | 
			
		|||
	auto node_config = system.default_config ();
 | 
			
		||||
	// Disable all election schedulers
 | 
			
		||||
	node_config.backlog_population.enable = false;
 | 
			
		||||
	node_config.hinted_scheduler.enabled = false;
 | 
			
		||||
	node_config.optimistic_scheduler.enabled = false;
 | 
			
		||||
	node_config.hinted_scheduler.enable = false;
 | 
			
		||||
	node_config.optimistic_scheduler.enable = false;
 | 
			
		||||
	auto & node = *system.add_node (node_config);
 | 
			
		||||
 | 
			
		||||
	auto blocks = nano::test::setup_chain (system, node, 1, nano::dev::genesis_key, false);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,10 +66,10 @@ class rate_limiter final
 | 
			
		|||
{
 | 
			
		||||
public:
 | 
			
		||||
	// initialize with limit 0 = unbounded
 | 
			
		||||
	rate_limiter (std::size_t limit, double burst_ratio);
 | 
			
		||||
	rate_limiter (std::size_t limit, double burst_ratio = 1.0);
 | 
			
		||||
 | 
			
		||||
	bool should_pass (std::size_t buffer_size);
 | 
			
		||||
	void reset (std::size_t limit, double burst_ratio);
 | 
			
		||||
	void reset (std::size_t limit, double burst_ratio = 1.0);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	nano::rate::token_bucket bucket;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,11 +60,13 @@ enum class type
 | 
			
		|||
	blockprocessor_overfill,
 | 
			
		||||
	bootstrap_ascending,
 | 
			
		||||
	bootstrap_ascending_accounts,
 | 
			
		||||
	bootstrap_ascending_verify,
 | 
			
		||||
	bootstrap_ascending_verify_blocks,
 | 
			
		||||
	bootstrap_ascending_verify_frontiers,
 | 
			
		||||
	bootstrap_ascending_process,
 | 
			
		||||
	bootstrap_ascending_request,
 | 
			
		||||
	bootstrap_ascending_reply,
 | 
			
		||||
	bootstrap_ascending_next,
 | 
			
		||||
	bootstrap_ascending_frontiers,
 | 
			
		||||
	bootstrap_server,
 | 
			
		||||
	bootstrap_server_request,
 | 
			
		||||
	bootstrap_server_overfill,
 | 
			
		||||
| 
						 | 
				
			
			@ -118,6 +120,7 @@ enum class detail
 | 
			
		|||
	inserted,
 | 
			
		||||
	erased,
 | 
			
		||||
	request,
 | 
			
		||||
	request_failed,
 | 
			
		||||
	broadcast,
 | 
			
		||||
	cleanup,
 | 
			
		||||
	top,
 | 
			
		||||
| 
						 | 
				
			
			@ -137,6 +140,8 @@ enum class detail
 | 
			
		|||
	empty,
 | 
			
		||||
	done,
 | 
			
		||||
	retry,
 | 
			
		||||
	prioritized,
 | 
			
		||||
	pending,
 | 
			
		||||
 | 
			
		||||
	// processing queue
 | 
			
		||||
	queue,
 | 
			
		||||
| 
						 | 
				
			
			@ -430,7 +435,7 @@ enum class detail
 | 
			
		|||
	missing_cookie,
 | 
			
		||||
	invalid_genesis,
 | 
			
		||||
 | 
			
		||||
	// bootstrap ascending
 | 
			
		||||
	// bootstrap_ascending
 | 
			
		||||
	missing_tag,
 | 
			
		||||
	reply,
 | 
			
		||||
	throttled,
 | 
			
		||||
| 
						 | 
				
			
			@ -438,13 +443,18 @@ enum class detail
 | 
			
		|||
	timeout,
 | 
			
		||||
	nothing_new,
 | 
			
		||||
	account_info_empty,
 | 
			
		||||
	frontiers_empty,
 | 
			
		||||
	loop_database,
 | 
			
		||||
	loop_dependencies,
 | 
			
		||||
	loop_frontiers,
 | 
			
		||||
	loop_frontiers_processing,
 | 
			
		||||
	duplicate_request,
 | 
			
		||||
	invalid_response_type,
 | 
			
		||||
	timestamp_reset,
 | 
			
		||||
	process_frontiers,
 | 
			
		||||
	dropped_frontiers,
 | 
			
		||||
 | 
			
		||||
	// bootstrap ascending accounts
 | 
			
		||||
	// bootstrap_ascending_accounts
 | 
			
		||||
	prioritize,
 | 
			
		||||
	prioritize_failed,
 | 
			
		||||
	block,
 | 
			
		||||
| 
						 | 
				
			
			@ -453,11 +463,20 @@ enum class detail
 | 
			
		|||
	dependency_update,
 | 
			
		||||
	dependency_update_failed,
 | 
			
		||||
 | 
			
		||||
	// bootstrap_ascending_frontiers
 | 
			
		||||
	done_range,
 | 
			
		||||
	done_empty,
 | 
			
		||||
	next_by_requests,
 | 
			
		||||
	next_by_timestamp,
 | 
			
		||||
	advance,
 | 
			
		||||
	advance_failed,
 | 
			
		||||
 | 
			
		||||
	next_none,
 | 
			
		||||
	next_priority,
 | 
			
		||||
	next_database,
 | 
			
		||||
	next_blocking,
 | 
			
		||||
	next_dependency,
 | 
			
		||||
	next_frontier,
 | 
			
		||||
 | 
			
		||||
	blocking_insert,
 | 
			
		||||
	blocking_erase_overflow,
 | 
			
		||||
| 
						 | 
				
			
			@ -475,6 +494,7 @@ enum class detail
 | 
			
		|||
	// active
 | 
			
		||||
	started_hinted,
 | 
			
		||||
	started_optimistic,
 | 
			
		||||
 | 
			
		||||
	// rep_crawler
 | 
			
		||||
	channel_dead,
 | 
			
		||||
	query_target_failed,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -104,7 +104,10 @@ std::string nano::thread_role::get_string (nano::thread_role::name role)
 | 
			
		|||
			thread_role_name_string = "Voting que";
 | 
			
		||||
			break;
 | 
			
		||||
		case nano::thread_role::name::ascending_bootstrap:
 | 
			
		||||
			thread_role_name_string = "Bootstrap asc";
 | 
			
		||||
			thread_role_name_string = "Ascboot";
 | 
			
		||||
			break;
 | 
			
		||||
		case nano::thread_role::name::ascending_bootstrap_worker:
 | 
			
		||||
			thread_role_name_string = "Ascboot work";
 | 
			
		||||
			break;
 | 
			
		||||
		case nano::thread_role::name::bootstrap_server:
 | 
			
		||||
			thread_role_name_string = "Bootstrap serv";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,6 +42,7 @@ enum class name
 | 
			
		|||
	bootstrap_server,
 | 
			
		||||
	telemetry,
 | 
			
		||||
	ascending_bootstrap,
 | 
			
		||||
	ascending_bootstrap_worker,
 | 
			
		||||
	bootstrap_server_requests,
 | 
			
		||||
	bootstrap_server_responses,
 | 
			
		||||
	scheduler_hinted,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,13 +46,15 @@ add_library(
 | 
			
		|||
  bootstrap/bootstrap.cpp
 | 
			
		||||
  bootstrap/bootstrap_server.hpp
 | 
			
		||||
  bootstrap/bootstrap_server.cpp
 | 
			
		||||
  bootstrap_ascending/common.hpp
 | 
			
		||||
  bootstrap_ascending/throttle.hpp
 | 
			
		||||
  bootstrap_ascending/throttle.cpp
 | 
			
		||||
  bootstrap_ascending/account_sets.hpp
 | 
			
		||||
  bootstrap_ascending/account_sets.cpp
 | 
			
		||||
  bootstrap_ascending/database_scan.hpp
 | 
			
		||||
  bootstrap_ascending/database_scan.cpp
 | 
			
		||||
  bootstrap_ascending/common.hpp
 | 
			
		||||
  bootstrap_ascending/frontier_scan.hpp
 | 
			
		||||
  bootstrap_ascending/frontier_scan.cpp
 | 
			
		||||
  bootstrap_ascending/throttle.hpp
 | 
			
		||||
  bootstrap_ascending/throttle.cpp
 | 
			
		||||
  bootstrap_ascending/peer_scoring.hpp
 | 
			
		||||
  bootstrap_ascending/peer_scoring.cpp
 | 
			
		||||
  bootstrap_ascending/service.hpp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@ nano::error nano::bootstrap_ascending_config::deserialize (nano::tomlconfig & to
 | 
			
		|||
	toml.get ("enable", enable);
 | 
			
		||||
	toml.get ("enable_database_scan", enable_database_scan);
 | 
			
		||||
	toml.get ("enable_dependency_walker", enable_dependency_walker);
 | 
			
		||||
	toml.get ("enable_frontier_scan", enable_frontier_scan);
 | 
			
		||||
 | 
			
		||||
	toml.get ("channel_limit", channel_limit);
 | 
			
		||||
	toml.get ("database_rate_limit", database_rate_limit);
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +60,7 @@ nano::error nano::bootstrap_ascending_config::serialize (nano::tomlconfig & toml
 | 
			
		|||
	toml.put ("enable", enable, "Enable or disable the ascending bootstrap. Disabling it is not recommended and will prevent the node from syncing.\ntype:bool");
 | 
			
		||||
	toml.put ("enable_database_scan", enable_database_scan, "Enable or disable the 'database scan` strategy for the ascending bootstrap.\ntype:bool");
 | 
			
		||||
	toml.put ("enable_dependency_walker", enable_dependency_walker, "Enable or disable the 'dependency walker` strategy for the ascending bootstrap.\ntype:bool");
 | 
			
		||||
	toml.put ("enable_frontier_scan", enable_frontier_scan, "Enable or disable the 'frontier scan` strategy for the ascending bootstrap.\ntype:bool");
 | 
			
		||||
 | 
			
		||||
	toml.put ("channel_limit", channel_limit, "Maximum number of un-responded requests per channel.\nNote: changing to unlimited (0) is not recommended.\ntype:uint64");
 | 
			
		||||
	toml.put ("database_rate_limit", database_rate_limit, "Rate limit on scanning accounts and pending entries from database.\nNote: changing to unlimited (0) is not recommended as this operation competes for resources on querying the database.\ntype:uint64");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,19 @@ public:
 | 
			
		|||
	std::chrono::milliseconds cooldown{ 1000 * 3 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: This should be moved next to `frontier_scan` class
 | 
			
		||||
class frontier_scan_config final
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	// TODO: Serialize & deserialize
 | 
			
		||||
 | 
			
		||||
	unsigned head_parallelistm{ 128 };
 | 
			
		||||
	unsigned consideration_count{ 4 };
 | 
			
		||||
	std::size_t candidates{ 1000 };
 | 
			
		||||
	std::chrono::milliseconds cooldown{ 1000 * 5 };
 | 
			
		||||
	std::size_t max_pending{ 16 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: This should be moved next to `bootstrap_ascending` class
 | 
			
		||||
class bootstrap_ascending_config final
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -31,12 +44,15 @@ public:
 | 
			
		|||
 | 
			
		||||
public:
 | 
			
		||||
	bool enable{ true };
 | 
			
		||||
	bool enable_database_scan{ true };
 | 
			
		||||
	bool enable_scan{ true };
 | 
			
		||||
	bool enable_database_scan{ false };
 | 
			
		||||
	bool enable_dependency_walker{ true };
 | 
			
		||||
	bool enable_frontier_scan{ true };
 | 
			
		||||
 | 
			
		||||
	// Maximum number of un-responded requests per channel, should be lower or equal to bootstrap server max queue size
 | 
			
		||||
	std::size_t channel_limit{ 16 };
 | 
			
		||||
	std::size_t database_rate_limit{ 256 };
 | 
			
		||||
	std::size_t frontier_rate_limit{ 8 };
 | 
			
		||||
	std::size_t database_warmup_ratio{ 10 };
 | 
			
		||||
	std::size_t max_pull_count{ nano::bootstrap_server::max_blocks };
 | 
			
		||||
	std::chrono::milliseconds request_timeout{ 1000 * 5 };
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +61,7 @@ public:
 | 
			
		|||
	std::size_t block_processor_threshold{ 1000 };
 | 
			
		||||
	std::size_t max_requests{ 1024 };
 | 
			
		||||
 | 
			
		||||
	nano::account_sets_config account_sets;
 | 
			
		||||
	account_sets_config account_sets;
 | 
			
		||||
	frontier_scan_config frontier_scan;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										187
									
								
								nano/node/bootstrap_ascending/crawlers.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								nano/node/bootstrap_ascending/crawlers.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,187 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <nano/secure/account_info.hpp>
 | 
			
		||||
#include <nano/secure/pending_info.hpp>
 | 
			
		||||
#include <nano/store/account.hpp>
 | 
			
		||||
#include <nano/store/component.hpp>
 | 
			
		||||
#include <nano/store/pending.hpp>
 | 
			
		||||
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
namespace nano::bootstrap_ascending
 | 
			
		||||
{
 | 
			
		||||
struct account_database_crawler
 | 
			
		||||
{
 | 
			
		||||
	using value_type = std::pair<nano::account, nano::account_info>;
 | 
			
		||||
 | 
			
		||||
	static constexpr size_t sequential_attempts = 10;
 | 
			
		||||
 | 
			
		||||
	account_database_crawler (nano::store::component & store, nano::store::transaction const & transaction, nano::account const & start) :
 | 
			
		||||
		store{ store },
 | 
			
		||||
		transaction{ transaction },
 | 
			
		||||
		it{ store.account.end (transaction) },
 | 
			
		||||
		end{ store.account.end (transaction) }
 | 
			
		||||
	{
 | 
			
		||||
		seek (start);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void seek (nano::account const & account)
 | 
			
		||||
	{
 | 
			
		||||
		it = store.account.begin (transaction, account);
 | 
			
		||||
		update_current ();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void advance ()
 | 
			
		||||
	{
 | 
			
		||||
		if (it == end)
 | 
			
		||||
		{
 | 
			
		||||
			debug_assert (!current);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		++it;
 | 
			
		||||
		update_current ();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void advance_to (nano::account const & account)
 | 
			
		||||
	{
 | 
			
		||||
		if (it == end)
 | 
			
		||||
		{
 | 
			
		||||
			debug_assert (!current);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// First try advancing sequentially
 | 
			
		||||
		for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it)
 | 
			
		||||
		{
 | 
			
		||||
			// Break if we've reached or overshoot the target account
 | 
			
		||||
			if (it->first.number () >= account.number ())
 | 
			
		||||
			{
 | 
			
		||||
				update_current ();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If that fails, perform a fresh lookup
 | 
			
		||||
		seek (account);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::optional<value_type> current{};
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void update_current ()
 | 
			
		||||
	{
 | 
			
		||||
		if (it != end)
 | 
			
		||||
		{
 | 
			
		||||
			current = *it;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			current = std::nullopt;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nano::store::component & store;
 | 
			
		||||
	nano::store::transaction const & transaction;
 | 
			
		||||
 | 
			
		||||
	nano::store::account::iterator it;
 | 
			
		||||
	nano::store::account::iterator const end;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct pending_database_crawler
 | 
			
		||||
{
 | 
			
		||||
	using value_type = std::pair<nano::pending_key, nano::pending_info>;
 | 
			
		||||
 | 
			
		||||
	static constexpr size_t sequential_attempts = 10;
 | 
			
		||||
 | 
			
		||||
	pending_database_crawler (nano::store::component & store, nano::store::transaction const & transaction, nano::account const & start) :
 | 
			
		||||
		store{ store },
 | 
			
		||||
		transaction{ transaction },
 | 
			
		||||
		it{ store.pending.end (transaction) },
 | 
			
		||||
		end{ store.pending.end (transaction) }
 | 
			
		||||
	{
 | 
			
		||||
		seek (start);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void seek (nano::account const & account)
 | 
			
		||||
	{
 | 
			
		||||
		it = store.pending.begin (transaction, { account, 0 });
 | 
			
		||||
		update_current ();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Advance to the next account
 | 
			
		||||
	void advance ()
 | 
			
		||||
	{
 | 
			
		||||
		if (it == end)
 | 
			
		||||
		{
 | 
			
		||||
			debug_assert (!current);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto const starting_account = it->first.account;
 | 
			
		||||
 | 
			
		||||
		// First try advancing sequentially
 | 
			
		||||
		for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it)
 | 
			
		||||
		{
 | 
			
		||||
			// Break if we've reached the next account
 | 
			
		||||
			if (it->first.account != starting_account)
 | 
			
		||||
			{
 | 
			
		||||
				update_current ();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (it != end)
 | 
			
		||||
		{
 | 
			
		||||
			// If that fails, perform a fresh lookup
 | 
			
		||||
			seek (starting_account.number () + 1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		update_current ();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void advance_to (nano::account const & account)
 | 
			
		||||
	{
 | 
			
		||||
		if (it == end)
 | 
			
		||||
		{
 | 
			
		||||
			debug_assert (!current);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// First try advancing sequentially
 | 
			
		||||
		for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it)
 | 
			
		||||
		{
 | 
			
		||||
			// Break if we've reached or overshoot the target account
 | 
			
		||||
			if (it->first.account.number () >= account.number ())
 | 
			
		||||
			{
 | 
			
		||||
				update_current ();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If that fails, perform a fresh lookup
 | 
			
		||||
		seek (account);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::optional<value_type> current{};
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void update_current ()
 | 
			
		||||
	{
 | 
			
		||||
		if (it != end)
 | 
			
		||||
		{
 | 
			
		||||
			current = *it;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			current = std::nullopt;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nano::store::component & store;
 | 
			
		||||
	nano::store::transaction const & transaction;
 | 
			
		||||
 | 
			
		||||
	nano::store::pending::iterator it;
 | 
			
		||||
	nano::store::pending::iterator const end;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
#include <nano/lib/utility.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/crawlers.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/database_scan.hpp>
 | 
			
		||||
#include <nano/secure/common.hpp>
 | 
			
		||||
#include <nano/secure/ledger.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -13,8 +14,8 @@
 | 
			
		|||
 | 
			
		||||
nano::bootstrap_ascending::database_scan::database_scan (nano::ledger & ledger_a) :
 | 
			
		||||
	ledger{ ledger_a },
 | 
			
		||||
	accounts_iterator{ ledger },
 | 
			
		||||
	pending_iterator{ ledger }
 | 
			
		||||
	account_scanner{ ledger },
 | 
			
		||||
	pending_scanner{ ledger }
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,8 +44,8 @@ void nano::bootstrap_ascending::database_scan::fill ()
 | 
			
		|||
{
 | 
			
		||||
	auto transaction = ledger.store.tx_begin_read ();
 | 
			
		||||
 | 
			
		||||
	auto set1 = accounts_iterator.next_batch (transaction, batch_size);
 | 
			
		||||
	auto set2 = pending_iterator.next_batch (transaction, batch_size);
 | 
			
		||||
	auto set1 = account_scanner.next_batch (transaction, batch_size);
 | 
			
		||||
	auto set2 = pending_scanner.next_batch (transaction, batch_size);
 | 
			
		||||
 | 
			
		||||
	queue.insert (queue.end (), set1.begin (), set1.end ());
 | 
			
		||||
	queue.insert (queue.end (), set2.begin (), set2.end ());
 | 
			
		||||
| 
						 | 
				
			
			@ -52,41 +53,36 @@ void nano::bootstrap_ascending::database_scan::fill ()
 | 
			
		|||
 | 
			
		||||
bool nano::bootstrap_ascending::database_scan::warmed_up () const
 | 
			
		||||
{
 | 
			
		||||
	return accounts_iterator.warmed_up () && pending_iterator.warmed_up ();
 | 
			
		||||
	return account_scanner.completed > 0 && pending_scanner.completed > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nano::container_info nano::bootstrap_ascending::database_scan::container_info () const
 | 
			
		||||
{
 | 
			
		||||
	nano::container_info info;
 | 
			
		||||
	info.put ("accounts_iterator", accounts_iterator.completed);
 | 
			
		||||
	info.put ("pending_iterator", pending_iterator.completed);
 | 
			
		||||
	info.put ("accounts_iterator", account_scanner.completed);
 | 
			
		||||
	info.put ("pending_iterator", pending_scanner.completed);
 | 
			
		||||
	return info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * account_database_iterator
 | 
			
		||||
 * account_database_scanner
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
nano::bootstrap_ascending::account_database_iterator::account_database_iterator (nano::ledger & ledger_a) :
 | 
			
		||||
	ledger{ ledger_a }
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::deque<nano::account> nano::bootstrap_ascending::account_database_iterator::next_batch (nano::store::transaction & transaction, size_t batch_size)
 | 
			
		||||
std::deque<nano::account> nano::bootstrap_ascending::account_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size)
 | 
			
		||||
{
 | 
			
		||||
	std::deque<nano::account> result;
 | 
			
		||||
 | 
			
		||||
	auto it = ledger.store.account.begin (transaction, next);
 | 
			
		||||
	auto const end = ledger.store.account.end (transaction);
 | 
			
		||||
	account_database_crawler crawler{ ledger.store, transaction, next };
 | 
			
		||||
 | 
			
		||||
	for (size_t count = 0; it != end && count < batch_size; ++it, ++count)
 | 
			
		||||
	for (size_t count = 0; crawler.current && count < batch_size; crawler.advance (), ++count)
 | 
			
		||||
	{
 | 
			
		||||
		auto const & account = it->first;
 | 
			
		||||
		auto const & [account, info] = crawler.current.value ();
 | 
			
		||||
		result.push_back (account);
 | 
			
		||||
		next = account.number () + 1;
 | 
			
		||||
		next = account.number () + 1; // TODO: Handle account number overflow
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (it == end)
 | 
			
		||||
	// Empty current value indicates the end of the table
 | 
			
		||||
	if (!crawler.current)
 | 
			
		||||
	{
 | 
			
		||||
		// Reset for the next ledger iteration
 | 
			
		||||
		next = { 0 };
 | 
			
		||||
| 
						 | 
				
			
			@ -96,72 +92,30 @@ std::deque<nano::account> nano::bootstrap_ascending::account_database_iterator::
 | 
			
		|||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nano::bootstrap_ascending::account_database_iterator::warmed_up () const
 | 
			
		||||
{
 | 
			
		||||
	return completed > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * pending_database_iterator
 | 
			
		||||
 * pending_database_scanner
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
nano::bootstrap_ascending::pending_database_iterator::pending_database_iterator (nano::ledger & ledger_a) :
 | 
			
		||||
	ledger{ ledger_a }
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::deque<nano::account> nano::bootstrap_ascending::pending_database_iterator::next_batch (nano::store::transaction & transaction, size_t batch_size)
 | 
			
		||||
std::deque<nano::account> nano::bootstrap_ascending::pending_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size)
 | 
			
		||||
{
 | 
			
		||||
	std::deque<nano::account> result;
 | 
			
		||||
 | 
			
		||||
	auto it = ledger.store.pending.begin (transaction, next);
 | 
			
		||||
	auto const end = ledger.store.pending.end (transaction);
 | 
			
		||||
	pending_database_crawler crawler{ ledger.store, transaction, next };
 | 
			
		||||
 | 
			
		||||
	// TODO: This pending iteration heuristic should be encapsulated in a pending_iterator class and reused across other components
 | 
			
		||||
	// The heuristic is to advance the iterator sequentially until we reach a new account or perform a fresh lookup if the account has too many pending blocks
 | 
			
		||||
	// This is to avoid the overhead of performing a fresh lookup for every pending account as majority of accounts have only a few pending blocks
 | 
			
		||||
	auto advance_iterator = [&] () {
 | 
			
		||||
		auto const starting_account = it->first.account;
 | 
			
		||||
 | 
			
		||||
		// For RocksDB, sequential access is ~10x faster than performing a fresh lookup (tested on my machine)
 | 
			
		||||
		const size_t sequential_attempts = 10;
 | 
			
		||||
 | 
			
		||||
		// First try advancing sequentially
 | 
			
		||||
		for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it)
 | 
			
		||||
		{
 | 
			
		||||
			if (it->first.account != starting_account)
 | 
			
		||||
			{
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If we didn't advance to the next account, perform a fresh lookup
 | 
			
		||||
		if (it != end && it->first.account == starting_account)
 | 
			
		||||
		{
 | 
			
		||||
			it = ledger.store.pending.begin (transaction, { starting_account.number () + 1, 0 });
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		debug_assert (it == end || it->first.account != starting_account);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	for (size_t count = 0; it != end && count < batch_size; advance_iterator (), ++count)
 | 
			
		||||
	for (size_t count = 0; crawler.current && count < batch_size; crawler.advance (), ++count)
 | 
			
		||||
	{
 | 
			
		||||
		auto const & account = it->first.account;
 | 
			
		||||
		result.push_back (account);
 | 
			
		||||
		next = { account.number () + 1, 0 };
 | 
			
		||||
		auto const & [key, info] = crawler.current.value ();
 | 
			
		||||
		result.push_back (key.account);
 | 
			
		||||
		next = key.account.number () + 1; // TODO: Handle account number overflow
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (it == end)
 | 
			
		||||
	// Empty current value indicates the end of the table
 | 
			
		||||
	if (!crawler.current)
 | 
			
		||||
	{
 | 
			
		||||
		// Reset for the next ledger iteration
 | 
			
		||||
		next = { 0, 0 };
 | 
			
		||||
		next = { 0 };
 | 
			
		||||
		++completed;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nano::bootstrap_ascending::pending_database_iterator::warmed_up () const
 | 
			
		||||
{
 | 
			
		||||
	return completed > 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,27 +9,23 @@
 | 
			
		|||
 | 
			
		||||
namespace nano::bootstrap_ascending
 | 
			
		||||
{
 | 
			
		||||
struct account_database_iterator
 | 
			
		||||
struct account_database_scanner
 | 
			
		||||
{
 | 
			
		||||
	explicit account_database_iterator (nano::ledger &);
 | 
			
		||||
	nano::ledger & ledger;
 | 
			
		||||
 | 
			
		||||
	std::deque<nano::account> next_batch (nano::store::transaction &, size_t batch_size);
 | 
			
		||||
	bool warmed_up () const;
 | 
			
		||||
 | 
			
		||||
	nano::ledger & ledger;
 | 
			
		||||
	nano::account next{ 0 };
 | 
			
		||||
	size_t completed{ 0 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct pending_database_iterator
 | 
			
		||||
struct pending_database_scanner
 | 
			
		||||
{
 | 
			
		||||
	explicit pending_database_iterator (nano::ledger &);
 | 
			
		||||
	nano::ledger & ledger;
 | 
			
		||||
 | 
			
		||||
	std::deque<nano::account> next_batch (nano::store::transaction &, size_t batch_size);
 | 
			
		||||
	bool warmed_up () const;
 | 
			
		||||
 | 
			
		||||
	nano::ledger & ledger;
 | 
			
		||||
	nano::pending_key next{ 0, 0 };
 | 
			
		||||
	nano::account next{ 0 };
 | 
			
		||||
	size_t completed{ 0 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,8 +48,8 @@ private:
 | 
			
		|||
	void fill ();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	account_database_iterator accounts_iterator;
 | 
			
		||||
	pending_database_iterator pending_iterator;
 | 
			
		||||
	account_database_scanner account_scanner;
 | 
			
		||||
	pending_database_scanner pending_scanner;
 | 
			
		||||
 | 
			
		||||
	std::deque<nano::account> queue;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										188
									
								
								nano/node/bootstrap_ascending/frontier_scan.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								nano/node/bootstrap_ascending/frontier_scan.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,188 @@
 | 
			
		|||
#include <nano/node/bootstrap_ascending/frontier_scan.hpp>
 | 
			
		||||
 | 
			
		||||
#include <boost/multiprecision/cpp_dec_float.hpp>
 | 
			
		||||
#include <boost/multiprecision/cpp_int.hpp>
 | 
			
		||||
 | 
			
		||||
nano::bootstrap_ascending::frontier_scan::frontier_scan (frontier_scan_config const & config_a, nano::stats & stats_a) :
 | 
			
		||||
	config{ config_a },
 | 
			
		||||
	stats{ stats_a }
 | 
			
		||||
{
 | 
			
		||||
	// Divide nano::account numeric range into consecutive and equal ranges
 | 
			
		||||
	nano::uint256_t max_account = std::numeric_limits<nano::uint256_t>::max ();
 | 
			
		||||
	nano::uint256_t range_size = max_account / config.head_parallelistm;
 | 
			
		||||
 | 
			
		||||
	for (unsigned i = 0; i < config.head_parallelistm; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		// Start at 1 to avoid the burn account
 | 
			
		||||
		nano::uint256_t start = (i == 0) ? 1 : i * range_size;
 | 
			
		||||
		nano::uint256_t end = (i == config.head_parallelistm - 1) ? max_account : start + range_size;
 | 
			
		||||
 | 
			
		||||
		heads.emplace_back (frontier_head{ nano::account{ start }, nano::account{ end } });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	release_assert (!heads.empty ());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nano::account nano::bootstrap_ascending::frontier_scan::next ()
 | 
			
		||||
{
 | 
			
		||||
	auto const cutoff = std::chrono::steady_clock::now () - config.cooldown;
 | 
			
		||||
 | 
			
		||||
	auto & heads_by_timestamp = heads.get<tag_timestamp> ();
 | 
			
		||||
	for (auto it = heads_by_timestamp.begin (); it != heads_by_timestamp.end (); ++it)
 | 
			
		||||
	{
 | 
			
		||||
		auto const & head = *it;
 | 
			
		||||
 | 
			
		||||
		if (head.requests < config.consideration_count || head.timestamp < cutoff)
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_frontiers, (head.requests < config.consideration_count) ? nano::stat::detail::next_by_requests : nano::stat::detail::next_by_timestamp);
 | 
			
		||||
 | 
			
		||||
			debug_assert (head.next.number () >= head.start.number ());
 | 
			
		||||
			debug_assert (head.next.number () < head.end.number ());
 | 
			
		||||
 | 
			
		||||
			auto result = head.next;
 | 
			
		||||
 | 
			
		||||
			heads_by_timestamp.modify (it, [this] (auto & entry) {
 | 
			
		||||
				entry.requests += 1;
 | 
			
		||||
				entry.timestamp = std::chrono::steady_clock::now ();
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::next_none);
 | 
			
		||||
	return { 0 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nano::bootstrap_ascending::frontier_scan::process (nano::account start, std::deque<std::pair<nano::account, nano::block_hash>> const & response)
 | 
			
		||||
{
 | 
			
		||||
	debug_assert (std::all_of (response.begin (), response.end (), [&] (auto const & pair) { return pair.first.number () >= start.number (); }));
 | 
			
		||||
 | 
			
		||||
	stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::process);
 | 
			
		||||
 | 
			
		||||
	// Find the first head with head.start <= start
 | 
			
		||||
	auto & heads_by_start = heads.get<tag_start> ();
 | 
			
		||||
	auto it = heads_by_start.upper_bound (start);
 | 
			
		||||
	release_assert (it != heads_by_start.begin ());
 | 
			
		||||
	it = std::prev (it);
 | 
			
		||||
 | 
			
		||||
	bool done = false;
 | 
			
		||||
	heads_by_start.modify (it, [this, &response, &done] (frontier_head & entry) {
 | 
			
		||||
		entry.completed += 1;
 | 
			
		||||
 | 
			
		||||
		for (auto const & [account, _] : response)
 | 
			
		||||
		{
 | 
			
		||||
			// Only consider candidates that actually advance the current frontier
 | 
			
		||||
			if (account.number () > entry.next.number ())
 | 
			
		||||
			{
 | 
			
		||||
				entry.candidates.insert (account);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Trim the candidates
 | 
			
		||||
		while (entry.candidates.size () > config.candidates)
 | 
			
		||||
		{
 | 
			
		||||
			release_assert (!entry.candidates.empty ());
 | 
			
		||||
			entry.candidates.erase (std::prev (entry.candidates.end ()));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Special case for the last frontier head that won't receive larger than max frontier
 | 
			
		||||
		if (entry.completed >= config.consideration_count * 2 && entry.candidates.empty ())
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::done_empty);
 | 
			
		||||
			entry.candidates.insert (entry.end);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check if done
 | 
			
		||||
		if (entry.completed >= config.consideration_count && !entry.candidates.empty ())
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::done);
 | 
			
		||||
 | 
			
		||||
			// Take the last candidate as the next frontier
 | 
			
		||||
			release_assert (!entry.candidates.empty ());
 | 
			
		||||
			auto it = std::prev (entry.candidates.end ());
 | 
			
		||||
 | 
			
		||||
			debug_assert (entry.next.number () < it->number ());
 | 
			
		||||
			entry.next = *it;
 | 
			
		||||
			entry.processed += entry.candidates.size ();
 | 
			
		||||
			entry.candidates.clear ();
 | 
			
		||||
			entry.requests = 0;
 | 
			
		||||
			entry.completed = 0;
 | 
			
		||||
			entry.timestamp = {};
 | 
			
		||||
 | 
			
		||||
			// Bound the search range
 | 
			
		||||
			if (entry.next.number () >= entry.end.number ())
 | 
			
		||||
			{
 | 
			
		||||
				stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::done_range);
 | 
			
		||||
				entry.next = entry.start;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			done = true;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return done;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nano::container_info nano::bootstrap_ascending::frontier_scan::container_info () const
 | 
			
		||||
{
 | 
			
		||||
	auto collect_progress = [&] () {
 | 
			
		||||
		nano::container_info info;
 | 
			
		||||
		for (int n = 0; n < heads.size (); ++n)
 | 
			
		||||
		{
 | 
			
		||||
			auto const & head = heads[n];
 | 
			
		||||
 | 
			
		||||
			boost::multiprecision::cpp_dec_float_50 start{ head.start.number ().str () };
 | 
			
		||||
			boost::multiprecision::cpp_dec_float_50 next{ head.next.number ().str () };
 | 
			
		||||
			boost::multiprecision::cpp_dec_float_50 end{ head.end.number ().str () };
 | 
			
		||||
 | 
			
		||||
			// Progress in the range [0, 1000000] since we can only represent `size_t` integers in the container_info data
 | 
			
		||||
			boost::multiprecision::cpp_dec_float_50 progress = (next - start) * boost::multiprecision::cpp_dec_float_50 (1000000) / (end - start);
 | 
			
		||||
 | 
			
		||||
			info.put (std::to_string (n), progress.convert_to<std::uint64_t> ());
 | 
			
		||||
		}
 | 
			
		||||
		return info;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	auto collect_candidates = [&] () {
 | 
			
		||||
		nano::container_info info;
 | 
			
		||||
		for (int n = 0; n < heads.size (); ++n)
 | 
			
		||||
		{
 | 
			
		||||
			auto const & head = heads[n];
 | 
			
		||||
			info.put (std::to_string (n), head.candidates.size ());
 | 
			
		||||
		}
 | 
			
		||||
		return info;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	auto collect_responses = [&] () {
 | 
			
		||||
		nano::container_info info;
 | 
			
		||||
		for (int n = 0; n < heads.size (); ++n)
 | 
			
		||||
		{
 | 
			
		||||
			auto const & head = heads[n];
 | 
			
		||||
			info.put (std::to_string (n), head.completed);
 | 
			
		||||
		}
 | 
			
		||||
		return info;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	auto collect_processed = [&] () {
 | 
			
		||||
		nano::container_info info;
 | 
			
		||||
		for (int n = 0; n < heads.size (); ++n)
 | 
			
		||||
		{
 | 
			
		||||
			auto const & head = heads[n];
 | 
			
		||||
			info.put (std::to_string (n), head.processed);
 | 
			
		||||
		}
 | 
			
		||||
		return info;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	auto total_processed = std::accumulate (heads.begin (), heads.end (), std::size_t{ 0 }, [] (auto total, auto const & head) {
 | 
			
		||||
		return total + head.processed;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	nano::container_info info;
 | 
			
		||||
	info.put ("total_processed", total_processed);
 | 
			
		||||
	info.add ("progress", collect_progress ());
 | 
			
		||||
	info.add ("candidates", collect_candidates ());
 | 
			
		||||
	info.add ("responses", collect_responses ());
 | 
			
		||||
	info.add ("processed", collect_processed ());
 | 
			
		||||
	return info;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								nano/node/bootstrap_ascending/frontier_scan.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								nano/node/bootstrap_ascending/frontier_scan.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <nano/node/bootstrap/bootstrap_config.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/common.hpp>
 | 
			
		||||
 | 
			
		||||
#include <boost/multi_index/hashed_index.hpp>
 | 
			
		||||
#include <boost/multi_index/mem_fun.hpp>
 | 
			
		||||
#include <boost/multi_index/member.hpp>
 | 
			
		||||
#include <boost/multi_index/ordered_index.hpp>
 | 
			
		||||
#include <boost/multi_index/random_access_index.hpp>
 | 
			
		||||
#include <boost/multi_index/sequenced_index.hpp>
 | 
			
		||||
#include <boost/multi_index_container.hpp>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <set>
 | 
			
		||||
 | 
			
		||||
namespace mi = boost::multi_index;
 | 
			
		||||
 | 
			
		||||
namespace nano::bootstrap_ascending
 | 
			
		||||
{
 | 
			
		||||
/*
 | 
			
		||||
 * Frontier scan divides the account space into ranges and scans each range for outdated frontiers in parallel.
 | 
			
		||||
 * This class is used to track the progress of each range.
 | 
			
		||||
 */
 | 
			
		||||
class frontier_scan
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	frontier_scan (frontier_scan_config const &, nano::stats &);
 | 
			
		||||
 | 
			
		||||
	nano::account next ();
 | 
			
		||||
	bool process (nano::account start, std::deque<std::pair<nano::account, nano::block_hash>> const & response);
 | 
			
		||||
 | 
			
		||||
	nano::container_info container_info () const;
 | 
			
		||||
 | 
			
		||||
private: // Dependencies
 | 
			
		||||
	frontier_scan_config const & config;
 | 
			
		||||
	nano::stats & stats;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	// Represents a range of accounts to scan, once the full range is scanned (goes past `end`) the head wraps around (to the `start`)
 | 
			
		||||
	struct frontier_head
 | 
			
		||||
	{
 | 
			
		||||
		frontier_head (nano::account start_a, nano::account end_a) :
 | 
			
		||||
			start{ start_a },
 | 
			
		||||
			end{ end_a },
 | 
			
		||||
			next{ start_a }
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// The range of accounts to scan is [start, end)
 | 
			
		||||
		nano::account const start;
 | 
			
		||||
		nano::account const end;
 | 
			
		||||
 | 
			
		||||
		// We scan the range by querying frontiers starting at 'next' and gathering candidates
 | 
			
		||||
		nano::account next;
 | 
			
		||||
		std::set<nano::account> candidates;
 | 
			
		||||
 | 
			
		||||
		unsigned requests{ 0 };
 | 
			
		||||
		unsigned completed{ 0 };
 | 
			
		||||
		std::chrono::steady_clock::time_point timestamp{};
 | 
			
		||||
		size_t processed{ 0 }; // Total number of accounts processed
 | 
			
		||||
 | 
			
		||||
		nano::account index () const
 | 
			
		||||
		{
 | 
			
		||||
			return start;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	class tag_sequenced {};
 | 
			
		||||
	class tag_start {};
 | 
			
		||||
	class tag_timestamp {};
 | 
			
		||||
 | 
			
		||||
	using ordered_heads = boost::multi_index_container<frontier_head,
 | 
			
		||||
	mi::indexed_by<
 | 
			
		||||
		mi::random_access<mi::tag<tag_sequenced>>,
 | 
			
		||||
		mi::ordered_unique<mi::tag<tag_start>,
 | 
			
		||||
			mi::const_mem_fun<frontier_head, nano::account, &frontier_head::index>>,
 | 
			
		||||
		mi::ordered_non_unique<mi::tag<tag_timestamp>,
 | 
			
		||||
			mi::member<frontier_head, std::chrono::steady_clock::time_point, &frontier_head::timestamp>>
 | 
			
		||||
	>>;
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	ordered_heads heads;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
#include <nano/lib/stats_enums.hpp>
 | 
			
		||||
#include <nano/lib/thread_roles.hpp>
 | 
			
		||||
#include <nano/node/blockprocessor.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/crawlers.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/service.hpp>
 | 
			
		||||
#include <nano/node/network.hpp>
 | 
			
		||||
#include <nano/node/nodeconfig.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -30,9 +31,12 @@ nano::bootstrap_ascending::service::service (nano::node_config const & node_conf
 | 
			
		|||
	logger{ logger_a },
 | 
			
		||||
	accounts{ config.account_sets, stats },
 | 
			
		||||
	database_scan{ ledger },
 | 
			
		||||
	frontiers{ config.frontier_scan, stats },
 | 
			
		||||
	throttle{ compute_throttle_size () },
 | 
			
		||||
	scoring{ config, node_config_a.network_params.network },
 | 
			
		||||
	database_limiter{ config.database_rate_limit, 1.0 }
 | 
			
		||||
	database_limiter{ config.database_rate_limit },
 | 
			
		||||
	frontiers_limiter{ config.frontier_rate_limit },
 | 
			
		||||
	workers{ 1, nano::thread_role::name::ascending_bootstrap_worker }
 | 
			
		||||
{
 | 
			
		||||
	block_processor.batch_processed.add ([this] (auto const & batch) {
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +61,9 @@ nano::bootstrap_ascending::service::~service ()
 | 
			
		|||
	debug_assert (!priorities_thread.joinable ());
 | 
			
		||||
	debug_assert (!database_thread.joinable ());
 | 
			
		||||
	debug_assert (!dependencies_thread.joinable ());
 | 
			
		||||
	debug_assert (!frontiers_thread.joinable ());
 | 
			
		||||
	debug_assert (!timeout_thread.joinable ());
 | 
			
		||||
	debug_assert (!workers.alive ());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::start ()
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +71,7 @@ void nano::bootstrap_ascending::service::start ()
 | 
			
		|||
	debug_assert (!priorities_thread.joinable ());
 | 
			
		||||
	debug_assert (!database_thread.joinable ());
 | 
			
		||||
	debug_assert (!dependencies_thread.joinable ());
 | 
			
		||||
	debug_assert (!frontiers_thread.joinable ());
 | 
			
		||||
	debug_assert (!timeout_thread.joinable ());
 | 
			
		||||
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
| 
						 | 
				
			
			@ -73,10 +80,15 @@ void nano::bootstrap_ascending::service::start ()
 | 
			
		|||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	priorities_thread = std::thread ([this] () {
 | 
			
		||||
		nano::thread_role::set (nano::thread_role::name::ascending_bootstrap);
 | 
			
		||||
		run_priorities ();
 | 
			
		||||
	});
 | 
			
		||||
	workers.start ();
 | 
			
		||||
 | 
			
		||||
	if (config.enable_scan)
 | 
			
		||||
	{
 | 
			
		||||
		priorities_thread = std::thread ([this] () {
 | 
			
		||||
			nano::thread_role::set (nano::thread_role::name::ascending_bootstrap);
 | 
			
		||||
			run_priorities ();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (config.enable_database_scan)
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +106,14 @@ void nano::bootstrap_ascending::service::start ()
 | 
			
		|||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (config.enable_frontier_scan)
 | 
			
		||||
	{
 | 
			
		||||
		frontiers_thread = std::thread ([this] () {
 | 
			
		||||
			nano::thread_role::set (nano::thread_role::name::ascending_bootstrap);
 | 
			
		||||
			run_frontiers ();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout_thread = std::thread ([this] () {
 | 
			
		||||
		nano::thread_role::set (nano::thread_role::name::ascending_bootstrap);
 | 
			
		||||
		run_timeouts ();
 | 
			
		||||
| 
						 | 
				
			
			@ -111,10 +131,13 @@ void nano::bootstrap_ascending::service::stop ()
 | 
			
		|||
	nano::join_or_pass (priorities_thread);
 | 
			
		||||
	nano::join_or_pass (database_thread);
 | 
			
		||||
	nano::join_or_pass (dependencies_thread);
 | 
			
		||||
	nano::join_or_pass (frontiers_thread);
 | 
			
		||||
	nano::join_or_pass (timeout_thread);
 | 
			
		||||
 | 
			
		||||
	workers.stop ();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::send (std::shared_ptr<nano::transport::channel> const & channel, async_tag tag)
 | 
			
		||||
bool nano::bootstrap_ascending::service::send (std::shared_ptr<nano::transport::channel> const & channel, async_tag tag)
 | 
			
		||||
{
 | 
			
		||||
	debug_assert (tag.type != query_type::invalid);
 | 
			
		||||
	debug_assert (tag.source != query_source::invalid);
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +148,8 @@ void nano::bootstrap_ascending::service::send (std::shared_ptr<nano::transport::
 | 
			
		|||
		tags.get<tag_id> ().insert (tag);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	on_request.notify (tag, channel);
 | 
			
		||||
 | 
			
		||||
	nano::asc_pull_req request{ network_constants };
 | 
			
		||||
	request.id = tag.id;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -152,6 +177,16 @@ void nano::bootstrap_ascending::service::send (std::shared_ptr<nano::transport::
 | 
			
		|||
			request.payload = pld;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
		case query_type::frontiers:
 | 
			
		||||
		{
 | 
			
		||||
			request.type = nano::asc_pull_type::frontiers;
 | 
			
		||||
 | 
			
		||||
			nano::asc_pull_req::frontiers_payload pld;
 | 
			
		||||
			pld.start = tag.start.as_account ();
 | 
			
		||||
			pld.count = nano::asc_pull_ack::frontiers_payload::max_frontiers;
 | 
			
		||||
			request.payload = pld;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
		default:
 | 
			
		||||
			debug_assert (false);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -165,6 +200,8 @@ void nano::bootstrap_ascending::service::send (std::shared_ptr<nano::transport::
 | 
			
		|||
	channel->send (
 | 
			
		||||
	request, nullptr,
 | 
			
		||||
	nano::transport::buffer_drop_policy::limiter, nano::transport::traffic_type::bootstrap);
 | 
			
		||||
 | 
			
		||||
	return true; // TODO: Return channel send result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t nano::bootstrap_ascending::service::priority_size () const
 | 
			
		||||
| 
						 | 
				
			
			@ -185,6 +222,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<nano::mutex> lock{ mutex };
 | 
			
		||||
	return accounts.prioritized (account);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nano::bootstrap_ascending::service::blocked (nano::account const & account) const
 | 
			
		||||
{
 | 
			
		||||
	nano::lock_guard<nano::mutex> 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -412,6 +461,24 @@ nano::block_hash nano::bootstrap_ascending::service::wait_blocking ()
 | 
			
		|||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nano::account nano::bootstrap_ascending::service::wait_frontier ()
 | 
			
		||||
{
 | 
			
		||||
	nano::account result{ 0 };
 | 
			
		||||
 | 
			
		||||
	wait ([this, &result] () {
 | 
			
		||||
		debug_assert (!mutex.try_lock ());
 | 
			
		||||
		result = frontiers.next ();
 | 
			
		||||
		if (!result.is_zero ())
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_next, nano::stat::detail::next_frontier);
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nano::bootstrap_ascending::service::request (nano::account account, size_t count, std::shared_ptr<nano::transport::channel> const & channel, query_source source)
 | 
			
		||||
{
 | 
			
		||||
	debug_assert (count > 0);
 | 
			
		||||
| 
						 | 
				
			
			@ -439,11 +506,7 @@ bool nano::bootstrap_ascending::service::request (nano::account account, size_t
 | 
			
		|||
		tag.start = account;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	on_request.notify (tag, channel);
 | 
			
		||||
 | 
			
		||||
	send (channel, tag);
 | 
			
		||||
 | 
			
		||||
	return true; // Request sent
 | 
			
		||||
	return send (channel, tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nano::bootstrap_ascending::service::request_info (nano::block_hash hash, std::shared_ptr<nano::transport::channel> const & channel, query_source source)
 | 
			
		||||
| 
						 | 
				
			
			@ -454,11 +517,17 @@ bool nano::bootstrap_ascending::service::request_info (nano::block_hash hash, st
 | 
			
		|||
	tag.start = hash;
 | 
			
		||||
	tag.hash = hash;
 | 
			
		||||
 | 
			
		||||
	on_request.notify (tag, channel);
 | 
			
		||||
	return send (channel, tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	send (channel, tag);
 | 
			
		||||
bool nano::bootstrap_ascending::service::request_frontiers (nano::account start, std::shared_ptr<nano::transport::channel> const & channel, query_source source)
 | 
			
		||||
{
 | 
			
		||||
	async_tag tag{};
 | 
			
		||||
	tag.type = query_type::frontiers;
 | 
			
		||||
	tag.source = source;
 | 
			
		||||
	tag.start = start;
 | 
			
		||||
 | 
			
		||||
	return true; // Request sent
 | 
			
		||||
	return send (channel, tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::run_one_priority ()
 | 
			
		||||
| 
						 | 
				
			
			@ -552,6 +621,43 @@ void nano::bootstrap_ascending::service::run_dependencies ()
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::run_one_frontier ()
 | 
			
		||||
{
 | 
			
		||||
	wait ([this] () {
 | 
			
		||||
		return !accounts.priority_half_full ();
 | 
			
		||||
	});
 | 
			
		||||
	wait ([this] () {
 | 
			
		||||
		return frontiers_limiter.should_pass (1);
 | 
			
		||||
	});
 | 
			
		||||
	wait ([this] () {
 | 
			
		||||
		return workers.queued_tasks () < config.frontier_scan.max_pending;
 | 
			
		||||
	});
 | 
			
		||||
	wait_tags ();
 | 
			
		||||
	auto channel = wait_channel ();
 | 
			
		||||
	if (!channel)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	auto frontier = wait_frontier ();
 | 
			
		||||
	if (frontier.is_zero ())
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	request_frontiers (frontier, channel, query_source::frontiers);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::run_frontiers ()
 | 
			
		||||
{
 | 
			
		||||
	nano::unique_lock<nano::mutex> lock{ mutex };
 | 
			
		||||
	while (!stopped)
 | 
			
		||||
	{
 | 
			
		||||
		lock.unlock ();
 | 
			
		||||
		stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::loop_frontiers);
 | 
			
		||||
		run_one_frontier ();
 | 
			
		||||
		lock.lock ();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::cleanup_and_sync ()
 | 
			
		||||
{
 | 
			
		||||
	debug_assert (!mutex.try_lock ());
 | 
			
		||||
| 
						 | 
				
			
			@ -625,7 +731,7 @@ void nano::bootstrap_ascending::service::process (nano::asc_pull_ack const & mes
 | 
			
		|||
		}
 | 
			
		||||
		bool operator() (const nano::asc_pull_ack::frontiers_payload & response) const
 | 
			
		||||
		{
 | 
			
		||||
			return false; // TODO: Handle frontiers
 | 
			
		||||
			return type == query_type::frontiers;
 | 
			
		||||
		}
 | 
			
		||||
		bool operator() (const nano::empty_payload & response) const
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			@ -667,7 +773,7 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::bloc
 | 
			
		|||
	{
 | 
			
		||||
		case verify_result::ok:
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::ok);
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify_blocks, nano::stat::detail::ok);
 | 
			
		||||
			stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::blocks, nano::stat::dir::in, response.blocks.size ());
 | 
			
		||||
 | 
			
		||||
			auto blocks = response.blocks;
 | 
			
		||||
| 
						 | 
				
			
			@ -708,7 +814,7 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::bloc
 | 
			
		|||
		break;
 | 
			
		||||
		case verify_result::nothing_new:
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::nothing_new);
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify_blocks, nano::stat::detail::nothing_new);
 | 
			
		||||
 | 
			
		||||
			nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
			accounts.priority_down (tag.account);
 | 
			
		||||
| 
						 | 
				
			
			@ -720,7 +826,7 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::bloc
 | 
			
		|||
		break;
 | 
			
		||||
		case verify_result::invalid:
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::invalid);
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify_blocks, nano::stat::detail::invalid);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -734,24 +840,69 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::acco
 | 
			
		|||
	if (response.account.is_zero ())
 | 
			
		||||
	{
 | 
			
		||||
		stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info_empty);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info);
 | 
			
		||||
 | 
			
		||||
		// Prioritize account containing the dependency
 | 
			
		||||
		{
 | 
			
		||||
			nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
			accounts.dependency_update (tag.hash, response.account);
 | 
			
		||||
			accounts.priority_set (response.account);
 | 
			
		||||
		}
 | 
			
		||||
	stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info);
 | 
			
		||||
 | 
			
		||||
	// Prioritize account containing the dependency
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		accounts.dependency_update (tag.hash, response.account);
 | 
			
		||||
		accounts.priority_set (response.account);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::frontiers_payload & response, const async_tag & tag)
 | 
			
		||||
{
 | 
			
		||||
	// TODO: Make use of frontiers info
 | 
			
		||||
	debug_assert (tag.type == query_type::frontiers);
 | 
			
		||||
	debug_assert (!tag.start.is_zero ());
 | 
			
		||||
 | 
			
		||||
	if (response.frontiers.empty ())
 | 
			
		||||
	{
 | 
			
		||||
		stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::frontiers_empty);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::frontiers);
 | 
			
		||||
 | 
			
		||||
	auto result = verify (response, tag);
 | 
			
		||||
	switch (result)
 | 
			
		||||
	{
 | 
			
		||||
		case verify_result::ok:
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify_frontiers, nano::stat::detail::ok);
 | 
			
		||||
			stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers, nano::stat::dir::in, response.frontiers.size ());
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
				frontiers.process (tag.start.as_account (), response.frontiers);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Allow some overfill to avoid unnecessarily dropping responses
 | 
			
		||||
			if (workers.queued_tasks () < config.frontier_scan.max_pending * 4)
 | 
			
		||||
			{
 | 
			
		||||
				workers.post ([this, frontiers = response.frontiers] {
 | 
			
		||||
					process_frontiers (frontiers);
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::dropped_frontiers);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
		case verify_result::nothing_new:
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify_frontiers, nano::stat::detail::nothing_new);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
		case verify_result::invalid:
 | 
			
		||||
		{
 | 
			
		||||
			stats.inc (nano::stat::type::bootstrap_ascending_verify_frontiers, nano::stat::detail::invalid);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::process (const nano::empty_payload & response, const async_tag & tag)
 | 
			
		||||
| 
						 | 
				
			
			@ -760,6 +911,86 @@ void nano::bootstrap_ascending::service::process (const nano::empty_payload & re
 | 
			
		|||
	debug_assert (false, "empty payload"); // Should not happen
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::bootstrap_ascending::service::process_frontiers (std::deque<std::pair<nano::account, nano::block_hash>> const & frontiers)
 | 
			
		||||
{
 | 
			
		||||
	release_assert (!frontiers.empty ());
 | 
			
		||||
 | 
			
		||||
	// Accounts must be passed in ascending order
 | 
			
		||||
	debug_assert (std::adjacent_find (frontiers.begin (), frontiers.end (), [] (auto const & lhs, auto const & rhs) {
 | 
			
		||||
		return lhs.first.number () >= rhs.first.number ();
 | 
			
		||||
	})
 | 
			
		||||
	== frontiers.end ());
 | 
			
		||||
 | 
			
		||||
	stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::process_frontiers);
 | 
			
		||||
 | 
			
		||||
	size_t outdated = 0;
 | 
			
		||||
	size_t pending = 0;
 | 
			
		||||
 | 
			
		||||
	// Accounts with outdated frontiers to sync
 | 
			
		||||
	std::deque<nano::account> result;
 | 
			
		||||
	{
 | 
			
		||||
		auto transaction = ledger.tx_begin_read ();
 | 
			
		||||
 | 
			
		||||
		auto const start = frontiers.front ().first;
 | 
			
		||||
		account_database_crawler account_crawler{ ledger.store, transaction, start };
 | 
			
		||||
		pending_database_crawler pending_crawler{ ledger.store, transaction, start };
 | 
			
		||||
 | 
			
		||||
		auto block_exists = [&] (nano::block_hash const & hash) {
 | 
			
		||||
			return ledger.any.block_exists_or_pruned (transaction, hash);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		auto should_prioritize = [&] (nano::account const & account, nano::block_hash const & frontier) {
 | 
			
		||||
			account_crawler.advance_to (account);
 | 
			
		||||
			pending_crawler.advance_to (account);
 | 
			
		||||
 | 
			
		||||
			// Check if account exists in our ledger
 | 
			
		||||
			if (account_crawler.current && account_crawler.current->first == account)
 | 
			
		||||
			{
 | 
			
		||||
				// Check for frontier mismatch
 | 
			
		||||
				if (account_crawler.current->second.head != frontier)
 | 
			
		||||
				{
 | 
			
		||||
					// Check if frontier block exists in our ledger
 | 
			
		||||
					if (!block_exists (frontier))
 | 
			
		||||
					{
 | 
			
		||||
						outdated++;
 | 
			
		||||
						return true; // Frontier is outdated
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return false; // Account exists and frontier is up-to-date
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Check if account has pending blocks in our ledger
 | 
			
		||||
			if (pending_crawler.current && pending_crawler.current->first.account == account)
 | 
			
		||||
			{
 | 
			
		||||
				pending++;
 | 
			
		||||
				return true; // Account doesn't exist but has pending blocks in the ledger
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false; // Account doesn't exist in the ledger and has no pending blocks, can't be prioritized right now
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		for (auto const & [account, frontier] : frontiers)
 | 
			
		||||
		{
 | 
			
		||||
			if (should_prioritize (account, frontier))
 | 
			
		||||
			{
 | 
			
		||||
				result.push_back (account);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stats.add (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::processed, frontiers.size ());
 | 
			
		||||
	stats.add (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::prioritized, result.size ());
 | 
			
		||||
	stats.add (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::outdated, outdated);
 | 
			
		||||
	stats.add (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::pending, pending);
 | 
			
		||||
 | 
			
		||||
	nano::lock_guard<nano::mutex> guard{ mutex };
 | 
			
		||||
 | 
			
		||||
	for (auto const & account : result)
 | 
			
		||||
	{
 | 
			
		||||
		accounts.priority_set (account);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nano::bootstrap_ascending::service::verify_result nano::bootstrap_ascending::service::verify (const nano::asc_pull_ack::blocks_payload & response, const nano::bootstrap_ascending::service::async_tag & tag) const
 | 
			
		||||
{
 | 
			
		||||
	auto const & blocks = response.blocks;
 | 
			
		||||
| 
						 | 
				
			
			@ -819,6 +1050,35 @@ nano::bootstrap_ascending::service::verify_result nano::bootstrap_ascending::ser
 | 
			
		|||
	return verify_result::ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nano::bootstrap_ascending::service::verify_result nano::bootstrap_ascending::service::verify (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag) const
 | 
			
		||||
{
 | 
			
		||||
	auto const & frontiers = response.frontiers;
 | 
			
		||||
 | 
			
		||||
	if (frontiers.empty ())
 | 
			
		||||
	{
 | 
			
		||||
		return verify_result::nothing_new;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure frontiers accounts are in ascending order
 | 
			
		||||
	nano::account previous{ 0 };
 | 
			
		||||
	for (auto const & [account, _] : frontiers)
 | 
			
		||||
	{
 | 
			
		||||
		if (account.number () <= previous.number ())
 | 
			
		||||
		{
 | 
			
		||||
			return verify_result::invalid;
 | 
			
		||||
		}
 | 
			
		||||
		previous = account;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure the frontiers are larger or equal to the requested frontier
 | 
			
		||||
	if (frontiers.front ().first.number () < tag.start.as_account ().number ())
 | 
			
		||||
	{
 | 
			
		||||
		return verify_result::invalid;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return verify_result::ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto nano::bootstrap_ascending::service::info () const -> nano::bootstrap_ascending::account_sets::info_t
 | 
			
		||||
{
 | 
			
		||||
	nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
| 
						 | 
				
			
			@ -843,6 +1103,8 @@ nano::container_info nano::bootstrap_ascending::service::container_info () const
 | 
			
		|||
	info.put ("throttle_successes", throttle.successes ());
 | 
			
		||||
	info.add ("accounts", accounts.container_info ());
 | 
			
		||||
	info.add ("database_scan", database_scan.container_info ());
 | 
			
		||||
	info.add ("frontiers", frontiers.container_info ());
 | 
			
		||||
	info.add ("workers", workers.container_info ());
 | 
			
		||||
	return info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,13 +5,15 @@
 | 
			
		|||
#include <nano/lib/numbers.hpp>
 | 
			
		||||
#include <nano/lib/observer_set.hpp>
 | 
			
		||||
#include <nano/lib/rate_limiting.hpp>
 | 
			
		||||
#include <nano/lib/thread_pool.hpp>
 | 
			
		||||
#include <nano/lib/timer.hpp>
 | 
			
		||||
#include <nano/node/bootstrap/bootstrap_config.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/account_sets.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/common.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/database_scan.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/frontier_scan.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/peer_scoring.hpp>
 | 
			
		||||
#include <nano/node/bootstrap_ascending/throttle.hpp>
 | 
			
		||||
#include <nano/node/fwd.hpp>
 | 
			
		||||
 | 
			
		||||
#include <boost/multi_index/hashed_index.hpp>
 | 
			
		||||
#include <boost/multi_index/member.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +45,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::container_info container_info () const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +68,7 @@ namespace bootstrap_ascending
 | 
			
		|||
			blocks_by_hash,
 | 
			
		||||
			blocks_by_account,
 | 
			
		||||
			account_info_by_hash,
 | 
			
		||||
			frontiers,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		enum class query_source
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +77,7 @@ namespace bootstrap_ascending
 | 
			
		|||
			priority,
 | 
			
		||||
			database,
 | 
			
		||||
			blocking,
 | 
			
		||||
			frontiers,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		struct async_tag
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +108,8 @@ namespace bootstrap_ascending
 | 
			
		|||
		void run_one_database (bool should_throttle);
 | 
			
		||||
		void run_dependencies ();
 | 
			
		||||
		void run_one_blocking ();
 | 
			
		||||
		void run_one_frontier ();
 | 
			
		||||
		void run_frontiers ();
 | 
			
		||||
		void run_timeouts ();
 | 
			
		||||
		void cleanup_and_sync ();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -123,16 +131,21 @@ namespace bootstrap_ascending
 | 
			
		|||
		/* Waits for next available blocking block */
 | 
			
		||||
		nano::block_hash next_blocking ();
 | 
			
		||||
		nano::block_hash wait_blocking ();
 | 
			
		||||
		/* Waits for next available frontier scan range */
 | 
			
		||||
		nano::account wait_frontier ();
 | 
			
		||||
 | 
			
		||||
		bool request (nano::account, size_t count, std::shared_ptr<nano::transport::channel> const &, query_source);
 | 
			
		||||
		bool request_info (nano::block_hash, std::shared_ptr<nano::transport::channel> const &, query_source);
 | 
			
		||||
		void send (std::shared_ptr<nano::transport::channel> const &, async_tag tag);
 | 
			
		||||
		bool request_frontiers (nano::account, std::shared_ptr<nano::transport::channel> const &, query_source);
 | 
			
		||||
		bool send (std::shared_ptr<nano::transport::channel> const &, async_tag tag);
 | 
			
		||||
 | 
			
		||||
		void process (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag);
 | 
			
		||||
		void process (nano::asc_pull_ack::account_info_payload const & response, async_tag const & tag);
 | 
			
		||||
		void process (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag);
 | 
			
		||||
		void process (nano::empty_payload const & response, async_tag const & tag);
 | 
			
		||||
 | 
			
		||||
		void process_frontiers (std::deque<std::pair<nano::account, nano::block_hash>> const & frontiers);
 | 
			
		||||
 | 
			
		||||
		enum class verify_result
 | 
			
		||||
		{
 | 
			
		||||
			ok,
 | 
			
		||||
| 
						 | 
				
			
			@ -147,6 +160,7 @@ namespace bootstrap_ascending
 | 
			
		|||
		 * - ok: otherwise, if all checks pass
 | 
			
		||||
		 */
 | 
			
		||||
		verify_result verify (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag) const;
 | 
			
		||||
		verify_result verify (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag) const;
 | 
			
		||||
 | 
			
		||||
		size_t count_tags (nano::account const & account, query_source source) const;
 | 
			
		||||
		size_t count_tags (nano::block_hash const & hash, query_source source) const;
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +173,7 @@ namespace bootstrap_ascending
 | 
			
		|||
		nano::bootstrap_ascending::database_scan database_scan;
 | 
			
		||||
		nano::bootstrap_ascending::throttle throttle;
 | 
			
		||||
		nano::bootstrap_ascending::peer_scoring scoring;
 | 
			
		||||
		nano::bootstrap_ascending::frontier_scan frontiers;
 | 
			
		||||
 | 
			
		||||
		// clang-format off
 | 
			
		||||
		class tag_sequenced {};
 | 
			
		||||
| 
						 | 
				
			
			@ -182,6 +197,7 @@ namespace bootstrap_ascending
 | 
			
		|||
		// Requests for accounts from database have much lower hitrate and could introduce strain on the network
 | 
			
		||||
		// A separate (lower) limiter ensures that we always reserve resources for querying accounts from priority queue
 | 
			
		||||
		nano::rate_limiter database_limiter;
 | 
			
		||||
		nano::rate_limiter frontiers_limiter;
 | 
			
		||||
 | 
			
		||||
		nano::interval sync_dependencies_interval;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -191,7 +207,10 @@ namespace bootstrap_ascending
 | 
			
		|||
		std::thread priorities_thread;
 | 
			
		||||
		std::thread database_thread;
 | 
			
		||||
		std::thread dependencies_thread;
 | 
			
		||||
		std::thread frontiers_thread;
 | 
			
		||||
		std::thread timeout_thread;
 | 
			
		||||
 | 
			
		||||
		nano::thread_pool workers;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	nano::stat::detail to_stat_detail (service::query_type);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -730,7 +730,7 @@ public: // Payload definitions
 | 
			
		|||
		static frontier deserialize_frontier (nano::stream &);
 | 
			
		||||
 | 
			
		||||
	public: // Payload
 | 
			
		||||
		std::vector<frontier> frontiers;
 | 
			
		||||
		std::deque<frontier> frontiers;
 | 
			
		||||
 | 
			
		||||
	public: // Logging
 | 
			
		||||
		void operator() (nano::object_stream &) const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ nano::monitor::~monitor ()
 | 
			
		|||
 | 
			
		||||
void nano::monitor::start ()
 | 
			
		||||
{
 | 
			
		||||
	if (!config.enabled)
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +115,7 @@ void nano::monitor::run_one ()
 | 
			
		|||
 | 
			
		||||
nano::error nano::monitor_config::serialize (nano::tomlconfig & toml) const
 | 
			
		||||
{
 | 
			
		||||
	toml.put ("enable", enabled, "Enable or disable periodic node status logging\ntype:bool");
 | 
			
		||||
	toml.put ("enable", enable, "Enable or disable periodic node status logging\ntype:bool");
 | 
			
		||||
	toml.put ("interval", interval.count (), "Interval between status logs\ntype:seconds");
 | 
			
		||||
 | 
			
		||||
	return toml.get_error ();
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +123,7 @@ nano::error nano::monitor_config::serialize (nano::tomlconfig & toml) const
 | 
			
		|||
 | 
			
		||||
nano::error nano::monitor_config::deserialize (nano::tomlconfig & toml)
 | 
			
		||||
{
 | 
			
		||||
	toml.get ("enable", enabled);
 | 
			
		||||
	toml.get ("enable", enable);
 | 
			
		||||
	auto interval_l = interval.count ();
 | 
			
		||||
	toml.get ("interval", interval_l);
 | 
			
		||||
	interval = std::chrono::seconds{ interval_l };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ public:
 | 
			
		|||
	nano::error serialize (nano::tomlconfig &) const;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	bool enabled{ true };
 | 
			
		||||
	bool enable{ true };
 | 
			
		||||
	std::chrono::seconds interval{ 60s };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ void nano::scheduler::hinted::start ()
 | 
			
		|||
{
 | 
			
		||||
	debug_assert (!thread.joinable ());
 | 
			
		||||
 | 
			
		||||
	if (!config.enabled)
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -260,7 +260,7 @@ nano::scheduler::hinted_config::hinted_config (nano::network_constants const & n
 | 
			
		|||
 | 
			
		||||
nano::error nano::scheduler::hinted_config::serialize (nano::tomlconfig & toml) const
 | 
			
		||||
{
 | 
			
		||||
	toml.put ("enable", enabled, "Enable or disable hinted elections\ntype:bool");
 | 
			
		||||
	toml.put ("enable", enable, "Enable or disable hinted elections\ntype:bool");
 | 
			
		||||
	toml.put ("hinting_threshold", hinting_threshold_percent, "Percentage of online weight needed to start a hinted election. \ntype:uint32,[0,100]");
 | 
			
		||||
	toml.put ("check_interval", check_interval.count (), "Interval between scans of the vote cache for possible hinted elections. \ntype:milliseconds");
 | 
			
		||||
	toml.put ("block_cooldown", block_cooldown.count (), "Cooldown period for blocks that failed to start an election. \ntype:milliseconds");
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +271,7 @@ nano::error nano::scheduler::hinted_config::serialize (nano::tomlconfig & toml)
 | 
			
		|||
 | 
			
		||||
nano::error nano::scheduler::hinted_config::deserialize (nano::tomlconfig & toml)
 | 
			
		||||
{
 | 
			
		||||
	toml.get ("enable", enabled);
 | 
			
		||||
	toml.get ("enable", enable);
 | 
			
		||||
	toml.get ("hinting_threshold", hinting_threshold_percent);
 | 
			
		||||
 | 
			
		||||
	auto check_interval_l = check_interval.count ();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ public:
 | 
			
		|||
	nano::error serialize (nano::tomlconfig & toml) const;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	bool enabled{ true };
 | 
			
		||||
	bool enable{ true };
 | 
			
		||||
	std::chrono::milliseconds check_interval{ 1000 };
 | 
			
		||||
	std::chrono::milliseconds block_cooldown{ 10000 };
 | 
			
		||||
	unsigned hinting_threshold_percent{ 10 };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ void nano::scheduler::optimistic::start ()
 | 
			
		|||
{
 | 
			
		||||
	debug_assert (!thread.joinable ());
 | 
			
		||||
 | 
			
		||||
	if (!config.enabled)
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +72,7 @@ bool nano::scheduler::optimistic::activate_predicate (const nano::account_info &
 | 
			
		|||
 | 
			
		||||
bool nano::scheduler::optimistic::activate (const nano::account & account, const nano::account_info & account_info, const nano::confirmation_height_info & conf_info)
 | 
			
		||||
{
 | 
			
		||||
	if (!config.enabled)
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
	{
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -183,7 +183,7 @@ nano::container_info nano::scheduler::optimistic::container_info () const
 | 
			
		|||
 | 
			
		||||
nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig & toml)
 | 
			
		||||
{
 | 
			
		||||
	toml.get ("enable", enabled);
 | 
			
		||||
	toml.get ("enable", enable);
 | 
			
		||||
	toml.get ("gap_threshold", gap_threshold);
 | 
			
		||||
	toml.get ("max_size", max_size);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +192,7 @@ nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig &
 | 
			
		|||
 | 
			
		||||
nano::error nano::scheduler::optimistic_config::serialize (nano::tomlconfig & toml) const
 | 
			
		||||
{
 | 
			
		||||
	toml.put ("enable", enabled, "Enable or disable optimistic elections\ntype:bool");
 | 
			
		||||
	toml.put ("enable", enable, "Enable or disable optimistic elections\ntype:bool");
 | 
			
		||||
	toml.put ("gap_threshold", gap_threshold, "Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation\ntype:uint64");
 | 
			
		||||
	toml.put ("max_size", max_size, "Maximum number of candidates stored in memory\ntype:uint64");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ public:
 | 
			
		|||
	nano::error serialize (nano::tomlconfig & toml) const;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	bool enabled{ true };
 | 
			
		||||
	bool enable{ true };
 | 
			
		||||
 | 
			
		||||
	/** Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation */
 | 
			
		||||
	std::size_t gap_threshold{ 32 };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,7 +88,7 @@ void nano::scheduler::priority::start ()
 | 
			
		|||
	debug_assert (!thread.joinable ());
 | 
			
		||||
	debug_assert (!cleanup_thread.joinable ());
 | 
			
		||||
 | 
			
		||||
	if (!config.enabled)
 | 
			
		||||
	if (!config.enable)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ public:
 | 
			
		|||
	// TODO: Serialization & deserialization
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	bool enabled{ true };
 | 
			
		||||
	bool enable{ true };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class priority final
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue