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