From 14a8f556e0f114b508829104795ef10c8f8ec477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Sat, 12 Oct 2024 12:32:20 +0200 Subject: [PATCH 1/5] Contains hash functionality --- nano/core_test/active_elections.cpp | 1 + nano/core_test/election_scheduler.cpp | 7 +++++ nano/core_test/vote_cache.cpp | 12 ++++++++ nano/node/scheduler/bucket.cpp | 6 ++++ nano/node/scheduler/bucket.hpp | 43 +++++++++++++++++---------- nano/node/scheduler/component.cpp | 5 ++++ nano/node/scheduler/component.hpp | 3 ++ nano/node/scheduler/manual.cpp | 8 +++++ nano/node/scheduler/manual.hpp | 2 ++ nano/node/scheduler/priority.cpp | 7 +++++ nano/node/scheduler/priority.hpp | 1 + nano/node/vote_cache.cpp | 8 +++++ nano/node/vote_cache.hpp | 1 + nano/node/vote_router.cpp | 7 +++++ nano/node/vote_router.hpp | 7 +++-- 15 files changed, 100 insertions(+), 18 deletions(-) diff --git a/nano/core_test/active_elections.cpp b/nano/core_test/active_elections.cpp index 32d2a89641..fc94228e68 100644 --- a/nano/core_test/active_elections.cpp +++ b/nano/core_test/active_elections.cpp @@ -514,6 +514,7 @@ TEST (inactive_votes_cache, election_start) node.vote_processor.vote (vote2, std::make_shared (node, node)); // Only election for send1 should start, other blocks are missing dependencies and don't have enough final weight ASSERT_TIMELY_EQ (5s, 1, node.active.size ()); + ASSERT_TRUE (node.vote_router.contains (send1->hash ())); ASSERT_TRUE (node.vote_router.active (send1->hash ())); // Confirm elections with weight quorum diff --git a/nano/core_test/election_scheduler.cpp b/nano/core_test/election_scheduler.cpp index 422df99719..a17620b444 100644 --- a/nano/core_test/election_scheduler.cpp +++ b/nano/core_test/election_scheduler.cpp @@ -269,7 +269,9 @@ TEST (election_scheduler_bucket, insert_one) nano::scheduler::priority_bucket_config bucket_config; nano::scheduler::bucket bucket{ 0, bucket_config, node.active, node.stats }; + ASSERT_FALSE (bucket.contains (block0 ()->hash ())); ASSERT_TRUE (bucket.push (1000, block0 ())); + ASSERT_TRUE (bucket.contains (block0 ()->hash ())); ASSERT_FALSE (bucket.empty ()); ASSERT_EQ (1, bucket.size ()); auto blocks = bucket.blocks (); @@ -320,10 +322,15 @@ TEST (election_scheduler_bucket, max_blocks) }; nano::scheduler::bucket bucket{ 0, bucket_config, node.active, node.stats }; ASSERT_TRUE (bucket.push (2000, block0 ())); + ASSERT_TRUE (bucket.contains (block0 ()->hash ())); ASSERT_TRUE (bucket.push (900, block1 ())); + ASSERT_TRUE (bucket.contains (block1 ()->hash ())); ASSERT_FALSE (bucket.push (3000, block2 ())); + ASSERT_FALSE (bucket.contains (block2 ()->hash ())); ASSERT_TRUE (bucket.push (1001, block3 ())); // Evicts 2000 + ASSERT_FALSE (bucket.contains (block0 ()->hash ())); ASSERT_TRUE (bucket.push (1000, block0 ())); // Evicts 1001 + ASSERT_FALSE (bucket.contains (block3 ()->hash ())); ASSERT_EQ (2, bucket.size ()); auto blocks = bucket.blocks (); ASSERT_EQ (2, blocks.size ()); diff --git a/nano/core_test/vote_cache.cpp b/nano/core_test/vote_cache.cpp index 711f8a31b4..4817e737a5 100644 --- a/nano/core_test/vote_cache.cpp +++ b/nano/core_test/vote_cache.cpp @@ -57,7 +57,9 @@ TEST (vote_cache, insert_one_hash) auto rep1 = create_rep (7); auto hash1 = nano::test::random_hash (); auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024); + ASSERT_FALSE (vote_cache.contains (hash1)); vote_cache.insert (vote1); + ASSERT_TRUE (vote_cache.contains (hash1)); ASSERT_EQ (1, vote_cache.size ()); auto peek1 = vote_cache.find (hash1); @@ -265,9 +267,14 @@ TEST (vote_cache, erase) auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024); auto vote2 = nano::test::make_vote (rep2, { hash2 }, 1024 * 1024); auto vote3 = nano::test::make_vote (rep3, { hash3 }, 1024 * 1024); + ASSERT_TRUE (vote_cache.empty ()); + ASSERT_FALSE (vote_cache.contains (hash1)); vote_cache.insert (vote1); vote_cache.insert (vote2); vote_cache.insert (vote3); + ASSERT_TRUE (vote_cache.contains (hash1)); + ASSERT_TRUE (vote_cache.contains (hash2)); + ASSERT_TRUE (vote_cache.contains (hash3)); ASSERT_EQ (3, vote_cache.size ()); ASSERT_FALSE (vote_cache.empty ()); ASSERT_FALSE (vote_cache.find (hash1).empty ()); @@ -275,11 +282,16 @@ TEST (vote_cache, erase) ASSERT_FALSE (vote_cache.find (hash3).empty ()); vote_cache.erase (hash2); ASSERT_EQ (2, vote_cache.size ()); + ASSERT_FALSE (vote_cache.contains (hash2)); ASSERT_FALSE (vote_cache.find (hash1).empty ()); ASSERT_TRUE (vote_cache.find (hash2).empty ()); ASSERT_FALSE (vote_cache.find (hash3).empty ()); vote_cache.erase (hash1); vote_cache.erase (hash3); + ASSERT_EQ (0, vote_cache.size ()); + ASSERT_FALSE (vote_cache.contains (hash1)); + ASSERT_FALSE (vote_cache.contains (hash2)); + ASSERT_FALSE (vote_cache.contains (hash3)); ASSERT_TRUE (vote_cache.find (hash1).empty ()); ASSERT_TRUE (vote_cache.find (hash2).empty ()); ASSERT_TRUE (vote_cache.find (hash3).empty ()); diff --git a/nano/node/scheduler/bucket.cpp b/nano/node/scheduler/bucket.cpp index f86fd4bf55..31a0152b3b 100644 --- a/nano/node/scheduler/bucket.cpp +++ b/nano/node/scheduler/bucket.cpp @@ -133,6 +133,12 @@ bool nano::scheduler::bucket::push (uint64_t time, std::shared_ptr return inserted; } +bool nano::scheduler::bucket::contains (nano::block_hash const & hash) const +{ + nano::lock_guard lock{ mutex }; + return queue.get ().contains (hash); +} + size_t nano::scheduler::bucket::size () const { nano::lock_guard lock{ mutex }; diff --git a/nano/node/scheduler/bucket.hpp b/nano/node/scheduler/bucket.hpp index a95878dabf..e9b6d48d87 100644 --- a/nano/node/scheduler/bucket.hpp +++ b/nano/node/scheduler/bucket.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -20,13 +21,6 @@ namespace mi = boost::multi_index; -namespace nano -{ -class election; -class active_elections; -class block; -} - namespace nano::scheduler { class priority_bucket_config final @@ -67,6 +61,7 @@ class bucket final bool push (uint64_t time, std::shared_ptr block); + bool contains (nano::block_hash const &) const; size_t size () const; size_t election_count () const; bool empty () const; @@ -90,11 +85,33 @@ class bucket final uint64_t time; std::shared_ptr block; - bool operator< (block_entry const & other_a) const; - bool operator== (block_entry const & other_a) const; + bool operator< (block_entry const & other) const; + bool operator== (block_entry const & other) const; + + nano::block_hash hash () const + { + return block->hash (); + } }; - std::set queue; + // clang-format off + class tag_sequenced {}; + class tag_root {}; + class tag_priority {}; + class tag_hash {}; + // clang-format on + + // clang-format off + using ordered_blocks = boost::multi_index_container, + mi::identity>, + mi::hashed_unique, + mi::const_mem_fun> + >>; + // clang-format on + + ordered_blocks queue; private: // Elections struct election_entry @@ -105,10 +122,6 @@ class bucket final }; // clang-format off - class tag_sequenced {}; - class tag_root {}; - class tag_priority {}; - using ordered_elections = boost::multi_index_container>, @@ -124,4 +137,4 @@ class bucket final private: mutable nano::mutex mutex; }; -} // namespace nano::scheduler +} diff --git a/nano/node/scheduler/component.cpp b/nano/node/scheduler/component.cpp index 2d8cf5633a..2de1556054 100644 --- a/nano/node/scheduler/component.cpp +++ b/nano/node/scheduler/component.cpp @@ -43,6 +43,11 @@ void nano::scheduler::component::stop () priority.stop (); } +bool nano::scheduler::component::contains (nano::block_hash const & hash) const +{ + return manual.contains (hash) || priority.contains (hash); +} + nano::container_info nano::scheduler::component::container_info () const { nano::container_info info; diff --git a/nano/node/scheduler/component.hpp b/nano/node/scheduler/component.hpp index 96de3d94ae..1af64ff004 100644 --- a/nano/node/scheduler/component.hpp +++ b/nano/node/scheduler/component.hpp @@ -16,6 +16,9 @@ class component final void start (); void stop (); + /// Does the block exist in any of the schedulers + bool contains (nano::block_hash const & hash) const; + nano::container_info container_info () const; private: diff --git a/nano/node/scheduler/manual.cpp b/nano/node/scheduler/manual.cpp index 5b1bec4f03..f2fa3d1e29 100644 --- a/nano/node/scheduler/manual.cpp +++ b/nano/node/scheduler/manual.cpp @@ -46,6 +46,14 @@ void nano::scheduler::manual::push (std::shared_ptr const & block_a notify (); } +bool nano::scheduler::manual::contains (nano::block_hash const & hash) const +{ + nano::lock_guard lock{ mutex }; + return std::any_of (queue.cbegin (), queue.cend (), [&hash] (auto const & item) { + return std::get<0> (item)->hash () == hash; + }); +} + bool nano::scheduler::manual::predicate () const { return !queue.empty (); diff --git a/nano/node/scheduler/manual.hpp b/nano/node/scheduler/manual.hpp index 9fe527e50f..c62f215bd5 100644 --- a/nano/node/scheduler/manual.hpp +++ b/nano/node/scheduler/manual.hpp @@ -37,6 +37,8 @@ class manual final // Call action with confirmed block, may be different than what we started with void push (std::shared_ptr const &, boost::optional const & = boost::none); + bool contains (nano::block_hash const &) const; + nano::container_info container_info () const; }; } diff --git a/nano/node/scheduler/priority.cpp b/nano/node/scheduler/priority.cpp index 6bc9091e64..a40816d2de 100644 --- a/nano/node/scheduler/priority.cpp +++ b/nano/node/scheduler/priority.cpp @@ -184,6 +184,13 @@ bool nano::scheduler::priority::activate_successors (secure::transaction const & return result; } +bool nano::scheduler::priority::contains (nano::block_hash const & hash) const +{ + return std::any_of (buckets.begin (), buckets.end (), [&hash] (auto const & bucket) { + return bucket->contains (hash); + }); +} + void nano::scheduler::priority::notify () { condition.notify_all (); diff --git a/nano/node/scheduler/priority.hpp b/nano/node/scheduler/priority.hpp index 62647336a4..8085281644 100644 --- a/nano/node/scheduler/priority.hpp +++ b/nano/node/scheduler/priority.hpp @@ -40,6 +40,7 @@ class priority final bool activate (nano::secure::transaction const &, nano::account const &, nano::account_info const &, nano::confirmation_height_info const &); bool activate_successors (nano::secure::transaction const &, nano::block const &); + bool contains (nano::block_hash const &) const; void notify (); std::size_t size () const; bool empty () const; diff --git a/nano/node/vote_cache.cpp b/nano/node/vote_cache.cpp index 4fedd3a04c..89ddd8e650 100644 --- a/nano/node/vote_cache.cpp +++ b/nano/node/vote_cache.cpp @@ -202,6 +202,14 @@ std::vector> nano::vote_cache::find (const nano::blo return {}; } +bool nano::vote_cache::contains (const nano::block_hash & hash) const +{ + nano::lock_guard lock{ mutex }; + + auto & cache_by_hash = cache.get (); + return cache_by_hash.find (hash) != cache_by_hash.end (); +} + bool nano::vote_cache::erase (const nano::block_hash & hash) { nano::lock_guard lock{ mutex }; diff --git a/nano/node/vote_cache.hpp b/nano/node/vote_cache.hpp index b371941ac7..93209a3203 100644 --- a/nano/node/vote_cache.hpp +++ b/nano/node/vote_cache.hpp @@ -126,6 +126,7 @@ class vote_cache final * Tries to find an entry associated with block hash */ std::vector> find (nano::block_hash const & hash) const; + bool contains (nano::block_hash const & hash) const; /** * Removes an entry associated with block hash, does nothing if entry does not exist diff --git a/nano/node/vote_router.cpp b/nano/node/vote_router.cpp index 0f80e4995a..75d641229b 100644 --- a/nano/node/vote_router.cpp +++ b/nano/node/vote_router.cpp @@ -156,6 +156,13 @@ std::shared_ptr nano::vote_router::election (nano::block_hash co return nullptr; } +// This is meant to be a fast check and may return false positives if weak pointers have expired, but we don't care about that here +bool nano::vote_router::contains (nano::block_hash const & hash) const +{ + std::shared_lock lock{ mutex }; + return elections.contains (hash); +} + void nano::vote_router::start () { thread = std::thread{ [this] () { diff --git a/nano/node/vote_router.hpp b/nano/node/vote_router.hpp index 37c6ef87c0..e74f06705d 100644 --- a/nano/node/vote_router.hpp +++ b/nano/node/vote_router.hpp @@ -40,6 +40,9 @@ class vote_router final vote_router (nano::vote_cache & cache, nano::recently_confirmed_cache & recently_confirmed); ~vote_router (); + void start (); + void stop (); + // Add a route for 'hash' to 'election' // Existing routes will be replaced // Election must hold the block for the hash being passed in @@ -55,9 +58,7 @@ class vote_router final std::unordered_map vote (std::shared_ptr const &, nano::vote_source = nano::vote_source::live, nano::block_hash filter = { 0 }); bool active (nano::block_hash const & hash) const; std::shared_ptr election (nano::block_hash const & hash) const; - - void start (); - void stop (); + bool contains (nano::block_hash const & hash) const; using vote_processed_event_t = nano::observer_set const &, nano::vote_source, std::unordered_map const &>; vote_processed_event_t vote_processed; From 1179b30aba96ee249b10c84a26782c88a9271f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:46:30 +0200 Subject: [PATCH 2/5] Introduce bucketing component --- nano/core_test/CMakeLists.txt | 1 + nano/core_test/bucketing.cpp | 55 +++++++++++++++++++ nano/core_test/election_scheduler.cpp | 3 +- nano/lib/numbers.hpp | 3 ++ nano/node/CMakeLists.txt | 2 + nano/node/bucketing.cpp | 51 ++++++++++++++++++ nano/node/bucketing.hpp | 21 ++++++++ nano/node/fwd.hpp | 1 + nano/node/node.cpp | 6 ++- nano/node/node.hpp | 2 + nano/node/scheduler/bucket.cpp | 6 +-- nano/node/scheduler/bucket.hpp | 12 ++--- nano/node/scheduler/component.cpp | 4 +- nano/node/scheduler/component.hpp | 2 +- nano/node/scheduler/priority.cpp | 78 ++++++++------------------- nano/node/scheduler/priority.hpp | 7 +-- nano/secure/ledger.cpp | 15 ++++++ nano/secure/ledger.hpp | 7 +++ 18 files changed, 201 insertions(+), 75 deletions(-) create mode 100644 nano/core_test/bucketing.cpp create mode 100644 nano/node/bucketing.cpp create mode 100644 nano/node/bucketing.hpp diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index bb4e6e9a09..dcc509212b 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable( blockprocessor.cpp bootstrap.cpp bootstrap_server.cpp + bucketing.cpp cli.cpp confirmation_solicitor.cpp confirming_set.cpp diff --git a/nano/core_test/bucketing.cpp b/nano/core_test/bucketing.cpp new file mode 100644 index 0000000000..94a6b41587 --- /dev/null +++ b/nano/core_test/bucketing.cpp @@ -0,0 +1,55 @@ +#include + +#include + +#include + +TEST (bucketing, construction) +{ + nano::bucketing bucketing; + ASSERT_EQ (63, bucketing.size ()); +} + +TEST (bucketing, zero_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (0, bucketing.bucket_index (0)); +} + +TEST (bucketing, raw_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (0, bucketing.bucket_index (nano::raw_ratio)); +} + +TEST (bucketing, nano_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (14, bucketing.bucket_index (nano::nano_ratio)); +} + +TEST (bucketing, Knano_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (49, bucketing.bucket_index (nano::Knano_ratio)); +} + +TEST (bucketing, max_index) +{ + nano::bucketing bucketing; + ASSERT_EQ (62, bucketing.bucket_index (std::numeric_limits::max ())); +} + +TEST (bucketing, indices) +{ + nano::bucketing bucketing; + auto indices = bucketing.bucket_indices (); + ASSERT_EQ (63, indices.size ()); + ASSERT_EQ (indices.size (), bucketing.size ()); + + // Check that the indices are in ascending order + ASSERT_TRUE (std::adjacent_find (indices.begin (), indices.end (), [] (auto const & lhs, auto const & rhs) { + return lhs >= rhs; + }) + == indices.end ()); +} \ No newline at end of file diff --git a/nano/core_test/election_scheduler.cpp b/nano/core_test/election_scheduler.cpp index a17620b444..3e4567b706 100644 --- a/nano/core_test/election_scheduler.cpp +++ b/nano/core_test/election_scheduler.cpp @@ -256,8 +256,7 @@ TEST (election_scheduler_bucket, construction) auto & node = *system.add_node (); nano::scheduler::priority_bucket_config bucket_config; - nano::scheduler::bucket bucket{ nano::Knano_ratio, bucket_config, node.active, node.stats }; - ASSERT_EQ (nano::Knano_ratio, bucket.minimum_balance); + nano::scheduler::bucket bucket{ 0, bucket_config, node.active, node.stats }; ASSERT_TRUE (bucket.empty ()); ASSERT_EQ (0, bucket.size ()); } diff --git a/nano/lib/numbers.hpp b/nano/lib/numbers.hpp index 26b6f14444..8220305875 100644 --- a/nano/lib/numbers.hpp +++ b/nano/lib/numbers.hpp @@ -24,6 +24,9 @@ nano::uint128_t const Knano_ratio = nano::uint128_t ("10000000000000000000000000 nano::uint128_t const nano_ratio = nano::uint128_t ("1000000000000000000000000000000"); // 10^30 = 1 nano nano::uint128_t const raw_ratio = nano::uint128_t ("1"); // 10^0 +using bucket_index = uint64_t; +using priority_timestamp = uint64_t; // Priority within the bucket + class uint128_union { public: diff --git a/nano/node/CMakeLists.txt b/nano/node/CMakeLists.txt index f50649b047..86ef4bd845 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -22,6 +22,8 @@ add_library( bandwidth_limiter.cpp blockprocessor.hpp blockprocessor.cpp + bucketing.hpp + bucketing.cpp bootstrap_weights_beta.hpp bootstrap_weights_live.hpp bootstrap/account_sets.hpp diff --git a/nano/node/bucketing.cpp b/nano/node/bucketing.cpp new file mode 100644 index 0000000000..271c1e1f8b --- /dev/null +++ b/nano/node/bucketing.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +nano::bucketing::bucketing () +{ + auto build_region = [this] (uint128_t const & begin, uint128_t const & end, size_t count) { + auto width = (end - begin) / count; + for (auto i = 0; i < count; ++i) + { + minimums.push_back (begin + i * width); + } + }; + + minimums.push_back (uint128_t{ 0 }); + build_region (uint128_t{ 1 } << 79, uint128_t{ 1 } << 88, 1); + build_region (uint128_t{ 1 } << 88, uint128_t{ 1 } << 92, 2); + build_region (uint128_t{ 1 } << 92, uint128_t{ 1 } << 96, 4); + build_region (uint128_t{ 1 } << 96, uint128_t{ 1 } << 100, 8); + build_region (uint128_t{ 1 } << 100, uint128_t{ 1 } << 104, 16); + build_region (uint128_t{ 1 } << 104, uint128_t{ 1 } << 108, 16); + build_region (uint128_t{ 1 } << 108, uint128_t{ 1 } << 112, 8); + build_region (uint128_t{ 1 } << 112, uint128_t{ 1 } << 116, 4); + build_region (uint128_t{ 1 } << 116, uint128_t{ 1 } << 120, 2); + minimums.push_back (uint128_t{ 1 } << 120); + + for (auto i = 0; i < minimums.size (); ++i) + { + indices.push_back (i); + } +} + +nano::bucket_index nano::bucketing::bucket_index (nano::amount balance) const +{ + release_assert (!minimums.empty ()); + auto it = std::upper_bound (minimums.begin (), minimums.end (), balance); + release_assert (it != minimums.begin ()); // There should always be a bucket with a minimum_balance of 0 + return std::distance (minimums.begin (), std::prev (it)); +} + +std::vector const & nano::bucketing::bucket_indices () const +{ + return indices; +} + +size_t nano::bucketing::size () const +{ + return minimums.size (); +} \ No newline at end of file diff --git a/nano/node/bucketing.hpp b/nano/node/bucketing.hpp new file mode 100644 index 0000000000..09a5bff03d --- /dev/null +++ b/nano/node/bucketing.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace nano +{ +class bucketing +{ +public: + bucketing (); + + nano::bucket_index bucket_index (nano::amount balance) const; + std::vector const & bucket_indices () const; + size_t size () const; + +private: + std::vector minimums; + std::vector indices; +}; +} \ No newline at end of file diff --git a/nano/node/fwd.hpp b/nano/node/fwd.hpp index b7b3740e1e..414c3e6c3c 100644 --- a/nano/node/fwd.hpp +++ b/nano/node/fwd.hpp @@ -10,6 +10,7 @@ namespace nano class account_sets_config; class active_elections; class block_processor; +class bucketing; class bootstrap_config; class bootstrap_server; class bootstrap_service; diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 16d5a6f8c1..9dea5c7702 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -129,6 +130,8 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy block_processor{ *block_processor_impl }, confirming_set_impl{ std::make_unique (config.confirming_set, ledger, block_processor, stats, logger) }, confirming_set{ *confirming_set_impl }, + bucketing_impl{ std::make_unique () }, + bucketing{ *bucketing_impl }, active_impl{ std::make_unique (*this, confirming_set, block_processor) }, active{ *active_impl }, rep_crawler (config.rep_crawler, *this), @@ -149,7 +152,7 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy generator{ *generator_impl }, final_generator_impl{ std::make_unique (config, *this, ledger, wallets, vote_processor, history, network, stats, logger, /* final */ true) }, final_generator{ *final_generator_impl }, - scheduler_impl{ std::make_unique (config, *this, ledger, block_processor, active, online_reps, vote_cache, confirming_set, stats, logger) }, + scheduler_impl{ std::make_unique (config, *this, ledger, bucketing, block_processor, active, online_reps, vote_cache, confirming_set, stats, logger) }, scheduler{ *scheduler_impl }, aggregator_impl{ std::make_unique (config.request_aggregator, *this, stats, generator, final_generator, history, ledger, wallets, vote_router) }, aggregator{ *aggregator_impl }, @@ -317,6 +320,7 @@ nano::node::node (std::shared_ptr io_ctx_a, std::filesy logger.info (nano::log::type::node, "Work pool threads: {} ({})", work.threads.size (), (work.opencl ? "OpenCL" : "CPU")); logger.info (nano::log::type::node, "Work peers: {}", config.work_peers.size ()); logger.info (nano::log::type::node, "Node ID: {}", node_id.pub.to_node_id ()); + logger.info (nano::log::type::node, "Number of buckets: {}", bucketing.size ()); if (!work_generation_enabled ()) { diff --git a/nano/node/node.hpp b/nano/node/node.hpp index 2d4f4a351e..8b6830bc71 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -179,6 +179,8 @@ class node final : public std::enable_shared_from_this nano::block_processor & block_processor; std::unique_ptr confirming_set_impl; nano::confirming_set & confirming_set; + std::unique_ptr bucketing_impl; + nano::bucketing & bucketing; std::unique_ptr active_impl; nano::active_elections & active; nano::online_reps online_reps; diff --git a/nano/node/scheduler/bucket.cpp b/nano/node/scheduler/bucket.cpp index 31a0152b3b..4e4ecb0a50 100644 --- a/nano/node/scheduler/bucket.cpp +++ b/nano/node/scheduler/bucket.cpp @@ -8,9 +8,9 @@ * bucket */ -nano::scheduler::bucket::bucket (nano::uint128_t minimum_balance_a, priority_bucket_config const & config_a, nano::active_elections & active_a, nano::stats & stats_a) : +nano::scheduler::bucket::bucket (nano::bucket_index index_a, priority_bucket_config const & config_a, nano::active_elections & active_a, nano::stats & stats_a) : + index{ index_a }, config{ config_a }, - minimum_balance{ minimum_balance_a }, active{ active_a }, stats{ stats_a } { @@ -34,7 +34,7 @@ bool nano::scheduler::bucket::available () const } } -bool nano::scheduler::bucket::election_vacancy (priority_t candidate) const +bool nano::scheduler::bucket::election_vacancy (nano::priority_timestamp candidate) const { debug_assert (!mutex.try_lock ()); diff --git a/nano/node/scheduler/bucket.hpp b/nano/node/scheduler/bucket.hpp index e9b6d48d87..b5b12b2b87 100644 --- a/nano/node/scheduler/bucket.hpp +++ b/nano/node/scheduler/bucket.hpp @@ -47,14 +47,12 @@ class priority_bucket_config final class bucket final { public: - using priority_t = uint64_t; + nano::bucket_index const index; public: - bucket (nano::uint128_t minimum_balance, priority_bucket_config const &, nano::active_elections &, nano::stats &); + bucket (nano::bucket_index, priority_bucket_config const &, nano::active_elections &, nano::stats &); ~bucket (); - nano::uint128_t const minimum_balance; - bool available () const; bool activate (); void update (); @@ -70,7 +68,7 @@ class bucket final void dump () const; private: - bool election_vacancy (priority_t candidate) const; + bool election_vacancy (nano::priority_timestamp candidate) const; bool election_overfill () const; void cancel_lowest_election (); @@ -118,7 +116,7 @@ class bucket final { std::shared_ptr election; nano::qualified_root root; - priority_t priority; + nano::priority_timestamp priority; }; // clang-format off @@ -128,7 +126,7 @@ class bucket final mi::hashed_unique, mi::member>, mi::ordered_non_unique, - mi::member> + mi::member> >>; // clang-format on diff --git a/nano/node/scheduler/component.cpp b/nano/node/scheduler/component.cpp index 2de1556054..82b034bd1b 100644 --- a/nano/node/scheduler/component.cpp +++ b/nano/node/scheduler/component.cpp @@ -5,11 +5,11 @@ #include #include -nano::scheduler::component::component (nano::node_config & node_config, nano::node & node, nano::ledger & ledger, nano::block_processor & block_processor, nano::active_elections & active, nano::online_reps & online_reps, nano::vote_cache & vote_cache, nano::confirming_set & confirming_set, nano::stats & stats, nano::logger & logger) : +nano::scheduler::component::component (nano::node_config & node_config, nano::node & node, nano::ledger & ledger, nano::bucketing & bucketing, nano::block_processor & block_processor, nano::active_elections & active, nano::online_reps & online_reps, nano::vote_cache & vote_cache, nano::confirming_set & confirming_set, nano::stats & stats, nano::logger & logger) : hinted_impl{ std::make_unique (node_config.hinted_scheduler, node, vote_cache, active, online_reps, stats) }, manual_impl{ std::make_unique (node) }, optimistic_impl{ std::make_unique (node_config.optimistic_scheduler, node, ledger, active, node_config.network_params.network, stats) }, - priority_impl{ std::make_unique (node_config, node, ledger, block_processor, active, confirming_set, stats, logger) }, + priority_impl{ std::make_unique (node_config, node, ledger, bucketing, block_processor, active, confirming_set, stats, logger) }, hinted{ *hinted_impl }, manual{ *manual_impl }, optimistic{ *optimistic_impl }, diff --git a/nano/node/scheduler/component.hpp b/nano/node/scheduler/component.hpp index 1af64ff004..97373577d2 100644 --- a/nano/node/scheduler/component.hpp +++ b/nano/node/scheduler/component.hpp @@ -10,7 +10,7 @@ namespace nano::scheduler class component final { public: - component (nano::node_config &, nano::node &, nano::ledger &, nano::block_processor &, nano::active_elections &, nano::online_reps &, nano::vote_cache &, nano::confirming_set &, nano::stats &, nano::logger &); + component (nano::node_config &, nano::node &, nano::ledger &, nano::bucketing &, nano::block_processor &, nano::active_elections &, nano::online_reps &, nano::vote_cache &, nano::confirming_set &, nano::stats &, nano::logger &); ~component (); void start (); diff --git a/nano/node/scheduler/priority.cpp b/nano/node/scheduler/priority.cpp index a40816d2de..a05d9a44e5 100644 --- a/nano/node/scheduler/priority.cpp +++ b/nano/node/scheduler/priority.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,44 +8,20 @@ #include #include -nano::scheduler::priority::priority (nano::node_config & node_config, nano::node & node_a, nano::ledger & ledger_a, nano::block_processor & block_processor_a, nano::active_elections & active_a, nano::confirming_set & confirming_set_a, nano::stats & stats_a, nano::logger & logger_a) : +nano::scheduler::priority::priority (nano::node_config & node_config, nano::node & node_a, nano::ledger & ledger_a, nano::bucketing & bucketing_a, nano::block_processor & block_processor_a, nano::active_elections & active_a, nano::confirming_set & confirming_set_a, nano::stats & stats_a, nano::logger & logger_a) : config{ node_config.priority_scheduler }, node{ node_a }, ledger{ ledger_a }, + bucketing{ bucketing_a }, block_processor{ block_processor_a }, active{ active_a }, confirming_set{ confirming_set_a }, stats{ stats_a }, logger{ logger_a } { - std::vector minimums; - - auto build_region = [&minimums] (uint128_t const & begin, uint128_t const & end, size_t count) { - auto width = (end - begin) / count; - for (auto i = 0; i < count; ++i) - { - minimums.push_back (begin + i * width); - } - }; - - minimums.push_back (uint128_t{ 0 }); - build_region (uint128_t{ 1 } << 79, uint128_t{ 1 } << 88, 1); - build_region (uint128_t{ 1 } << 88, uint128_t{ 1 } << 92, 2); - build_region (uint128_t{ 1 } << 92, uint128_t{ 1 } << 96, 4); - build_region (uint128_t{ 1 } << 96, uint128_t{ 1 } << 100, 8); - build_region (uint128_t{ 1 } << 100, uint128_t{ 1 } << 104, 16); - build_region (uint128_t{ 1 } << 104, uint128_t{ 1 } << 108, 16); - build_region (uint128_t{ 1 } << 108, uint128_t{ 1 } << 112, 8); - build_region (uint128_t{ 1 } << 112, uint128_t{ 1 } << 116, 4); - build_region (uint128_t{ 1 } << 116, uint128_t{ 1 } << 120, 2); - minimums.push_back (uint128_t{ 1 } << 120); - - logger.debug (nano::log::type::election_scheduler, "Number of buckets: {}", minimums.size ()); - - for (size_t i = 0u, n = minimums.size (); i < n; ++i) + for (auto const & index : bucketing.bucket_indices ()) { - auto bucket = std::make_unique (minimums[i], node_config.priority_bucket, active, stats); - buckets.emplace_back (std::move (bucket)); + buckets[index] = std::make_unique (index, node_config.priority_bucket, active, stats); } // Activate accounts with fresh blocks @@ -141,14 +118,14 @@ bool nano::scheduler::priority::activate (secure::transaction const & transactio if (ledger.dependents_confirmed (transaction, *block)) { - auto const balance = block->balance (); - auto const previous_balance = ledger.any.block_balance (transaction, conf_info.frontier).value_or (0); - auto const balance_priority = std::max (balance, previous_balance); + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *block); + auto const bucket_index = bucketing.bucket_index (priority_balance); bool added = false; { - auto & bucket = find_bucket (balance_priority); - added = bucket.push (account_info.modified, block); + auto const & bucket = buckets.at (bucket_index); + release_assert (bucket); + added = bucket->push (account_info.modified, block); } if (added) { @@ -157,7 +134,8 @@ bool nano::scheduler::priority::activate (secure::transaction const & transactio nano::log::arg{ "account", account.to_account () }, // TODO: Convert to lazy eval nano::log::arg{ "block", block }, nano::log::arg{ "time", account_info.modified }, - nano::log::arg{ "priority", balance_priority }); + nano::log::arg{ "priority_balance", priority_balance }, + nano::log::arg{ "priority_timestamp", priority_timestamp }); notify (); } @@ -187,7 +165,7 @@ bool nano::scheduler::priority::activate_successors (secure::transaction const & bool nano::scheduler::priority::contains (nano::block_hash const & hash) const { return std::any_of (buckets.begin (), buckets.end (), [&hash] (auto const & bucket) { - return bucket->contains (hash); + return bucket.second->contains (hash); }); } @@ -199,21 +177,21 @@ void nano::scheduler::priority::notify () std::size_t nano::scheduler::priority::size () const { return std::accumulate (buckets.begin (), buckets.end (), std::size_t{ 0 }, [] (auto const & sum, auto const & bucket) { - return sum + bucket->size (); + return sum + bucket.second->size (); }); } bool nano::scheduler::priority::empty () const { return std::all_of (buckets.begin (), buckets.end (), [] (auto const & bucket) { - return bucket->empty (); + return bucket.second->empty (); }); } bool nano::scheduler::priority::predicate () const { return std::any_of (buckets.begin (), buckets.end (), [] (auto const & bucket) { - return bucket->available (); + return bucket.second->available (); }); } @@ -232,7 +210,7 @@ void nano::scheduler::priority::run () lock.unlock (); - for (auto & bucket : buckets) + for (auto const & [index, bucket] : buckets) { if (bucket->available ()) { @@ -259,7 +237,7 @@ void nano::scheduler::priority::run_cleanup () lock.unlock (); - for (auto & bucket : buckets) + for (auto const & [index, bucket] : buckets) { bucket->update (); } @@ -269,34 +247,22 @@ void nano::scheduler::priority::run_cleanup () } } -auto nano::scheduler::priority::find_bucket (nano::uint128_t priority) -> bucket & -{ - auto it = std::upper_bound (buckets.begin (), buckets.end (), priority, [] (nano::uint128_t const & priority, std::unique_ptr const & bucket) { - return priority < bucket->minimum_balance; - }); - release_assert (it != buckets.begin ()); // There should always be a bucket with a minimum_balance of 0 - it = std::prev (it); - return **it; -} - nano::container_info nano::scheduler::priority::container_info () const { auto collect_blocks = [&] () { nano::container_info info; - for (auto i = 0; i < buckets.size (); ++i) + for (auto const & [index, bucket] : buckets) { - auto const & bucket = buckets[i]; - info.put (std::to_string (i), bucket->size ()); + info.put (std::to_string (index), bucket->size ()); } return info; }; auto collect_elections = [&] () { nano::container_info info; - for (auto i = 0; i < buckets.size (); ++i) + for (auto const & [index, bucket] : buckets) { - auto const & bucket = buckets[i]; - info.put (std::to_string (i), bucket->election_count ()); + info.put (std::to_string (index), bucket->election_count ()); } return info; }; diff --git a/nano/node/scheduler/priority.hpp b/nano/node/scheduler/priority.hpp index 8085281644..6f0e3badd2 100644 --- a/nano/node/scheduler/priority.hpp +++ b/nano/node/scheduler/priority.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -26,7 +27,7 @@ class priority_config class priority final { public: - priority (nano::node_config &, nano::node &, nano::ledger &, nano::block_processor &, nano::active_elections &, nano::confirming_set &, nano::stats &, nano::logger &); + priority (nano::node_config &, nano::node &, nano::ledger &, nano::bucketing &, nano::block_processor &, nano::active_elections &, nano::confirming_set &, nano::stats &, nano::logger &); ~priority (); void start (); @@ -51,6 +52,7 @@ class priority final priority_config const & config; nano::node & node; nano::ledger & ledger; + nano::bucketing & bucketing; nano::block_processor & block_processor; nano::active_elections & active; nano::confirming_set & confirming_set; @@ -61,10 +63,9 @@ class priority final void run (); void run_cleanup (); bool predicate () const; - bucket & find_bucket (nano::uint128_t priority); private: - std::vector> buckets; + std::map> buckets; bool stopped{ false }; nano::condition_variable condition; diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 23550e7435..b46b188f27 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -1254,6 +1254,21 @@ uint64_t nano::ledger::pruning_action (secure::write_transaction & transaction_a return pruned_count; } +auto nano::ledger::block_priority (nano::secure::transaction const & transaction, nano::block const & block) const -> block_priority_result +{ + auto const balance = block.balance (); + auto const previous_block = !block.previous ().is_zero () ? any.block_get (transaction, block.previous ()) : nullptr; + auto const previous_balance = previous_block ? previous_block->balance () : 0; + + // Handle full send case nicely where the balance would otherwise be 0 + auto const priority_balance = std::max (balance, block.is_send () ? previous_balance : 0); + + // Use previous block timestamp as priority timestamp for least recently used prioritization within the same bucket + // Account info timestamp is not used here because it will get out of sync when rollbacks happen + auto const priority_timestamp = previous_block ? previous_block->sideband ().timestamp : block.sideband ().timestamp; + return { priority_balance, priority_timestamp }; +} + // A precondition is that the store is an LMDB store bool nano::ledger::migrate_lmdb_to_rocksdb (std::filesystem::path const & data_path_a) const { diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index 69c8637805..61b79aa226 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -75,13 +75,20 @@ class ledger final nano::link const & epoch_link (nano::epoch) const; bool migrate_lmdb_to_rocksdb (std::filesystem::path const &) const; bool bootstrap_weight_reached () const; + static nano::epoch version (nano::block const & block); nano::epoch version (secure::transaction const &, nano::block_hash const & hash) const; + uint64_t cemented_count () const; uint64_t block_count () const; uint64_t account_count () const; uint64_t pruned_count () const; + // Returned priority balance is maximum of block balance and previous block balance to handle full account balance send cases + // Returned timestamp is the previous block timestamp or the current timestamp if there's no previous block + using block_priority_result = std::pair; + block_priority_result block_priority (secure::transaction const &, nano::block const &) const; + nano::container_info container_info () const; public: From bdced6bb38a1106f110fbb358d728d0fecfc7045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:11:08 +0100 Subject: [PATCH 3/5] Keep operators inlined --- nano/node/scheduler/bucket.cpp | 14 -------------- nano/node/scheduler/bucket.hpp | 13 ++++++++++--- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/nano/node/scheduler/bucket.cpp b/nano/node/scheduler/bucket.cpp index 4e4ecb0a50..de9083b34f 100644 --- a/nano/node/scheduler/bucket.cpp +++ b/nano/node/scheduler/bucket.cpp @@ -189,20 +189,6 @@ void nano::scheduler::bucket::dump () const } } -/* - * block_entry - */ - -bool nano::scheduler::bucket::block_entry::operator< (block_entry const & other_a) const -{ - return time < other_a.time || (time == other_a.time && block->hash () < other_a.block->hash ()); -} - -bool nano::scheduler::bucket::block_entry::operator== (block_entry const & other_a) const -{ - return time == other_a.time && block->hash () == other_a.block->hash (); -} - /* * priority_bucket_config */ diff --git a/nano/node/scheduler/bucket.hpp b/nano/node/scheduler/bucket.hpp index b5b12b2b87..be82aecf84 100644 --- a/nano/node/scheduler/bucket.hpp +++ b/nano/node/scheduler/bucket.hpp @@ -83,13 +83,20 @@ class bucket final uint64_t time; std::shared_ptr block; - bool operator< (block_entry const & other) const; - bool operator== (block_entry const & other) const; - nano::block_hash hash () const { return block->hash (); } + + // Keep operators inlined + bool operator< (block_entry const & other) const + { + return time < other.time || (time == other.time && hash () < other.hash ()); + } + bool operator== (block_entry const & other) const + { + return time == other.time && hash () == other.hash (); + } }; // clang-format off From 084bfaffcf2503d00bc06ab83802efbd1e223eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:38:39 +0100 Subject: [PATCH 4/5] Genesis blocks with timestamp 0 --- nano/node/json_handler.cpp | 2 +- nano/secure/common.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 91dca52cd4..e87a3d74c7 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -4548,7 +4548,7 @@ void nano::json_handler::wallet_frontiers () void nano::json_handler::wallet_history () { - uint64_t modified_since (1); + uint64_t modified_since (0); boost::optional modified_since_text (request.get_optional ("modified_since")); if (modified_since_text.is_initialized ()) { diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index 6b40c90b2f..5948826225 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -143,7 +143,7 @@ nano::ledger_constants::ledger_constants (nano::work_thresholds & work, nano::ne /* successor (block_hash) */ nano::block_hash{ 0 }, /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, /* height */ uint64_t{ 1 }, - /* local_timestamp */ nano::seconds_since_epoch (), + /* local_timestamp */ 0, /* epoch */ nano::epoch::epoch_0, /* is_send */ false, /* is_receive */ false, @@ -155,7 +155,7 @@ nano::ledger_constants::ledger_constants (nano::work_thresholds & work, nano::ne /* successor (block_hash) */ nano::block_hash{ 0 }, /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, /* height */ uint64_t{ 1 }, - /* local_timestamp */ nano::seconds_since_epoch (), + /* local_timestamp */ 0, /* epoch */ nano::epoch::epoch_0, /* is_send */ false, /* is_receive */ false, @@ -167,7 +167,7 @@ nano::ledger_constants::ledger_constants (nano::work_thresholds & work, nano::ne /* successor (block_hash) */ nano::block_hash{ 0 }, /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, /* height */ uint64_t{ 1 }, - /* local_timestamp */ nano::seconds_since_epoch (), + /* local_timestamp */ 0, /* epoch */ nano::epoch::epoch_0, /* is_send */ false, /* is_receive */ false, @@ -179,7 +179,7 @@ nano::ledger_constants::ledger_constants (nano::work_thresholds & work, nano::ne /* successor (block_hash) */ nano::block_hash{ 0 }, /* balance (amount) */ nano::amount{ std::numeric_limits::max () }, /* height */ uint64_t{ 1 }, - /* local_timestamp */ nano::seconds_since_epoch (), + /* local_timestamp */ 0, /* epoch */ nano::epoch::epoch_0, /* is_send */ false, /* is_receive */ false, From 63927631176a10db34ce881956bf06679964bcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:56:54 +0100 Subject: [PATCH 5/5] Test ledger priority --- nano/core_test/CMakeLists.txt | 1 + nano/core_test/ledger_priority.cpp | 324 +++++++++++++++++++++++++++++ nano/secure/fwd.hpp | 1 + nano/test_common/testutil.hpp | 15 +- 4 files changed, 327 insertions(+), 14 deletions(-) create mode 100644 nano/core_test/ledger_priority.cpp diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index dcc509212b..0e197344c7 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable( ipc.cpp ledger.cpp ledger_confirm.cpp + ledger_priority.cpp locks.cpp logging.cpp message.cpp diff --git a/nano/core_test/ledger_priority.cpp b/nano/core_test/ledger_priority.cpp new file mode 100644 index 0000000000..220b72d8c1 --- /dev/null +++ b/nano/core_test/ledger_priority.cpp @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Test priority of genesis block +TEST (ledger_priority, genesis_priority) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & store = ctx.store (); + auto transaction = ledger.tx_begin_write (); + + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *nano::dev::genesis); + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance); + ASSERT_EQ (0, priority_timestamp); +} + +// Test priority of legacy blocks +TEST (ledger_priority, legacy_blocks_priority) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + // Create legacy send block + nano::keypair key1; + nano::block_builder builder; + auto send = builder + .send () + .previous (nano::dev::genesis->hash ()) + .destination (key1.pub) + .balance (100) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send)); + + // Check send priority - should use max of current and previous balance + auto const [send_balance, send_timestamp] = ledger.block_priority (transaction, *send); + ASSERT_EQ (nano::dev::constants.genesis_amount, send_balance); + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, send_timestamp); + + // Create legacy open/receive block + auto open = builder + .open () + .source (send->hash ()) + .representative (key1.pub) + .account (key1.pub) + .sign (key1.prv, key1.pub) + .work (*pool.generate (key1.pub)) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, open)); + + // Check open priority - should use current balance + auto const [open_balance, open_timestamp] = ledger.block_priority (transaction, *open); + ASSERT_EQ (nano::dev::constants.genesis_amount - 100, open_balance); + ASSERT_EQ (open->sideband ().timestamp, open_timestamp); +} + +// Test priority of a send state block +TEST (ledger_priority, send_priority) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // Create state send block + auto send = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (100) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send)); + + // Check priority + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *send); + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance); + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, priority_timestamp); +} + +// Test priority of a full balance state send +TEST (ledger_priority, full_balance_send) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // Create state send block that sends full balance + auto send = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (0) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send)); + + // Check priority - should use previous balance since current is 0 + auto const [priority_balance, priority_timestamp] = ledger.block_priority (transaction, *send); + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance); + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, priority_timestamp); +} + +// Test priority of state blocks with multiple operations +TEST (ledger_priority, sequential_blocks) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // First state send + auto send1 = builder + .state () + .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 (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send1)); + + // State open + auto open = builder + .state () + .account (key1.pub) + .previous (0) + .representative (key1.pub) + .balance (100) + .link (send1->hash ()) + .sign (key1.prv, key1.pub) + .work (*pool.generate (key1.pub)) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, open)); + + // Second state send + auto send2 = builder + .state () + .account (key1.pub) + .previous (open->hash ()) + .representative (key1.pub) + .balance (50) + .link (nano::dev::genesis_key.pub) + .sign (key1.prv, key1.pub) + .work (*pool.generate (open->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2)); + + // Check priorities follow the chain + auto const [priority_balance1, timestamp1] = ledger.block_priority (transaction, *send1); + auto const [priority_balance2, timestamp2] = ledger.block_priority (transaction, *open); + auto const [priority_balance3, timestamp3] = ledger.block_priority (transaction, *send2); + + ASSERT_EQ (nano::dev::constants.genesis_amount, priority_balance1); + ASSERT_EQ (100, priority_balance2); + ASSERT_EQ (100, priority_balance3); // Max of current (50) and previous (100) + + ASSERT_EQ (nano::dev::genesis->sideband ().timestamp, timestamp1); + ASSERT_EQ (send1->sideband ().timestamp, timestamp2); + ASSERT_EQ (open->sideband ().timestamp, timestamp3); +} + +// Test priority after rolling back state blocks +TEST (ledger_priority, block_rollback) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1; + nano::block_builder builder; + + // First state send + auto send1 = builder + .state () + .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 (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send1)); + + // Get priority before rollback + auto const [priority_before, timestamp_before] = ledger.block_priority (transaction, *send1); + + // Rollback the send + ASSERT_FALSE (ledger.rollback (transaction, send1->hash ())); + + // Create new state send with different amount + auto send2 = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (nano::dev::genesis->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 200) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2)); + + // Check priority after rollback and new block + auto const [priority_after, timestamp_after] = ledger.block_priority (transaction, *send2); + + // Priorities should be the same since both use genesis as previous + ASSERT_EQ (priority_before, priority_after); + ASSERT_EQ (timestamp_before, timestamp_after); +} + +// Test priority with state block fork +TEST (ledger_priority, block_fork) +{ + auto ctx = nano::test::ledger_empty (); + auto & ledger = ctx.ledger (); + auto & pool = ctx.pool (); + auto transaction = ledger.tx_begin_write (); + + nano::keypair key1, key2; + nano::block_builder builder; + + // First state send + auto send1 = builder + .state () + .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 (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (nano::dev::genesis->hash ())) + .build (); + + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send1)); + + // Create two competing state sends + auto send2a = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (send1->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 200) + .link (key1.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (send1->hash ())) + .build (); + + auto send2b = builder + .state () + .account (nano::dev::genesis_key.pub) + .previous (send1->hash ()) + .representative (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - 150) + .link (key2.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*pool.generate (send1->hash ())) + .build (); + + // Process first fork + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2a)); + + // Try to process second fork + ASSERT_EQ (nano::block_status::fork, ledger.process (transaction, send2b)); + + // Get priority of first fork + auto const [priority_a, timestamp_a] = ledger.block_priority (transaction, *send2a); + + // Rollback first fork + ASSERT_FALSE (ledger.rollback (transaction, send2a->hash ())); + + // Process second fork + ASSERT_EQ (nano::block_status::progress, ledger.process (transaction, send2b)); + + // Check priority of second fork + auto const [priority_b, timestamp_b] = ledger.block_priority (transaction, *send2b); + + // Both should use send1's balance as previous + ASSERT_EQ (nano::dev::constants.genesis_amount - 100, priority_a); + ASSERT_EQ (nano::dev::constants.genesis_amount - 100, priority_b); + // Both should use send1's timestamp + ASSERT_EQ (send1->sideband ().timestamp, timestamp_a); + ASSERT_EQ (send1->sideband ().timestamp, timestamp_b); +} \ No newline at end of file diff --git a/nano/secure/fwd.hpp b/nano/secure/fwd.hpp index f6bb2076a3..bf0de82c8c 100644 --- a/nano/secure/fwd.hpp +++ b/nano/secure/fwd.hpp @@ -3,6 +3,7 @@ namespace nano { class account_info; +class keypair; class ledger; class ledger_cache; class ledger_constants; diff --git a/nano/test_common/testutil.hpp b/nano/test_common/testutil.hpp index a30925131c..6b23c12d8c 100644 --- a/nano/test_common/testutil.hpp +++ b/nano/test_common/testutil.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -159,20 +160,6 @@ class stop_guard /* Convenience globals for gtest projects */ namespace nano { -class node; -using uint128_t = boost::multiprecision::uint128_t; -class keypair; -class public_key; -class block_hash; -class telemetry_data; -class network_params; -class vote; -class block; -class election; -class ledger; - -extern nano::uint128_t const & genesis_amount; - namespace test { class system;