Benchmarks
This commit is contained in:
		
					parent
					
						
							
								98d9d04ccf
							
						
					
				
			
			
				commit
				
					
						0e9d1c2807
					
				
			
		
					 14 changed files with 2006 additions and 6 deletions
				
			
		| 
						 | 
					@ -312,10 +312,10 @@ spdlog::level::level_enum nano::logger::to_spdlog_level (nano::log::level level)
 | 
				
			||||||
 * logging config presets
 | 
					 * logging config presets
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nano::log_config nano::log_config::cli_default ()
 | 
					nano::log_config nano::log_config::cli_default (nano::log::level default_level)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	log_config config{};
 | 
						log_config config{};
 | 
				
			||||||
	config.default_level = nano::log::level::critical;
 | 
						config.default_level = default_level;
 | 
				
			||||||
	config.console.colors = false;
 | 
						config.console.colors = false;
 | 
				
			||||||
	config.console.to_cerr = true; // Use cerr to avoid interference with CLI output that goes to stdout
 | 
						config.console.to_cerr = true; // Use cerr to avoid interference with CLI output that goes to stdout
 | 
				
			||||||
	config.file.enable = false;
 | 
						config.file.enable = false;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,7 +134,7 @@ public:
 | 
				
			||||||
	nano::log::tracing_format tracing_format{ nano::log::tracing_format::standard };
 | 
						nano::log::tracing_format tracing_format{ nano::log::tracing_format::standard };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public: // Predefined defaults
 | 
					public: // Predefined defaults
 | 
				
			||||||
	static log_config cli_default ();
 | 
						static log_config cli_default (nano::log::level default_level = nano::log::level::critical);
 | 
				
			||||||
	static log_config daemon_default ();
 | 
						static log_config daemon_default ();
 | 
				
			||||||
	static log_config tests_default ();
 | 
						static log_config tests_default ();
 | 
				
			||||||
	static log_config dummy_default (); // For empty logger
 | 
						static log_config dummy_default (); // For empty logger
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,14 @@
 | 
				
			||||||
add_executable(nano_node daemon.cpp daemon.hpp entry.cpp)
 | 
					add_executable(
 | 
				
			||||||
 | 
					  nano_node
 | 
				
			||||||
 | 
					  benchmarks/benchmarks.cpp
 | 
				
			||||||
 | 
					  benchmarks/benchmarks.hpp
 | 
				
			||||||
 | 
					  benchmarks/benchmark_block_processing.cpp
 | 
				
			||||||
 | 
					  benchmarks/benchmark_cementing.cpp
 | 
				
			||||||
 | 
					  benchmarks/benchmark_elections.cpp
 | 
				
			||||||
 | 
					  benchmarks/benchmark_pipeline.cpp
 | 
				
			||||||
 | 
					  daemon.cpp
 | 
				
			||||||
 | 
					  daemon.hpp
 | 
				
			||||||
 | 
					  entry.cpp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
target_link_libraries(nano_node node Boost::process ${PLATFORM_LIBS})
 | 
					target_link_libraries(nano_node node Boost::process ${PLATFORM_LIBS})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										253
									
								
								nano/nano_node/benchmarks/benchmark_block_processing.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								nano/nano_node/benchmarks/benchmark_block_processing.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,253 @@
 | 
				
			||||||
 | 
					#include <nano/lib/config.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/locks.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/thread_runner.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/timer.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/work.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/work_version.hpp>
 | 
				
			||||||
 | 
					#include <nano/nano_node/benchmarks/benchmarks.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/cli.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/daemonconfig.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/ledger_notifications.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/asio/io_context.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include <unordered_set>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <fmt/format.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nano::cli
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Block Processing Benchmark
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Measures the performance of the block processor - the component responsible for validating
 | 
				
			||||||
 | 
					 * and inserting blocks into the ledger. This benchmark tests raw block processing throughput
 | 
				
			||||||
 | 
					 * without elections or confirmation.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * How it works:
 | 
				
			||||||
 | 
					 * 1. Setup: Creates a node with unlimited queue sizes and disabled work requirements
 | 
				
			||||||
 | 
					 * 2. Generate: Creates random transfer transactions (send/receive pairs) between accounts
 | 
				
			||||||
 | 
					 * 3. Submit: Adds all blocks to the block processor queue via block_processor.add()
 | 
				
			||||||
 | 
					 * 4. Measure: Tracks time from submission until all blocks are processed into the ledger
 | 
				
			||||||
 | 
					 * 5. Report: Calculates blocks/sec throughput and final account states
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is tested:
 | 
				
			||||||
 | 
					 * - Block validation speed (signature verification, balance checks, etc.)
 | 
				
			||||||
 | 
					 * - Ledger write performance (database insertion)
 | 
				
			||||||
 | 
					 * - Block processor queue management
 | 
				
			||||||
 | 
					 * - Unchecked block handling for out-of-order blocks
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is NOT tested:
 | 
				
			||||||
 | 
					 * - Elections or voting (blocks are not confirmed)
 | 
				
			||||||
 | 
					 * - Cementing (blocks remain unconfirmed)
 | 
				
			||||||
 | 
					 * - Network communication (local-only testing)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class block_processing_benchmark : public benchmark_base
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						// Blocks currently being processed
 | 
				
			||||||
 | 
						nano::locked<std::unordered_set<nano::block_hash>> current_blocks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Metrics
 | 
				
			||||||
 | 
						std::atomic<size_t> processed_blocks_count{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> failed_blocks_count{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> old_blocks_count{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> gap_previous_count{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> gap_source_count{ 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						block_processing_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void run ();
 | 
				
			||||||
 | 
						void run_iteration (std::deque<std::shared_ptr<nano::block>> & blocks);
 | 
				
			||||||
 | 
						void print_statistics ();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void run_block_processing_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto config = benchmark_config::parse (vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "=== BENCHMARK: Block Processing ===\n";
 | 
				
			||||||
 | 
						std::cout << "Configuration:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Accounts: {}\n", config.num_accounts);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Iterations: {}\n", config.num_iterations);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Batch size: {}\n", config.batch_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup node directly in run method
 | 
				
			||||||
 | 
						nano::network_constants::set_active_network ("dev");
 | 
				
			||||||
 | 
						nano::logger::initialize (nano::log_config::cli_default (nano::log::level::warn));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nano::node_flags node_flags;
 | 
				
			||||||
 | 
						nano::update_flags (node_flags, vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto io_ctx = std::make_shared<boost::asio::io_context> ();
 | 
				
			||||||
 | 
						nano::work_pool work_pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load configuration from current working directory (if exists) and cli config overrides
 | 
				
			||||||
 | 
						auto daemon_config = nano::load_config_file<nano::daemon_config> (nano::node_config_filename, {}, node_flags.config_overrides);
 | 
				
			||||||
 | 
						auto node_config = daemon_config.node;
 | 
				
			||||||
 | 
						node_config.network_params.work = nano::work_thresholds{ 0, 0, 0 };
 | 
				
			||||||
 | 
						node_config.peering_port = 0; // Use random available port
 | 
				
			||||||
 | 
						node_config.max_backlog = 0; // Disable bounded backlog
 | 
				
			||||||
 | 
						node_config.block_processor.max_system_queue = std::numeric_limits<size_t>::max (); // Unlimited queue size
 | 
				
			||||||
 | 
						node_config.max_unchecked_blocks = 1024 * 1024; // Large unchecked blocks cache to avoid dropping blocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto node = std::make_shared<nano::node> (io_ctx, nano::unique_path (), node_config, work_pool, node_flags);
 | 
				
			||||||
 | 
						node->start ();
 | 
				
			||||||
 | 
						nano::thread_runner runner (io_ctx, nano::default_logger (), node->config.io_threads);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "\nSystem Info:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Backend: {}\n", node->store.vendor_get ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Block processor threads: {}\n", 1); // TODO: Log number of block processor threads when upstreamed
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Block processor batch size: {}\n", node->config.block_processor.batch_size);
 | 
				
			||||||
 | 
						std::cout << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for node to be ready
 | 
				
			||||||
 | 
						std::this_thread::sleep_for (500ms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run benchmark
 | 
				
			||||||
 | 
						block_processing_benchmark benchmark{ node, config };
 | 
				
			||||||
 | 
						benchmark.run ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stop ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					block_processing_benchmark::block_processing_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a) :
 | 
				
			||||||
 | 
						benchmark_base (node_a, config_a)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Register notification handler to track block processing results
 | 
				
			||||||
 | 
						node->ledger_notifications.blocks_processed.add ([this] (std::deque<std::pair<nano::block_status, nano::block_context>> const & batch) {
 | 
				
			||||||
 | 
							auto current_l = current_blocks.lock ();
 | 
				
			||||||
 | 
							for (auto const & [status, context] : batch)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (status == nano::block_status::progress)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									current_l->erase (context.block->hash ());
 | 
				
			||||||
 | 
									processed_blocks_count++;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									switch (status)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										case nano::block_status::old:
 | 
				
			||||||
 | 
											// Block already exists in ledger
 | 
				
			||||||
 | 
											old_blocks_count++;
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										case nano::block_status::gap_previous:
 | 
				
			||||||
 | 
											// Missing previous block, should be handled by unchecked map
 | 
				
			||||||
 | 
											gap_previous_count++;
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										case nano::block_status::gap_source:
 | 
				
			||||||
 | 
											// Missing source block, should be handled by unchecked map
 | 
				
			||||||
 | 
											gap_source_count++;
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										default:
 | 
				
			||||||
 | 
											std::cout << fmt::format ("Block processing failed: {} for block {}\n", to_string (status), context.block->hash ().to_string ());
 | 
				
			||||||
 | 
											failed_blocks_count++;
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void block_processing_benchmark::run ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Create account pool and distribute genesis funds to a random account
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generating {} accounts...\n", config.num_accounts);
 | 
				
			||||||
 | 
						pool.generate_accounts (config.num_accounts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setup_genesis_distribution ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run multiple iterations to measure consistent performance
 | 
				
			||||||
 | 
						for (size_t iteration = 0; iteration < config.num_iterations; ++iteration)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::cout << fmt::format ("\n--- Iteration {}/{} --------------------------------------------------------------\n", iteration + 1, config.num_iterations);
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Generating {} random transfers...\n", config.batch_size / 2);
 | 
				
			||||||
 | 
							auto blocks = generate_random_transfers ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Processing {} blocks...\n", blocks.size ());
 | 
				
			||||||
 | 
							run_iteration (blocks);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print_statistics ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void block_processing_benchmark::run_iteration (std::deque<std::shared_ptr<nano::block>> & blocks)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto const total_blocks = blocks.size ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add all blocks to tracking set
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto current_l = current_blocks.lock ();
 | 
				
			||||||
 | 
							for (auto const & block : blocks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								current_l->insert (block->hash ());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_begin = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Process all blocks
 | 
				
			||||||
 | 
						while (!blocks.empty ())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto block = blocks.front ();
 | 
				
			||||||
 | 
							blocks.pop_front ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool added = node->block_processor.add (block, nano::block_source::test);
 | 
				
			||||||
 | 
							release_assert (added, "failed to add block to processor");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for processing to complete
 | 
				
			||||||
 | 
						nano::interval progress_interval;
 | 
				
			||||||
 | 
						while (true)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto current_l = current_blocks.lock ();
 | 
				
			||||||
 | 
								if (current_l->empty () || progress_interval.elapse (3s))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									std::cout << fmt::format ("Blocks remaining: {:>9} (block processor: {:>9} | unchecked: {:>5})\n",
 | 
				
			||||||
 | 
									current_l->size (),
 | 
				
			||||||
 | 
									node->block_processor.size (),
 | 
				
			||||||
 | 
									node->unchecked.count ());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (current_l->empty ())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::this_thread::sleep_for (1ms);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_end = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
						auto const time_us = std::chrono::duration_cast<std::chrono::microseconds> (time_end - time_begin).count ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\nPerformance: {} blocks/sec [{:.2f}s] {} blocks processed\n",
 | 
				
			||||||
 | 
						total_blocks * 1000000 / time_us, time_us / 1000000.0, total_blocks);
 | 
				
			||||||
 | 
						std::cout << "─────────────────────────────────────────────────────────────────\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stats.clear ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void block_processing_benchmark::print_statistics ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << "\n--- SUMMARY ---------------------------------------------------------------------\n\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks processed:        {:>10}\n", processed_blocks_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks failed:           {:>10}\n", failed_blocks_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks old:              {:>10}\n", old_blocks_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks gap_previous:     {:>10}\n", gap_previous_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks gap_source:       {:>10}\n", gap_source_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\n");
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Accounts total:          {:>10}\n", pool.total_accounts ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Accounts with balance:   {:>10} ({:.1f}%)\n",
 | 
				
			||||||
 | 
						pool.accounts_with_balance_count (),
 | 
				
			||||||
 | 
						100.0 * pool.accounts_with_balance_count () / pool.total_accounts ());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										285
									
								
								nano/nano_node/benchmarks/benchmark_cementing.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										285
									
								
								nano/nano_node/benchmarks/benchmark_cementing.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,285 @@
 | 
				
			||||||
 | 
					#include <nano/lib/config.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/locks.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/thread_runner.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/timer.hpp>
 | 
				
			||||||
 | 
					#include <nano/nano_node/benchmarks/benchmarks.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/active_elections.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/cli.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/daemonconfig.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/ledger_notifications.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/node_observers.hpp>
 | 
				
			||||||
 | 
					#include <nano/secure/ledger.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/asio/io_context.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include <unordered_map>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <fmt/format.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nano::cli
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Cementing Benchmark
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Measures the performance of the cementing subsystem - the component that marks blocks
 | 
				
			||||||
 | 
					 * as confirmed/immutable in the ledger.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * How it works:
 | 
				
			||||||
 | 
					 * 1. Setup: Creates a node and generates random transfer blocks
 | 
				
			||||||
 | 
					 * 2. Process: Inserts blocks directly into ledger (bypassing block processor)
 | 
				
			||||||
 | 
					 * 3. Submit: Adds blocks to cementing set for confirmation
 | 
				
			||||||
 | 
					 * 4. Measure: Tracks time from submission until all blocks are cemented
 | 
				
			||||||
 | 
					 * 5. Report: Calculates cementing throughput in blocks/sec
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Two modes:
 | 
				
			||||||
 | 
					 * - Sequential mode: Each block is submitted to cementing set individually
 | 
				
			||||||
 | 
					 * - Root mode: Only the final block is submitted, which triggers cascading cementing
 | 
				
			||||||
 | 
					 *              of all dependent blocks (tests dependency resolution performance)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is tested:
 | 
				
			||||||
 | 
					 * - Cementing set processing speed
 | 
				
			||||||
 | 
					 * - Database write performance for confirmation marks
 | 
				
			||||||
 | 
					 * - Dependency resolution (root mode only)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is NOT tested:
 | 
				
			||||||
 | 
					 * - Block processing (blocks inserted directly into ledger)
 | 
				
			||||||
 | 
					 * - Elections or voting (blocks pre-confirmed)
 | 
				
			||||||
 | 
					 * - Network communication
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class cementing_benchmark : public benchmark_base
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						// Track blocks waiting to be cemented
 | 
				
			||||||
 | 
						nano::locked<std::unordered_map<nano::block_hash, std::chrono::steady_clock::time_point>> pending_cementing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Metrics
 | 
				
			||||||
 | 
						std::atomic<size_t> processed_blocks_count{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> cemented_blocks_count{ 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						cementing_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void run ();
 | 
				
			||||||
 | 
						void run_iteration (std::deque<std::shared_ptr<nano::block>> & blocks);
 | 
				
			||||||
 | 
						void print_statistics ();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void run_cementing_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto config = benchmark_config::parse (vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "=== BENCHMARK: Cementing ===\n";
 | 
				
			||||||
 | 
						std::cout << "Configuration:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Mode: {}\n", config.cementing_mode == cementing_mode::root ? "root" : "sequential");
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Accounts: {}\n", config.num_accounts);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Iterations: {}\n", config.num_iterations);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Batch size: {}\n", config.batch_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup node directly in run method
 | 
				
			||||||
 | 
						nano::network_constants::set_active_network ("dev");
 | 
				
			||||||
 | 
						nano::logger::initialize (nano::log_config::cli_default (nano::log::level::warn));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nano::node_flags node_flags;
 | 
				
			||||||
 | 
						nano::update_flags (node_flags, vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto io_ctx = std::make_shared<boost::asio::io_context> ();
 | 
				
			||||||
 | 
						nano::work_pool work_pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load configuration from current working directory (if exists) and cli config overrides
 | 
				
			||||||
 | 
						auto daemon_config = nano::load_config_file<nano::daemon_config> (nano::node_config_filename, {}, node_flags.config_overrides);
 | 
				
			||||||
 | 
						auto node_config = daemon_config.node;
 | 
				
			||||||
 | 
						node_config.network_params.work = nano::work_thresholds{ 0, 0, 0 };
 | 
				
			||||||
 | 
						node_config.peering_port = 0; // Use random available port
 | 
				
			||||||
 | 
						node_config.max_backlog = 0; // Disable bounded backlog
 | 
				
			||||||
 | 
						node_config.block_processor.max_system_queue = std::numeric_limits<size_t>::max (); // Unlimited queue size
 | 
				
			||||||
 | 
						node_config.max_unchecked_blocks = 1024 * 1024; // Large unchecked blocks cache to avoid dropping blocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto node = std::make_shared<nano::node> (io_ctx, nano::unique_path (), node_config, work_pool, node_flags);
 | 
				
			||||||
 | 
						node->start ();
 | 
				
			||||||
 | 
						nano::thread_runner runner (io_ctx, nano::default_logger (), node->config.io_threads);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "\nSystem Info:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Backend: {}\n", node->store.vendor_get ());
 | 
				
			||||||
 | 
						std::cout << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for node to be ready
 | 
				
			||||||
 | 
						std::this_thread::sleep_for (500ms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run benchmark
 | 
				
			||||||
 | 
						cementing_benchmark benchmark{ node, config };
 | 
				
			||||||
 | 
						benchmark.run ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stop ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cementing_benchmark::cementing_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a) :
 | 
				
			||||||
 | 
						benchmark_base (node_a, config_a)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Track when blocks get processed
 | 
				
			||||||
 | 
						node->ledger_notifications.blocks_processed.add ([this] (std::deque<std::pair<nano::block_status, nano::block_context>> const & batch) {
 | 
				
			||||||
 | 
							for (auto const & [status, context] : batch)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (status == nano::block_status::progress)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									processed_blocks_count++;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track when blocks get cemented
 | 
				
			||||||
 | 
						node->cementing_set.batch_cemented.add ([this] (auto const & hashes) {
 | 
				
			||||||
 | 
							auto pending_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
							for (auto const & ctx : hashes)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								pending_l->erase (ctx.block->hash ());
 | 
				
			||||||
 | 
								cemented_blocks_count++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void cementing_benchmark::run ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generating {} accounts...\n", config.num_accounts);
 | 
				
			||||||
 | 
						pool.generate_accounts (config.num_accounts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setup_genesis_distribution ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Cementing mode: {}\n", config.cementing_mode == cementing_mode::root ? "root" : "sequential");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (size_t iteration = 0; iteration < config.num_iterations; ++iteration)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::cout << fmt::format ("\n--- Iteration {}/{} --------------------------------------------------------------\n", iteration + 1, config.num_iterations);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::deque<std::shared_ptr<nano::block>> blocks;
 | 
				
			||||||
 | 
							if (config.cementing_mode == cementing_mode::root)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								std::cout << fmt::format ("Generating dependent chain topology...\n");
 | 
				
			||||||
 | 
								blocks = generate_dependent_chain ();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								std::cout << fmt::format ("Generating {} random transfers...\n", config.batch_size / 2);
 | 
				
			||||||
 | 
								blocks = generate_random_transfers ();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Cementing {} blocks...\n", blocks.size ());
 | 
				
			||||||
 | 
							run_iteration (blocks);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print_statistics ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void cementing_benchmark::run_iteration (std::deque<std::shared_ptr<nano::block>> & blocks)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto const total_blocks = blocks.size ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add all blocks to tracking set
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto pending_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
							for (auto const & block : blocks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								pending_l->emplace (block->hash (), now);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Processing {} blocks directly into the ledger...\n", blocks.size ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Process all blocks directly into the ledger
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto transaction = node->ledger.tx_begin_write ();
 | 
				
			||||||
 | 
							for (auto const & block : blocks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto result = node->ledger.process (transaction, block);
 | 
				
			||||||
 | 
								release_assert (result == nano::block_status::progress, to_string (result));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "All blocks processed, starting cementing...\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_begin = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Mode-specific cementing
 | 
				
			||||||
 | 
						size_t blocks_submitted = 0;
 | 
				
			||||||
 | 
						if (config.cementing_mode == cementing_mode::root)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// In root mode, only submit the final block which depends on all others
 | 
				
			||||||
 | 
							if (!blocks.empty ())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto final_block = blocks.back ();
 | 
				
			||||||
 | 
								bool added = node->cementing_set.add (final_block->hash ());
 | 
				
			||||||
 | 
								release_assert (added, "failed to add final block to cementing set");
 | 
				
			||||||
 | 
								blocks_submitted = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								std::cout << fmt::format ("Submitted 1 root block to cement {} dependent blocks\n",
 | 
				
			||||||
 | 
								total_blocks);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Sequential mode - submit each block separately
 | 
				
			||||||
 | 
							while (!blocks.empty ())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto block = blocks.front ();
 | 
				
			||||||
 | 
								blocks.pop_front ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bool added = node->cementing_set.add (block->hash ());
 | 
				
			||||||
 | 
								release_assert (added, "failed to add block to cementing set");
 | 
				
			||||||
 | 
								blocks_submitted++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Submitted {} blocks to cementing set\n",
 | 
				
			||||||
 | 
							blocks_submitted);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for cementing to complete
 | 
				
			||||||
 | 
						nano::interval progress_interval;
 | 
				
			||||||
 | 
						while (true)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto pending_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
								if (pending_l->empty () || progress_interval.elapse (3s))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									std::cout << fmt::format ("Blocks remaining: {:>9} (cementing set: {:>5} | deferred: {:>5})\n",
 | 
				
			||||||
 | 
									pending_l->size (),
 | 
				
			||||||
 | 
									node->cementing_set.size (),
 | 
				
			||||||
 | 
									node->cementing_set.deferred_size ());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (pending_l->empty ())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::this_thread::sleep_for (1ms);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_end = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
						auto const time_us = std::chrono::duration_cast<std::chrono::microseconds> (time_end - time_begin).count ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\nPerformance: {} blocks/sec [{:.2f}s] {} blocks processed\n",
 | 
				
			||||||
 | 
						total_blocks * 1000000 / time_us, time_us / 1000000.0, total_blocks);
 | 
				
			||||||
 | 
						std::cout << "─────────────────────────────────────────────────────────────────\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stats.clear ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void cementing_benchmark::print_statistics ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << "\n--- SUMMARY ---------------------------------------------------------------------\n\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Mode:                    {:>10}\n", config.cementing_mode == cementing_mode::root ? "root" : "sequential");
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks processed:        {:>10}\n", processed_blocks_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks cemented:         {:>10}\n", cemented_blocks_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\n");
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Accounts total:          {:>10}\n", pool.total_accounts ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Accounts with balance:   {:>10} ({:.1f}%)\n",
 | 
				
			||||||
 | 
						pool.accounts_with_balance_count (),
 | 
				
			||||||
 | 
						100.0 * pool.accounts_with_balance_count () / pool.total_accounts ());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										344
									
								
								nano/nano_node/benchmarks/benchmark_elections.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								nano/nano_node/benchmarks/benchmark_elections.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,344 @@
 | 
				
			||||||
 | 
					#include <nano/lib/config.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/locks.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/thread_runner.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/timer.hpp>
 | 
				
			||||||
 | 
					#include <nano/nano_node/benchmarks/benchmarks.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/active_elections.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/cli.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/daemonconfig.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/election.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/ledger_notifications.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/node_observers.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/scheduler/component.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/scheduler/manual.hpp>
 | 
				
			||||||
 | 
					#include <nano/secure/ledger.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/asio/io_context.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <fmt/format.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nano::cli
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Elections Benchmark
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Measures the performance of the election subsystem - the component that runs voting
 | 
				
			||||||
 | 
					 * consensus to cement blocks. Tests how quickly the node can start elections, collect
 | 
				
			||||||
 | 
					 * votes, reach quorum, and cement blocks.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * How it works:
 | 
				
			||||||
 | 
					 * 1. Setup: Creates a node with genesis representative key for voting
 | 
				
			||||||
 | 
					 * 2. Prepare: Generates independent open blocks (send blocks are pre-cemented)
 | 
				
			||||||
 | 
					 * 3. Process: Inserts open blocks directly into ledger (bypassing block processor)
 | 
				
			||||||
 | 
					 * 4. Start: Manually triggers elections for all open blocks
 | 
				
			||||||
 | 
					 * 5. Measure: Tracks time from election start until blocks are confirmed and cemented
 | 
				
			||||||
 | 
					 * 6. Report: Calculates election throughput and timing statistics
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is tested:
 | 
				
			||||||
 | 
					 * - Election startup performance
 | 
				
			||||||
 | 
					 * - Vote generation and processing speed (with one local rep running on the same node)
 | 
				
			||||||
 | 
					 * - Quorum detection and confirmation logic
 | 
				
			||||||
 | 
					 * - Cementing after confirmation
 | 
				
			||||||
 | 
					 * - Concurrent election handling
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is NOT tested:
 | 
				
			||||||
 | 
					 * - Block processing (blocks inserted directly)
 | 
				
			||||||
 | 
					 * - Network vote propagation (local voting only)
 | 
				
			||||||
 | 
					 * - Election schedulers (elections started manually)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class elections_benchmark : public benchmark_base
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						struct block_timing
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point submitted;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point election_started;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point election_stopped;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point cemented;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track timing for each block through the election pipeline
 | 
				
			||||||
 | 
						nano::locked<std::unordered_map<nano::block_hash, block_timing>> block_timings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nano::locked<std::unordered_set<nano::block_hash>> pending_confirmation;
 | 
				
			||||||
 | 
						nano::locked<std::unordered_set<nano::block_hash>> pending_cementing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Metrics
 | 
				
			||||||
 | 
						std::atomic<size_t> elections_started{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> elections_stopped{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> elections_confirmed{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> blocks_cemented{ 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						elections_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void run ();
 | 
				
			||||||
 | 
						void run_iteration (std::deque<std::shared_ptr<nano::block>> & sends, std::deque<std::shared_ptr<nano::block>> & opens);
 | 
				
			||||||
 | 
						void print_statistics ();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void run_elections_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto config = benchmark_config::parse (vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "=== BENCHMARK: Elections ===\n";
 | 
				
			||||||
 | 
						std::cout << "Configuration:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Accounts: {}\n", config.num_accounts);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Iterations: {}\n", config.num_iterations);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Batch size: {}\n", config.batch_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup node directly in run method
 | 
				
			||||||
 | 
						nano::network_constants::set_active_network ("dev");
 | 
				
			||||||
 | 
						nano::logger::initialize (nano::log_config::cli_default (nano::log::level::warn));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nano::node_flags node_flags;
 | 
				
			||||||
 | 
						nano::update_flags (node_flags, vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto io_ctx = std::make_shared<boost::asio::io_context> ();
 | 
				
			||||||
 | 
						nano::work_pool work_pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load configuration from current working directory (if exists) and cli config overrides
 | 
				
			||||||
 | 
						auto daemon_config = nano::load_config_file<nano::daemon_config> (nano::node_config_filename, {}, node_flags.config_overrides);
 | 
				
			||||||
 | 
						auto node_config = daemon_config.node;
 | 
				
			||||||
 | 
						node_config.network_params.work = nano::work_thresholds{ 0, 0, 0 };
 | 
				
			||||||
 | 
						node_config.peering_port = 0; // Use random available port
 | 
				
			||||||
 | 
						node_config.max_backlog = 0; // Disable bounded backlog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Disable election schedulers and backlog scanning
 | 
				
			||||||
 | 
						node_config.hinted_scheduler.enable = false;
 | 
				
			||||||
 | 
						node_config.optimistic_scheduler.enable = false;
 | 
				
			||||||
 | 
						node_config.priority_scheduler.enable = false;
 | 
				
			||||||
 | 
						node_config.backlog_scan.enable = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node_config.block_processor.max_peer_queue = std::numeric_limits<size_t>::max (); // Unlimited queue size
 | 
				
			||||||
 | 
						node_config.block_processor.max_system_queue = std::numeric_limits<size_t>::max (); // Unlimited queue size
 | 
				
			||||||
 | 
						node_config.max_unchecked_blocks = 1024 * 1024; // Large unchecked blocks cache to avoid dropping blocks
 | 
				
			||||||
 | 
						node_config.vote_processor.max_pr_queue = std::numeric_limits<size_t>::max (); // Unlimited vote processing queue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto node = std::make_shared<nano::node> (io_ctx, nano::unique_path (), node_config, work_pool, node_flags);
 | 
				
			||||||
 | 
						node->start ();
 | 
				
			||||||
 | 
						nano::thread_runner runner (io_ctx, nano::default_logger (), node->config.io_threads);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "\nSystem Info:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Backend: {}\n", node->store.vendor_get ());
 | 
				
			||||||
 | 
						std::cout << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Insert dev genesis representative key for voting
 | 
				
			||||||
 | 
						auto wallet = node->wallets.create (nano::random_wallet_id ());
 | 
				
			||||||
 | 
						wallet->insert_adhoc (nano::dev::genesis_key.prv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for node to be ready
 | 
				
			||||||
 | 
						std::this_thread::sleep_for (500ms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run benchmark
 | 
				
			||||||
 | 
						elections_benchmark benchmark{ node, config };
 | 
				
			||||||
 | 
						benchmark.run ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stop ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					elections_benchmark::elections_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a) :
 | 
				
			||||||
 | 
						benchmark_base (node_a, config_a)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Track when elections start
 | 
				
			||||||
 | 
						node->active.election_started.add ([this] (std::shared_ptr<nano::election> const & election, nano::bucket_index const & bucket, nano::priority_timestamp const & priority) {
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto hash = election->winner ()->hash ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (auto it = timings_l->find (hash); it != timings_l->end ())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								it->second.election_started = now;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elections_started++;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track when elections stop (regardless of confirmation)
 | 
				
			||||||
 | 
						node->active.election_erased.add ([this] (std::shared_ptr<nano::election> const & election) {
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto hash = election->winner ()->hash ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
							auto pending_confirmation_l = pending_confirmation.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (auto it = timings_l->find (hash); it != timings_l->end ())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								it->second.election_stopped = now;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pending_confirmation_l->erase (hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elections_stopped++;
 | 
				
			||||||
 | 
							elections_confirmed += election->confirmed () ? 1 : 0;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track when blocks get cemented
 | 
				
			||||||
 | 
						node->cementing_set.batch_cemented.add ([this] (auto const & hashes) {
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto pending_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto const & ctx : hashes)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto hash = ctx.block->hash ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (auto it = timings_l->find (hash); it != timings_l->end ())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									it->second.cemented = now;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								pending_l->erase (hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								blocks_cemented++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void elections_benchmark::run ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generating {} accounts...\n", config.num_accounts);
 | 
				
			||||||
 | 
						pool.generate_accounts (config.num_accounts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setup_genesis_distribution (0.1); // Only distribute 10%, keep 90% for voting weight
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (size_t iteration = 0; iteration < config.num_iterations; ++iteration)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::cout << fmt::format ("\n--- Iteration {}/{} --------------------------------------------------------------\n", iteration + 1, config.num_iterations);
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Generating independent blocks...\n");
 | 
				
			||||||
 | 
							auto [sends, opens] = generate_independent_blocks ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Measuring elections performance for {} opens...\n", opens.size ());
 | 
				
			||||||
 | 
							run_iteration (sends, opens);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print_statistics ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void elections_benchmark::run_iteration (std::deque<std::shared_ptr<nano::block>> & sends, std::deque<std::shared_ptr<nano::block>> & opens)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto const total_opens = opens.size ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Process and cement all send blocks directly
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Processing and cementing {} send blocks...\n", sends.size ());
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto transaction = node->ledger.tx_begin_write ();
 | 
				
			||||||
 | 
							for (auto const & send : sends)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto result = node->ledger.process (transaction, send);
 | 
				
			||||||
 | 
								release_assert (result == nano::block_status::progress, to_string (result));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Add to cementing set for direct cementing
 | 
				
			||||||
 | 
								auto cemented = node->ledger.confirm (transaction, send->hash ());
 | 
				
			||||||
 | 
								release_assert (!cemented.empty () && cemented.back ()->hash () == send->hash ());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Process open blocks into ledger without confirming
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Processing {} open blocks into ledger...\n", opens.size ());
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto transaction = node->ledger.tx_begin_write ();
 | 
				
			||||||
 | 
							for (auto const & open : opens)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto result = node->ledger.process (transaction, open);
 | 
				
			||||||
 | 
								release_assert (result == nano::block_status::progress, to_string (result));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Initialize timing entries for open blocks only
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
							auto pending_cementing_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
							auto pending_confirmation_l = pending_confirmation.lock ();
 | 
				
			||||||
 | 
							for (auto const & open : opens)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								pending_cementing_l->emplace (open->hash ());
 | 
				
			||||||
 | 
								pending_confirmation_l->emplace (open->hash ());
 | 
				
			||||||
 | 
								timings_l->emplace (open->hash (), block_timing{ now });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_begin = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Manually start elections for open blocks only
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Starting elections manually for {} open blocks...\n", opens.size ());
 | 
				
			||||||
 | 
						for (auto const & open : opens)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Use manual scheduler to start election
 | 
				
			||||||
 | 
							node->scheduler.manual.push (open);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for all elections to complete and blocks to be cemented
 | 
				
			||||||
 | 
						nano::interval progress_interval;
 | 
				
			||||||
 | 
						while (true)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto pending_cementing_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
								auto pending_confirmation_l = pending_confirmation.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ((pending_cementing_l->empty () && pending_confirmation_l->empty ()) || progress_interval.elapse (3s))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									std::cout << fmt::format ("Confirming elections: {:>9} remaining | cementing: {:>9} remaining (active: {:>5} | cementing: {:>5} | deferred: {:>5})\n",
 | 
				
			||||||
 | 
									pending_confirmation_l->size (),
 | 
				
			||||||
 | 
									pending_cementing_l->size (),
 | 
				
			||||||
 | 
									node->active.size (),
 | 
				
			||||||
 | 
									node->cementing_set.size (),
 | 
				
			||||||
 | 
									node->cementing_set.deferred_size ());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (pending_cementing_l->empty () && pending_confirmation_l->empty ())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::this_thread::sleep_for (1ms);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_end = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
						auto const time_us = std::chrono::duration_cast<std::chrono::microseconds> (time_end - time_begin).count ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\nPerformance: {} blocks/sec [{:.2f}s] {} blocks processed\n",
 | 
				
			||||||
 | 
						total_opens * 1000000 / time_us, time_us / 1000000.0, total_opens);
 | 
				
			||||||
 | 
						std::cout << "─────────────────────────────────────────────────────────────────\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stats.clear ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void elections_benchmark::print_statistics ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << "\n--- SUMMARY ---------------------------------------------------------------------\n\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Elections started:       {:>10}\n", elections_started.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Elections stopped:       {:>10}\n", elections_stopped.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Elections confirmed:     {:>10}\n", elections_confirmed.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate timing statistics from raw data
 | 
				
			||||||
 | 
						auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint64_t total_election_time = 0;
 | 
				
			||||||
 | 
						uint64_t total_confirmation_time = 0;
 | 
				
			||||||
 | 
						size_t election_count = 0;
 | 
				
			||||||
 | 
						size_t confirmed_count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto const & [hash, timing] : *timings_l)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							release_assert (timing.election_started != std::chrono::steady_clock::time_point{});
 | 
				
			||||||
 | 
							release_assert (timing.election_stopped != std::chrono::steady_clock::time_point{});
 | 
				
			||||||
 | 
							release_assert (timing.cemented != std::chrono::steady_clock::time_point{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							total_election_time += std::chrono::duration_cast<std::chrono::microseconds> (timing.election_stopped - timing.election_started).count ();
 | 
				
			||||||
 | 
							election_count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							total_confirmation_time += std::chrono::duration_cast<std::chrono::microseconds> (timing.cemented - timing.election_started).count ();
 | 
				
			||||||
 | 
							confirmed_count++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Election time (activated > confirmed): {:>8.2f} ms/block avg\n", total_election_time / (election_count * 1000.0));
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Total time (activated > cemented):     {:>8.2f} ms/block avg\n", total_confirmation_time / (confirmed_count * 1000.0));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										359
									
								
								nano/nano_node/benchmarks/benchmark_pipeline.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								nano/nano_node/benchmarks/benchmark_pipeline.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,359 @@
 | 
				
			||||||
 | 
					#include <nano/lib/config.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/locks.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/thread_runner.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/timer.hpp>
 | 
				
			||||||
 | 
					#include <nano/nano_node/benchmarks/benchmarks.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/active_elections.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/cli.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/daemonconfig.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/election.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/ledger_notifications.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/node_observers.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/scheduler/component.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/asio/io_context.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <fmt/format.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nano::cli
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Full Pipeline Benchmark
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Measures the complete block confirmation pipeline from submission through processing,
 | 
				
			||||||
 | 
					 * elections, and cementing. Tests all stages together including inter-component coordination.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * How it works:
 | 
				
			||||||
 | 
					 * 1. Setup: Creates a node with genesis representative key for voting
 | 
				
			||||||
 | 
					 * 2. Generate: Creates random transfer transactions (send/receive pairs)
 | 
				
			||||||
 | 
					 * 3. Submit: Adds blocks via process_active() which triggers the full pipeline
 | 
				
			||||||
 | 
					 * 4. Measure: Tracks time from submission through processing, election, and cementing
 | 
				
			||||||
 | 
					 * 5. Report: Calculates overall throughput and timing breakdown for each stage
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Pipeline stages measured:
 | 
				
			||||||
 | 
					 * - Block processing: submission -> ledger insertion
 | 
				
			||||||
 | 
					 * - Election activation: ledger insertion -> election start
 | 
				
			||||||
 | 
					 * - Election confirmation: election start -> block cemented
 | 
				
			||||||
 | 
					 * - Total pipeline: submission -> cemented
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is tested:
 | 
				
			||||||
 | 
					 * - Block processor throughput
 | 
				
			||||||
 | 
					 * - Election startup and scheduling
 | 
				
			||||||
 | 
					 * - Vote generation and processing (with one local rep)
 | 
				
			||||||
 | 
					 * - Quorum detection and confirmation
 | 
				
			||||||
 | 
					 * - Cementing performance
 | 
				
			||||||
 | 
					 * - Inter-component coordination and queueing
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * What is NOT tested:
 | 
				
			||||||
 | 
					 * - Network communication (local-only)
 | 
				
			||||||
 | 
					 * - Multiple remote representatives
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class pipeline_benchmark : public benchmark_base
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						struct block_timing
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point submitted;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point processed;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point election_started;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point election_stopped;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point confirmed;
 | 
				
			||||||
 | 
							std::chrono::steady_clock::time_point cemented;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track timing for each block through the pipeline
 | 
				
			||||||
 | 
						nano::locked<std::unordered_map<nano::block_hash, block_timing>> block_timings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track blocks waiting to be cemented
 | 
				
			||||||
 | 
						nano::locked<std::unordered_map<nano::block_hash, std::chrono::steady_clock::time_point>> pending_cementing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Metrics
 | 
				
			||||||
 | 
						std::atomic<size_t> elections_started{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> elections_stopped{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> elections_confirmed{ 0 };
 | 
				
			||||||
 | 
						std::atomic<size_t> blocks_cemented{ 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						pipeline_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void run ();
 | 
				
			||||||
 | 
						void run_iteration (std::deque<std::shared_ptr<nano::block>> & blocks);
 | 
				
			||||||
 | 
						void print_statistics ();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void run_pipeline_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto config = benchmark_config::parse (vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "=== BENCHMARK: Full Pipeline ===\n";
 | 
				
			||||||
 | 
						std::cout << "Configuration:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Accounts: {}\n", config.num_accounts);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Iterations: {}\n", config.num_iterations);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Batch size: {}\n", config.batch_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup node directly in run method
 | 
				
			||||||
 | 
						nano::network_constants::set_active_network ("dev");
 | 
				
			||||||
 | 
						nano::logger::initialize (nano::log_config::cli_default (nano::log::level::warn));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nano::node_flags node_flags;
 | 
				
			||||||
 | 
						nano::update_flags (node_flags, vm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto io_ctx = std::make_shared<boost::asio::io_context> ();
 | 
				
			||||||
 | 
						nano::work_pool work_pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load configuration from current working directory (if exists) and cli config overrides
 | 
				
			||||||
 | 
						auto daemon_config = nano::load_config_file<nano::daemon_config> (nano::node_config_filename, {}, node_flags.config_overrides);
 | 
				
			||||||
 | 
						auto node_config = daemon_config.node;
 | 
				
			||||||
 | 
						node_config.network_params.work = nano::work_thresholds{ 0, 0, 0 };
 | 
				
			||||||
 | 
						node_config.peering_port = 0; // Use random available port
 | 
				
			||||||
 | 
						node_config.max_backlog = 0; // Disable bounded backlog
 | 
				
			||||||
 | 
						node_config.block_processor.max_peer_queue = std::numeric_limits<size_t>::max (); // Unlimited queue size
 | 
				
			||||||
 | 
						node_config.block_processor.max_system_queue = std::numeric_limits<size_t>::max (); // Unlimited queue size
 | 
				
			||||||
 | 
						node_config.max_unchecked_blocks = 1024 * 1024; // Large unchecked blocks cache to avoid dropping blocks
 | 
				
			||||||
 | 
						node_config.vote_processor.max_pr_queue = std::numeric_limits<size_t>::max (); // Unlimited vote processing queue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node_config.priority_bucket.max_blocks = std::numeric_limits<size_t>::max (); // Unlimited priority bucket
 | 
				
			||||||
 | 
						node_config.priority_bucket.max_elections = std::numeric_limits<size_t>::max (); // Unlimited bucket elections
 | 
				
			||||||
 | 
						node_config.priority_bucket.reserved_elections = std::numeric_limits<size_t>::max (); // Unlimited bucket elections
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto node = std::make_shared<nano::node> (io_ctx, nano::unique_path (), node_config, work_pool, node_flags);
 | 
				
			||||||
 | 
						node->start ();
 | 
				
			||||||
 | 
						nano::thread_runner runner (io_ctx, nano::default_logger (), node->config.io_threads);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "\nSystem Info:\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Backend: {}\n", node->store.vendor_get ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Block processor threads: {}\n", 1); // TODO: Log number of block processor threads when upstreamed
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Vote processor threads: {}\n", node->config.vote_processor.threads);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Active elections limit: {}\n", node->config.active_elections.size);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Priority bucket max blocks: {}\n", node->config.priority_bucket.max_blocks);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Priority bucket max elections: {}\n", node->config.priority_bucket.max_elections);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Block processor max peer queue: {}\n", node->config.block_processor.max_peer_queue);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Block processor max system queue: {}\n", node->config.block_processor.max_system_queue);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Vote processor max pr queue: {}\n", node->config.vote_processor.max_pr_queue);
 | 
				
			||||||
 | 
						std::cout << fmt::format ("  Max unchecked blocks: {}\n", node->config.max_unchecked_blocks);
 | 
				
			||||||
 | 
						std::cout << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Insert dev genesis representative key for voting
 | 
				
			||||||
 | 
						auto wallet = node->wallets.create (nano::random_wallet_id ());
 | 
				
			||||||
 | 
						wallet->insert_adhoc (nano::dev::genesis_key.prv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for node to be ready
 | 
				
			||||||
 | 
						std::this_thread::sleep_for (500ms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run benchmark
 | 
				
			||||||
 | 
						pipeline_benchmark benchmark{ node, config };
 | 
				
			||||||
 | 
						benchmark.run ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stop ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pipeline_benchmark::pipeline_benchmark (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a) :
 | 
				
			||||||
 | 
						benchmark_base (node_a, config_a)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Track when blocks get processed
 | 
				
			||||||
 | 
						node->ledger_notifications.blocks_processed.add ([this] (std::deque<std::pair<nano::block_status, nano::block_context>> const & batch) {
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto const & [status, context] : batch)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (status == nano::block_status::progress)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (auto it = timings_l->find (context.block->hash ()); it != timings_l->end ())
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										it->second.processed = now;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									processed_blocks_count++;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track when elections start
 | 
				
			||||||
 | 
						node->active.election_started.add ([this] (std::shared_ptr<nano::election> const & election, nano::bucket_index const & bucket, nano::priority_timestamp const & priority) {
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto hash = election->winner ()->hash ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (auto it = timings_l->find (hash); it != timings_l->end ())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								it->second.election_started = now;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elections_started++;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track when elections stop (regardless of confirmation)
 | 
				
			||||||
 | 
						node->active.election_erased.add ([this] (std::shared_ptr<nano::election> const & election) {
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto hash = election->winner ()->hash ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (auto it = timings_l->find (hash); it != timings_l->end ())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								it->second.election_stopped = now;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elections_stopped++;
 | 
				
			||||||
 | 
							elections_confirmed += election->confirmed () ? 1 : 0;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Track when blocks get cemented
 | 
				
			||||||
 | 
						node->cementing_set.batch_cemented.add ([this] (auto const & hashes) {
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto pending_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto const & ctx : hashes)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto hash = ctx.block->hash ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (auto it = timings_l->find (hash); it != timings_l->end ())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									it->second.cemented = now;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								pending_l->erase (hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								blocks_cemented++;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void pipeline_benchmark::run ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generating {} accounts...\n", config.num_accounts);
 | 
				
			||||||
 | 
						pool.generate_accounts (config.num_accounts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setup_genesis_distribution (0.1); // Only distribute 10%, keep 90% for voting weight
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (size_t iteration = 0; iteration < config.num_iterations; ++iteration)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::cout << fmt::format ("\n--- Iteration {}/{} --------------------------------------------------------------\n", iteration + 1, config.num_iterations);
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Generating {} random transfers...\n", config.batch_size / 2);
 | 
				
			||||||
 | 
							auto blocks = generate_random_transfers ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::cout << fmt::format ("Measuring full confirmation pipeline for {} blocks...\n", blocks.size ());
 | 
				
			||||||
 | 
							run_iteration (blocks);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						print_statistics ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void pipeline_benchmark::run_iteration (std::deque<std::shared_ptr<nano::block>> & blocks)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto const total_blocks = blocks.size ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Initialize timing entries for all blocks
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto now = std::chrono::steady_clock::now ();
 | 
				
			||||||
 | 
							auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
							auto pending_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
							for (auto const & block : blocks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								timings_l->emplace (block->hash (), block_timing{ now });
 | 
				
			||||||
 | 
								pending_l->emplace (block->hash (), now);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_begin = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Submit all blocks through the full pipeline
 | 
				
			||||||
 | 
						while (!blocks.empty ())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto block = blocks.front ();
 | 
				
			||||||
 | 
							blocks.pop_front ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Process block through full confirmation pipeline
 | 
				
			||||||
 | 
							node->process_active (block);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for all blocks to be confirmed and cemented
 | 
				
			||||||
 | 
						nano::interval progress_interval;
 | 
				
			||||||
 | 
						while (true)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto pending_l = pending_cementing.lock ();
 | 
				
			||||||
 | 
								if (pending_l->empty () || progress_interval.elapse (3s))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									std::cout << fmt::format ("Blocks remaining: {:>9} (block processor: {:>9} | active: {:>5} | cementing: {:>5} | pool: {:>5})\n",
 | 
				
			||||||
 | 
									pending_l->size (),
 | 
				
			||||||
 | 
									node->block_processor.size (),
 | 
				
			||||||
 | 
									node->active.size (),
 | 
				
			||||||
 | 
									node->cementing_set.size (),
 | 
				
			||||||
 | 
									node->scheduler.priority.size ());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (pending_l->empty ())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							std::this_thread::sleep_for (1ms);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto const time_end = std::chrono::high_resolution_clock::now ();
 | 
				
			||||||
 | 
						auto const time_us = std::chrono::duration_cast<std::chrono::microseconds> (time_end - time_begin).count ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\nPerformance: {} blocks/sec [{:.2f}s] {} blocks processed\n",
 | 
				
			||||||
 | 
						total_blocks * 1000000 / time_us, time_us / 1000000.0, total_blocks);
 | 
				
			||||||
 | 
						std::cout << "─────────────────────────────────────────────────────────────────\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node->stats.clear ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void pipeline_benchmark::print_statistics ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << "\n--- SUMMARY ---------------------------------------------------------------------\n\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Blocks processed:        {:>10}\n", processed_blocks_count.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Elections started:       {:>10}\n", elections_started.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Elections stopped:       {:>10}\n", elections_stopped.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Elections confirmed:     {:>10}\n", elections_confirmed.load ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("\n");
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Accounts total:          {:>10}\n", pool.total_accounts ());
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Accounts with balance:   {:>10} ({:.1f}%)\n",
 | 
				
			||||||
 | 
						pool.accounts_with_balance_count (),
 | 
				
			||||||
 | 
						100.0 * pool.accounts_with_balance_count () / pool.total_accounts ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate timing statistics from raw data
 | 
				
			||||||
 | 
						auto timings_l = block_timings.lock ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint64_t total_processing_time = 0;
 | 
				
			||||||
 | 
						uint64_t total_activation_time = 0;
 | 
				
			||||||
 | 
						uint64_t total_election_time = 0;
 | 
				
			||||||
 | 
						uint64_t total_cementing_time = 0;
 | 
				
			||||||
 | 
						size_t processed_count = 0;
 | 
				
			||||||
 | 
						size_t activation_count = 0;
 | 
				
			||||||
 | 
						size_t election_count = 0;
 | 
				
			||||||
 | 
						size_t cemented_count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto const & [hash, timing] : *timings_l)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							release_assert (timing.submitted != std::chrono::steady_clock::time_point{});
 | 
				
			||||||
 | 
							release_assert (timing.election_started != std::chrono::steady_clock::time_point{});
 | 
				
			||||||
 | 
							release_assert (timing.election_stopped != std::chrono::steady_clock::time_point{});
 | 
				
			||||||
 | 
							release_assert (timing.cemented != std::chrono::steady_clock::time_point{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							total_processing_time += std::chrono::duration_cast<std::chrono::microseconds> (timing.processed - timing.submitted).count ();
 | 
				
			||||||
 | 
							processed_count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							total_activation_time += std::chrono::duration_cast<std::chrono::microseconds> (timing.election_started - timing.processed).count ();
 | 
				
			||||||
 | 
							activation_count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							total_election_time += std::chrono::duration_cast<std::chrono::microseconds> (timing.cemented - timing.election_started).count ();
 | 
				
			||||||
 | 
							election_count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							total_cementing_time += std::chrono::duration_cast<std::chrono::microseconds> (timing.cemented - timing.submitted).count ();
 | 
				
			||||||
 | 
							cemented_count++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << "\n";
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Block processing (submitted > processed):    {:>8.2f} ms/block avg\n", total_processing_time / (processed_count * 1000.0));
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Election activation (processed > activated): {:>8.2f} ms/block avg\n", total_activation_time / (activation_count * 1000.0));
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Election time (activated > confirmed):       {:>8.2f} ms/block avg\n", total_election_time / (election_count * 1000.0));
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Total pipeline (submitted > cemented):       {:>8.2f} ms/block avg\n", total_cementing_time / (cemented_count * 1000.0));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										616
									
								
								nano/nano_node/benchmarks/benchmarks.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										616
									
								
								nano/nano_node/benchmarks/benchmarks.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,616 @@
 | 
				
			||||||
 | 
					#include <nano/lib/blockbuilders.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/config.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/thread_runner.hpp>
 | 
				
			||||||
 | 
					#include <nano/lib/timer.hpp>
 | 
				
			||||||
 | 
					#include <nano/nano_node/benchmarks/benchmarks.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/cli.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/daemonconfig.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/asio/io_context.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <set>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <fmt/format.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nano::cli
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					account_pool::account_pool () :
 | 
				
			||||||
 | 
						gen (rd ())
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void account_pool::generate_accounts (size_t count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						keys.clear ();
 | 
				
			||||||
 | 
						keys.reserve (count);
 | 
				
			||||||
 | 
						account_to_keypair.clear ();
 | 
				
			||||||
 | 
						balances.clear ();
 | 
				
			||||||
 | 
						accounts_with_balance.clear ();
 | 
				
			||||||
 | 
						balance_lookup.clear ();
 | 
				
			||||||
 | 
						frontiers.clear ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (size_t i = 0; i < count; ++i)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							keys.emplace_back ();
 | 
				
			||||||
 | 
							account_to_keypair[keys[i].pub] = keys[i];
 | 
				
			||||||
 | 
							balances[keys[i].pub] = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nano::account account_pool::get_random_account_with_balance ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						debug_assert (!accounts_with_balance.empty ());
 | 
				
			||||||
 | 
						std::uniform_int_distribution<size_t> dist (0, accounts_with_balance.size () - 1);
 | 
				
			||||||
 | 
						return accounts_with_balance[dist (gen)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nano::account account_pool::get_random_account ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						debug_assert (!keys.empty ());
 | 
				
			||||||
 | 
						std::uniform_int_distribution<size_t> dist (0, keys.size () - 1);
 | 
				
			||||||
 | 
						return keys[dist (gen)].pub;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nano::keypair const & account_pool::get_keypair (nano::account const & account)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto it = account_to_keypair.find (account);
 | 
				
			||||||
 | 
						debug_assert (it != account_to_keypair.end ());
 | 
				
			||||||
 | 
						return it->second;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void account_pool::update_balance (nano::account const & account, nano::uint128_t new_balance)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto old_balance = balances[account];
 | 
				
			||||||
 | 
						balances[account] = new_balance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool had_balance = balance_lookup.count (account) > 0;
 | 
				
			||||||
 | 
						bool has_balance_now = new_balance > 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!had_balance && has_balance_now)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Account gained balance
 | 
				
			||||||
 | 
							accounts_with_balance.push_back (account);
 | 
				
			||||||
 | 
							balance_lookup.insert (account);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else if (had_balance && !has_balance_now)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Account lost balance
 | 
				
			||||||
 | 
							auto it = std::find (accounts_with_balance.begin (), accounts_with_balance.end (), account);
 | 
				
			||||||
 | 
							if (it != accounts_with_balance.end ())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								accounts_with_balance.erase (it);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							balance_lookup.erase (account);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nano::uint128_t account_pool::get_balance (nano::account const & account)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto it = balances.find (account);
 | 
				
			||||||
 | 
						return (it != balances.end ()) ? it->second : 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool account_pool::has_balance (nano::account const & account)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return balance_lookup.count (account) > 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t account_pool::accounts_with_balance_count () const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return accounts_with_balance.size ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t account_pool::total_accounts () const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return keys.size ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<nano::account> account_pool::get_accounts_with_balance () const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return accounts_with_balance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void account_pool::set_initial_balance (nano::account const & account, nano::uint128_t balance)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						balances[account] = balance;
 | 
				
			||||||
 | 
						if (balance > 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (balance_lookup.count (account) == 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								accounts_with_balance.push_back (account);
 | 
				
			||||||
 | 
								balance_lookup.insert (account);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void account_pool::set_frontier (nano::account const & account, nano::block_hash const & frontier)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						frontiers[account] = frontier;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nano::block_hash account_pool::get_frontier (nano::account const & account) const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto it = frontiers.find (account);
 | 
				
			||||||
 | 
						return (it != frontiers.end ()) ? it->second : nano::block_hash (0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					benchmark_config benchmark_config::parse (boost::program_options::variables_map const & vm)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						benchmark_config config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (vm.count ("accounts"))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							config.num_accounts = std::stoull (vm["accounts"].as<std::string> ());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (vm.count ("iterations"))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							config.num_iterations = std::stoull (vm["iterations"].as<std::string> ());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (vm.count ("batch_size"))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							config.batch_size = std::stoull (vm["batch_size"].as<std::string> ());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (vm.count ("cementing_mode"))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto mode_str = vm["cementing_mode"].as<std::string> ();
 | 
				
			||||||
 | 
							if (mode_str == "root")
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								config.cementing_mode = cementing_mode::root;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if (mode_str == "sequential")
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								config.cementing_mode = cementing_mode::sequential;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								std::cerr << "Invalid cementing mode: " << mode_str << ". Using default (sequential).\n";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return config;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					benchmark_base::benchmark_base (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a) :
 | 
				
			||||||
 | 
						node (node_a), config (config_a)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Prepares the ledger for benchmarking by transferring all genesis funds to a single random account.
 | 
				
			||||||
 | 
					 * This creates a clean starting state where:
 | 
				
			||||||
 | 
					 * - One account holds all the balance (simulating a funded account)
 | 
				
			||||||
 | 
					 * - All other accounts start with zero balance
 | 
				
			||||||
 | 
					 * - The funded account can then distribute funds to other accounts during the benchmark
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Algorithm:
 | 
				
			||||||
 | 
					 * 1. Select a random account from the pool to be the initial holder
 | 
				
			||||||
 | 
					 * 2. Create a send block from genesis account sending all balance
 | 
				
			||||||
 | 
					 * 3. Create an open block for the selected account to receive all funds
 | 
				
			||||||
 | 
					 * 4. Process both blocks to establish the initial state
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void benchmark_base::setup_genesis_distribution (double distribution_percentage)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::cout << "Setting up genesis distribution...\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get genesis balance and latest block
 | 
				
			||||||
 | 
						nano::block_hash genesis_latest (node->latest (nano::dev::genesis_key.pub));
 | 
				
			||||||
 | 
						nano::uint128_t genesis_balance (std::numeric_limits<nano::uint128_t>::max ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate amount to send using 256-bit arithmetic to avoid precision loss
 | 
				
			||||||
 | 
						nano::uint256_t genesis_balance_256 = genesis_balance;
 | 
				
			||||||
 | 
						nano::uint256_t multiplier = static_cast<nano::uint256_t> (distribution_percentage * 1000000);
 | 
				
			||||||
 | 
						nano::uint256_t send_amount_256 = (genesis_balance_256 * multiplier) / 1000000;
 | 
				
			||||||
 | 
						release_assert (send_amount_256 <= std::numeric_limits<nano::uint128_t>::max (), "send amount overflows uint128_t");
 | 
				
			||||||
 | 
						nano::uint128_t send_amount = static_cast<nano::uint128_t> (send_amount_256);
 | 
				
			||||||
 | 
						nano::uint128_t remaining_balance = genesis_balance - send_amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Select random account to receive genesis funds
 | 
				
			||||||
 | 
						nano::account target_account = pool.get_random_account ();
 | 
				
			||||||
 | 
						auto & target_keypair = pool.get_keypair (target_account);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create send block from genesis to target account
 | 
				
			||||||
 | 
						nano::block_builder builder;
 | 
				
			||||||
 | 
						auto send = builder.state ()
 | 
				
			||||||
 | 
									.account (nano::dev::genesis_key.pub)
 | 
				
			||||||
 | 
									.previous (genesis_latest)
 | 
				
			||||||
 | 
									.representative (nano::dev::genesis_key.pub)
 | 
				
			||||||
 | 
									.balance (remaining_balance)
 | 
				
			||||||
 | 
									.link (target_account)
 | 
				
			||||||
 | 
									.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
 | 
				
			||||||
 | 
									.work (0)
 | 
				
			||||||
 | 
									.build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create open block for target account
 | 
				
			||||||
 | 
						auto open = builder.state ()
 | 
				
			||||||
 | 
									.account (target_account)
 | 
				
			||||||
 | 
									.previous (0)
 | 
				
			||||||
 | 
									.representative (target_account)
 | 
				
			||||||
 | 
									.balance (send_amount)
 | 
				
			||||||
 | 
									.link (send->hash ())
 | 
				
			||||||
 | 
									.sign (target_keypair.prv, target_keypair.pub)
 | 
				
			||||||
 | 
									.work (0)
 | 
				
			||||||
 | 
									.build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Process blocks
 | 
				
			||||||
 | 
						auto result1 = node->process (send);
 | 
				
			||||||
 | 
						release_assert (result1 == nano::block_status::progress, to_string (result1));
 | 
				
			||||||
 | 
						auto result2 = node->process (open);
 | 
				
			||||||
 | 
						release_assert (result2 == nano::block_status::progress, to_string (result2));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update pool balance tracking
 | 
				
			||||||
 | 
						pool.set_initial_balance (target_account, send_amount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Initialize frontier for target account
 | 
				
			||||||
 | 
						pool.set_frontier (target_account, open->hash ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Genesis distribution complete: {:.1f}% distributed, {:.1f}% retained for voting\n",
 | 
				
			||||||
 | 
						distribution_percentage * 100.0, (1.0 - distribution_percentage) * 100.0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Generates random transfer transactions between accounts with no specific dependency pattern.
 | 
				
			||||||
 | 
					 * This simulates typical network activity with independent transactions.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Algorithm:
 | 
				
			||||||
 | 
					 * 1. For each transfer (batch_size/2 transfers, since each creates 2 blocks):
 | 
				
			||||||
 | 
					 *    a. Select a random sender account that has balance
 | 
				
			||||||
 | 
					 *    b. Select a random receiver account (can be any account)
 | 
				
			||||||
 | 
					 *    c. Generate a random transfer amount (up to sender's balance)
 | 
				
			||||||
 | 
					 *    d. Create a send block from sender
 | 
				
			||||||
 | 
					 *    e. Create a receive/open block for receiver
 | 
				
			||||||
 | 
					 * 2. Update account balances and frontiers after each transfer
 | 
				
			||||||
 | 
					 * 3. Continue until batch_size blocks are generated or no accounts have balance
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The resulting blocks have no intentional dependency structure beyond the natural
 | 
				
			||||||
 | 
					 * send->receive pairs, making this suitable for testing sequential block processing.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					std::deque<std::shared_ptr<nano::block>> benchmark_base::generate_random_transfers ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::deque<std::shared_ptr<nano::block>> blocks;
 | 
				
			||||||
 | 
						std::random_device rd;
 | 
				
			||||||
 | 
						std::mt19937 gen (rd ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate batch_size number of transfer pairs (send + receive = 2 blocks each)
 | 
				
			||||||
 | 
						size_t transfers_generated = 0;
 | 
				
			||||||
 | 
						nano::block_builder builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while (transfers_generated < config.batch_size / 2) // Divide by 2 since each transfer creates 2 blocks
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (pool.accounts_with_balance_count () == 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								std::cout << "No accounts with balance remaining, stopping...\n";
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get random sender with balance
 | 
				
			||||||
 | 
							nano::account sender = pool.get_random_account_with_balance ();
 | 
				
			||||||
 | 
							auto & sender_keypair = pool.get_keypair (sender);
 | 
				
			||||||
 | 
							nano::uint128_t sender_balance = pool.get_balance (sender);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (sender_balance == 0)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get random receiver
 | 
				
			||||||
 | 
							nano::account receiver = pool.get_random_account ();
 | 
				
			||||||
 | 
							auto & receiver_keypair = pool.get_keypair (receiver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Random transfer amount (but not more than sender balance)
 | 
				
			||||||
 | 
							std::uniform_int_distribution<uint64_t> amount_dist (1, sender_balance.convert_to<uint64_t> ());
 | 
				
			||||||
 | 
							nano::uint128_t transfer_amount = std::min (static_cast<nano::uint128_t> (amount_dist (gen)), sender_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get or initialize sender frontier
 | 
				
			||||||
 | 
							nano::block_hash sender_frontier = pool.get_frontier (sender);
 | 
				
			||||||
 | 
							nano::root work_root;
 | 
				
			||||||
 | 
							if (sender_frontier != 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								work_root = sender_frontier;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								sender_frontier = 0; // First block for this account
 | 
				
			||||||
 | 
								work_root = sender; // Use account address for first block work
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create send block
 | 
				
			||||||
 | 
							nano::uint128_t new_sender_balance = sender_balance - transfer_amount;
 | 
				
			||||||
 | 
							auto send = builder.state ()
 | 
				
			||||||
 | 
										.account (sender)
 | 
				
			||||||
 | 
										.previous (sender_frontier)
 | 
				
			||||||
 | 
										.representative (sender)
 | 
				
			||||||
 | 
										.balance (new_sender_balance)
 | 
				
			||||||
 | 
										.link (receiver)
 | 
				
			||||||
 | 
										.sign (sender_keypair.prv, sender_keypair.pub)
 | 
				
			||||||
 | 
										.work (0)
 | 
				
			||||||
 | 
										.build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blocks.push_back (send);
 | 
				
			||||||
 | 
							pool.set_frontier (sender, send->hash ());
 | 
				
			||||||
 | 
							pool.update_balance (sender, new_sender_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create receive block
 | 
				
			||||||
 | 
							nano::uint128_t receiver_balance = pool.get_balance (receiver);
 | 
				
			||||||
 | 
							nano::uint128_t new_receiver_balance = receiver_balance + transfer_amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							nano::block_hash receiver_frontier = pool.get_frontier (receiver);
 | 
				
			||||||
 | 
							nano::root receiver_work_root;
 | 
				
			||||||
 | 
							if (receiver_frontier != 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								receiver_work_root = receiver_frontier;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								receiver_frontier = 0; // First block for this account (open block)
 | 
				
			||||||
 | 
								receiver_work_root = receiver; // Use account address for first block work
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto receive = builder.state ()
 | 
				
			||||||
 | 
										   .account (receiver)
 | 
				
			||||||
 | 
										   .previous (receiver_frontier)
 | 
				
			||||||
 | 
										   .representative (receiver)
 | 
				
			||||||
 | 
										   .balance (new_receiver_balance)
 | 
				
			||||||
 | 
										   .link (send->hash ())
 | 
				
			||||||
 | 
										   .sign (receiver_keypair.prv, receiver_keypair.pub)
 | 
				
			||||||
 | 
										   .work (0)
 | 
				
			||||||
 | 
										   .build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blocks.push_back (receive);
 | 
				
			||||||
 | 
							pool.set_frontier (receiver, receive->hash ());
 | 
				
			||||||
 | 
							pool.update_balance (receiver, new_receiver_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							transfers_generated++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generated {} blocks\n", blocks.size ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return blocks;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Generates blocks in a dependency tree structure optimized for root mode cementing.
 | 
				
			||||||
 | 
					 * All blocks are organized so they become dependencies of a single root block.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Algorithm:
 | 
				
			||||||
 | 
					 * 1. Random transfer phase (80% of blocks):
 | 
				
			||||||
 | 
					 *    - Generate random transfers between accounts (same as generate_random_transfers)
 | 
				
			||||||
 | 
					 *    - Creates a natural web of dependencies through send/receive pairs
 | 
				
			||||||
 | 
					 * 2. Convergence phase (20% of blocks):
 | 
				
			||||||
 | 
					 *    - All accounts with balance send their entire balance to a collector account
 | 
				
			||||||
 | 
					 *    - The collector receives all these sends in sequence
 | 
				
			||||||
 | 
					 *    - The final receive block becomes the root that depends on all previous blocks
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The last block in the returned deque is the ultimate root that depends on all others.
 | 
				
			||||||
 | 
					 * Cementing this single block will cascade and cement all blocks in the tree.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					std::deque<std::shared_ptr<nano::block>> benchmark_base::generate_dependent_chain ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::deque<std::shared_ptr<nano::block>> blocks;
 | 
				
			||||||
 | 
						std::random_device rd;
 | 
				
			||||||
 | 
						std::mt19937 gen (rd ());
 | 
				
			||||||
 | 
						nano::block_builder builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Phase 1: Random transfers (80% of blocks)
 | 
				
			||||||
 | 
						size_t random_transfer_blocks = config.batch_size * 0.8;
 | 
				
			||||||
 | 
						size_t transfers_to_generate = random_transfer_blocks / 2; // Each transfer creates 2 blocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generating dependent chain: {} random transfers, then convergence\n",
 | 
				
			||||||
 | 
						transfers_to_generate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Phase 1: Generate random transfers (same logic as generate_random_transfers)
 | 
				
			||||||
 | 
						size_t transfers_generated = 0;
 | 
				
			||||||
 | 
						while (transfers_generated < transfers_to_generate && pool.accounts_with_balance_count () > 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Get random sender with balance
 | 
				
			||||||
 | 
							nano::account sender = pool.get_random_account_with_balance ();
 | 
				
			||||||
 | 
							auto & sender_keypair = pool.get_keypair (sender);
 | 
				
			||||||
 | 
							nano::uint128_t sender_balance = pool.get_balance (sender);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (sender_balance == 0)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get random receiver
 | 
				
			||||||
 | 
							nano::account receiver = pool.get_random_account ();
 | 
				
			||||||
 | 
							auto & receiver_keypair = pool.get_keypair (receiver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Random transfer amount (but not more than sender balance)
 | 
				
			||||||
 | 
							std::uniform_int_distribution<uint64_t> amount_dist (1, sender_balance.convert_to<uint64_t> ());
 | 
				
			||||||
 | 
							nano::uint128_t transfer_amount = std::min (static_cast<nano::uint128_t> (amount_dist (gen)), sender_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get or initialize sender frontier
 | 
				
			||||||
 | 
							nano::block_hash sender_frontier = pool.get_frontier (sender);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create send block
 | 
				
			||||||
 | 
							nano::uint128_t new_sender_balance = sender_balance - transfer_amount;
 | 
				
			||||||
 | 
							auto send = builder.state ()
 | 
				
			||||||
 | 
										.account (sender)
 | 
				
			||||||
 | 
										.previous (sender_frontier)
 | 
				
			||||||
 | 
										.representative (sender)
 | 
				
			||||||
 | 
										.balance (new_sender_balance)
 | 
				
			||||||
 | 
										.link (receiver)
 | 
				
			||||||
 | 
										.sign (sender_keypair.prv, sender_keypair.pub)
 | 
				
			||||||
 | 
										.work (0)
 | 
				
			||||||
 | 
										.build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blocks.push_back (send);
 | 
				
			||||||
 | 
							pool.set_frontier (sender, send->hash ());
 | 
				
			||||||
 | 
							pool.update_balance (sender, new_sender_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create receive block
 | 
				
			||||||
 | 
							nano::uint128_t receiver_balance = pool.get_balance (receiver);
 | 
				
			||||||
 | 
							nano::uint128_t new_receiver_balance = receiver_balance + transfer_amount;
 | 
				
			||||||
 | 
							nano::block_hash receiver_frontier = pool.get_frontier (receiver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto receive = builder.state ()
 | 
				
			||||||
 | 
										   .account (receiver)
 | 
				
			||||||
 | 
										   .previous (receiver_frontier)
 | 
				
			||||||
 | 
										   .representative (receiver)
 | 
				
			||||||
 | 
										   .balance (new_receiver_balance)
 | 
				
			||||||
 | 
										   .link (send->hash ())
 | 
				
			||||||
 | 
										   .sign (receiver_keypair.prv, receiver_keypair.pub)
 | 
				
			||||||
 | 
										   .work (0)
 | 
				
			||||||
 | 
										   .build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blocks.push_back (receive);
 | 
				
			||||||
 | 
							pool.set_frontier (receiver, receive->hash ());
 | 
				
			||||||
 | 
							pool.update_balance (receiver, new_receiver_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							transfers_generated++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Phase 2: Convergence - all accounts with balance send to a collector
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Converging {} accounts to collector account\n",
 | 
				
			||||||
 | 
						pool.accounts_with_balance_count ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Select a collector account (can be new or existing)
 | 
				
			||||||
 | 
						nano::account collector = pool.get_random_account ();
 | 
				
			||||||
 | 
						auto & collector_keypair = pool.get_keypair (collector);
 | 
				
			||||||
 | 
						nano::block_hash collector_frontier = pool.get_frontier (collector);
 | 
				
			||||||
 | 
						nano::uint128_t collector_balance = pool.get_balance (collector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Collect all accounts with balance (except collector)
 | 
				
			||||||
 | 
						std::vector<std::pair<nano::account, nano::uint128_t>> accounts_to_drain;
 | 
				
			||||||
 | 
						auto accounts_with_balance = pool.get_accounts_with_balance ();
 | 
				
			||||||
 | 
						for (auto const & account : accounts_with_balance)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (account != collector)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								nano::uint128_t balance = pool.get_balance (account);
 | 
				
			||||||
 | 
								accounts_to_drain.push_back ({ account, balance });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// All accounts send a random amount to collector
 | 
				
			||||||
 | 
						std::vector<std::pair<nano::block_hash, nano::uint128_t>> convergence_sends;
 | 
				
			||||||
 | 
						for (auto const & [account, balance] : accounts_to_drain)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto & account_keypair = pool.get_keypair (account);
 | 
				
			||||||
 | 
							nano::block_hash account_frontier = pool.get_frontier (account);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Send random amount to collector (between 1 and full balance)
 | 
				
			||||||
 | 
							std::uniform_int_distribution<uint64_t> amount_dist (1, balance.convert_to<uint64_t> ());
 | 
				
			||||||
 | 
							nano::uint128_t send_amount = static_cast<nano::uint128_t> (amount_dist (gen));
 | 
				
			||||||
 | 
							nano::uint128_t remaining_balance = balance - send_amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto send = builder.state ()
 | 
				
			||||||
 | 
										.account (account)
 | 
				
			||||||
 | 
										.previous (account_frontier)
 | 
				
			||||||
 | 
										.representative (account)
 | 
				
			||||||
 | 
										.balance (remaining_balance)
 | 
				
			||||||
 | 
										.link (collector)
 | 
				
			||||||
 | 
										.sign (account_keypair.prv, account_keypair.pub)
 | 
				
			||||||
 | 
										.work (0)
 | 
				
			||||||
 | 
										.build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blocks.push_back (send);
 | 
				
			||||||
 | 
							convergence_sends.push_back ({ send->hash (), send_amount });
 | 
				
			||||||
 | 
							pool.set_frontier (account, send->hash ());
 | 
				
			||||||
 | 
							pool.update_balance (account, remaining_balance);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Collector receives all sends (these become the root blocks)
 | 
				
			||||||
 | 
						for (auto const & [send_hash, amount] : convergence_sends)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							collector_balance += amount;
 | 
				
			||||||
 | 
							auto receive = builder.state ()
 | 
				
			||||||
 | 
										   .account (collector)
 | 
				
			||||||
 | 
										   .previous (collector_frontier)
 | 
				
			||||||
 | 
										   .representative (collector)
 | 
				
			||||||
 | 
										   .balance (collector_balance)
 | 
				
			||||||
 | 
										   .link (send_hash)
 | 
				
			||||||
 | 
										   .sign (collector_keypair.prv, collector_keypair.pub)
 | 
				
			||||||
 | 
										   .work (0)
 | 
				
			||||||
 | 
										   .build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blocks.push_back (receive);
 | 
				
			||||||
 | 
							collector_frontier = receive->hash ();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update collector state
 | 
				
			||||||
 | 
						pool.set_frontier (collector, collector_frontier);
 | 
				
			||||||
 | 
						pool.update_balance (collector, collector_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generated {} blocks in dependent chain topology\n", blocks.size ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return blocks;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Generates independent blocks - one block per account with no dependencies.
 | 
				
			||||||
 | 
					 * Returns sends and opens separately so sends can be confirmed first, then opens processed for elections.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					std::pair<std::deque<std::shared_ptr<nano::block>>, std::deque<std::shared_ptr<nano::block>>> benchmark_base::generate_independent_blocks ()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::deque<std::shared_ptr<nano::block>> sends;
 | 
				
			||||||
 | 
						std::deque<std::shared_ptr<nano::block>> opens;
 | 
				
			||||||
 | 
						nano::block_builder builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Find accounts with balance to send from
 | 
				
			||||||
 | 
						auto accounts_with_balance = pool.get_accounts_with_balance ();
 | 
				
			||||||
 | 
						if (accounts_with_balance.empty ())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							std::cout << "No accounts with balance available\n";
 | 
				
			||||||
 | 
							return { sends, opens };
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate independent blocks up to batch_size
 | 
				
			||||||
 | 
						for (size_t i = 0; i < config.batch_size && !accounts_with_balance.empty (); ++i)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Pick a sender with balance
 | 
				
			||||||
 | 
							nano::account sender = accounts_with_balance[i % accounts_with_balance.size ()];
 | 
				
			||||||
 | 
							auto & sender_keypair = pool.get_keypair (sender);
 | 
				
			||||||
 | 
							nano::uint128_t sender_balance = pool.get_balance (sender);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (sender_balance == 0)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create a brand new receiver account
 | 
				
			||||||
 | 
							nano::keypair receiver_keypair;
 | 
				
			||||||
 | 
							nano::account receiver = receiver_keypair.pub;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Send a small amount to the new account
 | 
				
			||||||
 | 
							nano::uint128_t transfer_amount = std::min (sender_balance, nano::uint128_t (1000000)); // Small fixed amount
 | 
				
			||||||
 | 
							nano::block_hash sender_frontier = pool.get_frontier (sender);
 | 
				
			||||||
 | 
							nano::uint128_t new_sender_balance = sender_balance - transfer_amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create send block
 | 
				
			||||||
 | 
							auto send = builder.state ()
 | 
				
			||||||
 | 
										.account (sender)
 | 
				
			||||||
 | 
										.previous (sender_frontier)
 | 
				
			||||||
 | 
										.representative (sender)
 | 
				
			||||||
 | 
										.balance (new_sender_balance)
 | 
				
			||||||
 | 
										.link (receiver)
 | 
				
			||||||
 | 
										.sign (sender_keypair.prv, sender_keypair.pub)
 | 
				
			||||||
 | 
										.work (0)
 | 
				
			||||||
 | 
										.build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create open block for new receiver (this is the independent block)
 | 
				
			||||||
 | 
							auto open = builder.state ()
 | 
				
			||||||
 | 
										.account (receiver)
 | 
				
			||||||
 | 
										.previous (0) // First block for this account
 | 
				
			||||||
 | 
										.representative (receiver)
 | 
				
			||||||
 | 
										.balance (transfer_amount)
 | 
				
			||||||
 | 
										.link (send->hash ())
 | 
				
			||||||
 | 
										.sign (receiver_keypair.prv, receiver_keypair.pub)
 | 
				
			||||||
 | 
										.work (0)
 | 
				
			||||||
 | 
										.build ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Separate sends and opens
 | 
				
			||||||
 | 
							sends.push_back (send);
 | 
				
			||||||
 | 
							opens.push_back (open);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update pool state for sender only (receiver is new account not tracked)
 | 
				
			||||||
 | 
							pool.set_frontier (sender, send->hash ());
 | 
				
			||||||
 | 
							pool.update_balance (sender, new_sender_balance);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::cout << fmt::format ("Generated {} sends and {} opens\n", sends.size (), opens.size ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return { sends, opens };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										96
									
								
								nano/nano_node/benchmarks/benchmarks.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								nano/nano_node/benchmarks/benchmarks.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,96 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nano/lib/blocks.hpp>
 | 
				
			||||||
 | 
					#include <nano/node/node.hpp>
 | 
				
			||||||
 | 
					#include <nano/secure/common.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/program_options.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <random>
 | 
				
			||||||
 | 
					#include <unordered_map>
 | 
				
			||||||
 | 
					#include <unordered_set>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nano::cli
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					enum class cementing_mode
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						sequential,
 | 
				
			||||||
 | 
						root
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class account_pool
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						std::vector<nano::keypair> keys;
 | 
				
			||||||
 | 
						std::unordered_map<nano::account, nano::keypair> account_to_keypair;
 | 
				
			||||||
 | 
						std::unordered_map<nano::account, nano::uint128_t> balances;
 | 
				
			||||||
 | 
						std::vector<nano::account> accounts_with_balance;
 | 
				
			||||||
 | 
						std::unordered_set<nano::account> balance_lookup;
 | 
				
			||||||
 | 
						std::unordered_map<nano::account, nano::block_hash> frontiers;
 | 
				
			||||||
 | 
						std::random_device rd;
 | 
				
			||||||
 | 
						std::mt19937 gen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						account_pool ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void generate_accounts (size_t count);
 | 
				
			||||||
 | 
						nano::account get_random_account_with_balance ();
 | 
				
			||||||
 | 
						nano::account get_random_account ();
 | 
				
			||||||
 | 
						nano::keypair const & get_keypair (nano::account const & account);
 | 
				
			||||||
 | 
						void update_balance (nano::account const & account, nano::uint128_t new_balance);
 | 
				
			||||||
 | 
						nano::uint128_t get_balance (nano::account const & account);
 | 
				
			||||||
 | 
						bool has_balance (nano::account const & account);
 | 
				
			||||||
 | 
						size_t accounts_with_balance_count () const;
 | 
				
			||||||
 | 
						size_t total_accounts () const;
 | 
				
			||||||
 | 
						std::vector<nano::account> get_accounts_with_balance () const;
 | 
				
			||||||
 | 
						void set_initial_balance (nano::account const & account, nano::uint128_t balance);
 | 
				
			||||||
 | 
						void set_frontier (nano::account const & account, nano::block_hash const & frontier);
 | 
				
			||||||
 | 
						nano::block_hash get_frontier (nano::account const & account) const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct benchmark_config
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						size_t num_accounts{ 150000 };
 | 
				
			||||||
 | 
						size_t num_iterations{ 5 };
 | 
				
			||||||
 | 
						size_t batch_size{ 250000 };
 | 
				
			||||||
 | 
						nano::cli::cementing_mode cementing_mode{ nano::cli::cementing_mode::sequential };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static benchmark_config parse (boost::program_options::variables_map const & vm);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class benchmark_base
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						account_pool pool;
 | 
				
			||||||
 | 
						std::shared_ptr<nano::node> node;
 | 
				
			||||||
 | 
						benchmark_config config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Common metrics
 | 
				
			||||||
 | 
						std::atomic<size_t> processed_blocks_count{ 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						benchmark_base (std::shared_ptr<nano::node> node_a, benchmark_config const & config_a);
 | 
				
			||||||
 | 
						virtual ~benchmark_base () = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Transfers genesis balance to a random account to prepare for benchmarking
 | 
				
			||||||
 | 
						void setup_genesis_distribution (double distribution_percentage = 1.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generates random transfer pairs between accounts with no specific dependency structure
 | 
				
			||||||
 | 
						std::deque<std::shared_ptr<nano::block>> generate_random_transfers ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generates blocks that are dependencies of a single root block (last in deque)
 | 
				
			||||||
 | 
						std::deque<std::shared_ptr<nano::block>> generate_dependent_chain ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generates independent blocks - returns sends and opens separately
 | 
				
			||||||
 | 
						std::pair<std::deque<std::shared_ptr<nano::block>>, std::deque<std::shared_ptr<nano::block>>> generate_independent_blocks ();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Benchmark entry points - individual implementations are in separate cpp files
 | 
				
			||||||
 | 
					void run_block_processing_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path);
 | 
				
			||||||
 | 
					void run_cementing_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path);
 | 
				
			||||||
 | 
					void run_elections_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path);
 | 
				
			||||||
 | 
					void run_pipeline_benchmark (boost::program_options::variables_map const & vm, std::filesystem::path const & data_path);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@
 | 
				
			||||||
#include <nano/lib/thread_runner.hpp>
 | 
					#include <nano/lib/thread_runner.hpp>
 | 
				
			||||||
#include <nano/lib/utility.hpp>
 | 
					#include <nano/lib/utility.hpp>
 | 
				
			||||||
#include <nano/lib/work_version.hpp>
 | 
					#include <nano/lib/work_version.hpp>
 | 
				
			||||||
 | 
					#include <nano/nano_node/benchmarks/benchmarks.hpp>
 | 
				
			||||||
#include <nano/nano_node/daemon.hpp>
 | 
					#include <nano/nano_node/daemon.hpp>
 | 
				
			||||||
#include <nano/node/active_elections.hpp>
 | 
					#include <nano/node/active_elections.hpp>
 | 
				
			||||||
#include <nano/node/cementing_set.hpp>
 | 
					#include <nano/node/cementing_set.hpp>
 | 
				
			||||||
| 
						 | 
					@ -132,6 +133,10 @@ int main (int argc, char * const * argv)
 | 
				
			||||||
		("debug_profile_bootstrap", "Profile bootstrap style blocks processing (at least 10GB of free storage space required)")
 | 
							("debug_profile_bootstrap", "Profile bootstrap style blocks processing (at least 10GB of free storage space required)")
 | 
				
			||||||
		("debug_profile_sign", "Profile signature generation")
 | 
							("debug_profile_sign", "Profile signature generation")
 | 
				
			||||||
		("debug_profile_process", "Profile active blocks processing (only for nano_dev_network)")
 | 
							("debug_profile_process", "Profile active blocks processing (only for nano_dev_network)")
 | 
				
			||||||
 | 
							("benchmark_block_processing", "Run block processing throughput benchmark")
 | 
				
			||||||
 | 
							("benchmark_cementing", "Run cementing throughput benchmark")
 | 
				
			||||||
 | 
							("benchmark_elections", "Run elections confirmation and cementing benchmark")
 | 
				
			||||||
 | 
							("benchmark_pipeline", "Run full confirmation pipeline benchmark")
 | 
				
			||||||
		("debug_profile_votes", "Profile votes processing (only for nano_dev_network)")
 | 
							("debug_profile_votes", "Profile votes processing (only for nano_dev_network)")
 | 
				
			||||||
		("debug_profile_frontiers_confirmation", "Profile frontiers confirmation speed (only for nano_dev_network)")
 | 
							("debug_profile_frontiers_confirmation", "Profile frontiers confirmation speed (only for nano_dev_network)")
 | 
				
			||||||
		("debug_random_feed", "Generates output to RNG test suites")
 | 
							("debug_random_feed", "Generates output to RNG test suites")
 | 
				
			||||||
| 
						 | 
					@ -149,6 +154,10 @@ int main (int argc, char * const * argv)
 | 
				
			||||||
		("difficulty", boost::program_options::value<std::string> (), "Defines <difficulty> for OpenCL command, HEX")
 | 
							("difficulty", boost::program_options::value<std::string> (), "Defines <difficulty> for OpenCL command, HEX")
 | 
				
			||||||
		("multiplier", boost::program_options::value<std::string> (), "Defines <multiplier> for work generation. Overrides <difficulty>")
 | 
							("multiplier", boost::program_options::value<std::string> (), "Defines <multiplier> for work generation. Overrides <difficulty>")
 | 
				
			||||||
		("count", boost::program_options::value<std::string> (), "Defines <count> for various commands")
 | 
							("count", boost::program_options::value<std::string> (), "Defines <count> for various commands")
 | 
				
			||||||
 | 
							("accounts", boost::program_options::value<std::string> (), "Defines <accounts> for throughput benchmark (default 500000)")
 | 
				
			||||||
 | 
							("iterations", boost::program_options::value<std::string> (), "Defines <iterations> for throughput benchmark (default 10)")
 | 
				
			||||||
 | 
							("batch_size", boost::program_options::value<std::string> (), "Defines <batch_size> for throughput benchmark (default 250000)")
 | 
				
			||||||
 | 
							("cementing_mode", boost::program_options::value<std::string> (), "Defines cementing mode for benchmark: 'sequential' or 'root' (default sequential)")
 | 
				
			||||||
		("pow_sleep_interval", boost::program_options::value<std::string> (), "Defines the amount to sleep inbetween each pow calculation attempt")
 | 
							("pow_sleep_interval", boost::program_options::value<std::string> (), "Defines the amount to sleep inbetween each pow calculation attempt")
 | 
				
			||||||
		("address_column", boost::program_options::value<std::string> (), "Defines which column the addresses are located, 0 indexed (check --debug_output_last_backtrace_dump output)")
 | 
							("address_column", boost::program_options::value<std::string> (), "Defines which column the addresses are located, 0 indexed (check --debug_output_last_backtrace_dump output)")
 | 
				
			||||||
		("silent", "Silent command execution")
 | 
							("silent", "Silent command execution")
 | 
				
			||||||
| 
						 | 
					@ -1047,6 +1056,22 @@ int main (int argc, char * const * argv)
 | 
				
			||||||
			std::cout << boost::str (boost::format ("%|1$ 12d| us \n%2% blocks per second\n") % time % (max_blocks * 1000000 / time));
 | 
								std::cout << boost::str (boost::format ("%|1$ 12d| us \n%2% blocks per second\n") % time % (max_blocks * 1000000 / time));
 | 
				
			||||||
			release_assert (node->ledger.block_count () == max_blocks + 1);
 | 
								release_assert (node->ledger.block_count () == max_blocks + 1);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							else if (vm.count ("benchmark_block_processing"))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								nano::cli::run_block_processing_benchmark (vm, data_path);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if (vm.count ("benchmark_cementing"))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								nano::cli::run_cementing_benchmark (vm, data_path);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if (vm.count ("benchmark_elections"))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								nano::cli::run_elections_benchmark (vm, data_path);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if (vm.count ("benchmark_pipeline"))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								nano::cli::run_pipeline_benchmark (vm, data_path);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		else if (vm.count ("debug_profile_votes"))
 | 
							else if (vm.count ("debug_profile_votes"))
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			nano::block_builder builder;
 | 
								nano::block_builder builder;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ enum class block_source
 | 
				
			||||||
	local,
 | 
						local,
 | 
				
			||||||
	forced,
 | 
						forced,
 | 
				
			||||||
	election,
 | 
						election,
 | 
				
			||||||
 | 
						test,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::string_view to_string (block_source);
 | 
					std::string_view to_string (block_source);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,7 @@ nano::cementing_set::~cementing_set ()
 | 
				
			||||||
	debug_assert (!thread.joinable ());
 | 
						debug_assert (!thread.joinable ());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nano::cementing_set::add (nano::block_hash const & hash, std::shared_ptr<nano::election> const & election)
 | 
					bool nano::cementing_set::add (nano::block_hash const & hash, std::shared_ptr<nano::election> const & election)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	bool added = false;
 | 
						bool added = false;
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
| 
						 | 
					@ -71,6 +71,7 @@ void nano::cementing_set::add (nano::block_hash const & hash, std::shared_ptr<na
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		stats.inc (nano::stat::type::cementing_set, nano::stat::detail::duplicate);
 | 
							stats.inc (nano::stat::type::cementing_set, nano::stat::detail::duplicate);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return added;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nano::cementing_set::start ()
 | 
					void nano::cementing_set::start ()
 | 
				
			||||||
| 
						 | 
					@ -117,6 +118,12 @@ std::size_t nano::cementing_set::size () const
 | 
				
			||||||
	return set.size () + current.size ();
 | 
						return set.size () + current.size ();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::size_t nano::cementing_set::deferred_size () const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::lock_guard lock{ mutex };
 | 
				
			||||||
 | 
						return deferred.size ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nano::cementing_set::run ()
 | 
					void nano::cementing_set::run ()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	std::unique_lock lock{ mutex };
 | 
						std::unique_lock lock{ mutex };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,10 +61,12 @@ public:
 | 
				
			||||||
	void stop ();
 | 
						void stop ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Adds a block to the set of blocks to be confirmed
 | 
						// Adds a block to the set of blocks to be confirmed
 | 
				
			||||||
	void add (nano::block_hash const & hash, std::shared_ptr<nano::election> const & election = nullptr);
 | 
						bool add (nano::block_hash const & hash, std::shared_ptr<nano::election> const & election = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Added blocks will remain in this set until after ledger has them marked as confirmed.
 | 
						// Added blocks will remain in this set until after ledger has them marked as confirmed.
 | 
				
			||||||
	bool contains (nano::block_hash const & hash) const;
 | 
						bool contains (nano::block_hash const & hash) const;
 | 
				
			||||||
	std::size_t size () const;
 | 
						std::size_t size () const;
 | 
				
			||||||
 | 
						std::size_t deferred_size () const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	nano::container_info container_info () const;
 | 
						nano::container_info container_info () const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -119,6 +121,7 @@ private:
 | 
				
			||||||
	ordered_entries set;
 | 
						ordered_entries set;
 | 
				
			||||||
	// Blocks that could not be cemented immediately (e.g. waiting for rollbacks to complete)
 | 
						// Blocks that could not be cemented immediately (e.g. waiting for rollbacks to complete)
 | 
				
			||||||
	ordered_entries deferred;
 | 
						ordered_entries deferred;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Blocks that are being cemented in the current batch
 | 
						// Blocks that are being cemented in the current batch
 | 
				
			||||||
	std::unordered_set<nano::block_hash> current;
 | 
						std::unordered_set<nano::block_hash> current;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nano/lib/numbers.hpp>
 | 
				
			||||||
#include <nano/node/fwd.hpp>
 | 
					#include <nano/node/fwd.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue