From f379b3489f454c2539de8f1c6952049926cd6677 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?=
 <3044353+pwojcikdev@users.noreply.github.com>
Date: Mon, 6 Jan 2025 20:27:23 +0100
Subject: [PATCH 1/6] Reset bootstrap state command

---
 nano/core_test/bootstrap.cpp              |  4 +++-
 nano/lib/stats_enums.hpp                  |  1 +
 nano/node/bootstrap/account_sets.cpp      |  6 ++++++
 nano/node/bootstrap/account_sets.hpp      |  2 ++
 nano/node/bootstrap/bootstrap_service.cpp | 14 ++++++++++++++
 nano/node/bootstrap/bootstrap_service.hpp |  7 ++++++-
 nano/node/bootstrap/database_scan.cpp     | 11 +++++++++++
 nano/node/bootstrap/database_scan.hpp     |  2 ++
 nano/node/bootstrap/frontier_scan.cpp     | 10 ++++++++++
 nano/node/bootstrap/frontier_scan.hpp     | 12 ++++++++++++
 nano/node/bootstrap/peer_scoring.cpp      |  5 +++++
 nano/node/bootstrap/peer_scoring.hpp      |  6 ++++++
 nano/node/bootstrap/throttle.cpp          |  6 ++++++
 nano/node/bootstrap/throttle.hpp          |  6 ++++++
 nano/node/json_handler.cpp                |  8 ++++++++
 nano/node/json_handler.hpp                |  1 +
 nano/rpc/rpc_handler.cpp                  |  2 ++
 17 files changed, 101 insertions(+), 2 deletions(-)

diff --git a/nano/core_test/bootstrap.cpp b/nano/core_test/bootstrap.cpp
index ccebd97cd4..8d866031b0 100644
--- a/nano/core_test/bootstrap.cpp
+++ b/nano/core_test/bootstrap.cpp
@@ -571,4 +571,6 @@ TEST (bootstrap, frontier_scan_cannot_prioritize)
 	ASSERT_ALWAYS (1s, std::none_of (opens2.begin (), opens2.end (), [&node1] (auto const & block) {
 		return node1.bootstrap.prioritized (block->account ());
 	}));
-}
\ No newline at end of file
+}
+
+// TODO: Test bootstrap.reset ()
\ No newline at end of file
diff --git a/nano/lib/stats_enums.hpp b/nano/lib/stats_enums.hpp
index 429741d2f3..3f142013ac 100644
--- a/nano/lib/stats_enums.hpp
+++ b/nano/lib/stats_enums.hpp
@@ -166,6 +166,7 @@ enum class detail
 	other,
 	drop,
 	queued,
+	reset,
 
 	// processing queue
 	queue,
diff --git a/nano/node/bootstrap/account_sets.cpp b/nano/node/bootstrap/account_sets.cpp
index 421ffddb46..f0639ff9cb 100644
--- a/nano/node/bootstrap/account_sets.cpp
+++ b/nano/node/bootstrap/account_sets.cpp
@@ -19,6 +19,12 @@ nano::bootstrap::account_sets::account_sets (nano::account_sets_config const & c
 {
 }
 
+void nano::bootstrap::account_sets::reset ()
+{
+	priorities.clear ();
+	blocking.clear ();
+}
+
 void nano::bootstrap::account_sets::priority_up (nano::account const & account)
 {
 	if (account.is_zero ())
diff --git a/nano/node/bootstrap/account_sets.hpp b/nano/node/bootstrap/account_sets.hpp
index cac5785c8a..5a5a0f7796 100644
--- a/nano/node/bootstrap/account_sets.hpp
+++ b/nano/node/bootstrap/account_sets.hpp
@@ -33,6 +33,8 @@ class account_sets
 public:
 	account_sets (account_sets_config const &, nano::stats &);
 
+	void reset ();
+
 	/**
 	 * If an account is not blocked, increase its priority.
 	 * If the account does not exist in priority set and is not blocked, inserts a new entry.
diff --git a/nano/node/bootstrap/bootstrap_service.cpp b/nano/node/bootstrap/bootstrap_service.cpp
index 8f60f9924f..13727436e3 100644
--- a/nano/node/bootstrap/bootstrap_service.cpp
+++ b/nano/node/bootstrap/bootstrap_service.cpp
@@ -145,6 +145,20 @@ void nano::bootstrap_service::stop ()
 	workers.stop ();
 }
 
+void nano::bootstrap_service::reset ()
+{
+	nano::lock_guard<nano::mutex> lock{ mutex };
+
+	stats.inc (nano::stat::type::bootstrap, nano::stat::detail::reset);
+	logger.info (nano::log::type::bootstrap, "Resetting bootstrap state");
+
+	accounts.reset ();
+	database_scan.reset ();
+	frontiers.reset ();
+	scoring.reset ();
+	throttle.reset ();
+}
+
 bool nano::bootstrap_service::send (std::shared_ptr<nano::transport::channel> const & channel, async_tag tag)
 {
 	debug_assert (tag.type != query_type::invalid);
diff --git a/nano/node/bootstrap/bootstrap_service.hpp b/nano/node/bootstrap/bootstrap_service.hpp
index f28d3fbe14..408980b57c 100644
--- a/nano/node/bootstrap/bootstrap_service.hpp
+++ b/nano/node/bootstrap/bootstrap_service.hpp
@@ -37,10 +37,15 @@ class bootstrap_service
 	void stop ();
 
 	/**
-	 * Process `asc_pull_ack` message coming from network
+	 * Process bootstrap messages coming from the network
 	 */
 	void process (nano::asc_pull_ack const & message, std::shared_ptr<nano::transport::channel> const &);
 
+	/**
+	 * Clears priority and blocking accounts state
+	 */
+	void reset ();
+
 	std::size_t blocked_size () const;
 	std::size_t priority_size () const;
 	std::size_t score_size () const;
diff --git a/nano/node/bootstrap/database_scan.cpp b/nano/node/bootstrap/database_scan.cpp
index 0135129392..d985a2b48e 100644
--- a/nano/node/bootstrap/database_scan.cpp
+++ b/nano/node/bootstrap/database_scan.cpp
@@ -19,6 +19,17 @@ nano::bootstrap::database_scan::database_scan (nano::ledger & ledger_a) :
 {
 }
 
+void nano::bootstrap::database_scan::reset ()
+{
+	queue.clear ();
+
+	account_scanner.next = nano::account{ 0 };
+	account_scanner.completed = 0;
+
+	pending_scanner.next = nano::account{ 0 };
+	pending_scanner.completed = 0;
+}
+
 nano::account nano::bootstrap::database_scan::next (std::function<bool (nano::account const &)> const & filter)
 {
 	if (queue.empty ())
diff --git a/nano/node/bootstrap/database_scan.hpp b/nano/node/bootstrap/database_scan.hpp
index 758ad3efce..1dc932365c 100644
--- a/nano/node/bootstrap/database_scan.hpp
+++ b/nano/node/bootstrap/database_scan.hpp
@@ -39,6 +39,8 @@ class database_scan
 	// Indicates if a full ledger iteration has taken place e.g. warmed up
 	bool warmed_up () const;
 
+	void reset ();
+
 	nano::container_info container_info () const;
 
 private: // Dependencies
diff --git a/nano/node/bootstrap/frontier_scan.cpp b/nano/node/bootstrap/frontier_scan.cpp
index a65d64fa5e..4ab1240415 100644
--- a/nano/node/bootstrap/frontier_scan.cpp
+++ b/nano/node/bootstrap/frontier_scan.cpp
@@ -23,6 +23,16 @@ nano::bootstrap::frontier_scan::frontier_scan (frontier_scan_config const & conf
 	release_assert (!heads.empty ());
 }
 
+void nano::bootstrap::frontier_scan::reset ()
+{
+	for (auto it = heads.begin (); it != heads.end (); ++it)
+	{
+		heads.modify (it, [] (frontier_head & head) {
+			head.reset ();
+		});
+	}
+}
+
 nano::account nano::bootstrap::frontier_scan::next ()
 {
 	auto const cutoff = std::chrono::steady_clock::now () - config.cooldown;
diff --git a/nano/node/bootstrap/frontier_scan.hpp b/nano/node/bootstrap/frontier_scan.hpp
index fe7682ff8e..df961c347d 100644
--- a/nano/node/bootstrap/frontier_scan.hpp
+++ b/nano/node/bootstrap/frontier_scan.hpp
@@ -31,6 +31,8 @@ class frontier_scan
 	nano::account next ();
 	bool process (nano::account start, std::deque<std::pair<nano::account, nano::block_hash>> const & response);
 
+	void reset ();
+
 	nano::container_info container_info () const;
 
 private: // Dependencies
@@ -65,6 +67,16 @@ class frontier_scan
 		{
 			return start;
 		}
+
+		void reset ()
+		{
+			next = start;
+			candidates.clear ();
+			requests = 0;
+			completed = 0;
+			timestamp = {};
+			processed = 0;
+		}
 	};
 
 	// clang-format off
diff --git a/nano/node/bootstrap/peer_scoring.cpp b/nano/node/bootstrap/peer_scoring.cpp
index 1e406ab962..29daf7225d 100644
--- a/nano/node/bootstrap/peer_scoring.cpp
+++ b/nano/node/bootstrap/peer_scoring.cpp
@@ -12,6 +12,11 @@ nano::bootstrap::peer_scoring::peer_scoring (bootstrap_config const & config_a,
 {
 }
 
+void nano::bootstrap::peer_scoring::reset ()
+{
+	scoring.clear ();
+}
+
 bool nano::bootstrap::peer_scoring::limit_exceeded (std::shared_ptr<nano::transport::channel> const & channel) const
 {
 	auto & index = scoring.get<tag_channel> ();
diff --git a/nano/node/bootstrap/peer_scoring.hpp b/nano/node/bootstrap/peer_scoring.hpp
index 798015c543..09b46cf321 100644
--- a/nano/node/bootstrap/peer_scoring.hpp
+++ b/nano/node/bootstrap/peer_scoring.hpp
@@ -24,6 +24,8 @@ class peer_scoring
 public:
 	peer_scoring (bootstrap_config const &, nano::network_constants const &);
 
+	void reset ();
+
 	// Returns true if channel limit has been exceeded
 	bool limit_exceeded (std::shared_ptr<nano::transport::channel> const & channel) const;
 	bool try_send_message (std::shared_ptr<nano::transport::channel> const & channel);
@@ -52,10 +54,12 @@ class peer_scoring
 	{
 	public:
 		explicit peer_score (std::shared_ptr<nano::transport::channel> const &, uint64_t, uint64_t, uint64_t);
+
 		std::weak_ptr<nano::transport::channel> channel;
 		// std::weak_ptr does not provide ordering so the naked pointer is also tracked and used for ordering channels
 		// This pointer may be invalid if the channel has been destroyed
 		nano::transport::channel * channel_ptr;
+
 		// Acquire reference to the shared channel object if it is still valid
 		[[nodiscard]] std::shared_ptr<nano::transport::channel> shared () const
 		{
@@ -66,10 +70,12 @@ class peer_scoring
 			}
 			return result;
 		}
+
 		void decay ()
 		{
 			outstanding = outstanding > 0 ? outstanding - 1 : 0;
 		}
+
 		// Number of outstanding requests to a peer
 		uint64_t outstanding{ 0 };
 		uint64_t request_count_total{ 0 };
diff --git a/nano/node/bootstrap/throttle.cpp b/nano/node/bootstrap/throttle.cpp
index 422773d3f5..9df3721ab6 100644
--- a/nano/node/bootstrap/throttle.cpp
+++ b/nano/node/bootstrap/throttle.cpp
@@ -8,6 +8,12 @@ nano::bootstrap::throttle::throttle (std::size_t size) :
 	debug_assert (size > 0);
 }
 
+void nano::bootstrap::throttle::reset ()
+{
+	successes_m = samples.size ();
+	std::fill (samples.begin (), samples.end (), true);
+}
+
 bool nano::bootstrap::throttle::throttled () const
 {
 	return successes_m == 0;
diff --git a/nano/node/bootstrap/throttle.hpp b/nano/node/bootstrap/throttle.hpp
index a688e54d55..f71e5a9ad0 100644
--- a/nano/node/bootstrap/throttle.hpp
+++ b/nano/node/bootstrap/throttle.hpp
@@ -11,17 +11,23 @@ class throttle
 public:
 	// Initialized with all true samples
 	explicit throttle (std::size_t size);
+
 	[[nodiscard]] bool throttled () const;
 	void add (bool success);
+
 	// Resizes the number of samples tracked
 	// Drops the oldest samples if the size decreases
 	// Adds false samples if the size increases
 	void resize (std::size_t size);
+
 	[[nodiscard]] std::size_t size () const;
 	[[nodiscard]] std::size_t successes () const;
 
+	void reset ();
+
 private:
 	void pop ();
+
 	// Bit set that tracks sample results. True when something was retrieved, false otherwise
 	std::deque<bool> samples;
 	std::size_t successes_m;
diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp
index fcdca6a24c..6e9547aef6 100644
--- a/nano/node/json_handler.cpp
+++ b/nano/node/json_handler.cpp
@@ -5194,6 +5194,13 @@ void nano::json_handler::debug_bootstrap_priority_info ()
 	response_errors ();
 }
 
+void nano::json_handler::bootstrap_reset ()
+{
+	node.bootstrap.reset ();
+	response_l.put ("success", "");
+	response_errors ();
+}
+
 void nano::inprocess_rpc_handler::process_request (std::string const &, std::string const & body_a, std::function<void (std::string const &)> response_a)
 {
 	// Note that if the rpc action is async, the shared_ptr<json_handler> lifetime will be extended by the action handler
@@ -5360,6 +5367,7 @@ ipc_json_handler_no_arg_func_map create_ipc_json_handler_no_arg_func_map ()
 	no_arg_funcs.emplace ("work_peers_clear", &nano::json_handler::work_peers_clear);
 	no_arg_funcs.emplace ("populate_backlog", &nano::json_handler::populate_backlog);
 	no_arg_funcs.emplace ("debug_bootstrap_priority_info", &nano::json_handler::debug_bootstrap_priority_info);
+	no_arg_funcs.emplace ("bootstrap_reset", &nano::json_handler::bootstrap_reset);
 	return no_arg_funcs;
 }
 
diff --git a/nano/node/json_handler.hpp b/nano/node/json_handler.hpp
index 5780054871..b45b446782 100644
--- a/nano/node/json_handler.hpp
+++ b/nano/node/json_handler.hpp
@@ -71,6 +71,7 @@ class json_handler : public std::enable_shared_from_this<nano::json_handler>
 	void confirmation_info ();
 	void confirmation_quorum ();
 	void debug_bootstrap_priority_info ();
+	void bootstrap_reset ();
 	void database_txn_tracker ();
 	void delegators ();
 	void delegators_count ();
diff --git a/nano/rpc/rpc_handler.cpp b/nano/rpc/rpc_handler.cpp
index 4d4a6f36dc..804c38f300 100644
--- a/nano/rpc/rpc_handler.cpp
+++ b/nano/rpc/rpc_handler.cpp
@@ -152,6 +152,8 @@ std::unordered_set<std::string> create_rpc_control_impls ()
 	set.emplace ("backoff_info");
 	set.emplace ("block_create");
 	set.emplace ("bootstrap_lazy");
+	set.emplace ("bootstrap_reset");
+	set.emplace ("debug_bootstrap_priority_info");
 	set.emplace ("database_txn_tracker");
 	set.emplace ("epoch_upgrade");
 	set.emplace ("keepalive");

From b683a6179b7c874cfbabf79160a4c997b83ae65b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?=
 <3044353+pwojcikdev@users.noreply.github.com>
Date: Mon, 6 Jan 2025 19:57:13 +0100
Subject: [PATCH 2/6] Extend bootstrap priorities info rpc

---
 nano/node/json_handler.cpp | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp
index 6e9547aef6..64f40ec068 100644
--- a/nano/node/json_handler.cpp
+++ b/nano/node/json_handler.cpp
@@ -5166,29 +5166,32 @@ void nano::json_handler::debug_bootstrap_priority_info ()
 	{
 		auto [blocking, priorities] = node.bootstrap.info ();
 
-		// priorities
+		// Priorities
 		{
-			boost::property_tree::ptree response_priorities;
+			boost::property_tree::ptree response_l;
 			for (auto const & entry : priorities)
 			{
-				const auto account = entry.account;
-				const auto priority = entry.priority;
+				boost::property_tree::ptree entry_l;
+				entry_l.put ("account", entry.account.to_account ());
+				entry_l.put ("priority", entry.priority);
 
-				response_priorities.put (account.to_account (), priority);
+				response_l.push_back (std::make_pair ("", entry_l));
 			}
-			response_l.add_child ("priorities", response_priorities);
+			response_l.add_child ("priorities", response_l);
 		}
-		// blocking
+		// Blocking
 		{
-			boost::property_tree::ptree response_blocking;
+			boost::property_tree::ptree response_l;
 			for (auto const & entry : blocking)
 			{
-				const auto account = entry.account;
-				const auto dependency = entry.dependency;
+				boost::property_tree::ptree entry_l;
+				entry_l.put ("account", entry.account.to_account ());
+				entry_l.put ("dependency", entry.dependency.to_string ());
+				entry_l.put ("dependency_account", entry.dependency_account.to_account ());
 
-				response_blocking.put (account.to_account (), dependency.to_string ());
+				response_l.push_back (std::make_pair ("", entry_l));
 			}
-			response_l.add_child ("blocking", response_blocking);
+			response_l.add_child ("blocking", response_l);
 		}
 	}
 	response_errors ();

From 0cce7967f1319fa97afdbcb0b9cb4a3834274528 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?=
 <3044353+pwojcikdev@users.noreply.github.com>
Date: Tue, 7 Jan 2025 15:46:11 +0100
Subject: [PATCH 3/6] Rename rpc command `debug_bootstrap_priority_info` to
 `bootstrap_priorities`

---
 nano/node/json_handler.cpp | 4 ++--
 nano/node/json_handler.hpp | 2 +-
 nano/rpc/rpc_handler.cpp   | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp
index 64f40ec068..4fe43b3b92 100644
--- a/nano/node/json_handler.cpp
+++ b/nano/node/json_handler.cpp
@@ -5160,7 +5160,7 @@ void nano::json_handler::populate_backlog ()
 	response_errors ();
 }
 
-void nano::json_handler::debug_bootstrap_priority_info ()
+void nano::json_handler::bootstrap_priorities ()
 {
 	if (!ec)
 	{
@@ -5369,7 +5369,7 @@ ipc_json_handler_no_arg_func_map create_ipc_json_handler_no_arg_func_map ()
 	no_arg_funcs.emplace ("work_peers", &nano::json_handler::work_peers);
 	no_arg_funcs.emplace ("work_peers_clear", &nano::json_handler::work_peers_clear);
 	no_arg_funcs.emplace ("populate_backlog", &nano::json_handler::populate_backlog);
-	no_arg_funcs.emplace ("debug_bootstrap_priority_info", &nano::json_handler::debug_bootstrap_priority_info);
+	no_arg_funcs.emplace ("bootstrap_priorities", &nano::json_handler::bootstrap_priorities);
 	no_arg_funcs.emplace ("bootstrap_reset", &nano::json_handler::bootstrap_reset);
 	return no_arg_funcs;
 }
diff --git a/nano/node/json_handler.hpp b/nano/node/json_handler.hpp
index b45b446782..22ab99ad9f 100644
--- a/nano/node/json_handler.hpp
+++ b/nano/node/json_handler.hpp
@@ -70,7 +70,7 @@ class json_handler : public std::enable_shared_from_this<nano::json_handler>
 	void confirmation_history ();
 	void confirmation_info ();
 	void confirmation_quorum ();
-	void debug_bootstrap_priority_info ();
+	void bootstrap_priorities ();
 	void bootstrap_reset ();
 	void database_txn_tracker ();
 	void delegators ();
diff --git a/nano/rpc/rpc_handler.cpp b/nano/rpc/rpc_handler.cpp
index 804c38f300..053140ebaa 100644
--- a/nano/rpc/rpc_handler.cpp
+++ b/nano/rpc/rpc_handler.cpp
@@ -153,7 +153,7 @@ std::unordered_set<std::string> create_rpc_control_impls ()
 	set.emplace ("block_create");
 	set.emplace ("bootstrap_lazy");
 	set.emplace ("bootstrap_reset");
-	set.emplace ("debug_bootstrap_priority_info");
+	set.emplace ("bootstrap_priorities");
 	set.emplace ("database_txn_tracker");
 	set.emplace ("epoch_upgrade");
 	set.emplace ("keepalive");

From 0b56b3953b37b97544b2d4b074022caf1d43e3ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?=
 <3044353+pwojcikdev@users.noreply.github.com>
Date: Tue, 7 Jan 2025 17:21:59 +0100
Subject: [PATCH 4/6] Implement `bootstrap_status` rpc

---
 nano/node/bootstrap/bootstrap_service.cpp |  9 +++++++++
 nano/node/bootstrap/bootstrap_service.hpp | 11 +++++++++--
 nano/node/json_handler.cpp                |  7 ++++++-
 3 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/nano/node/bootstrap/bootstrap_service.cpp b/nano/node/bootstrap/bootstrap_service.cpp
index 13727436e3..5c98ba0c85 100644
--- a/nano/node/bootstrap/bootstrap_service.cpp
+++ b/nano/node/bootstrap/bootstrap_service.cpp
@@ -1164,6 +1164,15 @@ auto nano::bootstrap_service::info () const -> nano::bootstrap::account_sets::in
 	return accounts.info ();
 }
 
+auto nano::bootstrap_service::status () const -> status_result
+{
+	nano::lock_guard<nano::mutex> lock{ mutex };
+	return {
+		.priorities = accounts.priority_size (),
+		.blocking = accounts.blocked_size (),
+	};
+}
+
 std::size_t nano::bootstrap_service::compute_throttle_size () const
 {
 	auto ledger_size = ledger.account_count ();
diff --git a/nano/node/bootstrap/bootstrap_service.hpp b/nano/node/bootstrap/bootstrap_service.hpp
index 408980b57c..ee9e31cc33 100644
--- a/nano/node/bootstrap/bootstrap_service.hpp
+++ b/nano/node/bootstrap/bootstrap_service.hpp
@@ -53,10 +53,17 @@ class bootstrap_service
 	bool prioritized (nano::account const &) const;
 	bool blocked (nano::account const &) const;
 
-	nano::container_info container_info () const;
-
 	nano::bootstrap::account_sets::info_t info () const;
 
+	struct status_result
+	{
+		size_t priorities;
+		size_t blocking;
+	};
+	status_result status () const;
+
+	nano::container_info container_info () const;
+
 private: // Dependencies
 	bootstrap_config const & config;
 	nano::network_constants const & network_constants;
diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp
index 4fe43b3b92..091482b39b 100644
--- a/nano/node/json_handler.cpp
+++ b/nano/node/json_handler.cpp
@@ -1843,7 +1843,12 @@ void nano::json_handler::bootstrap_lazy ()
  */
 void nano::json_handler::bootstrap_status ()
 {
-	// TODO: Bootstrap status for ascending bootstrap
+	auto status = node.bootstrap.status ();
+
+	// Only summary information
+	response_l.put ("priorities", status.priorities);
+	response_l.put ("blocking", status.blocking);
+
 	response_errors ();
 }
 

From bff6d3513759849db01e089ea4739945799c5616 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?=
 <3044353+pwojcikdev@users.noreply.github.com>
Date: Tue, 7 Jan 2025 15:48:52 +0100
Subject: [PATCH 5/6] Cleanup

---
 nano/node/json_handler.cpp | 99 ++++++++++++++++++++------------------
 nano/node/json_handler.hpp |  6 +--
 2 files changed, 54 insertions(+), 51 deletions(-)

diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp
index 091482b39b..ce399d2db1 100644
--- a/nano/node/json_handler.cpp
+++ b/nano/node/json_handler.cpp
@@ -1831,10 +1831,7 @@ void nano::json_handler::bootstrap_lazy ()
 {
 	auto hash (hash_impl ());
 	bool const force = request.get<bool> ("force", false);
-	if (!ec)
-	{
-		ec = nano::error_rpc::disabled_bootstrap_lazy;
-	}
+	ec = nano::error_rpc::disabled_bootstrap_lazy;
 	response_errors ();
 }
 
@@ -1852,6 +1849,56 @@ void nano::json_handler::bootstrap_status ()
 	response_errors ();
 }
 
+/*
+ * @warning This is an internal/diagnostic RPC, do not rely on its interface being stable
+ */
+void nano::json_handler::bootstrap_priorities ()
+{
+	if (!ec)
+	{
+		auto [blocking, priorities] = node.bootstrap.info ();
+
+		// Priorities
+		{
+			boost::property_tree::ptree resp_l;
+			for (auto const & entry : priorities)
+			{
+				boost::property_tree::ptree entry_l;
+				entry_l.put ("account", entry.account.to_account ());
+				entry_l.put ("priority", entry.priority);
+
+				resp_l.push_back (std::make_pair ("", entry_l));
+			}
+			response_l.add_child ("priorities", resp_l);
+		}
+		// Blocking
+		{
+			boost::property_tree::ptree resp_l;
+			for (auto const & entry : blocking)
+			{
+				boost::property_tree::ptree entry_l;
+				entry_l.put ("account", entry.account.to_account ());
+				entry_l.put ("dependency", entry.dependency.to_string ());
+				entry_l.put ("dependency_account", entry.dependency_account.to_account ());
+
+				resp_l.push_back (std::make_pair ("", entry_l));
+			}
+			response_l.add_child ("blocking", resp_l);
+		}
+	}
+	response_errors ();
+}
+
+/*
+ * @warning This is an internal/diagnostic RPC, do not rely on its interface being stable
+ */
+void nano::json_handler::bootstrap_reset ()
+{
+	node.bootstrap.reset ();
+	response_l.put ("success", "");
+	response_errors ();
+}
+
 void nano::json_handler::chain (bool successors)
 {
 	successors = successors != request.get<bool> ("reverse", false);
@@ -5165,50 +5212,6 @@ void nano::json_handler::populate_backlog ()
 	response_errors ();
 }
 
-void nano::json_handler::bootstrap_priorities ()
-{
-	if (!ec)
-	{
-		auto [blocking, priorities] = node.bootstrap.info ();
-
-		// Priorities
-		{
-			boost::property_tree::ptree response_l;
-			for (auto const & entry : priorities)
-			{
-				boost::property_tree::ptree entry_l;
-				entry_l.put ("account", entry.account.to_account ());
-				entry_l.put ("priority", entry.priority);
-
-				response_l.push_back (std::make_pair ("", entry_l));
-			}
-			response_l.add_child ("priorities", response_l);
-		}
-		// Blocking
-		{
-			boost::property_tree::ptree response_l;
-			for (auto const & entry : blocking)
-			{
-				boost::property_tree::ptree entry_l;
-				entry_l.put ("account", entry.account.to_account ());
-				entry_l.put ("dependency", entry.dependency.to_string ());
-				entry_l.put ("dependency_account", entry.dependency_account.to_account ());
-
-				response_l.push_back (std::make_pair ("", entry_l));
-			}
-			response_l.add_child ("blocking", response_l);
-		}
-	}
-	response_errors ();
-}
-
-void nano::json_handler::bootstrap_reset ()
-{
-	node.bootstrap.reset ();
-	response_l.put ("success", "");
-	response_errors ();
-}
-
 void nano::inprocess_rpc_handler::process_request (std::string const &, std::string const & body_a, std::function<void (std::string const &)> response_a)
 {
 	// Note that if the rpc action is async, the shared_ptr<json_handler> lifetime will be extended by the action handler
diff --git a/nano/node/json_handler.hpp b/nano/node/json_handler.hpp
index 22ab99ad9f..95aa314fd4 100644
--- a/nano/node/json_handler.hpp
+++ b/nano/node/json_handler.hpp
@@ -65,13 +65,13 @@ class json_handler : public std::enable_shared_from_this<nano::json_handler>
 	void bootstrap_any ();
 	void bootstrap_lazy ();
 	void bootstrap_status ();
-	void chain (bool = false);
+	void bootstrap_priorities ();
+	void bootstrap_reset ();
+	void chain (bool successors = false);
 	void confirmation_active ();
 	void confirmation_history ();
 	void confirmation_info ();
 	void confirmation_quorum ();
-	void bootstrap_priorities ();
-	void bootstrap_reset ();
 	void database_txn_tracker ();
 	void delegators ();
 	void delegators_count ();

From f9d7c76a6824e92f3432a104fc4f29454660af05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?=
 <3044353+pwojcikdev@users.noreply.github.com>
Date: Tue, 7 Jan 2025 16:15:27 +0100
Subject: [PATCH 6/6] Tests

---
 nano/core_test/bootstrap.cpp |  56 +++++++++++++++---
 nano/node/node.cpp           |  10 ++++
 nano/node/node.hpp           |   4 +-
 nano/rpc_test/rpc.cpp        | 108 +++++++++++++++++++++++++++++++++++
 4 files changed, 168 insertions(+), 10 deletions(-)

diff --git a/nano/core_test/bootstrap.cpp b/nano/core_test/bootstrap.cpp
index 8d866031b0..b2c17a6744 100644
--- a/nano/core_test/bootstrap.cpp
+++ b/nano/core_test/bootstrap.cpp
@@ -270,18 +270,12 @@ TEST (bootstrap, trace_base)
 					.sign (key.prv, key.pub)
 					.work (*system.work.generate (key.pub))
 					.build ();
-	//	std::cerr << "Genesis key: " << nano::dev::genesis_key.pub.to_account () << std::endl;
-	//	std::cerr << "Key: " << key.pub.to_account () << std::endl;
-	//	std::cerr << "Genesis: " << nano::dev::genesis->hash ().to_string () << std::endl;
-	//	std::cerr << "send1: " << send1->hash ().to_string () << std::endl;
-	//	std::cerr << "receive1: " << receive1->hash ().to_string () << std::endl;
 	auto & node1 = *system.add_node ();
-	//	std::cerr << "--------------- Start ---------------\n";
+
 	ASSERT_EQ (nano::block_status::progress, node0.process (send1));
 	ASSERT_EQ (nano::block_status::progress, node0.process (receive1));
+
 	ASSERT_EQ (node1.ledger.any.receivable_end (), node1.ledger.any.receivable_upper_bound (node1.ledger.tx_begin_read (), key.pub, 0));
-	//	std::cerr << "node0: " << node0.network.endpoint () << std::endl;
-	//	std::cerr << "node1: " << node1.network.endpoint () << std::endl;
 	ASSERT_TIMELY (10s, node1.block (receive1->hash ()) != nullptr);
 }
 
@@ -573,4 +567,48 @@ TEST (bootstrap, frontier_scan_cannot_prioritize)
 	}));
 }
 
-// TODO: Test bootstrap.reset ()
\ No newline at end of file
+/*
+ * Tests that bootstrap.reset() properly recovers when called during an ongoing bootstrap
+ * This mimics node restart behaviour so this also ensures that the bootstrap is able to recover from a node restart
+ */
+TEST (bootstrap, reset)
+{
+	nano::test::system system;
+
+	// Test configuration
+	int const chain_count = 10;
+	int const blocks_per_chain = 5;
+
+	nano::node_config config;
+	// Disable election activation
+	config.backlog_scan.enable = false;
+	config.priority_scheduler.enable = false;
+	config.optimistic_scheduler.enable = false;
+	config.hinted_scheduler.enable = false;
+	// Add request limits to slow down bootstrap
+	config.bootstrap.rate_limit = 30;
+
+	// Start server node
+	auto & node_server = *system.add_node (config);
+
+	// Create multiple chains of blocks
+	auto chains = nano::test::setup_chains (system, node_server, chain_count, blocks_per_chain);
+
+	int const total_blocks = node_server.block_count ();
+	int const halfway_blocks = total_blocks / 2;
+
+	// Start client node and begin bootstrap
+	auto & node_client = *system.add_node (config);
+	ASSERT_LE (node_client.block_count (), halfway_blocks); // Should not be synced yet
+
+	// Wait until bootstrap has started and processed some blocks but not all
+	// Target approximately halfway through
+	ASSERT_TIMELY (15s, node_client.block_count () >= halfway_blocks);
+	ASSERT_LT (node_client.block_count (), total_blocks);
+
+	// Reset bootstrap halfway through the process
+	node_client.bootstrap.reset ();
+
+	// Bootstrap should automatically restart and eventually sync all blocks
+	ASSERT_TIMELY_EQ (30s, node_client.block_count (), total_blocks);
+}
\ No newline at end of file
diff --git a/nano/node/node.cpp b/nano/node/node.cpp
index b6838515fa..96847d1c37 100644
--- a/nano/node/node.cpp
+++ b/nano/node/node.cpp
@@ -1105,6 +1105,16 @@ void nano::node::bootstrap_block (const nano::block_hash & hash)
 	}
 }
 
+uint64_t nano::node::block_count () const
+{
+	return ledger.block_count ();
+}
+
+uint64_t nano::node::cemented_count () const
+{
+	return ledger.cemented_count ();
+}
+
 nano::account nano::node::get_node_id () const
 {
 	return node_id.pub;
diff --git a/nano/node/node.hpp b/nano/node/node.hpp
index fc6581829a..6b58ff1548 100644
--- a/nano/node/node.hpp
+++ b/nano/node/node.hpp
@@ -77,11 +77,13 @@ class node final : public std::enable_shared_from_this<node>
 	void add_initial_peers ();
 	void start_election (std::shared_ptr<nano::block> const & block);
 	bool block_confirmed (nano::block_hash const &);
-
 	// This function may spuriously return false after returning true until the database transaction is refreshed
 	bool block_confirmed_or_being_confirmed (nano::secure::transaction const &, nano::block_hash const &);
 	bool block_confirmed_or_being_confirmed (nano::block_hash const &);
 
+	uint64_t block_count () const;
+	uint64_t cemented_count () const;
+
 	void do_rpc_callback (boost::asio::ip::tcp::resolver::iterator i_a, std::string const &, uint16_t, std::shared_ptr<std::string> const &, std::shared_ptr<std::string> const &, std::shared_ptr<boost::asio::ip::tcp::resolver> const &);
 	bool online () const;
 	bool init_error () const;
diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp
index 82c9837d5b..f3a2e50644 100644
--- a/nano/rpc_test/rpc.cpp
+++ b/nano/rpc_test/rpc.cpp
@@ -6876,3 +6876,111 @@ TEST (rpc, election_statistics)
 	ASSERT_LT (response.get<int> ("max_election_age"), 5000);
 	ASSERT_LT (response.get<int> ("average_election_age"), 5000);
 }
+
+TEST (rpc, bootstrap_priorities)
+{
+	nano::test::system system;
+
+	// Test configuration
+	int const chain_count = 5;
+	int const blocks_per_chain = 2;
+
+	nano::node_config config;
+	config.bootstrap.rate_limit = 10; // Add request limits to slow down bootstrap
+
+	// Start server node
+	auto & node_server = *system.add_node (config);
+
+	// Create multiple chains of blocks
+	auto chains = nano::test::setup_chains (system, node_server, chain_count, blocks_per_chain);
+
+	int const total_blocks = node_server.block_count ();
+	int const halfway_blocks = total_blocks / 2;
+
+	// Start client node to observe bootstrap
+	auto node = add_ipc_enabled_node (system);
+	auto const rpc_ctx = add_rpc (system, node);
+
+	// Wait until bootstrap has started and processed some blocks but not all
+	// Target approximately halfway through
+	ASSERT_TIMELY (15s, node->block_count () >= halfway_blocks);
+
+	boost::property_tree::ptree request;
+	request.put ("action", "bootstrap_priorities");
+	auto response = wait_response (system, rpc_ctx, request);
+
+	ASSERT_TRUE (response.get_child_optional ("priorities"));
+	ASSERT_TRUE (response.get_child_optional ("blocking"));
+	ASSERT_FALSE (response.get_child_optional ("priorities").value ().empty ());
+}
+
+TEST (rpc, bootstrap_reset)
+{
+	nano::test::system system;
+
+	// Test configuration
+	int const chain_count = 5;
+	int const blocks_per_chain = 2;
+
+	nano::node_config config;
+	config.bootstrap.rate_limit = 10; // Add request limits to slow down bootstrap
+
+	// Start server node
+	auto & node_server = *system.add_node (config);
+
+	// Create multiple chains of blocks
+	auto chains = nano::test::setup_chains (system, node_server, chain_count, blocks_per_chain);
+
+	int const total_blocks = node_server.block_count ();
+	int const halfway_blocks = total_blocks / 2;
+
+	// Start client node to observe bootstrap
+	auto node = add_ipc_enabled_node (system);
+	auto const rpc_ctx = add_rpc (system, node);
+
+	// Wait until bootstrap has started and processed some blocks but not all
+	// Target approximately halfway through
+	ASSERT_TIMELY (15s, node->block_count () >= halfway_blocks);
+
+	boost::property_tree::ptree request;
+	request.put ("action", "bootstrap_reset");
+	auto response = wait_response (system, rpc_ctx, request);
+
+	ASSERT_TRUE (response.get<std::string> ("success").empty ());
+}
+
+TEST (rpc, bootstrap_status)
+{
+	nano::test::system system;
+
+	// Test configuration
+	int const chain_count = 5;
+	int const blocks_per_chain = 2;
+
+	nano::node_config config;
+	config.bootstrap.rate_limit = 10; // Add request limits to slow down bootstrap
+
+	// Start server node
+	auto & node_server = *system.add_node (config);
+
+	// Create multiple chains of blocks
+	auto chains = nano::test::setup_chains (system, node_server, chain_count, blocks_per_chain);
+
+	int const total_blocks = node_server.block_count ();
+	int const halfway_blocks = total_blocks / 2;
+
+	// Start client node to observe bootstrap
+	auto node = add_ipc_enabled_node (system);
+	auto const rpc_ctx = add_rpc (system, node);
+
+	// Wait until bootstrap has started and processed some blocks but not all
+	// Target approximately halfway through
+	ASSERT_TIMELY (15s, node->block_count () >= halfway_blocks);
+
+	boost::property_tree::ptree request;
+	request.put ("action", "bootstrap_status");
+	auto response = wait_response (system, rpc_ctx, request);
+
+	ASSERT_GT (response.get<int> ("priorities"), 0);
+	ASSERT_EQ (response.get<int> ("blocking"), 0);
+}
\ No newline at end of file