Merge remote-tracking branch 'upstream/develop' into develop
Some checks failed
code_sanitizers.yml / Merge remote-tracking branch 'upstream/develop' into develop (push) Failing after 0s
Static Analyzers / clang_format (push) Has been cancelled
Static Analyzers / cmake_format (push) Has been cancelled
Static Analyzers / code_inspector (push) Has been cancelled
Code Flamegraphs / Linux [large_confirmation] (push) Has been cancelled
Code Flamegraphs / Linux [large_direct_processing] (push) Has been cancelled
Unit Tests / macOS [lmdb] (push) Has been cancelled
Unit Tests / macOS [rocksdb] (push) Has been cancelled
Unit Tests / Linux [lmdb | clang] (push) Has been cancelled
Unit Tests / Linux [lmdb | gcc] (push) Has been cancelled
Unit Tests / Linux [rocksdb | clang] (push) Has been cancelled
Unit Tests / Linux [rocksdb | gcc] (push) Has been cancelled
Unit Tests / Windows [lmdb] (push) Has been cancelled
Unit Tests / Windows [rocksdb] (push) Has been cancelled

This commit is contained in:
Minecon724 2025-10-13 12:02:50 +02:00
commit 902c841471
Signed by untrusted user who does not match committer: m724
GPG key ID: A02E6E67AB961189
110 changed files with 4778 additions and 1051 deletions

View file

@ -21,7 +21,7 @@ jobs:
# Bug when running with TSAN: "ThreadSanitizer: CHECK failed: sanitizer_deadlock_detector"
- BACKEND: rocksdb
SANITIZER: { name: TSAN }
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
env:
COMPILER: ${{ matrix.COMPILER }}
BACKEND: ${{ matrix.BACKEND }}

View file

@ -56,7 +56,7 @@ jobs:
COMPILER: [gcc, clang]
RELEASE:
- ${{ startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
env:
COMPILER: ${{ matrix.COMPILER }}
BACKEND: ${{ matrix.BACKEND }}

View file

@ -526,11 +526,9 @@ target_link_libraries(boost_property_tree INTERFACE Boost::multi_index)
# RocksDB
include_directories(submodules/rocksdb/include)
if(WIN32)
set(FAIL_ON_WARNINGS
OFF
CACHE BOOL "")
endif()
set(FAIL_ON_WARNINGS
OFF
CACHE BOOL "")
set(USE_RTTI
ON
CACHE BOOL "")

View file

@ -4,10 +4,7 @@ set -euox pipefail
# Clang installer dependencies
DEBIAN_FRONTEND=noninteractive apt-get install -yqq lsb-release software-properties-common gnupg
CLANG_VERSION=16
# TODO: Verify integrity (at this time, the clang build is not used for any production artifacts)
curl -O https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh $CLANG_VERSION
CLANG_VERSION=18
update-alternatives --install /usr/bin/cc cc /usr/bin/clang-$CLANG_VERSION 100
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-$CLANG_VERSION 100

View file

@ -23,7 +23,7 @@ static void BM_ledger_iterate_accounts (benchmark::State & state)
auto store_impl{ nano::make_store (logger, application_path, network_params.ledger) };
auto & store{ *store_impl };
auto ledger_impl{ std::make_unique<nano::ledger> (store, network_params.ledger, stats, logger, nano::generate_cache_flags::all_disabled ()) };
auto ledger_impl{ std::make_unique<nano::ledger> (store, network_params, stats, logger, nano::generate_cache_flags::all_disabled ()) };
auto & ledger{ *ledger_impl };
auto transaction = ledger.tx_begin_read ();

View file

@ -4,6 +4,7 @@ add_executable(
fakes/websocket_client.hpp
fakes/work_peer.hpp
active_elections.cpp
active_elections_index.cpp
assert.cpp
async.cpp
backlog.cpp
@ -49,6 +50,7 @@ add_executable(
random.cpp
random_pool.cpp
rate_limiting.cpp
recently_cache.cpp
rep_crawler.cpp
receivable.cpp
peer_history.cpp

View file

@ -20,7 +20,9 @@
#include <gtest/gtest.h>
#include <future>
#include <numeric>
#include <random>
using namespace std::chrono_literals;
@ -147,17 +149,18 @@ TEST (active_elections, confirm_frontier)
// start node2 later so that we do not get the gossip traffic
auto & node2 = *system.add_node (node_config2, node_flags2);
// Add representative to disabled rep crawler
auto peers (node2.network.random_set (1));
ASSERT_FALSE (peers.empty ());
node2.rep_crawler.force_add_rep (nano::dev::genesis_key.pub, *peers.begin ());
ASSERT_EQ (nano::block_status::progress, node2.process (send));
ASSERT_TIMELY (5s, !node2.active.empty ());
// Save election to check request count afterwards
std::shared_ptr<nano::election> election2;
ASSERT_TIMELY (5s, election2 = node2.active.election (send->qualified_root ()));
// Add representative to disabled rep crawler
auto peers (node2.network.random_set (1));
ASSERT_FALSE (peers.empty ());
node2.rep_crawler.force_add_rep (nano::dev::genesis_key.pub, *peers.begin ());
ASSERT_TIMELY (5s, nano::test::confirmed (node2, { send }));
ASSERT_TIMELY_EQ (5s, node2.ledger.cemented_count (), 2);
ASSERT_TIMELY (5s, node2.active.empty ());
@ -336,7 +339,7 @@ TEST (active_elections, DISABLED_keep_local)
// ASSERT_EQ (1, node.scheduler.size ());
}
TEST (inactive_votes_cache, basic)
TEST (active_elections, cached_vote_basic)
{
nano::test::system system (1);
auto & node = *system.nodes[0];
@ -360,7 +363,7 @@ TEST (inactive_votes_cache, basic)
/**
* This test case confirms that a non final vote cannot cause an election to become confirmed
*/
TEST (inactive_votes_cache, non_final)
TEST (active_elections, cached_vote_non_final)
{
nano::test::system system (1);
auto & node = *system.nodes[0];
@ -386,7 +389,7 @@ TEST (inactive_votes_cache, non_final)
ASSERT_FALSE (election->confirmed ());
}
TEST (inactive_votes_cache, fork)
TEST (active_elections, cached_vote_fork)
{
nano::test::system system{ 1 };
auto & node = *system.nodes[0];
@ -426,7 +429,7 @@ TEST (inactive_votes_cache, fork)
ASSERT_EQ (1, node.stats.count (nano::stat::type::election_vote, nano::stat::detail::cache));
}
TEST (inactive_votes_cache, existing_vote)
TEST (active_elections, cached_vote_existing)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
@ -480,7 +483,7 @@ TEST (inactive_votes_cache, existing_vote)
ASSERT_EQ (0, node.stats.count (nano::stat::type::election_vote, nano::stat::detail::cache));
}
TEST (inactive_votes_cache, multiple_votes)
TEST (active_elections, cached_vote_multiple)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
@ -533,7 +536,7 @@ TEST (inactive_votes_cache, multiple_votes)
ASSERT_EQ (2, node.stats.count (nano::stat::type::election_vote, nano::stat::detail::cache));
}
TEST (inactive_votes_cache, election_start)
TEST (active_elections, cached_vote_election_start)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
@ -658,7 +661,6 @@ TEST (active_elections, vote_replays)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
ASSERT_NE (nullptr, send1);
// create open block for key receing Knano_ratio raw
auto open1 = builder.make_block ()
@ -670,11 +672,9 @@ TEST (active_elections, vote_replays)
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();
ASSERT_NE (nullptr, open1);
// wait for elections objects to appear in the AEC
node.process_active (send1);
node.process_active (open1);
nano::test::process (node, { send1, open1 });
ASSERT_TRUE (nano::test::start_elections (system, node, { send1, open1 }));
ASSERT_EQ (2, node.active.size ());
@ -694,7 +694,6 @@ TEST (active_elections, vote_replays)
// Open new account
auto vote_open1 = nano::test::make_final_vote (nano::dev::genesis_key, { open1 });
ASSERT_EQ (nano::vote_code::vote, node.vote_router.vote (vote_open1).at (open1->hash ()));
ASSERT_EQ (nano::vote_code::replay, node.vote_router.vote (vote_open1).at (open1->hash ()));
ASSERT_TIMELY (5s, node.active.empty ());
ASSERT_EQ (nano::vote_code::late, node.vote_router.vote (vote_open1).at (open1->hash ()));
ASSERT_EQ (nano::Knano_ratio, node.ledger.weight (key.pub));
@ -709,8 +708,7 @@ TEST (active_elections, vote_replays)
.sign (key.prv, key.pub)
.work (*system.work.generate (open1->hash ()))
.build ();
ASSERT_NE (nullptr, send2);
node.process_active (send2);
nano::test::process (node, { send2 });
ASSERT_TRUE (nano::test::start_elections (system, node, { send2 }));
ASSERT_EQ (1, node.active.size ());
@ -1582,13 +1580,13 @@ TEST (active_elections, broadcast_block_on_activation)
ASSERT_TIMELY (5s, node2->block_or_pruned_exists (send1->hash ()));
}
TEST (active_elections, bootstrap_stale)
TEST (active_elections, stale_election)
{
nano::test::system system;
// Configure node with short stale threshold for testing
nano::node_config node_config = system.default_config ();
node_config.active_elections.bootstrap_stale_threshold = 2s; // Short threshold for faster testing
node_config.active_elections.stale_threshold = 2s; // Short threshold for faster testing
auto & node = *system.add_node (node_config);
@ -1605,6 +1603,12 @@ TEST (active_elections, bootstrap_stale)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
std::atomic<bool> stale_detected{ false };
node.active.election_stale.add ([&] (auto const & election) {
EXPECT_EQ (send->qualified_root (), election->qualified_root);
stale_detected = true;
});
// Process the block and start an election
node.process_active (send);
@ -1613,8 +1617,263 @@ TEST (active_elections, bootstrap_stale)
ASSERT_TIMELY (5s, (election = node.active.election (send->qualified_root ())) != nullptr);
// Check initial state
ASSERT_EQ (0, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::bootstrap_stale));
ASSERT_EQ (0, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::stale));
// Wait for bootstrap_stale_threshold to pass and the statistic to be incremented
ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::bootstrap_stale) > 0);
// Wait for stale_threshold to pass and stats to be incremented
ASSERT_TIMELY (5s, stale_detected);
ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::stale) > 0);
}
TEST (active_elections, stale_election_multiple)
{
nano::test::system system;
// Configure node with short stale threshold for testing
nano::node_config node_config = system.default_config ();
node_config.active_elections.stale_threshold = 2s; // Short threshold for faster testing
auto & node = *system.add_node (node_config);
// Create 10 independent blocks that will each have their own election
auto blocks = nano::test::setup_independent_blocks (system, node, 10);
// Track which elections had stale events fired
nano::locked<std::set<nano::qualified_root>> stale_detected;
node.active.election_stale.add ([&] (auto const & election) {
stale_detected.lock ()->insert (election->qualified_root);
});
// Start elections for all blocks
ASSERT_TRUE (nano::test::start_elections (system, node, blocks));
// Ensure all elections are active
ASSERT_TIMELY_EQ (5s, node.active.size (), blocks.size ());
for (auto const & block : blocks)
{
ASSERT_TRUE (node.active.active (block->qualified_root ()));
}
// Check initial state
ASSERT_EQ (0, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::stale));
// Wait for stale_threshold to pass (2s) plus some buffer
// The stale event should fire for ALL elections that are stale
ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::stale) >= blocks.size ());
// Check that all elections had their stale event fired
ASSERT_TIMELY_EQ (5s, blocks.size (), stale_detected.lock ()->size ());
// Verify each block's election was marked as stale
for (auto const & block : blocks)
{
ASSERT_TRUE (stale_detected.lock ()->count (block->qualified_root ()) > 0)
<< "Election for block " << block->hash ().to_string () << " was not marked as stale";
}
}
TEST (active_elections, transition_optimistic_to_priority)
{
nano::test::system system;
auto & node = *system.add_node ();
// Create a block for optimistic election
nano::state_block_builder builder;
auto block = builder
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.link (nano::keypair{}.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
ASSERT_TRUE (nano::test::process (node, { block }));
// Start optimistic election
auto result = node.active.insert (block, nano::election_behavior::optimistic);
ASSERT_TRUE (result.inserted);
auto election = result.election;
ASSERT_EQ (nano::election_behavior::optimistic, election->behavior ());
// Verify initial sizes
ASSERT_EQ (1, node.active.size (nano::election_behavior::optimistic));
ASSERT_EQ (0, node.active.size (nano::election_behavior::priority));
// Transition to priority
auto transition_result = node.active.insert (block, nano::election_behavior::priority);
ASSERT_FALSE (transition_result.inserted);
ASSERT_EQ (election, transition_result.election);
// Verify transition
ASSERT_EQ (nano::election_behavior::priority, election->behavior ());
ASSERT_EQ (1, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::transition_priority));
ASSERT_EQ (0, node.active.size (nano::election_behavior::optimistic));
ASSERT_EQ (1, node.active.size (nano::election_behavior::priority));
}
TEST (active_elections, transition_hinted_to_priority)
{
nano::test::system system;
auto & node = *system.add_node ();
// Create a block for hinted election
nano::state_block_builder builder;
auto block = builder
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.link (nano::keypair{}.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
ASSERT_TRUE (nano::test::process (node, { block }));
// Start hinted election
auto result = node.active.insert (block, nano::election_behavior::hinted);
ASSERT_TRUE (result.inserted);
auto election = result.election;
ASSERT_EQ (nano::election_behavior::hinted, election->behavior ());
// Verify initial sizes
ASSERT_EQ (1, node.active.size (nano::election_behavior::hinted));
ASSERT_EQ (0, node.active.size (nano::election_behavior::priority));
// Transition to priority
auto transition_result = node.active.insert (block, nano::election_behavior::priority);
ASSERT_FALSE (transition_result.inserted);
ASSERT_EQ (election, transition_result.election);
// Verify transition
ASSERT_EQ (nano::election_behavior::priority, election->behavior ());
ASSERT_EQ (1, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::transition_priority));
ASSERT_EQ (0, node.active.size (nano::election_behavior::hinted));
ASSERT_EQ (1, node.active.size (nano::election_behavior::priority));
}
TEST (active_elections, cancel_cemented_races)
{
nano::test::system system;
nano::node_config config = system.default_config ();
// Disable schedulers and backlog scan to have full control
config.backlog_scan.enable = false;
config.priority_scheduler.enable = false;
config.hinted_scheduler.enable = false;
config.optimistic_scheduler.enable = false;
auto & node = *system.add_node (config);
// Create many chains with many blocks
const int chain_count = 10;
const int blocks_per_chain = 20;
const auto duration = 2s;
auto const chains = nano::test::setup_chains (system, node, chain_count,
blocks_per_chain,
nano::dev::genesis_key,
false); // Don't auto-confirm
// Collect all blocks for random access
std::vector<std::shared_ptr<nano::block>> all_blocks;
for (auto & [account, blocks] : chains)
{
all_blocks.insert (all_blocks.end (), blocks.begin (), blocks.end ());
}
std::atomic<bool> stop_tasks{ false };
// Cement chain heads
auto cementing_task = std::async (std::launch::async, [&] () {
for (auto & [account, blocks] : chains)
{
// Cement using the cementing set so that notifications are properly handled
node.cementing_set.add (blocks.back ()->hash ());
std::this_thread::sleep_for (10ms);
}
});
// Insert elections continuously
auto insertion_task = std::async (std::launch::async, [&] () {
std::random_device rd;
std::mt19937 gen (rd ());
std::uniform_int_distribution<> dis (0, all_blocks.size () - 1);
while (!stop_tasks)
{
auto block = all_blocks[dis (gen)];
node.active.insert (block, nano::election_behavior::priority);
std::this_thread::yield ();
}
});
// Let tasks run for 2 seconds
WAIT (2s);
// Signal tasks to stop
stop_tasks = true;
// Wait for all async tasks to complete
cementing_task.wait ();
insertion_task.wait ();
// Wait for all cementing operations to complete
ASSERT_TIMELY (5s, node.cementing_set.size () == 0);
// Verify all blocks were cemented
auto transaction = node.ledger.tx_begin_read ();
for (auto & [account, blocks] : chains)
{
for (auto & block : blocks)
{
ASSERT_TRUE (node.ledger.confirmed.block_exists (transaction, block->hash ()));
}
}
// Critical assertion: No elections should remain active
ASSERT_TIMELY (5s, node.active.empty ());
}
TEST (active_elections, cancel_already_cemented)
{
nano::test::system system;
nano::node_config config;
// Configure checkup interval for faster test execution
config.active_elections.checkup_interval = 100ms;
auto & node = *system.add_node (config);
// Create a chain of blocks
auto const chain_count = 1;
auto const blocks_per_chain = 5;
auto const chains = nano::test::setup_chains (system, node, chain_count, blocks_per_chain, nano::dev::genesis_key, false);
auto & [account, blocks] = *chains.begin ();
auto last_block = blocks.back ();
// First, cement the block through direct ledger confirmation process, skips callbacks
{
auto transaction = node.ledger.tx_begin_write ();
node.ledger.confirm (transaction, last_block->hash ());
}
// Verify the block is actually cemented
ASSERT_TRUE (node.ledger.confirmed.block_exists (node.ledger.tx_begin_read (), last_block->hash ()));
// Now start an election for the already cemented block
auto election = node.active.insert (last_block, nano::election_behavior::priority);
ASSERT_NE (nullptr, election.election);
// Wait for the cleanup thread to detect and cancel the election
// The cleanup thread runs every checkup_interval (100ms) and only cancels elections
// that have been running for at least 3 * aec_loop_interval (3 * 50ms = 150ms by default)
ASSERT_TIMELY (5s, node.active.empty ());
// Verify that the election was cancelled by the checkup thread
ASSERT_GT (node.stats.count (nano::stat::type::active_elections, nano::stat::detail::cancel_checkup), 0)
<< "Expected election to be cancelled by checkup thread";
}

View file

@ -0,0 +1,462 @@
#include <nano/node/active_elections_index.hpp>
#include <nano/node/election.hpp>
#include <nano/test_common/chains.hpp>
#include <nano/test_common/random.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
#include <chrono>
#include <deque>
#include <numeric>
using namespace std::chrono_literals;
namespace
{
// Full node is necessary to create elections
class test_context final
{
public:
nano::test::system system;
nano::node & node;
std::deque<std::shared_ptr<nano::block>> blocks;
explicit test_context (size_t count = 10) :
node{ *system.add_node () }
{
auto chain = nano::test::setup_chain (system, node, count);
blocks.insert (blocks.end (), chain.begin (), chain.end ());
}
std::shared_ptr<nano::block> next_block ()
{
debug_assert (!blocks.empty ());
auto block = blocks.front ();
blocks.pop_front ();
return block;
}
std::shared_ptr<nano::election> random_election (nano::election_behavior behavior = nano::election_behavior::priority)
{
return std::make_shared<nano::election> (node, next_block (), behavior);
}
};
}
TEST (active_elections_index, insert)
{
test_context context{ 10 };
nano::active_elections_index index;
// Create elections with different behaviors and buckets
auto election1 = context.random_election (nano::election_behavior::priority);
auto election2 = context.random_election (nano::election_behavior::hinted);
auto election3 = context.random_election (nano::election_behavior::optimistic);
// Test initial state
ASSERT_EQ (index.size (), 0);
ASSERT_FALSE (index.exists (election1));
// Insert first election
index.insert (election1, nano::election_behavior::priority, 1, 100);
ASSERT_EQ (index.size (), 1);
ASSERT_TRUE (index.exists (election1));
ASSERT_TRUE (index.exists (election1->qualified_root));
ASSERT_EQ (index.size (nano::election_behavior::priority), 1);
ASSERT_EQ (index.size (nano::election_behavior::priority, 1), 1);
// Insert second election with different behavior
index.insert (election2, nano::election_behavior::hinted, 2, 200);
ASSERT_EQ (index.size (), 2);
ASSERT_TRUE (index.exists (election2));
ASSERT_EQ (index.size (nano::election_behavior::hinted), 1);
ASSERT_EQ (index.size (nano::election_behavior::hinted, 2), 1);
// Insert third election
index.insert (election3, nano::election_behavior::optimistic, 1, 50);
ASSERT_EQ (index.size (), 3);
ASSERT_EQ (index.size (nano::election_behavior::optimistic), 1);
ASSERT_EQ (index.size (nano::election_behavior::optimistic, 1), 1);
}
TEST (active_elections_index, erase)
{
test_context context{ 5 };
nano::active_elections_index index;
auto election1 = context.random_election ();
auto election2 = context.random_election ();
auto election3 = context.random_election ();
// Insert elections
index.insert (election1, nano::election_behavior::priority, 1, 100);
index.insert (election2, nano::election_behavior::priority, 1, 200);
index.insert (election3, nano::election_behavior::hinted, 2, 300);
ASSERT_EQ (index.size (), 3);
ASSERT_EQ (index.size (nano::election_behavior::priority), 2);
ASSERT_EQ (index.size (nano::election_behavior::priority, 1), 2);
// Erase existing election
ASSERT_TRUE (index.erase (election1));
ASSERT_EQ (index.size (), 2);
ASSERT_FALSE (index.exists (election1));
ASSERT_FALSE (index.exists (election1->qualified_root));
ASSERT_EQ (index.size (nano::election_behavior::priority), 1);
ASSERT_EQ (index.size (nano::election_behavior::priority, 1), 1);
// Try to erase non-existent election
ASSERT_FALSE (index.erase (election1));
ASSERT_EQ (index.size (), 2);
// Erase remaining elections
ASSERT_TRUE (index.erase (election2));
ASSERT_TRUE (index.erase (election3));
ASSERT_EQ (index.size (), 0);
ASSERT_EQ (index.size (nano::election_behavior::priority), 0);
ASSERT_EQ (index.size (nano::election_behavior::hinted), 0);
}
TEST (active_elections_index, update)
{
test_context context{ 5 };
nano::active_elections_index index;
auto election1 = context.random_election ();
auto election2 = context.random_election ();
// Insert elections
index.insert (election1, nano::election_behavior::priority, 1, 100);
index.insert (election2, nano::election_behavior::hinted, 2, 200);
ASSERT_EQ (index.size (nano::election_behavior::priority), 1);
ASSERT_EQ (index.size (nano::election_behavior::hinted), 1);
ASSERT_EQ (index.size (nano::election_behavior::priority, 1), 1);
ASSERT_EQ (index.size (nano::election_behavior::hinted, 2), 1);
// Update election1 behavior from priority to optimistic
index.update (election1, nano::election_behavior::optimistic);
ASSERT_EQ (index.size (), 2);
ASSERT_EQ (index.size (nano::election_behavior::priority), 0);
ASSERT_EQ (index.size (nano::election_behavior::optimistic), 1);
ASSERT_EQ (index.size (nano::election_behavior::hinted), 1);
ASSERT_EQ (index.size (nano::election_behavior::priority, 1), 0);
ASSERT_EQ (index.size (nano::election_behavior::optimistic, 1), 1);
// Verify election still exists
ASSERT_TRUE (index.exists (election1));
// Update with same behavior (no change)
index.update (election2, nano::election_behavior::hinted);
ASSERT_EQ (index.size (nano::election_behavior::hinted), 1);
ASSERT_EQ (index.size (nano::election_behavior::hinted, 2), 1);
}
TEST (active_elections_index, exists)
{
test_context context{ 3 };
nano::active_elections_index index;
auto election1 = context.random_election ();
auto election2 = context.random_election ();
auto election3 = context.random_election ();
// Test non-existent elections
ASSERT_FALSE (index.exists (election1));
ASSERT_FALSE (index.exists (election1->qualified_root));
// Insert election1
index.insert (election1, nano::election_behavior::priority, 1, 100);
// Test exists with election pointer
ASSERT_TRUE (index.exists (election1));
ASSERT_FALSE (index.exists (election2));
ASSERT_FALSE (index.exists (election3));
// Test exists with qualified_root
ASSERT_TRUE (index.exists (election1->qualified_root));
ASSERT_FALSE (index.exists (election2->qualified_root));
ASSERT_FALSE (index.exists (election3->qualified_root));
// Add more elections and test
index.insert (election2, nano::election_behavior::hinted, 2, 200);
ASSERT_TRUE (index.exists (election2));
ASSERT_TRUE (index.exists (election2->qualified_root));
}
TEST (active_elections_index, election_lookup)
{
test_context context{ 3 };
nano::active_elections_index index;
auto election1 = context.random_election ();
auto election2 = context.random_election ();
// Test lookup on empty index
ASSERT_EQ (index.election (election1->qualified_root), nullptr);
// Insert elections
index.insert (election1, nano::election_behavior::priority, 1, 100);
index.insert (election2, nano::election_behavior::hinted, 2, 200);
// Test successful lookup
ASSERT_EQ (index.election (election1->qualified_root), election1);
ASSERT_EQ (index.election (election2->qualified_root), election2);
// Test lookup for non-existent root
auto election3 = context.random_election ();
ASSERT_EQ (index.election (election3->qualified_root), nullptr);
// Test info method
auto info1 = index.info (election1);
ASSERT_TRUE (info1.has_value ());
ASSERT_EQ (info1->election, election1);
ASSERT_EQ (info1->behavior, nano::election_behavior::priority);
ASSERT_EQ (info1->bucket, 1);
ASSERT_EQ (info1->priority, 100);
auto info3 = index.info (election3);
ASSERT_FALSE (info3.has_value ());
}
TEST (active_elections_index, size_operations)
{
test_context context{ 10 };
nano::active_elections_index index;
// Test empty index
ASSERT_EQ (index.size (), 0);
ASSERT_EQ (index.size (nano::election_behavior::priority), 0);
ASSERT_EQ (index.size (nano::election_behavior::priority, 1), 0);
// Add elections with different behaviors and buckets
auto e1 = context.random_election ();
auto e2 = context.random_election ();
auto e3 = context.random_election ();
auto e4 = context.random_election ();
auto e5 = context.random_election ();
index.insert (e1, nano::election_behavior::priority, 1, 100);
index.insert (e2, nano::election_behavior::priority, 1, 200);
index.insert (e3, nano::election_behavior::priority, 2, 300);
index.insert (e4, nano::election_behavior::hinted, 1, 400);
index.insert (e5, nano::election_behavior::optimistic, 3, 500);
// Test total size
ASSERT_EQ (index.size (), 5);
// Test size by behavior
ASSERT_EQ (index.size (nano::election_behavior::priority), 3);
ASSERT_EQ (index.size (nano::election_behavior::hinted), 1);
ASSERT_EQ (index.size (nano::election_behavior::optimistic), 1);
ASSERT_EQ (index.size (nano::election_behavior::manual), 0);
// Test size by behavior and bucket
ASSERT_EQ (index.size (nano::election_behavior::priority, 1), 2);
ASSERT_EQ (index.size (nano::election_behavior::priority, 2), 1);
ASSERT_EQ (index.size (nano::election_behavior::priority, 3), 0);
ASSERT_EQ (index.size (nano::election_behavior::hinted, 1), 1);
ASSERT_EQ (index.size (nano::election_behavior::optimistic, 3), 1);
}
TEST (active_elections_index, last)
{
test_context context{ 10 };
nano::active_elections_index index;
// Test empty index
auto [empty_election, empty_priority] = index.last (nano::election_behavior::priority, 1);
ASSERT_EQ (empty_election, nullptr);
ASSERT_EQ (empty_priority, std::numeric_limits<nano::priority_timestamp>::max ());
// Add elections with different priorities
auto e1 = context.random_election ();
auto e2 = context.random_election ();
auto e3 = context.random_election ();
auto e4 = context.random_election ();
index.insert (e1, nano::election_behavior::priority, 1, 300); // Highest priority value in bucket 1
index.insert (e2, nano::election_behavior::priority, 1, 100);
index.insert (e3, nano::election_behavior::priority, 1, 200);
index.insert (e4, nano::election_behavior::priority, 2, 50); // Only election in bucket 2
// Test last for bucket 1 (should return e1 with priority 300 - highest value)
auto [top1_election, top1_priority] = index.last (nano::election_behavior::priority, 1);
ASSERT_EQ (top1_election, e1);
ASSERT_EQ (top1_priority, 300);
// Test last for bucket 2 (should return e4 with priority 50)
auto [top2_election, top2_priority] = index.last (nano::election_behavior::priority, 2);
ASSERT_EQ (top2_election, e4);
ASSERT_EQ (top2_priority, 50);
// Test last for empty bucket
auto [top3_election, top3_priority] = index.last (nano::election_behavior::priority, 3);
ASSERT_EQ (top3_election, nullptr);
ASSERT_EQ (top3_priority, std::numeric_limits<nano::priority_timestamp>::max ());
// Test last for different behavior
auto e5 = context.random_election ();
index.insert (e5, nano::election_behavior::hinted, 1, 75);
auto [top4_election, top4_priority] = index.last (nano::election_behavior::hinted, 1);
ASSERT_EQ (top4_election, e5);
ASSERT_EQ (top4_priority, 75);
}
TEST (active_elections_index, list_with_cutoff)
{
test_context context{ 5 };
nano::active_elections_index index;
auto e1 = context.random_election ();
auto e2 = context.random_election ();
auto e3 = context.random_election ();
// Insert elections (they start with timestamp at epoch)
index.insert (e1, nano::election_behavior::priority, 1, 100);
index.insert (e2, nano::election_behavior::priority, 2, 200);
index.insert (e3, nano::election_behavior::hinted, 1, 300);
auto initial_time = std::chrono::steady_clock::now ();
auto cutoff = initial_time + 1s; // All elections should be before this cutoff
// Process all elections and update their timestamps to initial_time
auto elections_list = index.list (cutoff, initial_time);
// All elections should have been processed
ASSERT_EQ (elections_list.size (), 3);
std::set<std::shared_ptr<nano::election>> processed (elections_list.begin (), elections_list.end ());
ASSERT_TRUE (processed.count (e1));
ASSERT_TRUE (processed.count (e2));
ASSERT_TRUE (processed.count (e3));
// Now test with earlier cutoff - no elections should be processed
// because their timestamps were updated to initial_time in the previous list call
auto earlier_cutoff = initial_time - 1s;
auto elections_list2 = index.list (earlier_cutoff);
ASSERT_EQ (elections_list2.size (), 0);
ASSERT_TRUE (elections_list2.empty ());
// Test with future time - should process all elections again
auto future_time = initial_time + 2s;
auto future_cutoff = future_time - 1s; // Between initial_time and future_time
auto elections_list3 = index.list (future_cutoff, future_time);
// All elections should be processed since cutoff is after their timestamp
ASSERT_EQ (elections_list3.size (), 3);
}
TEST (active_elections_index, trigger)
{
test_context context{ 3 };
nano::active_elections_index index;
auto e1 = context.random_election ();
auto e2 = context.random_election ();
// Insert elections (they start with timestamp at epoch)
index.insert (e1, nano::election_behavior::priority, 1, 100);
index.insert (e2, nano::election_behavior::priority, 2, 200);
// Process elections to update their timestamps to a future time
auto future_time = std::chrono::steady_clock::now () + 1h;
auto cutoff = future_time + 1s;
index.list (cutoff, future_time);
// Trigger e1 to reset its timestamp
index.trigger (e1);
// Check that e1 has been triggered (timestamp reset to epoch)
// We can verify this by using list with a very early cutoff
auto very_early_cutoff = std::chrono::steady_clock::time_point{} + 1ms;
auto triggered_elections = index.list (very_early_cutoff);
// Only e1 should have been processed (because it was triggered)
ASSERT_EQ (triggered_elections.size (), 1);
ASSERT_EQ (triggered_elections[0], e1);
}
TEST (active_elections_index, any)
{
test_context context{ 3 };
nano::active_elections_index index;
// Test empty index
auto cutoff = std::chrono::steady_clock::now ();
ASSERT_FALSE (index.any (cutoff));
auto e1 = context.random_election ();
auto e2 = context.random_election ();
// Insert elections (they will have timestamp at epoch initially)
index.insert (e1, nano::election_behavior::priority, 1, 100);
index.insert (e2, nano::election_behavior::priority, 2, 200);
// Check with cutoff in the future - should find elections
auto future_cutoff = std::chrono::steady_clock::now () + 1s;
ASSERT_TRUE (index.any (future_cutoff));
// Update timestamps by processing elections
auto now = std::chrono::steady_clock::now ();
index.list (future_cutoff, now);
// Check with cutoff in the past - should not find elections
auto past_cutoff = now - 1s;
ASSERT_FALSE (index.any (past_cutoff));
// Trigger one election to reset its timestamp
index.trigger (e1);
// Now should find the triggered election even with past cutoff
ASSERT_TRUE (index.any (past_cutoff));
}
TEST (active_elections_index, clear)
{
test_context context{ 5 };
nano::active_elections_index index;
// Add multiple elections
auto e1 = context.random_election ();
auto e2 = context.random_election ();
auto e3 = context.random_election ();
auto e4 = context.random_election ();
index.insert (e1, nano::election_behavior::priority, 1, 100);
index.insert (e2, nano::election_behavior::priority, 2, 200);
index.insert (e3, nano::election_behavior::hinted, 1, 300);
index.insert (e4, nano::election_behavior::optimistic, 3, 400);
// Verify index is populated
ASSERT_EQ (index.size (), 4);
ASSERT_EQ (index.size (nano::election_behavior::priority), 2);
ASSERT_EQ (index.size (nano::election_behavior::hinted), 1);
ASSERT_EQ (index.size (nano::election_behavior::optimistic), 1);
ASSERT_TRUE (index.exists (e1));
ASSERT_TRUE (index.exists (e2->qualified_root));
// Clear the index
index.clear ();
// Verify everything is cleared
ASSERT_EQ (index.size (), 0);
ASSERT_EQ (index.size (nano::election_behavior::priority), 0);
ASSERT_EQ (index.size (nano::election_behavior::hinted), 0);
ASSERT_EQ (index.size (nano::election_behavior::optimistic), 0);
ASSERT_EQ (index.size (nano::election_behavior::manual), 0);
ASSERT_FALSE (index.exists (e1));
ASSERT_FALSE (index.exists (e2));
ASSERT_FALSE (index.exists (e3));
ASSERT_FALSE (index.exists (e4));
ASSERT_FALSE (index.exists (e1->qualified_root));
// Test that we can still use the index after clearing
index.insert (e1, nano::election_behavior::manual, 0, 50);
ASSERT_EQ (index.size (), 1);
ASSERT_TRUE (index.exists (e1));
ASSERT_EQ (index.size (nano::election_behavior::manual), 1);
}

View file

@ -627,7 +627,7 @@ TEST (mdb_block_store, supported_version_upgrades)
{
nano::store::lmdb::component store (logger, path, nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (store, nano::dev::constants, stats, logger);
nano::ledger ledger (store, nano::dev::network_params, stats, logger);
auto transaction (store.tx_begin_write ());
// Lower the database to the max version unsupported for upgrades
store.version.put (transaction, store.version_minimum - 1);
@ -643,7 +643,7 @@ TEST (mdb_block_store, supported_version_upgrades)
{
nano::store::lmdb::component store (logger, path1, nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (store, nano::dev::constants, stats, logger);
nano::ledger ledger (store, nano::dev::network_params, stats, logger);
auto transaction (store.tx_begin_write ());
// Lower the database version to the minimum version supported for upgrade.
store.version.put (transaction, store.version_minimum);
@ -875,7 +875,7 @@ TEST (block_store, cemented_count_cache)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ASSERT_EQ (1, ledger.cemented_count ());
}
@ -960,7 +960,7 @@ TEST (mdb_block_store, sideband_height)
nano::store::lmdb::component store (logger, nano::unique_path () / "data.ldb", nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (store, nano::dev::constants, stats, logger);
nano::ledger ledger (store, nano::dev::network_params, stats, logger);
nano::block_builder builder;
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };

View file

@ -309,11 +309,10 @@ TEST (bootstrap, account_inductive)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
// std::cerr << "Genesis: " << nano::dev::genesis->hash ().to_string () << std::endl;
// std::cerr << "Send1: " << send1->hash ().to_string () << std::endl;
// std::cerr << "Send2: " << send2->hash ().to_string () << std::endl;
ASSERT_EQ (nano::block_status::progress, node0.process (send1));
ASSERT_EQ (nano::block_status::progress, node0.process (send2));
auto & node1 = *system.add_node (flags);
ASSERT_TIMELY (50s, node1.block (send2->hash ()) != nullptr);
}
@ -352,7 +351,7 @@ TEST (bootstrap, trace_base)
ASSERT_EQ (nano::block_status::progress, node0.process (send1));
ASSERT_EQ (nano::block_status::progress, node0.process (receive1));
ASSERT_EQ (node1.ledger.any.receivable_end (), node1.ledger.any.receivable_upper_bound (node1.ledger.tx_begin_read (), key.pub, 0));
ASSERT_TIMELY (10s, node1.ledger.any.receivable_upper_bound (node1.ledger.tx_begin_read (), key.pub, 0) != node1.ledger.any.receivable_end ());
ASSERT_TIMELY (10s, node1.block (receive1->hash ()) != nullptr);
}

View file

@ -1,6 +1,7 @@
#include <nano/lib/blocks.hpp>
#include <nano/node/transport/fake.hpp>
#include <nano/test_common/chains.hpp>
#include <nano/test_common/random.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>

View file

@ -180,7 +180,7 @@ TEST (confirmation_callback, confirmed_history)
// Confirm send1
election->force_confirm ();
ASSERT_TIMELY_EQ (10s, node->active.size (), 0);
ASSERT_EQ (0, node->active.recently_cemented.list ().size ());
ASSERT_EQ (0, node->active.recently_cemented.size ());
ASSERT_TRUE (node->active.empty ());
auto transaction = node->ledger.tx_begin_read ();
@ -200,7 +200,7 @@ TEST (confirmation_callback, confirmed_history)
ASSERT_TIMELY_EQ (10s, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out), 1);
// Each block that's confirmed is in the recently_cemented history
ASSERT_EQ (2, node->active.recently_cemented.list ().size ());
ASSERT_EQ (2, node->active.recently_cemented.size ());
ASSERT_TRUE (node->active.empty ());
// Confirm the callback is not called under this circumstance

View file

@ -46,11 +46,11 @@ TEST (confirmation_solicitor, batches)
nano::lock_guard<nano::mutex> guard (node2.active.mutex);
for (size_t i (0); i < nano::network::confirm_req_hashes_max; ++i)
{
auto election (std::make_shared<nano::election> (node2, send, nullptr, nullptr, nano::election_behavior::priority));
auto election (std::make_shared<nano::election> (node2, send, nano::election_behavior::priority));
ASSERT_FALSE (solicitor.add (*election));
}
// Reached the maximum amount of requests for the channel
auto election (std::make_shared<nano::election> (node2, send, nullptr, nullptr, nano::election_behavior::priority));
auto election (std::make_shared<nano::election> (node2, send, nano::election_behavior::priority));
// Broadcasting should be immediate
ASSERT_EQ (0, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out));
ASSERT_FALSE (solicitor.broadcast (*election));
@ -92,7 +92,7 @@ TEST (confirmation_solicitor, different_hash)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
send->sideband_set ({});
auto election (std::make_shared<nano::election> (node2, send, nullptr, nullptr, nano::election_behavior::priority));
auto election (std::make_shared<nano::election> (node2, send, nano::election_behavior::priority));
// Add a vote for something else, not the winner
election->last_votes[representative.account] = { std::chrono::steady_clock::now (), 1, 1 };
// Ensure the request and broadcast goes through
@ -136,7 +136,7 @@ TEST (confirmation_solicitor, bypass_max_requests_cap)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
send->sideband_set ({});
auto election (std::make_shared<nano::election> (node2, send, nullptr, nullptr, nano::election_behavior::priority));
auto election (std::make_shared<nano::election> (node2, send, nano::election_behavior::priority));
// Add a vote for something else, not the winner
for (auto const & rep : representatives)
{
@ -149,7 +149,7 @@ TEST (confirmation_solicitor, bypass_max_requests_cap)
ASSERT_TIMELY_EQ (6s, max_representatives + 1, node2.stats.count (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::out));
solicitor.prepare (representatives);
auto election2 (std::make_shared<nano::election> (node2, send, nullptr, nullptr, nano::election_behavior::priority));
auto election2 (std::make_shared<nano::election> (node2, send, nano::election_behavior::priority));
ASSERT_FALSE (solicitor.add (*election2));
ASSERT_FALSE (solicitor.broadcast (*election2));

View file

@ -10,11 +10,8 @@
#include <gtest/gtest.h>
TEST (difficultyDeathTest, multipliers)
TEST (difficulty, multipliers)
{
// For ASSERT_DEATH_IF_SUPPORTED
testing::FLAGS_gtest_death_test_style = "threadsafe";
{
uint64_t base = 0xff00000000000000;
uint64_t difficulty = 0xfff27e7a57c285cd;
@ -51,19 +48,14 @@ TEST (difficultyDeathTest, multipliers)
ASSERT_EQ (difficulty, nano::difficulty::from_multiplier (expected_multiplier, base));
}
// The death checks don't fail on a release config, so guard against them
#ifndef NDEBUG
// Causes valgrind to be noisy
if (!nano::running_within_valgrind ())
{
uint64_t base = 0xffffffc000000000;
uint64_t difficulty_nil = 0;
double multiplier_nil = 0.;
ASSERT_DEATH_IF_SUPPORTED (nano::difficulty::to_multiplier (difficulty_nil, base), "");
ASSERT_DEATH_IF_SUPPORTED (nano::difficulty::from_multiplier (multiplier_nil, base), "");
ASSERT_EQ (0., nano::difficulty::to_multiplier (difficulty_nil, base));
ASSERT_EQ (0, nano::difficulty::from_multiplier (multiplier_nil, base));
}
#endif
}
TEST (difficulty, overflow)

View file

@ -19,7 +19,7 @@ TEST (election, construction)
nano::test::system system (1);
auto & node = *system.nodes[0];
auto election = std::make_shared<nano::election> (
node, nano::dev::genesis, [] (auto const &) {}, [] (auto const &) {}, nano::election_behavior::priority);
node, nano::dev::genesis, nano::election_behavior::priority, [] (auto const &) {}, [] (auto const &) {}, [] (auto const &) {});
}
TEST (election, behavior)
@ -152,10 +152,12 @@ TEST (election, quorum_minimum_confirm_success)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
node1.work_generate_blocking (*send1);
node1.process_active (send1);
nano::test::process (node1, { send1 });
auto election = nano::test::start_election (system, node1, send1->hash ());
ASSERT_NE (nullptr, election);
ASSERT_EQ (1, election->blocks ().size ());
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () });
ASSERT_EQ (nano::vote_code::vote, node1.vote_router.vote (vote).at (send1->hash ()));
ASSERT_NE (nullptr, node1.block (send1->hash ()));
@ -182,7 +184,7 @@ TEST (election, quorum_minimum_confirm_fail)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
node1.process_active (send1);
nano::test::process (node1, { send1 });
auto election = nano::test::start_election (system, node1, send1->hash ());
ASSERT_NE (nullptr, election);
ASSERT_EQ (1, election->blocks ().size ());

View file

@ -1,4 +1,5 @@
#include <nano/node/fork_cache.hpp>
#include <nano/test_common/random.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>

View file

@ -876,7 +876,7 @@ TEST (ledger, double_open)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
nano::keypair key2;
@ -4810,7 +4810,7 @@ TEST (ledger, dependents_confirmed_pruning)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
auto transaction = ledger.tx_begin_write ();
nano::block_builder builder;
@ -4908,7 +4908,7 @@ TEST (ledger, cache)
};
cache_check (ledger);
cache_check (nano::ledger (store, nano::dev::constants, stats, logger));
cache_check (nano::ledger (store, nano::dev::network_params, stats, logger));
nano::keypair key;
auto const latest = ledger.any.account_head (ledger.tx_begin_read (), nano::dev::genesis_key.pub);
@ -4938,7 +4938,7 @@ TEST (ledger, cache)
++block_count;
--genesis_weight;
cache_check (ledger);
cache_check (nano::ledger (store, nano::dev::constants, stats, logger));
cache_check (nano::ledger (store, nano::dev::network_params, stats, logger));
{
auto transaction = ledger.tx_begin_write ();
@ -4948,7 +4948,7 @@ TEST (ledger, cache)
++block_count;
++account_count;
cache_check (ledger);
cache_check (nano::ledger (store, nano::dev::constants, stats, logger));
cache_check (nano::ledger (store, nano::dev::network_params, stats, logger));
{
auto transaction = ledger.tx_begin_write ();
@ -4958,7 +4958,7 @@ TEST (ledger, cache)
++cemented_count;
cache_check (ledger);
cache_check (nano::ledger (store, nano::dev::constants, stats, logger));
cache_check (nano::ledger (store, nano::dev::network_params, stats, logger));
{
auto transaction = ledger.tx_begin_write ();
@ -4968,7 +4968,7 @@ TEST (ledger, cache)
++cemented_count;
cache_check (ledger);
cache_check (nano::ledger (store, nano::dev::constants, stats, logger));
cache_check (nano::ledger (store, nano::dev::network_params, stats, logger));
{
auto transaction = ledger.tx_begin_write ();
@ -4976,7 +4976,7 @@ TEST (ledger, cache)
}
++pruned_count;
cache_check (ledger);
cache_check (nano::ledger (store, nano::dev::constants, stats, logger));
cache_check (nano::ledger (store, nano::dev::network_params, stats, logger));
}
}
@ -4985,7 +4985,7 @@ TEST (ledger, pruning_action)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
@ -5069,7 +5069,7 @@ TEST (ledger, pruning_large_chain)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
@ -5123,7 +5123,7 @@ TEST (ledger, pruning_source_rollback)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
@ -5210,7 +5210,7 @@ TEST (ledger, pruning_source_rollback_legacy)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
@ -5322,7 +5322,7 @@ TEST (ledger, pruning_legacy_blocks)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
nano::keypair key1;
auto transaction = ledger.tx_begin_write ();
@ -5407,7 +5407,7 @@ TEST (ledger, pruning_safe_functions)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
@ -5457,7 +5457,7 @@ TEST (ledger, random_blocks)
nano::logger logger;
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::stats stats{ logger };
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
@ -5544,7 +5544,7 @@ TEST (ledger, migrate_lmdb_to_rocksdb)
boost::asio::ip::address_v6 address (boost::asio::ip::make_address_v6 ("::ffff:127.0.0.1"));
uint16_t port = 100;
nano::store::lmdb::component store{ logger, path / "data.ldb", nano::dev::constants };
nano::ledger ledger{ store, nano::dev::constants, system.stats, system.logger };
nano::ledger ledger{ store, nano::dev::network_params, system.stats, system.logger };
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
std::shared_ptr<nano::block> send = nano::state_block_builder ()

View file

@ -760,7 +760,7 @@ TEST (ledger_confirm, pruned_source)
auto path (nano::unique_path ());
auto store = nano::make_store (system.logger, path, nano::dev::constants);
nano::ledger ledger (*store, nano::dev::constants, system.stats, system.logger);
nano::ledger ledger (*store, nano::dev::network_params, system.stats, system.logger);
ledger.pruning = true;
nano::store::write_queue write_queue;
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
@ -844,7 +844,7 @@ TEST (ledger_confirmDeathTest, rollback_added_block)
auto path (nano::unique_path ());
auto store = nano::make_store (system.logger, path, nano::dev::constants);
nano::ledger ledger (*store, nano::dev::constants, system.stats, system.logger);
nano::ledger ledger (*store, nano::dev::network_params, system.stats, system.logger);
nano::store::write_queue write_queue;
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
nano::keypair key1;

View file

@ -4,6 +4,7 @@
#include <nano/node/endpoint.hpp>
#include <nano/node/network.hpp>
#include <nano/secure/vote.hpp>
#include <nano/test_common/random.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>

View file

@ -1,6 +1,7 @@
#include <nano/lib/blocks.hpp>
#include <nano/node/transport/message_deserializer.hpp>
#include <nano/secure/vote.hpp>
#include <nano/test_common/random.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>

View file

@ -1605,7 +1605,7 @@ TEST (node, block_confirm)
ASSERT_TIMELY (5s, election = node2.active.election (send1_copy->qualified_root ()));
// Make node2 genesis representative so it can vote
system.wallet (1)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_TIMELY_EQ (10s, node1.active.recently_cemented.list ().size (), 1);
ASSERT_TIMELY_EQ (10s, node1.active.recently_cemented.size (), 1);
}
TEST (node, confirm_quorum)

View file

@ -17,8 +17,11 @@ using namespace std::chrono_literals;
*/
TEST (optimistic_scheduler, activate_one)
{
nano::test::system system{};
auto & node = *system.add_node ();
nano::test::system system;
nano::node_config config;
config.priority_scheduler.enable = false; // Disable priority scheduler to avoid interference
auto & node = *system.add_node (config);
// Needs to be greater than optimistic scheduler `gap_threshold`
const int howmany_blocks = 64;
@ -41,8 +44,11 @@ TEST (optimistic_scheduler, activate_one)
*/
TEST (optimistic_scheduler, activate_one_zero_conf)
{
nano::test::system system{};
auto & node = *system.add_node ();
nano::test::system system;
nano::node_config config;
config.priority_scheduler.enable = false; // Disable priority scheduler to avoid interference
auto & node = *system.add_node (config);
// Can be smaller than optimistic scheduler `gap_threshold`
// This is meant to activate short account chains (eg. binary tree spam leaf accounts)
@ -63,8 +69,11 @@ TEST (optimistic_scheduler, activate_one_zero_conf)
*/
TEST (optimistic_scheduler, activate_many)
{
nano::test::system system{};
auto & node = *system.add_node ();
nano::test::system system;
nano::node_config config;
config.priority_scheduler.enable = false; // Disable priority scheduler to avoid interference
auto & node = *system.add_node (config);
// Needs to be greater than optimistic scheduler `gap_threshold`
const int howmany_blocks = 64;
@ -72,8 +81,8 @@ TEST (optimistic_scheduler, activate_many)
auto chains = nano::test::setup_chains (system, node, howmany_chains, howmany_blocks, nano::dev::genesis_key, /* do not confirm */ false);
// Ensure all unconfirmed accounts head block gets activated
ASSERT_TIMELY (5s, std::all_of (chains.begin (), chains.end (), [&] (auto const & entry) {
// Ensure all unconfirmed account head blocks get activated
ASSERT_TIMELY (15s, std::all_of (chains.begin (), chains.end (), [&] (auto const & entry) {
auto const & [account, blocks] = entry;
auto const & block = blocks.back ();
auto election = node.active.election (block->qualified_root ());
@ -86,7 +95,8 @@ TEST (optimistic_scheduler, activate_many)
*/
TEST (optimistic_scheduler, under_gap_threshold)
{
nano::test::system system{};
nano::test::system system;
nano::node_config config = system.default_config ();
config.backlog_scan.enable = false;
auto & node = *system.add_node (config);

View file

@ -17,7 +17,7 @@ TEST (processor_service, bad_send_signature)
nano::test::system system;
auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants);
nano::ledger ledger (*store, nano::dev::constants, system.stats, system.logger);
nano::ledger ledger (*store, nano::dev::network_params, system.stats, system.logger);
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
auto info1 = ledger.any.account_get (transaction, nano::dev::genesis_key.pub);
@ -41,7 +41,7 @@ TEST (processor_service, bad_receive_signature)
nano::test::system system;
auto store = nano::make_store (system.logger, nano::unique_path (), nano::dev::constants);
nano::ledger ledger (*store, nano::dev::constants, system.stats, system.logger);
nano::ledger ledger (*store, nano::dev::network_params, system.stats, system.logger);
auto transaction = ledger.tx_begin_write ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
auto info1 = ledger.any.account_get (transaction, nano::dev::genesis_key.pub);

View file

@ -0,0 +1,210 @@
#include <nano/lib/blockbuilders.hpp>
#include <nano/node/election_status.hpp>
#include <nano/node/recently_cemented_cache.hpp>
#include <nano/node/recently_confirmed_cache.hpp>
#include <nano/test_common/random.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
namespace
{
std::shared_ptr<nano::block> make_test_block ()
{
nano::block_builder builder;
return builder.state ()
.account (nano::dev::genesis_key.pub)
.previous (nano::test::random_hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 1)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
}
nano::election_status make_test_election_status ()
{
auto block = make_test_block ();
nano::election_status status;
status.winner = block;
status.type = nano::election_status_type::active_confirmed_quorum;
status.election_end = std::chrono::system_clock::now ();
status.election_duration = std::chrono::milliseconds (100);
return status;
}
}
/*
* recently_confirmed_cache
*/
TEST (recently_confirmed_cache, construction)
{
nano::recently_confirmed_cache cache (10);
ASSERT_EQ (0, cache.size ());
}
TEST (recently_confirmed_cache, put)
{
nano::recently_confirmed_cache cache (3);
std::vector<nano::qualified_root> roots;
std::vector<nano::block_hash> hashes;
// Add entries and test size limit
for (int i = 0; i < 5; ++i)
{
auto root = nano::test::random_qualified_root ();
auto hash = nano::test::random_hash ();
roots.push_back (root);
hashes.push_back (hash);
cache.put (root, hash);
}
ASSERT_EQ (3, cache.size ());
// Test duplicate filtering
cache.put (roots[4], hashes[4]);
ASSERT_EQ (3, cache.size ());
// First entries should have been evicted (LRU)
ASSERT_FALSE (cache.contains (roots[0]));
ASSERT_FALSE (cache.contains (roots[1]));
// Last entries should still be present
ASSERT_TRUE (cache.contains (roots[2]));
ASSERT_TRUE (cache.contains (roots[3]));
ASSERT_TRUE (cache.contains (roots[4]));
}
TEST (recently_confirmed_cache, erase)
{
nano::recently_confirmed_cache cache (10);
auto root = nano::test::random_qualified_root ();
auto hash = nano::test::random_hash ();
cache.put (root, hash);
ASSERT_TRUE (cache.contains (hash));
ASSERT_EQ (1, cache.size ());
cache.erase (hash);
ASSERT_FALSE (cache.contains (hash));
ASSERT_FALSE (cache.contains (root));
ASSERT_EQ (0, cache.size ());
}
TEST (recently_confirmed_cache, clear)
{
nano::recently_confirmed_cache cache (10);
for (int i = 0; i < 5; ++i)
{
auto root = nano::test::random_qualified_root ();
auto hash = nano::test::random_hash ();
cache.put (root, hash);
}
ASSERT_EQ (5, cache.size ());
cache.clear ();
ASSERT_EQ (0, cache.size ());
}
/*
* recently_cemented_cache
*/
TEST (recently_cemented_cache, construction)
{
nano::recently_cemented_cache cache (10);
ASSERT_EQ (0, cache.size ());
}
TEST (recently_cemented_cache, put)
{
nano::recently_cemented_cache cache (3);
std::vector<nano::election_status> statuses;
// Add entries and test size limit
for (int i = 0; i < 5; ++i)
{
auto status = make_test_election_status ();
statuses.push_back (status);
cache.put (status);
}
ASSERT_EQ (3, cache.size ());
// Test duplicate filtering
cache.put (statuses[4]);
ASSERT_EQ (3, cache.size ());
// First entries should have been evicted (LRU)
ASSERT_FALSE (cache.contains (statuses[0].winner->qualified_root ()));
ASSERT_FALSE (cache.contains (statuses[1].winner->qualified_root ()));
// Last entries should still be present
ASSERT_TRUE (cache.contains (statuses[2].winner->qualified_root ()));
ASSERT_TRUE (cache.contains (statuses[3].winner->qualified_root ()));
ASSERT_TRUE (cache.contains (statuses[4].winner->qualified_root ()));
}
TEST (recently_cemented_cache, erase)
{
nano::recently_cemented_cache cache (10);
auto status = make_test_election_status ();
cache.put (status);
ASSERT_TRUE (cache.contains (status.winner->hash ()));
ASSERT_EQ (1, cache.size ());
cache.erase (status.winner->hash ());
ASSERT_FALSE (cache.contains (status.winner->hash ()));
ASSERT_FALSE (cache.contains (status.winner->qualified_root ()));
ASSERT_EQ (0, cache.size ());
}
TEST (recently_cemented_cache, clear)
{
nano::recently_cemented_cache cache (10);
for (int i = 0; i < 5; ++i)
{
auto status = make_test_election_status ();
cache.put (status);
}
ASSERT_EQ (5, cache.size ());
cache.clear ();
ASSERT_EQ (0, cache.size ());
}
TEST (recently_cemented_cache, list)
{
nano::recently_cemented_cache cache (10);
// Test empty list
auto list = cache.list ();
ASSERT_TRUE (list.empty ());
// Add entries and test list functionality
std::vector<nano::election_status> statuses;
for (int i = 0; i < 5; ++i)
{
auto status = make_test_election_status ();
statuses.push_back (status);
cache.put (status);
}
// Test full list
list = cache.list ();
ASSERT_EQ (5, list.size ());
// List should be in reverse order (most recent first)
for (size_t i = 0; i < list.size (); ++i)
{
ASSERT_EQ (statuses[4 - i].winner->hash (), list[i].winner->hash ());
}
// Test list with limit
auto limited_list = cache.list (3);
ASSERT_EQ (3, limited_list.size ());
}

View file

@ -11,6 +11,11 @@ TEST (stats, counters)
nano::test::system system;
auto & node = *system.add_node ();
// Initial state
ASSERT_EQ (0, node.stats.count (nano::stat::type::ledger, nano::stat::dir::in));
ASSERT_EQ (0, node.stats.count (nano::stat::type::ledger, nano::stat::detail::test, nano::stat::dir::in));
ASSERT_EQ (0, node.stats.count (nano::stat::type::ledger, nano::stat::detail::send, nano::stat::dir::out));
node.stats.add (nano::stat::type::ledger, nano::stat::detail::test, nano::stat::dir::in, 1);
node.stats.add (nano::stat::type::ledger, nano::stat::detail::test, nano::stat::dir::in, 5);
node.stats.inc (nano::stat::type::ledger, nano::stat::detail::test, nano::stat::dir::in);

View file

@ -308,7 +308,7 @@ TEST (toml_config, daemon_config_deserialize_defaults)
ASSERT_EQ (conf.node.signature_checker_threads, defaults.node.signature_checker_threads);
ASSERT_EQ (conf.node.unchecked_cutoff_time, defaults.node.unchecked_cutoff_time);
ASSERT_EQ (conf.node.use_memory_pools, defaults.node.use_memory_pools);
ASSERT_EQ (conf.node.vote_generator_delay, defaults.node.vote_generator_delay);
ASSERT_EQ (conf.node.vote_generator.delay, defaults.node.vote_generator.delay);
ASSERT_EQ (conf.node.vote_minimum, defaults.node.vote_minimum);
ASSERT_EQ (conf.node.work_peers, defaults.node.work_peers);
ASSERT_EQ (conf.node.work_threads, defaults.node.work_threads);
@ -473,7 +473,6 @@ TEST (toml_config, daemon_config_deserialize_no_defaults)
signature_checker_threads = 999
unchecked_cutoff_time = 999
use_memory_pools = false
vote_generator_delay = 999
vote_minimum = "999"
work_peers = ["dev.org:999"]
work_threads = 999
@ -670,6 +669,9 @@ TEST (toml_config, daemon_config_deserialize_no_defaults)
duplicate_filter_cutoff = 999
minimum_fanout = 99
[node.vote_generator]
delay = 999
[opencl]
device = 999
enable = true
@ -740,7 +742,7 @@ TEST (toml_config, daemon_config_deserialize_no_defaults)
ASSERT_NE (conf.node.signature_checker_threads, defaults.node.signature_checker_threads);
ASSERT_NE (conf.node.unchecked_cutoff_time, defaults.node.unchecked_cutoff_time);
ASSERT_NE (conf.node.use_memory_pools, defaults.node.use_memory_pools);
ASSERT_NE (conf.node.vote_generator_delay, defaults.node.vote_generator_delay);
ASSERT_NE (conf.node.vote_generator.delay, defaults.node.vote_generator.delay);
ASSERT_NE (conf.node.vote_minimum, defaults.node.vote_minimum);
ASSERT_NE (conf.node.work_peers, defaults.node.work_peers);
ASSERT_NE (conf.node.work_threads, defaults.node.work_threads);

View file

@ -129,11 +129,6 @@ TEST (unchecked, simple)
TEST (unchecked, multiple)
{
nano::test::system system{};
if (nano::rocksdb_config::using_rocksdb_in_tests ())
{
// Don't test this in rocksdb mode
GTEST_SKIP ();
}
nano::unchecked_map unchecked{ max_unchecked_blocks, system.stats, false };
nano::block_builder builder;
auto block = builder

View file

@ -1,5 +1,6 @@
#include <nano/node/election.hpp>
#include <nano/node/vote_cache.hpp>
#include <nano/test_common/random.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>

View file

@ -63,27 +63,27 @@ bool slow_instrumentation ()
std::string get_node_toml_config_path (std::filesystem::path const & data_path)
{
return (data_path / "config-node.toml").string ();
return (data_path / node_config_filename).string ();
}
std::string get_rpc_toml_config_path (std::filesystem::path const & data_path)
{
return (data_path / "config-rpc.toml").string ();
return (data_path / rpc_config_filename).string ();
}
std::string get_qtwallet_toml_config_path (std::filesystem::path const & data_path)
{
return (data_path / "config-qtwallet.toml").string ();
return (data_path / qtwallet_config_filename).string ();
}
std::string get_access_toml_config_path (std::filesystem::path const & data_path)
{
return (data_path / "config-access.toml").string ();
return (data_path / access_config_filename).string ();
}
std::string get_tls_toml_config_path (std::filesystem::path const & data_path)
{
return (data_path / "config-tls.toml").string ();
return (data_path / tls_config_filename).string ();
}
}

View file

@ -5,12 +5,12 @@
#include <boost/config.hpp>
#include <boost/version.hpp>
#include <algorithm>
#include <array>
#include <chrono>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
using namespace std::chrono_literals;
@ -86,6 +86,14 @@ uint16_t test_websocket_port ();
std::array<uint8_t, 2> test_magic_number ();
uint32_t test_scan_wallet_reps_delay (); // How often to scan for representatives in local wallet, in milliseconds
// Configuration file names
constexpr std::string_view node_config_filename{ "config-node.toml" };
constexpr std::string_view rpc_config_filename{ "config-rpc.toml" };
constexpr std::string_view log_config_filename{ "config-log.toml" };
constexpr std::string_view access_config_filename{ "config-access.toml" };
constexpr std::string_view qtwallet_config_filename{ "config-qtwallet.toml" };
constexpr std::string_view tls_config_filename{ "config-tls.toml" };
std::string get_node_toml_config_path (std::filesystem::path const & data_path);
std::string get_rpc_toml_config_path (std::filesystem::path const & data_path);
std::string get_access_toml_config_path (std::filesystem::path const & data_path);
@ -139,4 +147,10 @@ T load_config_file (T fallback, const std::filesystem::path & config_filename, c
}
return config;
}
template <typename T>
T load_config_file (const std::filesystem::path & config_filename, const std::filesystem::path & data_path, const std::vector<std::string> & config_overrides)
{
return load_config_file<T> (T{}, config_filename, data_path, config_overrides);
}
}

View file

@ -35,6 +35,10 @@ struct HexTo
};
}
/*
* Work thresholds
*/
nano::work_thresholds const nano::work_thresholds::publish_full (
0xffffffc000000000,
0xfffffff800000000, // 8x higher than epoch_1
@ -59,6 +63,10 @@ nano::env::get<HexTo<uint64_t>> ("NANO_TEST_EPOCH_2").value_or (0xfffffff8000000
nano::env::get<HexTo<uint64_t>> ("NANO_TEST_EPOCH_2_RECV").value_or (0xfffffe0000000000) // 8x lower than epoch_1
);
/*
*
*/
uint64_t nano::work_thresholds::threshold_entry (nano::work_version const version_a, nano::block_type const type_a) const
{
uint64_t result{ std::numeric_limits<uint64_t>::max () };
@ -168,7 +176,6 @@ double nano::work_thresholds::denormalized_multiplier (double const multiplier_a
if (threshold_a == epoch_1 || threshold_a == epoch_2_receive)
{
auto ratio (nano::difficulty::to_multiplier (epoch_2, threshold_a));
debug_assert (ratio >= 1);
multiplier = multiplier * ratio + 1.0 - ratio;
debug_assert (multiplier >= 1);
}

View file

@ -29,16 +29,17 @@ std::string_view to_string (nano::networks);
class work_thresholds
{
public:
uint64_t const epoch_1;
uint64_t const epoch_2;
uint64_t const epoch_2_receive;
uint64_t epoch_1;
uint64_t epoch_2;
uint64_t epoch_2_receive;
// Automatically calculated. The base threshold is the maximum of all thresholds and is used for all work multiplier calculations
uint64_t const base;
uint64_t base;
// Automatically calculated. The entry threshold is the minimum of all thresholds and defines the required work to enter the node, but does not guarantee a block is processed
uint64_t const entry;
uint64_t entry;
public:
constexpr work_thresholds (uint64_t epoch_1_a, uint64_t epoch_2_a, uint64_t epoch_2_receive_a) :
epoch_1 (epoch_1_a), epoch_2 (epoch_2_a), epoch_2_receive (epoch_2_receive_a),
base (std::max ({ epoch_1, epoch_2, epoch_2_receive })),
@ -46,25 +47,21 @@ public:
{
}
work_thresholds () = delete;
work_thresholds operator= (nano::work_thresholds const & other_a)
{
return other_a;
}
uint64_t threshold_entry (nano::work_version const, nano::block_type const) const;
uint64_t threshold_entry (nano::work_version, nano::block_type) const;
uint64_t threshold (nano::block_details const &) const;
// Ledger threshold
uint64_t threshold (nano::work_version const, nano::block_details const) const;
uint64_t threshold_base (nano::work_version const) const;
uint64_t value (nano::root const & root_a, uint64_t work_a) const;
double normalized_multiplier (double const, uint64_t const) const;
double denormalized_multiplier (double const, uint64_t const) const;
uint64_t difficulty (nano::work_version const, nano::root const &, uint64_t const) const;
uint64_t difficulty (nano::block const & block_a) const;
bool validate_entry (nano::work_version const, nano::root const &, uint64_t const) const;
bool validate_entry (nano::block const &) const;
uint64_t threshold (nano::work_version, nano::block_details) const;
uint64_t threshold_base (nano::work_version) const;
uint64_t value (nano::root const & root, uint64_t work) const;
double normalized_multiplier (double multiplier, uint64_t threshold) const;
double denormalized_multiplier (double multiplier, uint64_t threshold) const;
uint64_t difficulty (nano::work_version, nano::root const & root, uint64_t work) const;
uint64_t difficulty (nano::block const & block) const;
bool validate_entry (nano::work_version, nano::root const & root, uint64_t work) const;
bool validate_entry (nano::block const & block) const;
/** Network work thresholds. Define these inline as constexpr when moving to cpp17. */
public: // Network work thresholds
static nano::work_thresholds const publish_full;
static nano::work_thresholds const publish_beta;
static nano::work_thresholds const publish_dev;
@ -73,22 +70,20 @@ public:
class network_constants
{
static constexpr std::chrono::seconds default_cleanup_period = std::chrono::seconds (60);
public:
network_constants (nano::work_thresholds const & work_, nano::networks network_a) :
network_constants (nano::work_thresholds const & work_a, nano::networks network_a) :
current_network (network_a),
work (work_),
work (work_a),
principal_weight_factor (1000), // 0.1% A representative is classified as principal based on its weight and this factor
default_node_port (44000),
default_rpc_port (45000),
default_ipc_port (46000),
default_websocket_port (47000),
aec_loop_interval (300ms), // Update AEC ~3 times per second
cleanup_period (default_cleanup_period),
cleanup_period (60s),
merge_period (std::chrono::milliseconds (250)),
keepalive_period (std::chrono::seconds (15)),
idle_timeout (default_cleanup_period * 2),
idle_timeout (120s),
silent_connection_tolerance_time (std::chrono::seconds (120)),
syn_cookie_cutoff (std::chrono::seconds (5)),
bootstrap_interval (std::chrono::seconds (15 * 60)),
@ -132,7 +127,6 @@ public:
telemetry_cache_cutoff = 2000ms;
telemetry_request_interval = 500ms;
telemetry_broadcast_interval = 500ms;
optimistic_activation_delay = 2s;
rep_crawler_normal_interval = 500ms;
rep_crawler_warmup_interval = 500ms;
}
@ -183,9 +177,6 @@ public:
/** Telemetry data older than this value is considered stale */
std::chrono::milliseconds telemetry_cache_cutoff{ 1000 * 130 }; // 2 * `telemetry_broadcast_interval` + some margin
/** How much to delay activation of optimistic elections to avoid interfering with election scheduler */
std::chrono::seconds optimistic_activation_delay{ 30 };
std::chrono::milliseconds rep_crawler_normal_interval{ 1000 * 7 };
std::chrono::milliseconds rep_crawler_warmup_interval{ 1000 * 3 };

View file

@ -312,10 +312,10 @@ spdlog::level::level_enum nano::logger::to_spdlog_level (nano::log::level level)
* 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{};
config.default_level = nano::log::level::critical;
config.default_level = default_level;
config.console.colors = false;
config.console.to_cerr = true; // Use cerr to avoid interference with CLI output that goes to stdout
config.file.enable = false;
@ -474,10 +474,9 @@ std::map<nano::log::logger_id, nano::log::level> nano::log_config::default_level
// Using std::cerr here, since logging may not be initialized yet
nano::log_config nano::load_log_config (nano::log_config fallback, const std::filesystem::path & data_path, const std::vector<std::string> & config_overrides)
{
const std::string config_filename = "config-log.toml";
try
{
auto config = nano::load_config_file<nano::log_config> (fallback, config_filename, data_path, config_overrides);
auto config = nano::load_config_file<nano::log_config> (fallback, log_config_filename, data_path, config_overrides);
// Parse default log level from environment variable, e.g. "NANO_LOG=debug"
auto env_level = nano::env::get ("NANO_LOG");

View file

@ -134,7 +134,7 @@ public:
nano::log::tracing_format tracing_format{ nano::log::tracing_format::standard };
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 tests_default ();
static log_config dummy_default (); // For empty logger

View file

@ -809,7 +809,10 @@ std::ostream & nano::operator<< (std::ostream & os, const nano::account & val)
uint64_t nano::difficulty::from_multiplier (double const multiplier_a, uint64_t const base_difficulty_a)
{
debug_assert (multiplier_a > 0.);
if (multiplier_a <= 0.)
{
return 0;
}
nano::uint128_t reverse_difficulty ((-base_difficulty_a) / multiplier_a);
if (reverse_difficulty > std::numeric_limits<std::uint64_t>::max ())
{
@ -827,7 +830,10 @@ uint64_t nano::difficulty::from_multiplier (double const multiplier_a, uint64_t
double nano::difficulty::to_multiplier (uint64_t const difficulty_a, uint64_t const base_difficulty_a)
{
debug_assert (difficulty_a > 0);
if (difficulty_a == 0)
{
return 0;
}
return static_cast<double> (-base_difficulty_a) / (-difficulty_a);
}

View file

@ -5,6 +5,7 @@
#include <boost/functional/hash_fwd.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <algorithm>
#include <array>
#include <compare>
#include <limits>

View file

@ -15,8 +15,19 @@
#include <fstream>
#include <sstream>
#include <magic_enum.hpp>
using namespace std::chrono_literals;
// Static assertions to ensure our predefined array sizes are sufficient for the enums
// We check the _last value's integer value, which should be the highest valid index we need
static_assert (magic_enum::enum_integer (nano::stat::type::_last) <= nano::stats::types_count,
"stat::type enum has grown beyond the predefined array size. Increase types_count in stats.hpp");
static_assert (magic_enum::enum_integer (nano::stat::detail::_last) <= nano::stats::details_count,
"stat::detail enum has grown beyond the predefined array size. Increase details_count in stats.hpp");
static_assert (magic_enum::enum_integer (nano::stat::dir::_last) <= nano::stats::dirs_count,
"stat::dir enum has grown beyond the predefined array size. Increase dirs_count in stats.hpp");
/*
* stat_log_sink
*/
@ -31,6 +42,8 @@ std::string nano::stat_log_sink::tm_to_string (tm & tm)
*/
nano::stats::stats (nano::logger & logger_a, nano::stats_config config_a) :
counters_impl{ std::make_unique<counters_array_t> () },
counters{ *counters_impl },
config{ std::move (config_a) },
logger{ logger_a },
enable_logging{ is_stat_logging_enabled () }
@ -71,21 +84,50 @@ void nano::stats::stop ()
void nano::stats::clear ()
{
std::lock_guard guard{ mutex };
counters.clear ();
samplers.clear ();
timestamp = std::chrono::steady_clock::now ();
// Clear all counters
for (auto & counter : counters)
{
counter.store (0, std::memory_order_relaxed);
}
// Clear samplers (still needs mutex)
{
std::lock_guard guard{ mutex };
samplers.clear ();
timestamp = std::chrono::steady_clock::now ();
}
}
size_t nano::stats::idx (stat::type type, stat::detail detail, stat::dir dir)
{
// Dir is the slowest changing dimension, so it goes first for better cache locality
auto type_idx = magic_enum::enum_integer (type);
auto detail_idx = magic_enum::enum_integer (detail);
auto dir_idx = magic_enum::enum_integer (dir);
return (dir_idx * types_count * details_count) + (type_idx * details_count) + detail_idx;
}
std::atomic<nano::stats::counter_value_t> & nano::stats::counter_ref (stat::type type, stat::detail detail, stat::dir dir)
{
auto index = idx (type, detail, dir);
debug_assert (index < counters.size ());
return counters[index];
}
std::atomic<nano::stats::counter_value_t> const & nano::stats::counter_ref (stat::type type, stat::detail detail, stat::dir dir) const
{
auto index = idx (type, detail, dir);
debug_assert (index < counters.size ());
return counters[index];
}
void nano::stats::add (stat::type type, stat::detail detail, stat::dir dir, counter_value_t value, bool aggregate_all)
{
debug_assert (type != stat::type::_invalid);
debug_assert (type != stat::type::_last);
debug_assert (detail != stat::detail::_invalid);
if (value == 0)
{
return;
}
debug_assert (detail != stat::detail::_last);
debug_assert (dir != stat::dir::_last);
if (enable_logging)
{
@ -96,71 +138,30 @@ void nano::stats::add (stat::type type, stat::detail detail, stat::dir dir, coun
value);
}
// Updates need to happen while holding the mutex
auto update_counter = [this, aggregate_all] (nano::stats::counter_key key, auto && updater) {
counter_key all_key{ key.type, stat::detail::all, key.dir };
counter_ref (type, detail, dir).fetch_add (value, std::memory_order_relaxed);
// This is a two-step process to avoid exclusively locking the mutex in the common case
{
std::shared_lock lock{ mutex };
if (auto it = counters.find (key); it != counters.end ())
{
updater (*it->second);
if (aggregate_all && key != all_key)
{
auto it_all = counters.find (all_key);
release_assert (it_all != counters.end ()); // The `all` counter should always be created together
updater (*it_all->second); // Also update the `all` counter
}
return;
}
}
// Not found, create a new entry
{
std::unique_lock lock{ mutex };
// Insertions will be ignored if the key already exists
auto [it, inserted] = counters.emplace (key, std::make_unique<counter_entry> ());
updater (*it->second);
if (aggregate_all && key != all_key)
{
auto [it_all, inserted_all] = counters.emplace (all_key, std::make_unique<counter_entry> ());
updater (*it_all->second); // Also update the `all` counter
}
}
};
update_counter (counter_key{ type, detail, dir }, [value] (counter_entry & counter) {
counter.value += value;
});
if (aggregate_all && detail != stat::detail::all)
{
counter_ref (type, stat::detail::all, dir).fetch_add (value, std::memory_order_relaxed);
}
}
nano::stats::counter_value_t nano::stats::count (stat::type type, stat::detail detail, stat::dir dir) const
{
std::shared_lock lock{ mutex };
if (auto it = counters.find (counter_key{ type, detail, dir }); it != counters.end ())
{
return it->second->value;
}
return 0;
return counter_ref (type, detail, dir).load (std::memory_order_relaxed);
}
nano::stats::counter_value_t nano::stats::count (stat::type type, stat::dir dir) const
{
std::shared_lock lock{ mutex };
counter_value_t result = 0;
auto it = counters.lower_bound (counter_key{ type, stat::detail::all, dir });
while (it != counters.end () && it->first.type == type)
// Sum all detail counters for this type and direction (except the 'all' detail)
for (auto detail : magic_enum::enum_values<stat::detail> ())
{
if (it->first.dir == dir && it->first.detail != stat::detail::all)
if (detail != stat::detail::all && detail != stat::detail::_invalid && detail != stat::detail::_last)
{
result += it->second->value;
result += counter_ref (type, detail, dir).load (std::memory_order_relaxed);
}
++it;
}
return result;
}
@ -225,6 +226,7 @@ void nano::stats::log_counters (stat_log_sink & sink)
void nano::stats::log_counters_impl (stat_log_sink & sink, tm & tm)
{
sink.begin ();
if (sink.entries () >= config.log_rotation_count)
{
sink.rotate ();
@ -236,14 +238,35 @@ void nano::stats::log_counters_impl (stat_log_sink & sink, tm & tm)
sink.write_header ("counters", walltime);
}
for (auto const & [key, entry] : counters)
for (auto dir : magic_enum::enum_values<stat::dir> ())
{
std::string type{ to_string (key.type) };
std::string detail{ to_string (key.detail) };
std::string dir{ to_string (key.dir) };
if (dir == stat::dir::_last)
continue;
sink.write_counter_entry (tm, type, detail, dir, entry->value);
for (auto type : magic_enum::enum_values<stat::type> ())
{
if (type == stat::type::_invalid || type == stat::type::_last)
continue;
for (auto detail : magic_enum::enum_values<stat::detail> ())
{
if (detail == stat::detail::_invalid || detail == stat::detail::_last)
continue;
auto value = counter_ref (type, detail, dir).load (std::memory_order_relaxed);
if (value > 0) // Only log non-zero counters
{
std::string type_str{ to_string (type) };
std::string detail_str{ to_string (detail) };
std::string dir_str{ to_string (dir) };
sink.write_counter_entry (tm, type_str, detail_str, dir_str, value);
}
}
}
}
sink.entries ()++;
sink.finalize ();
}
@ -301,7 +324,9 @@ void nano::stats::run ()
std::unique_lock lock{ mutex };
while (!stopped)
{
condition.wait_for (lock, 1s);
condition.wait_for (lock, 1s, [this] {
return stopped;
});
if (!stopped)
{
run_one (lock);

View file

@ -7,6 +7,8 @@
#include <boost/circular_buffer.hpp>
#include <array>
#include <atomic>
#include <chrono>
#include <initializer_list>
#include <map>
@ -71,6 +73,11 @@ public:
using counter_value_t = uint64_t;
using sampler_value_t = int64_t;
public: // Array dimensions - must match the enum sizes in stats_enums.hpp
static constexpr size_t types_count = 256; // Enough for stat::type enum range
static constexpr size_t details_count = 1024; // Enough for stat::detail enum range
static constexpr size_t dirs_count = 2; // Enough for stat::dir enum range
public:
explicit stats (nano::logger &, nano::stats_config = {});
~stats ();
@ -132,15 +139,6 @@ public:
std::string dump (category category = category::counters);
private:
struct counter_key
{
stat::type type;
stat::detail detail;
stat::dir dir;
auto operator<=> (const counter_key &) const = default;
};
struct sampler_key
{
stat::sample sample;
@ -149,18 +147,6 @@ private:
};
private:
class counter_entry
{
public:
// Prevent copying
counter_entry () = default;
counter_entry (counter_entry const &) = delete;
counter_entry & operator= (counter_entry const &) = delete;
public:
std::atomic<counter_value_t> value{ 0 };
};
class sampler_entry
{
public:
@ -183,9 +169,13 @@ private:
mutable nano::mutex mutex;
};
// Wrap in unique_ptrs because mutex/atomic members are not movable
// TODO: Compare performance of map vs unordered_map
std::map<counter_key, std::unique_ptr<counter_entry>> counters;
// Flat array for direct-indexed counter access
// Allocate on the heap to avoid stack overflows when intantiating the stats class in tests
using counters_array_t = std::array<std::atomic<counter_value_t>, types_count * details_count * dirs_count>;
std::unique_ptr<counters_array_t> counters_impl;
counters_array_t & counters;
// Keep samplers as map since they have different behavior and lower frequency
std::map<sampler_key, std::unique_ptr<sampler_entry>> samplers;
private:
@ -201,6 +191,12 @@ private:
static bool is_stat_logging_enabled ();
std::atomic<counter_value_t> & counter_ref (stat::type type, stat::detail detail, stat::dir dir);
std::atomic<counter_value_t> const & counter_ref (stat::type type, stat::detail detail, stat::dir dir) const;
// Helper function to calculate flat array index
static size_t idx (stat::type type, stat::detail detail, stat::dir dir);
private:
nano::stats_config const config;
nano::logger & logger;

View file

@ -144,6 +144,7 @@ enum class detail
total,
loop,
loop_cleanup,
loop_checkup,
process,
processed,
ignored,
@ -515,7 +516,10 @@ enum class detail
started,
stopped,
confirm_dependent,
cancel_dependent,
cancel_checkup,
forks_cached,
stale,
bootstrap_stale,
// unchecked

View file

@ -49,6 +49,9 @@ std::string nano::thread_role::get_string (nano::thread_role::name role)
case nano::thread_role::name::aec_loop:
thread_role_name_string = "AEC";
break;
case nano::thread_role::name::aec_checkup:
thread_role_name_string = "AEC checkup";
break;
case nano::thread_role::name::aec_notifications:
thread_role_name_string = "AEC notif";
break;

View file

@ -21,6 +21,7 @@ enum class name
block_processing,
ledger_notifications,
aec_loop,
aec_checkup,
aec_notifications,
wallet_actions,
bootstrap_initiator,

View file

@ -100,29 +100,29 @@ private:
using millis_t = uint64_t;
inline millis_t milliseconds_since_epoch ()
inline millis_t milliseconds_since_epoch (std::chrono::system_clock::time_point tp = std::chrono::system_clock::now ())
{
return std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ()).count ();
return std::chrono::duration_cast<std::chrono::milliseconds> (tp.time_since_epoch ()).count ();
}
inline std::chrono::time_point<std::chrono::system_clock> from_milliseconds_since_epoch (nano::millis_t millis)
inline std::chrono::system_clock::time_point from_milliseconds_since_epoch (nano::millis_t millis)
{
return std::chrono::time_point<std::chrono::system_clock> (std::chrono::milliseconds{ millis });
return std::chrono::system_clock::time_point (std::chrono::milliseconds{ millis });
}
using seconds_t = uint64_t;
inline seconds_t seconds_since_epoch ()
inline seconds_t seconds_since_epoch (std::chrono::system_clock::time_point tp = std::chrono::system_clock::now ())
{
return std::chrono::duration_cast<std::chrono::seconds> (std::chrono::system_clock::now ().time_since_epoch ()).count ();
return std::chrono::duration_cast<std::chrono::seconds> (tp.time_since_epoch ()).count ();
}
inline std::chrono::time_point<std::chrono::system_clock> from_seconds_since_epoch (nano::seconds_t seconds)
inline std::chrono::system_clock::time_point from_seconds_since_epoch (nano::seconds_t seconds)
{
return std::chrono::time_point<std::chrono::system_clock> (std::chrono::seconds{ seconds });
return std::chrono::system_clock::time_point (std::chrono::seconds{ seconds });
}
inline nano::millis_t time_difference (nano::millis_t start, nano::millis_t end)
inline millis_t time_difference (millis_t start, millis_t end)
{
return end > start ? (end - start) : 0;
}

View file

@ -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})

View 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 ());
}
}

View 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 ());
}
}

View 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));
}
}

View 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));
}
}

View 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 };
}
}

View 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);
}

View file

@ -6,6 +6,7 @@
#include <nano/lib/thread_runner.hpp>
#include <nano/lib/utility.hpp>
#include <nano/lib/work_version.hpp>
#include <nano/nano_node/benchmarks/benchmarks.hpp>
#include <nano/nano_node/daemon.hpp>
#include <nano/node/active_elections.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_sign", "Profile signature generation")
("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_frontiers_confirmation", "Profile frontiers confirmation speed (only for nano_dev_network)")
("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")
("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")
("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")
("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")
@ -196,12 +205,7 @@ int main (int argc, char * const * argv)
{
nano::daemon daemon;
nano::node_flags flags;
auto flags_ec = nano::update_flags (flags, vm);
if (flags_ec)
{
std::cerr << flags_ec.message () << std::endl;
std::exit (1);
}
nano::update_flags (flags, vm);
daemon.run (data_path, flags);
}
else if (vm.count ("compare_rep_weights"))
@ -1052,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));
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"))
{
nano::block_builder builder;

View file

@ -319,11 +319,7 @@ int main (int argc, char * const * argv)
data_path = nano::working_path ();
}
nano::node_flags flags;
auto flags_ec = nano::update_flags (flags, vm);
if (flags_ec)
{
throw std::runtime_error (flags_ec.message ());
}
nano::update_flags (flags, vm);
result = daemon.run_wallet (application, argc, argv, data_path, flags);
}
catch (std::exception const & e)

View file

@ -16,6 +16,8 @@ add_library(
${platform_sources}
active_elections.hpp
active_elections.cpp
active_elections_index.hpp
active_elections_index.cpp
backlog_scan.hpp
backlog_scan.cpp
bandwidth_limiter.hpp

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,11 @@
#pragma once
#include <nano/lib/enum_util.hpp>
#include <nano/lib/interval.hpp>
#include <nano/lib/numbers.hpp>
#include <nano/lib/observer_set.hpp>
#include <nano/lib/thread_pool.hpp>
#include <nano/node/active_elections_index.hpp>
#include <nano/node/election_behavior.hpp>
#include <nano/node/election_insertion_result.hpp>
#include <nano/node/election_status.hpp>
#include <nano/node/fwd.hpp>
#include <nano/node/recently_cemented_cache.hpp>
@ -15,20 +14,12 @@
#include <nano/node/vote_with_weight_info.hpp>
#include <nano/secure/common.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>
#include <condition_variable>
#include <deque>
#include <memory>
#include <thread>
#include <unordered_map>
namespace mi = boost::multi_index;
namespace nano
{
class active_elections_config final
@ -46,14 +37,13 @@ public:
std::size_t hinted_limit_percentage{ 20 };
// Limit of optimistic elections as percentage of `active_elections_size`
std::size_t optimistic_limit_percentage{ 10 };
// Maximum confirmation history size
std::size_t confirmation_history_size{ 2048 };
// Maximum cache size for recently_confirmed
std::size_t confirmation_cache{ 65536 };
std::size_t confirmation_cache{ 1024 * 64 };
// Maximum size of election winner details set
std::size_t max_election_winners{ 1024 * 16 };
std::chrono::seconds bootstrap_stale_threshold{ 60s };
std::chrono::milliseconds checkup_interval{ 1s };
std::chrono::seconds stale_threshold{ nano::is_dev_run () ? 1s : 60s };
};
/**
@ -65,34 +55,6 @@ class active_elections final
public:
using erased_callback_t = std::function<void (std::shared_ptr<nano::election>)>;
private: // Elections
class entry final
{
public:
nano::qualified_root root;
std::shared_ptr<nano::election> election;
erased_callback_t erased_callback;
};
friend class nano::election;
// clang-format off
class tag_account {};
class tag_root {};
class tag_sequenced {};
class tag_uncemented {};
class tag_arrival {};
class tag_hash {};
using ordered_roots = boost::multi_index_container<entry,
mi::indexed_by<
mi::sequenced<mi::tag<tag_sequenced>>,
mi::hashed_unique<mi::tag<tag_root>,
mi::member<entry, nano::qualified_root, &entry::root>>
>>;
// clang-format on
ordered_roots roots;
public:
active_elections (nano::node &, nano::ledger_notifications &, nano::cementing_set &);
~active_elections ();
@ -100,46 +62,76 @@ public:
void start ();
void stop ();
/**
* Starts new election with a specified behavior type
*/
nano::election_insertion_result insert (std::shared_ptr<nano::block> const &, nano::election_behavior = nano::election_behavior::priority, erased_callback_t = nullptr);
// Is the root of this block in the roots container
bool active (nano::block const &) const;
bool active (nano::qualified_root const &) const;
std::shared_ptr<nano::election> election (nano::qualified_root const &) const;
// Returns a list of elections sorted by difficulty
std::vector<std::shared_ptr<nano::election>> list_active (std::size_t max_count = std::numeric_limits<std::size_t>::max ());
bool erase (nano::block const &);
bool erase (nano::qualified_root const &);
bool empty () const;
std::size_t size () const;
std::size_t size (nano::election_behavior) const;
struct insert_result
{
std::shared_ptr<nano::election> election;
bool inserted;
};
/// Starts new election
insert_result insert (
std::shared_ptr<nano::block> const &,
nano::election_behavior = nano::election_behavior::priority,
nano::bucket_index bucket = 0,
nano::priority_timestamp priority = 0,
erased_callback_t = nullptr);
// Notify this container about a new block (potential fork)
bool publish (std::shared_ptr<nano::block> const &);
/**
* Maximum number of elections that should be present in this container
* NOTE: This is only a soft limit, it is possible for this container to exceed this count
*/
// Trigger an immediate election update (e.g. after it is confirmed)
bool trigger (nano::qualified_root const &);
/// Is the root of this block in the roots container
bool active (nano::block const &) const;
bool active (nano::qualified_root const &) const;
std::shared_ptr<nano::election> election (nano::qualified_root const &) const;
/// Returns a list of elections sorted by difficulty
std::vector<std::shared_ptr<nano::election>> list_active (std::size_t max_count = std::numeric_limits<std::size_t>::max ());
bool erase (nano::block const &);
bool erase (nano::qualified_root const &);
bool empty () const;
size_t size () const;
size_t size (nano::election_behavior) const;
size_t size (nano::election_behavior, nano::bucket_index) const;
/// Maximum number of elections that should be present in this container
/// NOTE: This is only a soft limit, it is possible for this container to exceed this count
int64_t limit (nano::election_behavior behavior) const;
/**
* How many election slots are available for specified election type
*/
/// How many election slots are available for specified election type
int64_t vacancy (nano::election_behavior behavior) const;
nano::container_info container_info () const;
public: // Events
nano::observer_set<> vacancy_updated;
nano::observer_set<std::shared_ptr<nano::election>, nano::bucket_index, nano::priority_timestamp> election_started;
nano::observer_set<std::shared_ptr<nano::election>> election_erased;
nano::observer_set<std::shared_ptr<nano::election>> election_stale;
private:
bool predicate () const;
void run ();
void run_checkup ();
void tick_elections (nano::unique_lock<nano::mutex> &);
void checkup_elections (nano::unique_lock<nano::mutex> &);
// Erase all blocks from active and, if not confirmed, clear digests from network filters
void cleanup_election (nano::unique_lock<nano::mutex> & lock_a, std::shared_ptr<nano::election>);
void erase_election (nano::unique_lock<nano::mutex> & lock_a, std::shared_ptr<nano::election>);
struct block_cemented_result
{
std::shared_ptr<nano::election> election;
nano::election_status status;
std::vector<nano::vote_with_weight_info> votes;
};
using block_cemented_result = std::pair<nano::election_status, std::vector<nano::vote_with_weight_info>>;
block_cemented_result block_cemented (std::shared_ptr<nano::block> const & block, nano::block_hash const & confirmation_root, std::shared_ptr<nano::election> const & source_election);
void notify_observers (nano::secure::transaction const &, nano::election_status const & status, std::vector<nano::vote_with_weight_info> const & votes) const;
@ -153,6 +145,10 @@ private: // Dependencies
nano::cementing_set & cementing_set;
public:
nano::active_elections_index index;
std::unordered_map<nano::qualified_root, erased_callback_t> erased_callbacks;
nano::recently_confirmed_cache recently_confirmed;
nano::recently_cemented_cache recently_cemented;
@ -161,34 +157,18 @@ public:
mutable nano::mutex mutex{ mutex_identifier (mutexes::active) };
private:
/** Keeps track of number of elections by election behavior (normal, hinted, optimistic) */
nano::enum_array<nano::election_behavior, int64_t> count_by_behavior{};
nano::condition_variable condition;
bool stopped{ false };
std::thread thread;
std::thread checkup_thread;
nano::thread_pool workers;
nano::interval bootstrap_stale_interval;
nano::interval stale_interval;
nano::interval warning_interval;
friend class election;
public: // Tests
void clear ();
friend class node_fork_storm_Test;
friend class system_block_sequence_Test;
friend class node_mass_block_new_Test;
friend class active_elections_vote_replays_Test;
friend class frontiers_confirmation_prioritize_frontiers_Test;
friend class frontiers_confirmation_prioritize_frontiers_max_optimistic_elections_Test;
friend class confirmation_height_prioritize_frontiers_overwrite_Test;
friend class active_elections_confirmation_consistency_Test;
friend class node_deferred_dependent_elections_Test;
friend class active_elections_pessimistic_elections_Test;
friend class frontiers_confirmation_expired_optimistic_elections_removal_Test;
};
nano::stat::type to_stat_type (nano::election_state);

View file

@ -0,0 +1,196 @@
#include <nano/node/active_elections_index.hpp>
#include <nano/node/election.hpp>
#include <ranges>
void nano::active_elections_index::insert (std::shared_ptr<nano::election> const & election, nano::election_behavior behavior, nano::bucket_index bucket, nano::priority_timestamp priority)
{
debug_assert (!entries.get<tag_ptr> ().contains (election));
debug_assert (!entries.get<tag_root> ().contains (election->qualified_root));
auto [it, inserted] = entries.emplace_back (entry{ election, election->qualified_root, behavior, bucket, priority });
debug_assert (inserted);
// Update cached size
size_by_behavior[{ behavior }]++;
size_by_bucket[{ behavior, bucket }]++;
}
bool nano::active_elections_index::erase (std::shared_ptr<nano::election> const & election)
{
auto maybe_entry = info (election);
if (!maybe_entry)
{
return false; // Not found
}
auto entry = *maybe_entry;
auto & index = entries.get<tag_ptr> ();
auto erased = index.erase (election);
release_assert (erased == 1);
// Update cached size
size_by_behavior[{ entry.behavior }]--;
size_by_bucket[{ entry.behavior, entry.bucket }]--;
return true; // Erased
}
void nano::active_elections_index::update (std::shared_ptr<nano::election> const & election, nano::election_behavior behavior)
{
auto & index = entries.get<tag_ptr> ();
auto existing = index.find (election);
if (existing != index.end ())
{
auto old_behavior = existing->behavior;
auto bucket = existing->bucket;
if (old_behavior != behavior)
{
// Update cached sizes
size_by_behavior[{ old_behavior }]--;
size_by_bucket[{ old_behavior, bucket }]--;
size_by_behavior[{ behavior }]++;
size_by_bucket[{ behavior, bucket }]++;
// Update the entry
index.modify (existing, [behavior] (entry & e) {
e.behavior = behavior;
});
}
}
else
{
debug_assert (false, "election not found in index");
}
}
bool nano::active_elections_index::exists (nano::qualified_root const & root) const
{
return entries.get<tag_root> ().contains (root);
}
bool nano::active_elections_index::exists (std::shared_ptr<nano::election> const & election) const
{
return entries.get<tag_ptr> ().contains (election);
}
std::shared_ptr<nano::election> nano::active_elections_index::election (nano::qualified_root const & root) const
{
if (auto existing = entries.get<tag_root> ().find (root); existing != entries.get<tag_root> ().end ())
{
return existing->election;
}
return nullptr;
}
auto nano::active_elections_index::info (std::shared_ptr<nano::election> const & election) const -> std::optional<entry>
{
if (auto existing = entries.get<tag_ptr> ().find (election); existing != entries.get<tag_ptr> ().end ())
{
return *existing;
}
return std::nullopt;
}
auto nano::active_elections_index::list () const -> std::deque<std::shared_ptr<nano::election>>
{
auto r = entries.get<tag_sequenced> () | std::views::transform ([] (auto const & entry) { return entry.election; });
return { r.begin (), r.end () };
}
bool nano::active_elections_index::empty () const
{
return entries.empty ();
}
size_t nano::active_elections_index::size () const
{
return entries.size ();
}
size_t nano::active_elections_index::size (nano::election_behavior behavior) const
{
if (auto existing = size_by_behavior.find ({ behavior }); existing != size_by_behavior.end ())
{
return existing->second;
}
return 0;
}
size_t nano::active_elections_index::size (nano::election_behavior behavior, nano::bucket_index bucket) const
{
if (auto existing = size_by_bucket.find ({ behavior, bucket }); existing != size_by_bucket.end ())
{
return existing->second;
}
return 0;
}
auto nano::active_elections_index::last (nano::election_behavior behavior, nano::bucket_index bucket) const -> priority_result
{
auto & index = entries.get<tag_key> ();
// Find the range of entries with matching behavior and bucket
auto range = index.equal_range (std::make_tuple (behavior, bucket));
if (range.first != range.second)
{
// Since the index is ordered, the last element has the highest priority (largest value)
auto last = std::prev (range.second);
return { last->election, last->priority };
}
return { nullptr, std::numeric_limits<nano::priority_timestamp>::max () };
}
auto nano::active_elections_index::list (std::chrono::steady_clock::time_point cutoff, std::chrono::steady_clock::time_point now) -> std::deque<std::shared_ptr<nano::election>>
{
auto & index = entries.get<tag_timestamp> ();
// Collect entries to process first to avoid iterator invalidation issues
std::deque<decltype (index.begin ())> to_process;
auto end = index.upper_bound (cutoff);
for (auto it = index.begin (); it != end; ++it)
{
to_process.push_back (it);
}
// Process and update timestamps
for (auto it : to_process)
{
// Update timestamp to 'now' for processed entries
index.modify (it, [now] (entry & e) {
e.timestamp = now;
});
}
auto r = to_process | std::views::transform ([] (auto const & it) { return it->election; });
return { r.begin (), r.end () };
}
bool nano::active_elections_index::trigger (std::shared_ptr<nano::election> const & election)
{
auto & index = entries.get<tag_ptr> ();
if (auto existing = index.find (election); existing != index.end ())
{
index.modify (existing, [] (entry & e) {
e.timestamp = {};
});
return true;
}
return false; // Not found
}
bool nano::active_elections_index::any (std::chrono::steady_clock::time_point cutoff) const
{
auto & index = entries.get<tag_timestamp> ();
auto it = index.begin ();
return it != index.end () && it->timestamp <= cutoff;
}
void nano::active_elections_index::clear ()
{
entries.clear ();
size_by_behavior.clear ();
size_by_bucket.clear ();
}

View file

@ -0,0 +1,112 @@
#pragma once
#include <nano/lib/numbers.hpp>
#include <nano/lib/numbers_templ.hpp>
#include <nano/node/fwd.hpp>
#include <nano/secure/common.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>
#include <chrono>
#include <deque>
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <unordered_map>
namespace mi = boost::multi_index;
namespace nano
{
class active_elections_index
{
public:
struct entry
{
std::shared_ptr<nano::election> election;
nano::qualified_root root;
nano::election_behavior behavior;
nano::bucket_index bucket;
nano::priority_timestamp priority;
std::chrono::steady_clock::time_point timestamp{};
};
public:
void insert (std::shared_ptr<nano::election> const &, nano::election_behavior, nano::bucket_index, nano::priority_timestamp);
bool erase (std::shared_ptr<nano::election> const &);
void update (std::shared_ptr<nano::election> const &, nano::election_behavior);
bool exists (nano::qualified_root const &) const;
bool exists (std::shared_ptr<nano::election> const &) const;
std::shared_ptr<nano::election> election (nano::qualified_root const &) const;
std::optional<entry> info (std::shared_ptr<nano::election> const &) const;
size_t size () const;
size_t size (nano::election_behavior) const;
size_t size (nano::election_behavior, nano::bucket_index) const;
bool empty () const;
// Returns election with the highest priority value. NOTE: Lower "priority" is better
using priority_result = std::pair<std::shared_ptr<nano::election>, nano::priority_timestamp>;
priority_result last (nano::election_behavior, nano::bucket_index) const;
std::deque<std::shared_ptr<nano::election>> list () const;
// Return list of elections with a timestamp before the specified cutoff time
std::deque<std::shared_ptr<nano::election>> list (
std::chrono::steady_clock::time_point cutoff,
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now ());
// Mark an election for update (reset its timestamp)
bool trigger (std::shared_ptr<nano::election> const &);
// Are there any elections with a timestamp before the specified cutoff time
bool any (std::chrono::steady_clock::time_point cutoff) const;
void clear ();
private:
// clang-format off
class tag_sequenced {};
class tag_root {};
class tag_ptr {};
class tag_key {};
class tag_timestamp {};
using ordered_entries = boost::multi_index_container<entry,
mi::indexed_by<
mi::sequenced<mi::tag<tag_sequenced>>,
mi::hashed_unique<mi::tag<tag_root>,
mi::member<entry, nano::qualified_root, &entry::root>>,
mi::hashed_unique<mi::tag<tag_ptr>,
mi::member<entry, std::shared_ptr<nano::election>, &entry::election>>,
mi::ordered_non_unique<mi::tag<tag_key>,
mi::composite_key<entry,
mi::member<entry, nano::election_behavior, &entry::behavior>,
mi::member<entry, nano::bucket_index, &entry::bucket>,
mi::member<entry, nano::priority_timestamp, &entry::priority>>>,
mi::ordered_non_unique<mi::tag<tag_timestamp>,
mi::member<entry, std::chrono::steady_clock::time_point, &entry::timestamp>>
>>;
// clang-format on
ordered_entries entries;
// Keep track of the total number of elections to provide constant time lookups
using behavior_key_t = nano::election_behavior;
std::map<behavior_key_t, size_t> size_by_behavior;
using bucket_key_t = std::pair<nano::election_behavior, nano::bucket_index>;
std::map<bucket_key_t, size_t> size_by_bucket;
};
}

View file

@ -49,7 +49,6 @@ nano::block_processor::block_processor (nano::node_config const & node_config_a,
return config.priority_live;
case nano::block_source::bootstrap:
case nano::block_source::bootstrap_legacy:
case nano::block_source::unchecked:
return config.priority_bootstrap;
case nano::block_source::local:
return config.priority_local;
@ -278,7 +277,7 @@ void nano::block_processor::run ()
// It's possible that ledger processing happens faster than the notifications can be processed by other components, cooldown here
ledger_notifications.wait ([this] {
stats.inc (nano::stat::type::block_processor, nano::stat::detail::cooldown);
if (log_cooldown_interval.elapse (15s))
if (log_cooldown_interval.elapse (nano::is_dev_run () ? 1s : 15s))
{
logger.warn (nano::log::type::block_processor, "Cooldown in block processing, waiting for remaining ledger notifications to be processed");
}

View file

@ -17,6 +17,7 @@ enum class block_source
local,
forced,
election,
test,
};
std::string_view to_string (block_source);

View file

@ -274,7 +274,7 @@ bool nano::bounded_backlog::should_rollback (nano::block_hash const & hash) cons
{
return false;
}
if (node.active.recently_confirmed.exists (hash))
if (node.active.recently_confirmed.contains (hash))
{
return false;
}

View file

@ -54,7 +54,7 @@ nano::cementing_set::~cementing_set ()
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;
{
@ -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);
}
return added;
}
void nano::cementing_set::start ()
@ -117,6 +118,12 @@ std::size_t nano::cementing_set::size () const
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 ()
{
std::unique_lock lock{ mutex };

View file

@ -61,10 +61,12 @@ public:
void stop ();
// 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.
bool contains (nano::block_hash const & hash) const;
std::size_t size () const;
std::size_t deferred_size () const;
nano::container_info container_info () const;
@ -119,6 +121,7 @@ private:
ordered_entries set;
// Blocks that could not be cemented immediately (e.g. waiting for rollbacks to complete)
ordered_entries deferred;
// Blocks that are being cemented in the current batch
std::unordered_set<nano::block_hash> current;

View file

@ -132,9 +132,8 @@ void nano::add_node_flag_options (boost::program_options::options_description &
// clang-format on
}
std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_options::variables_map const & vm)
void nano::update_flags (nano::node_flags & flags_a, boost::program_options::variables_map const & vm)
{
std::error_code ec;
flags_a.disable_add_initial_peers = (vm.count ("disable_add_initial_peers") > 0);
flags_a.disable_max_peers_per_ip = (vm.count ("disable_max_peers_per_ip") > 0);
flags_a.disable_max_peers_per_subnetwork = (vm.count ("disable_max_peers_per_subnetwork") > 0);
@ -209,7 +208,6 @@ std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_o
{
flags_a.rpc_config_overrides = nano::config_overrides (rpcconfig->second.as<std::vector<nano::config_key_value_pair>> ());
}
return ec;
}
std::error_code nano::flags_config_conflicts (nano::node_flags const & flags_a, nano::node_config const & config_a)

View file

@ -21,7 +21,7 @@ enum class error_cli
void add_node_options (boost::program_options::options_description &);
void add_node_flag_options (boost::program_options::options_description &);
std::error_code update_flags (nano::node_flags &, boost::program_options::variables_map const &);
void update_flags (nano::node_flags &, boost::program_options::variables_map const &);
std::error_code flags_config_conflicts (nano::node_flags const &, nano::node_config const &);
std::error_code handle_node_options (boost::program_options::variables_map const &);
}

View file

@ -23,9 +23,10 @@ std::chrono::milliseconds nano::election::base_latency () const
* election
*/
nano::election::election (nano::node & node_a, std::shared_ptr<nano::block> const & block_a, std::function<void (std::shared_ptr<nano::block> const &)> const & confirmation_action_a, std::function<void (nano::account const &)> const & vote_action_a, nano::election_behavior election_behavior_a) :
confirmation_action (confirmation_action_a),
vote_action (vote_action_a),
nano::election::election (nano::node & node_a, std::shared_ptr<nano::block> const & block_a, nano::election_behavior election_behavior_a, std::function<void (std::shared_ptr<nano::block> const &)> confirmation_action_a, std::function<void (nano::account const &)> vote_action_a, std::function<void (nano::qualified_root const &)> update_action_a) :
confirmation_action (std::move (confirmation_action_a)),
vote_action (std::move (vote_action_a)),
update_action (std::move (update_action_a)),
node (node_a),
behavior_m (election_behavior_a),
status (block_a),
@ -45,10 +46,11 @@ void nano::election::confirm_once (nano::unique_lock<nano::mutex> & lock)
bool just_confirmed = state_m != nano::election_state::confirmed;
state_m = nano::election_state::confirmed;
state_start = std::chrono::steady_clock::now ();
if (just_confirmed)
{
status.election_end = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ());
status.election_end = std::chrono::system_clock::now (); // Timestamp as system time
status.election_duration = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::steady_clock::now () - election_start);
status.confirmation_request_count = confirmation_request_count;
status.block_count = nano::narrow_cast<decltype (status.block_count)> (last_blocks.size ());
@ -78,12 +80,19 @@ void nano::election::confirm_once (nano::unique_lock<nano::mutex> & lock)
lock.unlock ();
node.election_workers.post ([status_l, confirmation_action_l = confirmation_action] () {
if (confirmation_action_l)
{
if (update_action)
{
node.election_workers.post ([qualified_root_l = qualified_root, update_action_l = update_action] () {
update_action_l (qualified_root_l);
});
}
if (confirmation_action)
{
node.election_workers.post ([status_l, confirmation_action_l = confirmation_action] () {
confirmation_action_l (status_l.winner);
}
});
});
}
}
else
{
@ -145,8 +154,15 @@ bool nano::election::state_change (nano::election_state expected_a, nano::electi
if (state_m == expected_a)
{
state_m = desired_a;
state_start = std::chrono::steady_clock::now ().time_since_epoch ();
state_start = std::chrono::steady_clock::now ();
result = false;
if (update_action)
{
node.election_workers.post ([qualified_root_l = qualified_root, update_action_l = update_action] () {
update_action_l (qualified_root_l);
});
}
}
}
return result;
@ -191,12 +207,6 @@ void nano::election::send_confirm_req (nano::confirmation_solicitor & solicitor_
}
}
void nano::election::transition_active ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
state_change (nano::election_state::passive, nano::election_state::active);
}
bool nano::election::transition_priority ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
@ -214,13 +224,26 @@ bool nano::election::transition_priority ()
qualified_root,
duration ().count ());
if (update_action)
{
node.election_workers.post ([qualified_root_l = qualified_root, update_action_l = update_action] () {
update_action_l (qualified_root_l);
});
}
return true;
}
void nano::election::cancel ()
bool nano::election::transition_active ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
state_change (state_m, nano::election_state::cancelled);
return !state_change (nano::election_state::passive, nano::election_state::active); // Invert since false => success
}
bool nano::election::cancel ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
return !state_change (state_m, nano::election_state::cancelled); // Invert since false => success
}
bool nano::election::confirmed_locked () const
@ -306,26 +329,26 @@ nano::election_status nano::election::get_status () const
return status;
}
bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a)
bool nano::election::tick (nano::confirmation_solicitor & solicitor)
{
nano::unique_lock<nano::mutex> lock{ mutex };
bool result = false;
switch (state_m)
{
case nano::election_state::passive:
if (base_latency () * passive_duration_factor < std::chrono::steady_clock::now ().time_since_epoch () - state_start)
if (base_latency () * passive_duration_factor < std::chrono::steady_clock::now () - state_start)
{
state_change (nano::election_state::passive, nano::election_state::active);
}
break;
case nano::election_state::active:
broadcast_vote_locked (lock);
broadcast_block (solicitor_a);
send_confirm_req (solicitor_a);
broadcast_block (solicitor);
send_confirm_req (solicitor);
break;
case nano::election_state::confirmed:
result = true; // Return true to indicate this election should be cleaned up
broadcast_block (solicitor_a); // Ensure election winner is broadcasted
broadcast_block (solicitor); // Ensure election winner is broadcasted
state_change (nano::election_state::confirmed, nano::election_state::expired_confirmed);
break;
case nano::election_state::expired_unconfirmed:
@ -351,6 +374,7 @@ bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a
status.type = nano::election_status_type::stopped;
}
}
return result;
}

View file

@ -70,13 +70,14 @@ private:
// Callbacks
std::function<void (std::shared_ptr<nano::block> const &)> confirmation_action;
std::function<void (nano::account const &)> vote_action;
std::function<void (nano::qualified_root const &)> update_action;
private: // State management
static unsigned constexpr passive_duration_factor = 5;
static unsigned constexpr active_request_count_min = 2;
nano::election_state state_m{ election_state::passive };
std::chrono::steady_clock::duration state_start{ std::chrono::steady_clock::now ().time_since_epoch () };
std::chrono::steady_clock::time_point state_start{ std::chrono::steady_clock::now () };
// These are modified while not holding the mutex from transition_time only
std::chrono::steady_clock::time_point last_block{};
@ -89,10 +90,12 @@ private: // State management
bool state_change (nano::election_state, nano::election_state);
public: // State transitions
bool transition_time (nano::confirmation_solicitor &);
void transition_active ();
// Returns true if the election should be cleaned up
bool tick (nano::confirmation_solicitor &);
bool transition_active ();
bool transition_priority ();
void cancel ();
bool cancel ();
public: // Status
bool confirmed () const;
@ -100,6 +103,7 @@ public: // Status
nano::election_extended_status current_status () const;
std::shared_ptr<nano::block> winner () const;
std::chrono::milliseconds duration () const;
std::atomic<unsigned> confirmation_request_count{ 0 };
std::atomic<unsigned> vote_broadcast_count{ 0 };
@ -110,7 +114,13 @@ public: // Status
nano::election_status status;
public: // Interface
election (nano::node &, std::shared_ptr<nano::block> const & block, std::function<void (std::shared_ptr<nano::block> const &)> const & confirmation_action, std::function<void (nano::account const &)> const & vote_action, nano::election_behavior behavior);
election (
nano::node &,
std::shared_ptr<nano::block> const & block,
nano::election_behavior behavior,
std::function<void (std::shared_ptr<nano::block> const &)> confirmation_action = nullptr,
std::function<void (nano::account const &)> vote_action = nullptr,
std::function<void (nano::qualified_root const &)> update_action = nullptr);
std::shared_ptr<nano::block> find (nano::block_hash const &) const;
/*
@ -131,10 +141,16 @@ public: // Interface
nano::vote_info get_last_vote (nano::account const & account);
void set_last_vote (nano::account const & account, nano::vote_info vote_info);
nano::election_status get_status () const;
std::chrono::steady_clock::time_point get_election_start () const
{
return election_start;
}
std::chrono::steady_clock::time_point get_state_start () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return state_start;
}
private: // Dependencies
nano::node & node;

View file

@ -23,6 +23,7 @@ enum class election_status_type : uint8_t
stopped = 5
};
std::string_view to_string (election_status_type);
nano::stat::detail to_stat_detail (election_status_type);
/* Holds a summary of an election */
@ -32,8 +33,8 @@ public:
std::shared_ptr<nano::block> winner;
nano::amount tally{ 0 };
nano::amount final_tally{ 0 };
std::chrono::milliseconds election_end{ std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ()) };
std::chrono::milliseconds election_duration{ std::chrono::duration_values<std::chrono::milliseconds>::zero () };
std::chrono::system_clock::time_point election_end{};
std::chrono::milliseconds election_duration{};
unsigned confirmation_request_count{ 0 };
unsigned vote_broadcast_count{ 0 };
unsigned block_count{ 0 };

View file

@ -54,7 +54,7 @@ void nano::ipc::broker::start ()
confirmation->block = nano::ipc::flatbuffers_builder::block_to_union (*status_a.winner, amount_a, is_state_send_a, is_state_epoch_a);
confirmation->election_info = std::make_unique<nanoapi::ElectionInfoT> ();
confirmation->election_info->duration = status_a.election_duration.count ();
confirmation->election_info->time = status_a.election_end.count ();
confirmation->election_info->time = milliseconds_since_epoch (status_a.election_end);
confirmation->election_info->tally = status_a.tally.to_string_dec ();
confirmation->election_info->block_count = status_a.block_count;
confirmation->election_info->voter_count = status_a.voter_count;

View file

@ -3,7 +3,7 @@
#include <nano/lib/tomlconfig.hpp>
#include <nano/node/ipc/ipc_config.hpp>
nano::ipc::ipc_config_tcp_socket::ipc_config_tcp_socket (nano::network_constants & network_constants) :
nano::ipc::ipc_config_tcp_socket::ipc_config_tcp_socket (nano::network_constants const & network_constants) :
network_constants{ network_constants },
port{ network_constants.default_ipc_port }
{

View file

@ -15,6 +15,7 @@ namespace ipc
{
public:
virtual ~ipc_config_transport () = default;
bool enabled{ false };
bool allow_unsafe{ false };
std::size_t io_timeout{ 15 };
@ -46,8 +47,9 @@ namespace ipc
class ipc_config_tcp_socket : public ipc_config_transport
{
public:
ipc_config_tcp_socket (nano::network_constants & network_constants);
nano::network_constants & network_constants;
ipc_config_tcp_socket (nano::network_constants const &);
nano::network_constants const & network_constants;
/** Listening port */
uint16_t port;
};
@ -56,12 +58,14 @@ namespace ipc
class ipc_config
{
public:
ipc_config (nano::network_constants & network_constants) :
ipc_config (nano::network_constants const & network_constants) :
transport_tcp{ network_constants }
{
}
nano::error deserialize_toml (nano::tomlconfig & toml_a);
nano::error serialize_toml (nano::tomlconfig & toml) const;
ipc_config_domain_socket transport_domain;
ipc_config_tcp_socket transport_tcp;
ipc_config_flatbuffers flatbuffers;

View file

@ -2062,14 +2062,16 @@ void nano::json_handler::confirmation_history ()
}
if (!ec)
{
for (auto const & status : node.active.recently_cemented.list ())
// TODO: Allow passing a count parameter to limit the number of results
// Default to 2000 for now since it was the previous limit
for (auto const & status : node.active.recently_cemented.list (2000))
{
if (hash.is_zero () || status.winner->hash () == hash)
{
boost::property_tree::ptree election;
election.put ("hash", status.winner->hash ().to_string ());
election.put ("duration", status.election_duration.count ());
election.put ("time", status.election_end.count ());
election.put ("time", milliseconds_since_epoch (status.election_end));
election.put ("tally", status.tally.to_string_dec ());
election.add ("final", status.final_tally.to_string_dec ());
election.put ("blocks", std::to_string (status.block_count));

View file

@ -18,6 +18,7 @@
#include <nano/node/bucketing.hpp>
#include <nano/node/cementing_set.hpp>
#include <nano/node/daemonconfig.hpp>
#include <nano/node/election.hpp>
#include <nano/node/election_status.hpp>
#include <nano/node/endpoint.hpp>
#include <nano/node/fork_cache.hpp>
@ -102,7 +103,7 @@ nano::node::node (std::shared_ptr<boost::asio::io_context> io_ctx_a, std::filesy
wallets_store{ *wallets_store_impl },
wallets_impl{ std::make_unique<nano::wallets> (false, *this) },
wallets{ *wallets_impl },
ledger_impl{ std::make_unique<nano::ledger> (store, network_params.ledger, stats, logger, flags_a.generate_cache, config.representative_vote_weight_minimum.number (), config.max_backlog) },
ledger_impl{ std::make_unique<nano::ledger> (store, network_params, stats, logger, flags_a.generate_cache, config.representative_vote_weight_minimum.number (), config.max_backlog) },
ledger{ *ledger_impl },
runner_impl{ std::make_unique<nano::thread_runner> (io_ctx_shared, logger, config.io_threads) },
runner{ *runner_impl },
@ -176,9 +177,9 @@ nano::node::node (std::shared_ptr<boost::asio::io_context> io_ctx_a, std::filesy
vote_processor{ *vote_processor_impl },
vote_cache_processor_impl{ std::make_unique<nano::vote_cache_processor> (config.vote_processor, vote_router, vote_cache, stats, logger) },
vote_cache_processor{ *vote_cache_processor_impl },
generator_impl{ std::make_unique<nano::vote_generator> (config, *this, ledger, wallets, vote_processor, history, network, stats, logger, /* non-final */ false, loopback_channel) },
generator_impl{ std::make_unique<nano::vote_generator> (config.vote_generator, *this, ledger, wallets, vote_processor, history, network, stats, logger, /* non-final */ false, loopback_channel) },
generator{ *generator_impl },
final_generator_impl{ std::make_unique<nano::vote_generator> (config, *this, ledger, wallets, vote_processor, history, network, stats, logger, /* final */ true, loopback_channel) },
final_generator_impl{ std::make_unique<nano::vote_generator> (config.vote_generator, *this, ledger, wallets, vote_processor, history, network, stats, logger, /* final */ true, loopback_channel) },
final_generator{ *final_generator_impl },
scheduler_impl{ std::make_unique<nano::scheduler::component> (config, *this, ledger, ledger_notifications, bucketing, active, online_reps, vote_cache, cementing_set, stats, logger) },
scheduler{ *scheduler_impl },
@ -217,6 +218,11 @@ nano::node::node (std::shared_ptr<boost::asio::io_context> io_ctx_a, std::filesy
return ledger.weight (rep);
};
// Prioritize bootstrapping accounts with stale elections to find alternative forks
active.election_stale.add ([this] (auto const & election) {
bootstrap.prioritize (election->account);
});
// TODO: Hook this direclty in the schedulers
backlog_scan.batch_activated.add ([this] (auto const & batch) {
auto transaction = ledger.tx_begin_read ();

View file

@ -22,12 +22,7 @@ std::string const default_beta_peer_network = nano::env::get ("NANO_DEFAULT_PEER
std::string const default_test_peer_network = nano::env::get ("NANO_DEFAULT_PEER").value_or ("peering-test.nano.org");
}
nano::node_config::node_config (nano::network_params & network_params) :
node_config (std::nullopt, network_params)
{
}
nano::node_config::node_config (const std::optional<uint16_t> & peering_port_a, nano::network_params & network_params) :
nano::node_config::node_config (std::optional<uint16_t> peering_port_a, nano::network_params const & network_params) :
network_params{ network_params },
peering_port{ peering_port_a },
hinted_scheduler{ network_params.network },
@ -92,6 +87,11 @@ nano::node_config::node_config (const std::optional<uint16_t> & peering_port_a,
}
}
nano::node_config::node_config (nano::network_params const & network_params) :
node_config{ std::nullopt, network_params }
{
}
nano::node_config::~node_config ()
{
// Keep the node_config destructor definition here to avoid incomplete type issues
@ -123,7 +123,6 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const
toml.put ("block_processor_batch_max_time", block_processor_batch_max_time.count (), "The maximum time the block processor can continuously process blocks for.\ntype:milliseconds");
toml.put ("allow_local_peers", allow_local_peers, "Enable or disable local host peering.\ntype:bool");
toml.put ("vote_minimum", vote_minimum.to_string_dec (), "Local representatives do not vote if the delegated weight is under this threshold. Saves on system resources.\ntype:string,amount,raw");
toml.put ("vote_generator_delay", vote_generator_delay.count (), "Delay before votes are sent to allow for efficient bundling of hashes in votes.\ntype:milliseconds");
toml.put ("unchecked_cutoff_time", unchecked_cutoff_time.count (), "Number of seconds before deleting an unchecked entry.\nWarning: lower values (e.g., 3600 seconds, or 1 hour) may result in unsuccessful bootstraps, especially a bootstrap from scratch.\ntype:seconds");
toml.put ("pow_sleep_interval", pow_sleep_interval.count (), "Time to sleep between batch work generation attempts. Reduces max CPU usage at the expense of a longer generation time.\ntype:nanoseconds");
toml.put ("external_address", external_address, "The external address of this node (NAT). If not set, the node will request this information via UPnP.\ntype:string,ip");
@ -249,6 +248,10 @@ nano::error nano::node_config::serialize_toml (nano::tomlconfig & toml) const
block_processor.serialize (block_processor_l);
toml.put_child ("block_processor", block_processor_l);
nano::tomlconfig vote_generator_l;
vote_generator.serialize (vote_generator_l);
toml.put_child ("vote_generator", vote_generator_l);
nano::tomlconfig vote_processor_l;
vote_processor.serialize (vote_processor_l);
toml.put_child ("vote_processor", vote_processor_l);
@ -410,6 +413,12 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml)
block_processor.deserialize (config_l);
}
if (toml.has_key ("vote_generator"))
{
auto config_l = toml.get_required_child ("vote_generator");
vote_generator.deserialize (config_l);
}
if (toml.has_key ("vote_processor"))
{
auto config_l = toml.get_required_child ("vote_processor");
@ -566,10 +575,6 @@ nano::error nano::node_config::deserialize_toml (nano::tomlconfig & toml)
toml.get_error ().set ("vote_minimum contains an invalid decimal amount");
}
auto delay_l = vote_generator_delay.count ();
toml.get ("vote_generator_delay", delay_l);
vote_generator_delay = std::chrono::milliseconds (delay_l);
auto block_processor_batch_max_time_l = block_processor_batch_max_time.count ();
toml.get ("block_processor_batch_max_time", block_processor_batch_max_time_l);
block_processor_batch_max_time = std::chrono::milliseconds (block_processor_batch_max_time_l);

View file

@ -31,6 +31,7 @@
#include <nano/node/transport/tcp_config.hpp>
#include <nano/node/transport/tcp_listener.hpp>
#include <nano/node/vote_cache.hpp>
#include <nano/node/vote_generator.hpp>
#include <nano/node/vote_processor.hpp>
#include <nano/node/vote_rebroadcaster.hpp>
#include <nano/node/websocketconfig.hpp>
@ -60,9 +61,8 @@ std::optional<database_backend> parse_database_backend (std::string const &);
class node_config
{
public:
// TODO: Users of this class rely on the default copy consturctor. This prevents using unique_ptrs with forward declared types.
node_config (nano::network_params & network_params = nano::dev::network_params);
node_config (const std::optional<uint16_t> &, nano::network_params & network_params = nano::dev::network_params);
node_config (std::optional<uint16_t> peering_port, nano::network_params const & = nano::dev::network_params);
node_config (nano::network_params const & = nano::dev::network_params);
~node_config ();
nano::error serialize_toml (nano::tomlconfig &) const;
@ -85,7 +85,6 @@ public:
nano::amount receive_minimum{ nano::nano_ratio / 1000 / 1000 }; // 0.000001 nano
nano::amount vote_minimum{ nano::nano_ratio * 42 }; // 42 nano
nano::amount rep_crawler_weight_minimum{ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" };
std::chrono::milliseconds vote_generator_delay{ std::chrono::milliseconds (100) };
nano::amount online_weight_minimum{ nano::nano_ratio * 42 }; // TODO increase later as we get nodes
/*
* The minimum vote weight that a representative must have for its vote to be counted.
@ -150,6 +149,7 @@ public:
nano::rep_crawler_config rep_crawler;
nano::block_processor_config block_processor;
nano::active_elections_config active_elections;
nano::vote_generator_config vote_generator;
nano::vote_processor_config vote_processor;
nano::peer_history_config peer_history;
nano::transport::tcp_config tcp;

View file

@ -1,9 +1,8 @@
#include <nano/lib/blocks.hpp>
#include <nano/lib/utility.hpp>
#include <nano/node/recently_cemented_cache.hpp>
/*
* class recently_cemented
*/
#include <ranges>
nano::recently_cemented_cache::recently_cemented_cache (std::size_t max_size_a) :
max_size{ max_size_a }
@ -13,23 +12,53 @@ nano::recently_cemented_cache::recently_cemented_cache (std::size_t max_size_a)
void nano::recently_cemented_cache::put (const nano::election_status & status)
{
nano::lock_guard<nano::mutex> guard{ mutex };
cemented.push_back (status);
if (cemented.size () > max_size)
entries.emplace_back (entry{ status.winner->qualified_root (), status.winner->hash (), status });
if (entries.size () > max_size)
{
cemented.pop_front ();
entries.pop_front (); // Remove oldest
}
}
nano::recently_cemented_cache::queue_t nano::recently_cemented_cache::list () const
void nano::recently_cemented_cache::erase (const nano::block_hash & hash)
{
nano::lock_guard<nano::mutex> guard{ mutex };
return cemented;
entries.get<tag_hash> ().erase (hash);
}
void nano::recently_cemented_cache::clear ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
entries.clear ();
}
auto nano::recently_cemented_cache::list (size_t max_count) const -> std::deque<nano::election_status>
{
nano::lock_guard<nano::mutex> guard{ mutex };
std::deque<nano::election_status> result;
auto it = entries.rbegin ();
for (size_t i = 0; i < max_count && it != entries.rend (); ++i, ++it)
{
result.push_back (it->status);
}
return result;
}
std::size_t nano::recently_cemented_cache::size () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return cemented.size ();
return entries.size ();
}
bool nano::recently_cemented_cache::contains (const nano::qualified_root & root) const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return entries.get<tag_root> ().contains (root);
}
bool nano::recently_cemented_cache::contains (const nano::block_hash & hash) const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return entries.get<tag_hash> ().contains (hash);
}
nano::container_info nano::recently_cemented_cache::container_info () const
@ -37,6 +66,6 @@ nano::container_info nano::recently_cemented_cache::container_info () const
nano::lock_guard<nano::mutex> guard{ mutex };
nano::container_info info;
info.put ("cemented", cemented);
info.put ("entries", entries);
return info;
}

View file

@ -1,14 +1,20 @@
#pragma once
#include <nano/lib/locks.hpp>
#include <nano/lib/numbers.hpp>
#include <nano/lib/numbers_templ.hpp>
#include <nano/node/election_status.hpp>
#include <nano/secure/common.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>
#include <deque>
namespace nano
{
class container_info_component;
}
namespace mi = boost::multi_index;
namespace nano
{
@ -18,18 +24,44 @@ namespace nano
class recently_cemented_cache final
{
public:
using queue_t = std::deque<nano::election_status>;
explicit recently_cemented_cache (std::size_t max_size);
explicit recently_cemented_cache (size_t max_size);
void put (nano::election_status const &);
queue_t list () const;
void erase (nano::block_hash const &);
void clear ();
std::size_t size () const;
// Returns up to max_count most recent entries
std::deque<nano::election_status> list (size_t max_count = std::numeric_limits<size_t>::max ()) const;
bool contains (nano::qualified_root const &) const;
bool contains (nano::block_hash const &) const;
nano::container_info container_info () const;
private:
queue_t cemented;
struct entry
{
nano::qualified_root root;
nano::block_hash hash;
nano::election_status status;
};
// clang-format off
class tag_hash {};
class tag_root {};
class tag_sequenced {};
using ordered_entries = boost::multi_index_container<entry,
mi::indexed_by<
mi::sequenced<mi::tag<tag_sequenced>>,
mi::hashed_unique<mi::tag<tag_root>,
mi::member<entry, nano::qualified_root, &entry::root>>,
mi::hashed_unique<mi::tag<tag_hash>,
mi::member<entry, nano::block_hash, &entry::hash>>>>;
// clang-format on
ordered_entries entries;
std::size_t const max_size;
mutable nano::mutex mutex;

View file

@ -1,10 +1,6 @@
#include <nano/lib/utility.hpp>
#include <nano/node/recently_confirmed_cache.hpp>
/*
* class recently_confirmed
*/
nano::recently_confirmed_cache::recently_confirmed_cache (std::size_t max_size_a) :
max_size{ max_size_a }
{
@ -13,47 +9,47 @@ nano::recently_confirmed_cache::recently_confirmed_cache (std::size_t max_size_a
void nano::recently_confirmed_cache::put (const nano::qualified_root & root, const nano::block_hash & hash)
{
nano::lock_guard<nano::mutex> guard{ mutex };
confirmed.get<tag_sequence> ().emplace_back (root, hash);
if (confirmed.size () > max_size)
entries.emplace_back (root, hash);
if (entries.size () > max_size)
{
confirmed.get<tag_sequence> ().pop_front ();
entries.pop_front ();
}
}
void nano::recently_confirmed_cache::erase (const nano::block_hash & hash)
{
nano::lock_guard<nano::mutex> guard{ mutex };
confirmed.get<tag_hash> ().erase (hash);
entries.get<tag_hash> ().erase (hash);
}
void nano::recently_confirmed_cache::clear ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
confirmed.clear ();
entries.clear ();
}
bool nano::recently_confirmed_cache::exists (const nano::block_hash & hash) const
bool nano::recently_confirmed_cache::contains (const nano::block_hash & hash) const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return confirmed.get<tag_hash> ().find (hash) != confirmed.get<tag_hash> ().end ();
return entries.get<tag_hash> ().contains (hash);
}
bool nano::recently_confirmed_cache::exists (const nano::qualified_root & root) const
bool nano::recently_confirmed_cache::contains (const nano::qualified_root & root) const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return confirmed.get<tag_root> ().find (root) != confirmed.get<tag_root> ().end ();
return entries.get<tag_root> ().contains (root);
}
std::size_t nano::recently_confirmed_cache::size () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return confirmed.size ();
return entries.size ();
}
nano::recently_confirmed_cache::entry_t nano::recently_confirmed_cache::back () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return confirmed.back ();
return entries.back ();
}
nano::container_info nano::recently_confirmed_cache::container_info () const
@ -61,6 +57,6 @@ nano::container_info nano::recently_confirmed_cache::container_info () const
nano::lock_guard<nano::mutex> guard{ mutex };
nano::container_info info;
info.put ("confirmed", confirmed);
info.put ("entries", entries);
return info;
}

View file

@ -13,11 +13,6 @@
namespace mi = boost::multi_index;
namespace nano
{
class container_info_component;
}
namespace nano
{
class recently_confirmed_cache final
@ -32,8 +27,8 @@ public:
void clear ();
std::size_t size () const;
bool exists (nano::qualified_root const &) const;
bool exists (nano::block_hash const &) const;
bool contains (nano::qualified_root const &) const;
bool contains (nano::block_hash const &) const;
nano::container_info container_info () const;
@ -44,17 +39,17 @@ private:
// clang-format off
class tag_hash {};
class tag_root {};
class tag_sequence {};
class tag_sequenced {};
using ordered_recent_confirmations = boost::multi_index_container<entry_t,
using ordered_entries = boost::multi_index_container<entry_t,
mi::indexed_by<
mi::sequenced<mi::tag<tag_sequence>>,
mi::sequenced<mi::tag<tag_sequenced>>,
mi::hashed_unique<mi::tag<tag_root>,
mi::member<entry_t, nano::qualified_root, &entry_t::first>>,
mi::hashed_unique<mi::tag<tag_hash>,
mi::member<entry_t, nano::block_hash, &entry_t::second>>>>;
// clang-format on
ordered_recent_confirmations confirmed;
ordered_entries entries;
std::size_t const max_size;

View file

@ -301,7 +301,7 @@ auto nano::rep_crawler::prepare_query_target () const -> hash_root_t
for (auto const & block : random_blocks)
{
// Avoid blocks that could still have live votes coming in
if (active.recently_confirmed.exists (block->hash ()))
if (active.recently_confirmed.contains (block->hash ()))
{
continue;
}

View file

@ -91,7 +91,7 @@ bool nano::scheduler::bucket::activate ()
elections.get<tag_root> ().erase (election->qualified_root);
};
auto result = active.insert (block, nano::election_behavior::priority, erase_callback);
auto result = active.insert (block, nano::election_behavior::priority, index, priority, erase_callback);
if (result.inserted)
{
release_assert (result.election);

View file

@ -1,5 +1,6 @@
#pragma once
#include <nano/lib/numbers.hpp>
#include <nano/node/fwd.hpp>
#include <memory>

View file

@ -49,8 +49,8 @@ void nano::scheduler::hinted::stop ()
nano::lock_guard<nano::mutex> lock{ mutex };
stopped = true;
}
notify ();
nano::join_or_pass (thread);
condition.notify_all ();
join_or_pass (thread);
}
void nano::scheduler::hinted::notify ()

View file

@ -64,6 +64,7 @@ private:
nano::uint128_t final_tally_threshold () const;
private: // Dependencies
hinted_config const & config;
nano::node & node;
nano::vote_cache & vote_cache;
nano::active_elections & active;
@ -71,8 +72,6 @@ private: // Dependencies
nano::stats & stats;
private:
hinted_config const & config;
std::atomic<bool> stopped{ false };
nano::condition_variable condition;
mutable nano::mutex mutex;

View file

@ -30,7 +30,7 @@ void nano::scheduler::manual::stop ()
nano::lock_guard<nano::mutex> lock{ mutex };
stopped = true;
}
notify ();
condition.notify_all ();
nano::join_or_pass (thread);
}
@ -39,23 +39,43 @@ void nano::scheduler::manual::notify ()
condition.notify_all ();
}
void nano::scheduler::manual::push (std::shared_ptr<nano::block> const & block_a, boost::optional<nano::uint128_t> const & previous_balance_a)
auto nano::scheduler::manual::push (std::shared_ptr<nano::block> const & block) -> std::future<std::shared_ptr<nano::election>>
{
nano::lock_guard<nano::mutex> lock{ mutex };
queue.push_back (std::make_tuple (block_a, previous_balance_a, nano::election_behavior::manual));
notify ();
// Check if block already exists
auto & hash_index = queue.get<tag_hash> ();
if (hash_index.contains (block->hash ()))
{
// Block already exists, return future that immediately resolves to nullptr
std::promise<std::shared_ptr<nano::election>> promise;
auto future = promise.get_future ();
promise.set_value (nullptr);
return future;
}
// Create entry and get future before inserting
entry new_entry{ block };
auto future = new_entry.promise.get_future ();
auto [it, inserted] = queue.push_back (std::move (new_entry));
debug_assert (inserted);
condition.notify_all ();
return future;
}
bool nano::scheduler::manual::contains (nano::block_hash const & hash) const
{
nano::lock_guard<nano::mutex> lock{ mutex };
return std::any_of (queue.cbegin (), queue.cend (), [&hash] (auto const & item) {
return std::get<0> (item)->hash () == hash;
});
auto & hash_index = queue.get<tag_hash> ();
return hash_index.contains (hash);
}
bool nano::scheduler::manual::predicate () const
{
debug_assert (!mutex.try_lock ());
return !queue.empty ();
}
@ -67,28 +87,37 @@ void nano::scheduler::manual::run ()
condition.wait (lock, [this] () {
return stopped || predicate ();
});
debug_assert ((std::this_thread::yield (), true)); // Introduce some random delay in debug builds
if (!stopped)
if (stopped)
{
return;
}
debug_assert ((std::this_thread::yield (), true));
if (predicate ())
{
node.stats.inc (nano::stat::type::election_scheduler, nano::stat::detail::loop);
if (predicate ())
auto promise = std::move (queue.front ().promise);
auto block = queue.front ().block;
queue.pop_front ();
lock.unlock ();
auto result = node.active.insert (block, nano::election_behavior::manual);
if (result.inserted)
{
auto const [block, previous_balance, election_behavior] = queue.front ();
queue.pop_front ();
lock.unlock ();
node.stats.inc (nano::stat::type::election_scheduler, nano::stat::detail::insert_manual);
auto result = node.active.insert (block, election_behavior);
if (result.election != nullptr)
{
result.election->transition_active ();
}
}
else
if (result.election != nullptr)
{
lock.unlock ();
result.election->transition_active ();
}
notify ();
// Fulfill the promise
promise.set_value (result.election);
lock.lock ();
}
}

View file

@ -4,41 +4,73 @@
#include <nano/lib/numbers.hpp>
#include <nano/node/fwd.hpp>
#include <boost/optional.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>
#include <deque>
#include <condition_variable>
#include <future>
#include <memory>
#include <mutex>
#include <thread>
namespace mi = boost::multi_index;
namespace nano::scheduler
{
class buckets;
class manual final
{
std::deque<std::tuple<std::shared_ptr<nano::block>, boost::optional<nano::uint128_t>, nano::election_behavior>> queue;
nano::node & node;
mutable nano::mutex mutex;
nano::condition_variable condition;
bool stopped{ false };
std::thread thread;
void notify ();
bool predicate () const;
void run ();
public:
explicit manual (nano::node & node);
explicit manual (nano::node &);
~manual ();
void start ();
void stop ();
// Manually start an election for a block
// Call action with confirmed block, may be different than what we started with
void push (std::shared_ptr<nano::block> const &, boost::optional<nano::uint128_t> const & = boost::none);
std::future<std::shared_ptr<nano::election>> push (std::shared_ptr<nano::block> const & block);
bool contains (nano::block_hash const &) const;
nano::container_info container_info () const;
private:
bool predicate () const;
void notify ();
void run ();
private: // Dependencies
nano::node & node;
private:
struct entry
{
std::shared_ptr<nano::block> block;
mutable std::promise<std::shared_ptr<nano::election>> promise;
nano::block_hash hash () const
{
return block->hash ();
}
};
// clang-format off
class tag_sequenced {};
class tag_hash {};
using ordered_queue = boost::multi_index_container<entry,
mi::indexed_by<
mi::sequenced<mi::tag<tag_sequenced>>,
mi::hashed_unique<mi::tag<tag_hash>,
mi::const_mem_fun<entry, nano::block_hash, &entry::hash>>
>>;
// clang-format on
ordered_queue queue;
bool stopped{ false };
nano::condition_variable condition;
mutable nano::mutex mutex;
std::thread thread;
};
}

View file

@ -46,13 +46,17 @@ void nano::scheduler::optimistic::stop ()
nano::lock_guard<nano::mutex> guard{ mutex };
stopped = true;
}
notify ();
nano::join_or_pass (thread);
condition.notify_all ();
join_or_pass (thread);
}
void nano::scheduler::optimistic::notify ()
{
condition.notify_all ();
// Only wake up the thread if there is space inside AEC for optimistic elections
if (active.vacancy (nano::election_behavior::optimistic) > 0)
{
condition.notify_all ();
}
}
bool nano::scheduler::optimistic::activate_predicate (const nano::account_info & account_info, const nano::confirmation_height_info & conf_info) const
@ -72,33 +76,36 @@ bool nano::scheduler::optimistic::activate_predicate (const nano::account_info &
bool nano::scheduler::optimistic::activate (const nano::account & account, const nano::account_info & account_info, const nano::confirmation_height_info & conf_info)
{
debug_assert (account_info.block_count >= conf_info.height);
if (!config.enable)
{
return false;
}
debug_assert (account_info.block_count >= conf_info.height);
if (activate_predicate (account_info, conf_info))
{
nano::lock_guard<nano::mutex> lock{ mutex };
// Assume gap_threshold for accounts with nothing confirmed
auto const unconfirmed_height = std::max (account_info.block_count - conf_info.height, config.gap_threshold);
auto [it, inserted] = candidates.push_back ({ account, unconfirmed_height, std::chrono::steady_clock::now () });
stats.inc (nano::stat::type::optimistic_scheduler, inserted ? nano::stat::detail::activated : nano::stat::detail::duplicate);
// Limit candidates container size
if (candidates.size () > config.max_size)
{
nano::lock_guard<nano::mutex> lock{ mutex };
// Prevent duplicate candidate accounts
if (candidates.get<tag_account> ().contains (account))
{
return false; // Not activated
}
// Limit candidates container size
if (candidates.size () >= config.max_size)
{
return false; // Not activated
}
stats.inc (nano::stat::type::optimistic_scheduler, nano::stat::detail::activated);
candidates.push_back ({ account, nano::clock::now () });
// Remove oldest candidate
candidates.pop_front ();
}
return true; // Activated
// Not notifying the thread immediately here, since we need to wait for activation_delay to elapse
return inserted;
}
return false; // Not activated
}
@ -106,17 +113,25 @@ bool nano::scheduler::optimistic::predicate () const
{
debug_assert (!mutex.try_lock ());
if (active.vacancy (nano::election_behavior::optimistic) <= 0)
// Check if there is space inside AEC for a new optimistic election
return !candidates.empty () && active.vacancy (nano::election_behavior::optimistic) > 0;
}
auto nano::scheduler::optimistic::snapshot (size_t max_count) const -> std::deque<entry>
{
auto const now = std::chrono::steady_clock::now ();
std::deque<entry> result;
auto & height_index = candidates.get<tag_unconfirmed_height> ();
for (auto it = height_index.begin (); it != height_index.end () && result.size () < max_count; ++it)
{
return false;
}
if (candidates.empty ())
{
return false;
if (elapsed (it->timestamp, config.activation_delay, now))
{
result.push_back (*it);
}
}
auto candidate = candidates.front ();
bool result = nano::elapsed (candidate.timestamp, network_constants.optimistic_activation_delay);
return result;
}
@ -125,33 +140,60 @@ void nano::scheduler::optimistic::run ()
nano::unique_lock<nano::mutex> lock{ mutex };
while (!stopped)
{
stats.inc (nano::stat::type::optimistic_scheduler, nano::stat::detail::loop);
// Ignore predicate in condition, we always want to wait for activation_delay to elapse before next wake up
condition.wait_for (lock, config.activation_delay, [this] () {
return stopped.load ();
});
if (stopped)
{
return;
}
if (predicate ())
{
auto transaction = ledger.tx_begin_read ();
stats.inc (nano::stat::type::optimistic_scheduler, nano::stat::detail::loop);
while (predicate ())
{
debug_assert (!candidates.empty ());
auto candidate = candidates.front ();
candidates.pop_front ();
lock.unlock ();
run_one (transaction, candidate);
lock.lock ();
}
run_iterative (lock);
debug_assert (!lock.owns_lock ());
lock.lock ();
}
condition.wait_for (lock, network_constants.optimistic_activation_delay / 2, [this] () {
return stopped || predicate ();
});
}
}
void nano::scheduler::optimistic::run_one (secure::transaction const & transaction, entry const & candidate)
void nano::scheduler::optimistic::run_iterative (nano::unique_lock<nano::mutex> & lock)
{
debug_assert (lock.owns_lock ());
debug_assert (!mutex.try_lock ());
auto tops = snapshot (active.limit (nano::election_behavior::optimistic));
lock.unlock ();
auto transaction = ledger.tx_begin_read ();
for (auto const & candidate : tops)
{
if (stopped)
{
return;
}
transaction.refresh_if_needed ();
bool good = run_one (transaction, candidate);
if (!good)
{
// Remove no longer valid candidate
nano::lock_guard<nano::mutex> guard{ mutex };
auto & account_index = candidates.get<tag_account> ();
account_index.erase (candidate.account);
stats.inc (nano::stat::type::optimistic_scheduler, nano::stat::detail::erased);
}
}
}
bool nano::scheduler::optimistic::run_one (secure::transaction const & transaction, entry const & candidate)
{
auto block = ledger.any.block_get (transaction, ledger.any.account_head (transaction, candidate.account));
if (block)
@ -160,12 +202,13 @@ void nano::scheduler::optimistic::run_one (secure::transaction const & transacti
if (!node.block_confirmed_or_being_confirmed (transaction, block->hash ()))
{
// Try to insert it into AEC
// We check for AEC vacancy inside our predicate
auto result = node.active.insert (block, nano::election_behavior::optimistic);
stats.inc (nano::stat::type::optimistic_scheduler, result.inserted ? nano::stat::detail::insert : nano::stat::detail::insert_failed);
return true; // Activation attempted
}
}
return false; // Activation not attempted, block not found or already confirmed, should be erased from candidates
}
nano::container_info nano::scheduler::optimistic::container_info () const
@ -186,6 +229,7 @@ nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig &
toml.get ("enable", enable);
toml.get ("gap_threshold", gap_threshold);
toml.get ("max_size", max_size);
toml.get_duration ("activation_delay", activation_delay);
return toml.get_error ();
}
@ -195,6 +239,7 @@ nano::error nano::scheduler::optimistic_config::serialize (nano::tomlconfig & to
toml.put ("enable", enable, "Enable or disable optimistic elections\ntype:bool");
toml.put ("gap_threshold", gap_threshold, "Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation\ntype:uint64");
toml.put ("max_size", max_size, "Maximum number of candidates stored in memory\ntype:uint64");
toml.put ("activation_delay", activation_delay.count (), "How much to delay activation of optimistic elections to avoid interfering with election scheduler\ntype:milliseconds");
return toml.get_error ();
}

View file

@ -33,10 +33,13 @@ public:
bool enable{ true };
/** Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation */
std::size_t gap_threshold{ 32 };
uint64_t gap_threshold{ 16 };
/** Maximum number of candidates stored in memory */
std::size_t max_size{ 1024 * 64 };
std::size_t max_size{ 1024 * 4 };
/** How much to delay activation of optimistic elections to avoid interfering with election scheduler */
std::chrono::milliseconds activation_delay{ 1s };
};
class optimistic final
@ -67,7 +70,10 @@ private:
bool predicate () const;
void run ();
void run_one (secure::transaction const &, entry const & candidate);
void run_iterative (nano::unique_lock<nano::mutex> &);
bool run_one (secure::transaction const &, entry const & candidate);
std::deque<entry> snapshot (size_t max_count) const;
private: // Dependencies
optimistic_config const & config;
@ -81,25 +87,29 @@ private:
struct entry
{
nano::account account;
nano::clock::time_point timestamp;
uint64_t unconfirmed_height;
std::chrono::steady_clock::time_point timestamp;
};
// clang-format off
class tag_sequenced {};
class tag_account {};
class tag_unconfirmed_height {};
using ordered_candidates = boost::multi_index_container<entry,
mi::indexed_by<
mi::sequenced<mi::tag<tag_sequenced>>,
mi::hashed_unique<mi::tag<tag_account>,
mi::member<entry, nano::account, &entry::account>>
mi::member<entry, nano::account, &entry::account>>,
mi::ordered_non_unique<mi::tag<tag_unconfirmed_height>,
mi::member<entry, uint64_t, &entry::unconfirmed_height>, std::greater<>> // Descending
>>;
// clang-format on
/** Accounts eligible for optimistic scheduling */
ordered_candidates candidates;
bool stopped{ false };
std::atomic<bool> stopped{ false };
nano::condition_variable condition;
mutable nano::mutex mutex;
std::thread thread;

View file

@ -3,6 +3,7 @@
#include <nano/lib/utility.hpp>
#include <nano/node/local_vote_history.hpp>
#include <nano/node/network.hpp>
#include <nano/node/node.hpp>
#include <nano/node/nodeconfig.hpp>
#include <nano/node/transport/inproc.hpp>
#include <nano/node/vote_generator.hpp>
@ -16,21 +17,21 @@
#include <chrono>
nano::vote_generator::vote_generator (nano::node_config const & config_a, nano::node & node_a, nano::ledger & ledger_a, nano::wallets & wallets_a, nano::vote_processor & vote_processor_a, nano::local_vote_history & history_a, nano::network & network_a, nano::stats & stats_a, nano::logger & logger_a, bool is_final_a, std::shared_ptr<nano::transport::channel> inproc_channel_a) :
nano::vote_generator::vote_generator (vote_generator_config const & config_a, nano::node & node_a, nano::ledger & ledger_a, nano::wallets & wallets_a, nano::vote_processor & vote_processor_a, nano::local_vote_history & history_a, nano::network & network_a, nano::stats & stats_a, nano::logger & logger_a, bool is_final_a, std::shared_ptr<nano::transport::channel> inproc_channel_a) :
config (config_a),
node (node_a),
ledger (ledger_a),
wallets (wallets_a),
vote_processor (vote_processor_a),
history (history_a),
spacing_impl{ std::make_unique<nano::vote_spacing> (config_a.network_params.voting.delay) },
spacing_impl{ std::make_unique<nano::vote_spacing> (node_a.network_params.voting.delay) },
spacing{ *spacing_impl },
network (network_a),
stats (stats_a),
logger (logger_a),
is_final (is_final_a),
inproc_channel{ inproc_channel_a },
vote_generation_queue{ stats, nano::stat::type::vote_generator, is_final ? nano::thread_role::name::voting_final : nano::thread_role::name::voting, /* single threaded */ 1, /* max queue size */ 1024 * 32, /* max batch size */ 256 }
vote_generation_queue{ stats, nano::stat::type::vote_generator, is_final ? nano::thread_role::name::voting_final : nano::thread_role::name::voting, /* single threaded */ 1, config.max_queue, config.batch_size }
{
vote_generation_queue.process_batch = [this] (auto & batch) {
process_batch (batch);
@ -246,7 +247,7 @@ void nano::vote_generator::reply (nano::unique_lock<nano::mutex> & lock_a, reque
stats.add (nano::stat::type::requests, nano::stat::detail::requests_generated_hashes, stat::dir::in, hashes.size ());
vote (hashes, roots, [this, channel = request_a.second] (std::shared_ptr<nano::vote> const & vote_a) {
nano::confirm_ack confirm{ config.network_params.network, vote_a };
nano::confirm_ack confirm{ node.network_params.network, vote_a };
channel->send (confirm, nano::transport::traffic_type::vote_reply);
stats.inc (nano::stat::type::requests, nano::stat::detail::requests_generated_votes, stat::dir::in);
});
@ -292,8 +293,8 @@ void nano::vote_generator::run ()
nano::unique_lock<nano::mutex> lock{ mutex };
while (!stopped)
{
// Wait for at most vote_generator_delay in case no further notification is received
condition.wait_for (lock, config.vote_generator_delay, [this] () {
// Wait for at most delay in case no further notification is received
condition.wait_for (lock, config.delay, [this] () {
return stopped || broadcast_predicate () || !requests.empty ();
});
@ -315,7 +316,7 @@ void nano::vote_generator::run ()
if (broadcast_predicate ())
{
broadcast (lock);
next_broadcast = std::chrono::steady_clock::now () + config.vote_generator_delay;
next_broadcast = std::chrono::steady_clock::now () + config.delay;
}
if (!requests.empty ())
@ -363,3 +364,25 @@ nano::log::type nano::vote_generator::log_type () const
{
return is_final ? nano::log::type::vote_generator_final : nano::log::type::vote_generator;
}
/*
* vote_generator_config
*/
nano::error nano::vote_generator_config::serialize (nano::tomlconfig & toml) const
{
toml.put ("max_queue", max_queue, "Maximum number of entries in the vote generation queue. \ntype:uint64");
toml.put ("batch_size", batch_size, "Maximum number of entries to process in a single batch. \ntype:uint64");
toml.put ("delay", delay.count (), "Delay before votes are sent to allow for efficient bundling of hashes in votes. \ntype:milliseconds");
return toml.get_error ();
}
nano::error nano::vote_generator_config::deserialize (nano::tomlconfig & toml)
{
toml.get ("max_queue", max_queue);
toml.get ("batch_size", batch_size);
toml.get_duration ("delay", delay);
return toml.get_error ();
}

View file

@ -25,6 +25,18 @@ namespace mi = boost::multi_index;
namespace nano
{
class vote_generator_config final
{
public:
nano::error serialize (nano::tomlconfig & toml) const;
nano::error deserialize (nano::tomlconfig & toml);
public:
size_t max_queue{ 1024 * 32 };
size_t batch_size{ 256 };
std::chrono::milliseconds delay{ 100ms };
};
class vote_generator final
{
private:
@ -34,7 +46,7 @@ private:
std::chrono::steady_clock::time_point next_broadcast = { std::chrono::steady_clock::now () };
public:
vote_generator (nano::node_config const &, nano::node &, nano::ledger &, nano::wallets &, nano::vote_processor &, nano::local_vote_history &, nano::network &, nano::stats &, nano::logger &, bool is_final, std::shared_ptr<nano::transport::channel> inproc_channel);
vote_generator (vote_generator_config const &, nano::node &, nano::ledger &, nano::wallets &, nano::vote_processor &, nano::local_vote_history &, nano::network &, nano::stats &, nano::logger &, bool is_final, std::shared_ptr<nano::transport::channel> inproc_channel);
~vote_generator ();
/** Queue items for vote generation, or broadcast votes already in cache */
@ -63,7 +75,7 @@ private:
nano::log::type log_type () const;
private: // Dependencies
nano::node_config const & config;
vote_generator_config const & config;
nano::node & node;
nano::ledger & ledger;
nano::wallets & wallets;

View file

@ -86,7 +86,7 @@ std::unordered_map<nano::block_hash, nano::vote_code> nano::vote_router::vote (s
}
else
{
if (recently_confirmed.exists (hash))
if (recently_confirmed.contains (hash))
{
results[hash] = nano::vote_code::late;
}

View file

@ -901,7 +901,7 @@ std::shared_ptr<nano::block> nano::wallet::receive_action (nano::block_hash cons
nano::raw_key prv;
if (!store.fetch (transaction, account_a, prv))
{
logger.info (nano::log::type::wallet, "Receiving block {} from account {}, amount: {}",
logger.info (nano::log::type::wallet, "Receiving block: {} from account: {}, amount: {}",
send_hash_a.to_string (),
account_a.to_account (),
pending_info->amount.number ().convert_to<std::string> ());
@ -924,7 +924,7 @@ std::shared_ptr<nano::block> nano::wallet::receive_action (nano::block_hash cons
}
else
{
logger.warn (nano::log::type::wallet, "Unable to receive, wallet locked, block {} to account: {}",
logger.warn (nano::log::type::wallet, "Unable to receive, wallet locked, block: {} to account: {}",
send_hash_a.to_string (),
account_a.to_account ());
}
@ -932,19 +932,19 @@ std::shared_ptr<nano::block> nano::wallet::receive_action (nano::block_hash cons
else
{
// Ledger doesn't have this marked as available to receive anymore
logger.warn (nano::log::type::wallet, "Not receiving block {}, block already received", send_hash_a.to_string ());
logger.warn (nano::log::type::wallet, "Not receiving block: {}, block already received", send_hash_a.to_string ());
}
}
else
{
// Ledger doesn't have this block anymore.
logger.warn (nano::log::type::wallet, "Not receiving block {}, block no longer exists or pruned", send_hash_a.to_string ());
logger.warn (nano::log::type::wallet, "Not receiving block: {}, block no longer exists or pruned", send_hash_a.to_string ());
}
}
else
{
// Someone sent us something below the threshold of receiving
logger.warn (nano::log::type::wallet, "Not receiving block {} due to minimum receive threshold", send_hash_a.to_string ());
logger.warn (nano::log::type::wallet, "Not receiving block: {} due to minimum receive threshold", send_hash_a.to_string ());
}
if (block != nullptr)
{
@ -969,7 +969,7 @@ std::shared_ptr<nano::block> nano::wallet::change_action (nano::account const &
auto existing (store.find (transaction, source_a));
if (existing != store.end (transaction) && !wallets.node.ledger.any.account_head (block_transaction, source_a).is_zero ())
{
logger.info (nano::log::type::wallet, "Changing representative for account {} to {}",
logger.info (nano::log::type::wallet, "Changing representative for account: {} to: {}",
source_a.to_account (),
representative_a.to_account ());
@ -988,13 +988,13 @@ std::shared_ptr<nano::block> nano::wallet::change_action (nano::account const &
}
else
{
logger.warn (nano::log::type::wallet, "Changing representative for account {} failed, wallet locked or account not found",
logger.warn (nano::log::type::wallet, "Changing representative for account: {} failed, wallet locked or account not found",
source_a.to_account ());
}
}
else
{
logger.warn (nano::log::type::wallet, "Changing representative for account {} failed, wallet locked",
logger.warn (nano::log::type::wallet, "Changing representative for account: {} failed, wallet locked",
source_a.to_account ());
}
}
@ -1148,7 +1148,7 @@ bool nano::wallet::action_complete (std::shared_ptr<nano::block> const & block_a
auto required_difficulty{ wallets.node.network_params.work.threshold (block_a->work_version (), details_a) };
if (wallets.node.network_params.work.difficulty (*block_a) < required_difficulty)
{
logger.info (nano::log::type::wallet, "Cached or provided work for block {} account {} is invalid, regenerating...",
logger.info (nano::log::type::wallet, "Cached or provided work for block: {}, account {}: is invalid, regenerating...",
block_a->hash ().to_string (),
account_a.to_account ());
@ -1292,9 +1292,15 @@ bool nano::wallet::search_receivable (store::transaction const & wallet_transact
auto amount (pending.amount.number ());
if (wallets.node.config.receive_minimum.number () <= amount)
{
logger.info (nano::log::type::wallet, "Found a receivable block {} for account {}", hash.to_string (), pending.source.to_account ());
bool const confirmed = wallets.node.ledger.confirmed.block_exists_or_pruned (block_transaction, hash);
if (wallets.node.ledger.confirmed.block_exists_or_pruned (block_transaction, hash))
logger.info (nano::log::type::wallet, "Found a receivable block: {} ({}) for account: {} from: {}",
hash.to_string (),
confirmed ? "confirmed" : "unconfirmed",
key.account.to_account (),
pending.source.to_account ());
if (confirmed)
{
auto representative = store.representative (wallet_transaction_a);
// Receive confirmed block
@ -1371,7 +1377,7 @@ nano::public_key nano::wallet::change_seed (store::transaction const & transacti
if (count == 0)
{
count = deterministic_check (transaction_a, 0);
logger.info (nano::log::type::wallet, "Auto-detected {} accounts to generate", count);
logger.info (nano::log::type::wallet, "Auto-detected {} accounts to generate from seed", count);
}
for (uint32_t i (0); i < count; ++i)
{
@ -1416,7 +1422,7 @@ void nano::wallet::work_cache_blocking (nano::account const & account_a, nano::r
}
else if (!wallets.node.stopped)
{
logger.warn (nano::log::type::wallet, "Could not precache work for root {} due to work generation failure", root_a.to_string ());
logger.warn (nano::log::type::wallet, "Could not precache work for root: {} due to work generation failure", root_a.to_string ());
}
}
}

View file

@ -772,7 +772,7 @@ nano::websocket::message nano::websocket::message_builder::block_confirmed (std:
{
boost::property_tree::ptree election_node_l;
election_node_l.add ("duration", election_status.election_duration.count ());
election_node_l.add ("time", election_status.election_end.count ());
election_node_l.add ("time", milliseconds_since_epoch (election_status.election_end));
election_node_l.add ("tally", election_status.tally.to_string_dec ());
election_node_l.add ("final", election_status.final_tally.to_string_dec ());
election_node_l.add ("blocks", std::to_string (election_status.block_count));

View file

@ -2,7 +2,7 @@
#include <nano/lib/tomlconfig.hpp>
#include <nano/node/websocketconfig.hpp>
nano::websocket::config::config (nano::network_constants & network_constants) :
nano::websocket::config::config (nano::network_constants const & network_constants) :
network_constants{ network_constants },
port{ network_constants.default_websocket_port },
address{ boost::asio::ip::address_v6::loopback ().to_string () }

View file

@ -14,10 +14,12 @@ namespace websocket
class config final
{
public:
config (nano::network_constants & network_constants);
nano::error deserialize_toml (nano::tomlconfig & toml_a);
nano::error serialize_toml (nano::tomlconfig & toml) const;
nano::network_constants & network_constants;
config (nano::network_constants const &);
nano::error deserialize_toml (nano::tomlconfig &);
nano::error serialize_toml (nano::tomlconfig &) const;
nano::network_constants const & network_constants;
bool enabled{ false };
uint16_t port;
std::string address;

View file

@ -520,7 +520,7 @@ TEST (history, short_text)
nano::logger logger;
nano::stats stats{ logger };
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
{
auto transaction (ledger.tx_begin_write ());
nano::keypair key;
@ -557,7 +557,7 @@ TEST (history, pruned_source)
nano::logger logger;
nano::stats stats{ logger };
auto store = nano::make_store (logger, nano::unique_path (), nano::dev::constants);
nano::ledger ledger (*store, nano::dev::constants, stats, logger);
nano::ledger ledger (*store, nano::dev::network_params, stats, logger);
ledger.pruning = true;
nano::block_hash next_pruning;
// Basic pruning for legacy blocks. Previous block is pruned, source is pruned
@ -833,32 +833,48 @@ TEST (wallet, import)
TEST (wallet, republish)
{
nano_qt::eventloop_processor processor;
nano::test::system system (2);
// Configure nodes to disable bootstrap, election schedulers, and other background processes for test isolation
nano::node_config node_config;
node_config.bootstrap.enable = false;
node_config.priority_scheduler.enable = false;
node_config.optimistic_scheduler.enable = false;
node_config.hinted_scheduler.enable = false;
nano::test::system system;
auto & node1 = *system.add_node (node_config);
auto & node2 = *system.add_node (node_config);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key;
nano::block_hash hash;
{
auto transaction = system.nodes[0]->ledger.tx_begin_write ();
auto latest (system.nodes[0]->ledger.any.account_head (transaction, nano::dev::genesis_key.pub));
auto transaction = node1.ledger.tx_begin_write ();
auto latest (node1.ledger.any.account_head (transaction, nano::dev::genesis_key.pub));
auto block = std::make_shared<nano::send_block> (latest, key.pub, 0, nano::dev::genesis_key.prv, nano::dev::genesis_key.pub, *system.work.generate (latest));
hash = block->hash ();
ASSERT_EQ (nano::block_status::progress, system.nodes[0]->ledger.process (transaction, block));
ASSERT_EQ (nano::block_status::progress, node1.ledger.process (transaction, block));
}
// Ensure node2 does not have the block initially
ASSERT_FALSE (node2.ledger.any.block_exists (node2.ledger.tx_begin_read (), hash));
auto account (nano::dev::genesis_key.pub);
auto wallet (std::make_shared<nano_qt::wallet> (*test_application, processor, *system.nodes[0], system.wallet (0), account));
auto wallet (std::make_shared<nano_qt::wallet> (*test_application, processor, node1, system.wallet (0), account));
wallet->start ();
QTest::mouseClick (wallet->show_advanced, Qt::LeftButton);
ASSERT_EQ (wallet->advanced.window, wallet->main_stack->currentWidget ());
QTest::mouseClick (wallet->advanced.block_viewer, Qt::LeftButton);
ASSERT_EQ (wallet->block_viewer.window, wallet->main_stack->currentWidget ());
QTest::keyClicks (wallet->block_viewer.hash, hash.to_string ().c_str ());
// Verify the block exists in node1 before rebroadcast
ASSERT_TRUE (node1.ledger.any.block_exists (node1.ledger.tx_begin_read (), hash));
QTest::mouseClick (wallet->block_viewer.rebroadcast, Qt::LeftButton);
ASSERT_FALSE (system.nodes[1]->balance (nano::dev::genesis_key.pub).is_zero ());
system.deadline_set (10s);
while (system.nodes[1]->balance (nano::dev::genesis_key.pub).is_zero ())
{
ASSERT_NO_ERROR (system.poll ());
}
// Now verify that the block was received by node2 due to the rebroadcast
ASSERT_TIMELY (10s, node2.ledger.any.block_exists (node2.ledger.tx_begin_read (), hash));
}
TEST (wallet, ignore_empty_adhoc)

View file

@ -6820,8 +6820,7 @@ TEST (rpc, confirmation_active)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
node1->process_active (send1);
node1->process_active (send2);
nano::test::process (*node1, { send1, send2 });
ASSERT_TRUE (nano::test::start_elections (system, *node1, { send1, send2 }));
ASSERT_EQ (2, node1->active.size ());
auto election (node1->active.election (send1->qualified_root ()));

View file

@ -90,7 +90,7 @@ std::shared_ptr<nano::block> & nano::dev::genesis = nano::dev::constants.genesis
*
*/
nano::work_thresholds const & nano::work_thresholds_for_network (nano::networks network_type)
nano::work_thresholds nano::work_thresholds_for_network (nano::networks network_type)
{
switch (network_type)
{
@ -110,7 +110,7 @@ nano::work_thresholds const & nano::work_thresholds_for_network (nano::networks
nano::network_params::network_params (nano::networks network_type) :
work{ work_thresholds_for_network (network_type) },
network{ work, network_type },
ledger{ work, network_type },
ledger{ network_type },
voting{ network },
node{ network },
portmapping{ network },
@ -125,8 +125,7 @@ nano::network_params::network_params (nano::networks network_type) :
*
*/
nano::ledger_constants::ledger_constants (nano::work_thresholds & work, nano::networks network_type) :
work{ work },
nano::ledger_constants::ledger_constants (nano::networks network_type) :
zero_key{ "0" },
nano_beta_account{ beta_public_key_data },
nano_live_account{ live_public_key_data },
@ -249,7 +248,7 @@ nano::hardened_constants::hardened_constants () :
*
*/
nano::node_constants::node_constants (nano::network_constants & network_constants)
nano::node_constants::node_constants (nano::network_constants const & network_constants)
{
backup_interval = std::chrono::minutes (5);
search_pending_interval = network_constants.is_dev_network () ? std::chrono::seconds (1) : std::chrono::seconds (5 * 60);
@ -263,7 +262,7 @@ nano::node_constants::node_constants (nano::network_constants & network_constant
*
*/
nano::voting_constants::voting_constants (nano::network_constants & network_constants) :
nano::voting_constants::voting_constants (nano::network_constants const & network_constants) :
max_cache{ network_constants.is_dev_network () ? 256U : 128U * 1024 },
delay{ network_constants.is_dev_network () ? 1 : 15 }
{
@ -273,7 +272,7 @@ nano::voting_constants::voting_constants (nano::network_constants & network_cons
*
*/
nano::portmapping_constants::portmapping_constants (nano::network_constants & network_constants)
nano::portmapping_constants::portmapping_constants (nano::network_constants const & network_constants)
{
lease_duration = std::chrono::seconds (1787); // ~30 minutes
health_check_period = std::chrono::seconds (53);
@ -283,7 +282,7 @@ nano::portmapping_constants::portmapping_constants (nano::network_constants & ne
*
*/
nano::bootstrap_constants::bootstrap_constants (nano::network_constants & network_constants)
nano::bootstrap_constants::bootstrap_constants (nano::network_constants const & network_constants)
{
lazy_max_pull_blocks = network_constants.is_dev_network () ? 2 : 512;
lazy_min_pull_blocks = network_constants.is_dev_network () ? 1 : 32;

View file

@ -166,8 +166,8 @@ class network_params;
class ledger_constants
{
public:
ledger_constants (nano::work_thresholds &, nano::networks);
nano::work_thresholds & work;
ledger_constants (nano::networks);
nano::keypair zero_key;
nano::account nano_beta_account;
nano::account nano_live_account;
@ -207,7 +207,8 @@ private:
class node_constants
{
public:
node_constants (nano::network_constants & network_constants);
explicit node_constants (nano::network_constants const &);
std::chrono::minutes backup_interval;
std::chrono::seconds search_pending_interval;
std::chrono::minutes unchecked_cleaning_interval;
@ -223,7 +224,8 @@ public:
class voting_constants
{
public:
voting_constants (nano::network_constants & network_constants);
explicit voting_constants (nano::network_constants const &);
size_t const max_cache;
std::chrono::seconds const delay;
};
@ -232,7 +234,8 @@ public:
class portmapping_constants
{
public:
portmapping_constants (nano::network_constants & network_constants);
explicit portmapping_constants (nano::network_constants const &);
// Timeouts are primes so they infrequently happen at the same time
std::chrono::seconds lease_duration;
std::chrono::seconds health_check_period;
@ -242,7 +245,8 @@ public:
class bootstrap_constants
{
public:
bootstrap_constants (nano::network_constants & network_constants);
explicit bootstrap_constants (nano::network_constants const &);
uint32_t lazy_max_pull_blocks;
uint32_t lazy_min_pull_blocks;
unsigned frontier_retry_limit;
@ -252,7 +256,7 @@ public:
uint32_t default_frontiers_age_seconds;
};
nano::work_thresholds const & work_thresholds_for_network (nano::networks);
nano::work_thresholds work_thresholds_for_network (nano::networks);
/** Constants whose value depends on the active network */
class network_params

View file

@ -30,9 +30,10 @@
#include <cryptopp/words.h>
nano::ledger::ledger (nano::store::component & store_a, nano::ledger_constants & constants_a, nano::stats & stats_a, nano::logger & logger_a, nano::generate_cache_flags generate_cache_flags_a, nano::uint128_t min_rep_weight_a, uint64_t max_backlog_a) :
nano::ledger::ledger (nano::store::component & store_a, nano::network_params const & params_a, nano::stats & stats_a, nano::logger & logger_a, nano::generate_cache_flags generate_cache_flags_a, nano::uint128_t min_rep_weight_a, uint64_t max_backlog_a) :
constants{ params_a.ledger },
work{ params_a.work },
store{ store_a },
constants{ constants_a },
stats{ stats_a },
logger{ logger_a },
rep_weights{ store_a.rep_weight, min_rep_weight_a },
@ -356,11 +357,12 @@ void nano::ledger::confirm_one (secure::write_transaction & transaction, nano::b
stats.inc (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed);
}
nano::block_status nano::ledger::process (secure::write_transaction const & transaction_a, std::shared_ptr<nano::block> block_a)
nano::block_status nano::ledger::process (secure::write_transaction const & transaction, std::shared_ptr<nano::block> block)
{
debug_assert (!constants.work.validate_entry (*block_a) || constants.genesis == nano::dev::genesis);
ledger_processor processor (transaction_a, *this);
block_a->visit (processor);
debug_assert (!work.validate_entry (*block) || constants.genesis == nano::dev::genesis);
ledger_processor processor (transaction, *this);
block->visit (processor);
if (processor.result == nano::block_status::progress)
{
++cache.block_count;

View file

@ -4,6 +4,7 @@
#include <nano/lib/numbers.hpp>
#include <nano/lib/timer.hpp>
#include <nano/secure/account_info.hpp>
#include <nano/secure/common.hpp>
#include <nano/secure/fwd.hpp>
#include <nano/secure/generate_cache_flags.hpp>
#include <nano/secure/pending_info.hpp>
@ -37,7 +38,7 @@ class ledger final
friend class receivable_iterator;
public:
ledger (nano::store::component &, nano::ledger_constants &, nano::stats &, nano::logger &, nano::generate_cache_flags = {}, nano::uint128_t min_rep_weight = 0, uint64_t max_backlog = 0);
ledger (nano::store::component &, nano::network_params const &, nano::stats &, nano::logger &, nano::generate_cache_flags = {}, nano::uint128_t min_rep_weight = 0, uint64_t max_backlog = 0);
~ledger ();
/** Start read-write transaction */
@ -101,8 +102,9 @@ public:
public:
static nano::uint128_t const unit;
nano::ledger_constants const & constants;
nano::work_thresholds const & work;
nano::store::component & store;
nano::ledger_constants & constants;
nano::stats & stats;
nano::logger & logger;

View file

@ -43,7 +43,7 @@ void nano::ledger_processor::send_block (nano::send_block & block_a)
if (result == nano::block_status::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
result = ledger.work.difficulty (block_a) >= ledger.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result == nano::block_status::progress)
{
debug_assert (!validate_message (account, hash, block_a.signature));
@ -107,7 +107,7 @@ void nano::ledger_processor::receive_block (nano::receive_block & block_a)
if (result == nano::block_status::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
result = ledger.work.difficulty (block_a) >= ledger.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result == nano::block_status::progress)
{
auto new_balance (info->balance.number () + pending.value ().amount.number ());
@ -160,7 +160,7 @@ void nano::ledger_processor::open_block (nano::open_block & block_a)
if (result == nano::block_status::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
result = ledger.work.difficulty (block_a) >= ledger.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result == nano::block_status::progress)
{
ledger.store.pending.del (transaction, key);
@ -205,7 +205,7 @@ void nano::ledger_processor::change_block (nano::change_block & block_a)
if (result == nano::block_status::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
result = ledger.work.difficulty (block_a) >= ledger.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result == nano::block_status::progress)
{
debug_assert (!validate_message (account, hash, block_a.signature));
@ -325,7 +325,7 @@ void nano::ledger_processor::state_block_impl (nano::state_block & block_a)
if (result == nano::block_status::progress)
{
nano::block_details block_details (epoch, is_send, is_receive, false);
result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
result = ledger.work.difficulty (block_a) >= ledger.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result == nano::block_status::progress)
{
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::state_block);
@ -415,7 +415,7 @@ void nano::ledger_processor::epoch_block_impl (nano::state_block & block_a)
if (result == nano::block_status::progress)
{
nano::block_details block_details (epoch, false, false, true);
result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
result = ledger.work.difficulty (block_a) >= ledger.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result == nano::block_status::progress)
{
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block);

Some files were not shown because too many files have changed in this diff Show more