From 712f7a1bd95142e0308db79fdad7d580e7fa6ca9 Mon Sep 17 00:00:00 2001 From: clemahieu Date: Fri, 16 Apr 2021 21:33:36 +0200 Subject: [PATCH] Prioritization (#3190) This adds a class called prioritization which tracks accounts to be confirmed in priority order determined by time_since_use * balance order where the time since use is per-account. The priority order is approximated by segmented each account in to a series of buckets determined by the number of leading 0 bits in the balance. Within each bucket transactions are sorted by last-use timestamp of account where the numerically lowest last-use timestamp represents the account with the oldest modification date and thus, highest priority. --- nano/core_test/CMakeLists.txt | 1 + nano/core_test/prioritization.cpp | 149 ++++++++++++++++++++++++++++++ nano/node/CMakeLists.txt | 2 + nano/node/prioritization.cpp | 132 ++++++++++++++++++++++++++ nano/node/prioritization.hpp | 44 +++++++++ 5 files changed, 328 insertions(+) create mode 100644 nano/core_test/prioritization.cpp create mode 100644 nano/node/prioritization.cpp create mode 100644 nano/node/prioritization.hpp diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index 81c7e8d0..276829df 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable( node.cpp processor_service.cpp peer_container.cpp + prioritization.cpp request_aggregator.cpp signing.cpp socket.cpp diff --git a/nano/core_test/prioritization.cpp b/nano/core_test/prioritization.cpp new file mode 100644 index 00000000..7decb838 --- /dev/null +++ b/nano/core_test/prioritization.cpp @@ -0,0 +1,149 @@ +#include +#include + +#include + +#include + +static nano::keypair keyzero; +static nano::keypair key0; +static nano::keypair key1; +static nano::keypair key2; +static nano::keypair key3; +static auto blockzero = std::make_shared (keyzero.pub, 0, keyzero.pub, 0, 0, keyzero.prv, keyzero.pub, 0); +static auto block0 = std::make_shared (key0.pub, 0, key0.pub, nano::Gxrb_ratio, 0, key0.prv, key0.pub, 0); +static auto block1 = std::make_shared (key1.pub, 0, key1.pub, nano::Mxrb_ratio, 0, key1.prv, key1.pub, 0); +static auto block2 = std::make_shared (key2.pub, 0, key2.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, 0); +static auto block3 = std::make_shared (key3.pub, 0, key3.pub, nano::Mxrb_ratio, 0, key3.prv, key3.pub, 0); + +TEST (prioritization, construction) +{ + nano::prioritization prioritization; + ASSERT_EQ (0, prioritization.size ()); + ASSERT_TRUE (prioritization.empty ()); + ASSERT_EQ (129, prioritization.bucket_count ()); +} + +TEST (prioritization, insert_zero) +{ + nano::prioritization prioritization; + prioritization.push (1000, block0); + ASSERT_EQ (1, prioritization.size ()); + ASSERT_EQ (1, prioritization.bucket_size (110)); +} + +TEST (prioritization, insert_one) +{ + nano::prioritization prioritization; + prioritization.push (1000, block1); + ASSERT_EQ (1, prioritization.size ()); + ASSERT_EQ (1, prioritization.bucket_size (100)); +} + +TEST (prioritization, insert_same_priority) +{ + nano::prioritization prioritization; + prioritization.push (1000, block0); + prioritization.push (1000, block2); + ASSERT_EQ (2, prioritization.size ()); + ASSERT_EQ (2, prioritization.bucket_size (110)); +} + +TEST (prioritization, insert_duplicate) +{ + nano::prioritization prioritization; + prioritization.push (1000, block0); + prioritization.push (1000, block0); + ASSERT_EQ (1, prioritization.size ()); + ASSERT_EQ (1, prioritization.bucket_size (110)); +} + +TEST (prioritization, insert_older) +{ + nano::prioritization prioritization; + prioritization.push (1000, block0); + prioritization.push (1100, block2); + ASSERT_EQ (block0, prioritization.top ()); + prioritization.pop (); + ASSERT_EQ (block2, prioritization.top ()); + prioritization.pop (); +} + +TEST (prioritization, pop) +{ + nano::prioritization prioritization; + ASSERT_TRUE (prioritization.empty ()); + prioritization.push (1000, block0); + ASSERT_FALSE (prioritization.empty ()); + prioritization.pop (); + ASSERT_TRUE (prioritization.empty ()); +} + +TEST (prioritization, top_one) +{ + nano::prioritization prioritization; + prioritization.push (1000, block0); + ASSERT_EQ (block0, prioritization.top ()); +} + +TEST (prioritization, top_two) +{ + nano::prioritization prioritization; + prioritization.push (1000, block0); + prioritization.push (1, block1); + ASSERT_EQ (block0, prioritization.top ()); + prioritization.pop (); + ASSERT_EQ (block1, prioritization.top ()); + prioritization.pop (); + ASSERT_TRUE (prioritization.empty ()); +} + +TEST (prioritization, top_round_robin) +{ + nano::prioritization prioritization; + prioritization.push (1000, blockzero); + ASSERT_EQ (blockzero, prioritization.top ()); + prioritization.push (1000, block0); + prioritization.push (1000, block1); + prioritization.push (1100, block3); + prioritization.pop (); // blockzero + EXPECT_EQ (block1, prioritization.top ()); + prioritization.pop (); + EXPECT_EQ (block0, prioritization.top ()); + prioritization.pop (); + EXPECT_EQ (block3, prioritization.top ()); + prioritization.pop (); + EXPECT_TRUE (prioritization.empty ()); +} + +TEST (prioritization, trim_normal) +{ + nano::prioritization prioritization{ 1 }; + prioritization.push (1000, block0); + prioritization.push (1100, block2); + ASSERT_EQ (1, prioritization.size ()); + ASSERT_EQ (block0, prioritization.top ()); +} + +TEST (prioritization, trim_reverse) +{ + nano::prioritization prioritization{ 1 }; + prioritization.push (1100, block2); + prioritization.push (1000, block0); + ASSERT_EQ (1, prioritization.size ()); + ASSERT_EQ (block0, prioritization.top ()); +} + +TEST (prioritization, trim_even) +{ + nano::prioritization prioritization{ 2 }; + prioritization.push (1000, block0); + prioritization.push (1100, block2); + ASSERT_EQ (1, prioritization.size ()); + ASSERT_EQ (block0, prioritization.top ()); + prioritization.push (1000, block1); + ASSERT_EQ (2, prioritization.size ()); + ASSERT_EQ (block0, prioritization.top ()); + prioritization.pop (); + ASSERT_EQ (block1, prioritization.top ()); +} diff --git a/nano/node/CMakeLists.txt b/nano/node/CMakeLists.txt index 9d97cb0c..c0dec134 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -105,6 +105,8 @@ add_library( peer_exclusion.cpp portmapping.hpp portmapping.cpp + prioritization.cpp + prioritization.hpp node_pow_server_config.hpp node_pow_server_config.cpp repcrawler.hpp diff --git a/nano/node/prioritization.cpp b/nano/node/prioritization.cpp new file mode 100644 index 00000000..64771f34 --- /dev/null +++ b/nano/node/prioritization.cpp @@ -0,0 +1,132 @@ +#include +#include +#include + +#include + +bool nano::prioritization::value_type::operator< (value_type const & other_a) const +{ + return time < other_a.time || (time == other_a.time && block->hash () < other_a.block->hash ()); +} + +bool nano::prioritization::value_type::operator== (value_type const & other_a) const +{ + return time == other_a.time && block->hash () == other_a.block->hash (); +} + +void nano::prioritization::next () +{ + ++current; + if (current == schedule.end ()) + { + current = schedule.begin (); + } +} + +void nano::prioritization::seek () +{ + next (); + for (size_t i = 0, n = schedule.size (); buckets[*current].empty () && i < n; ++i) + { + next (); + } +} + +void nano::prioritization::populate_schedule () +{ + for (auto i = 0; i < buckets.size (); ++i) + { + schedule.push_back (i); + } +} + +nano::prioritization::prioritization (uint64_t maximum, std::function)> const & drop_a) : +drop{ drop_a }, +maximum{ maximum } +{ + static size_t constexpr bucket_count = 129; + buckets.resize (bucket_count); + (void)minimums[0]; + nano::uint128_t minimum{ 1 }; + minimums.push_back (0); + for (auto i = 1; i < bucket_count; ++i) + { + minimums.push_back (minimum); + minimum <<= 1; + } + populate_schedule (); + current = schedule.begin (); +} + +void nano::prioritization::push (uint64_t time, std::shared_ptr block) +{ + auto was_empty = empty (); + auto block_has_balance = block->type () == nano::block_type::state || block->type () == nano::block_type::send; + debug_assert (block_has_balance || block->has_sideband ()); + auto balance = block_has_balance ? block->balance () : block->sideband ().balance; + auto index = std::upper_bound (minimums.begin (), minimums.end (), balance.number ()) - 1 - minimums.begin (); + auto & bucket = buckets[index]; + bucket.emplace (value_type{ time, block }); + if (bucket.size () > std::max (decltype (maximum){ 1 }, maximum / buckets.size ())) + { + bucket.erase (--bucket.end ()); + } + if (was_empty) + { + seek (); + } +} + +std::shared_ptr nano::prioritization::top () const +{ + debug_assert (!empty ()); + debug_assert (!buckets[*current].empty ()); + auto result = buckets[*current].begin ()->block; + return result; +} + +void nano::prioritization::pop () +{ + debug_assert (!empty ()); + debug_assert (!buckets[*current].empty ()); + auto & bucket = buckets[*current]; + bucket.erase (bucket.begin ()); + seek (); +} + +size_t nano::prioritization::size () const +{ + size_t result{ 0 }; + for (auto const & queue : buckets) + { + result += queue.size (); + } + return result; +} + +size_t nano::prioritization::bucket_count () const +{ + return buckets.size (); +} + +size_t nano::prioritization::bucket_size (size_t index) const +{ + return buckets[index].size (); +} + +bool nano::prioritization::empty () const +{ + return std::all_of (buckets.begin (), buckets.end (), [] (priority const & bucket_a) { return bucket_a.empty (); }); +} + +void nano::prioritization::dump () +{ + for (auto const & i : buckets) + { + for (auto const & j : i) + { + std::cerr << j.time << ' ' << j.block->hash ().to_string () << '\n'; + } + } + std::cerr << "current: " << std::to_string (*current) << '\n'; +} diff --git a/nano/node/prioritization.hpp b/nano/node/prioritization.hpp new file mode 100644 index 00000000..9d0fdf1f --- /dev/null +++ b/nano/node/prioritization.hpp @@ -0,0 +1,44 @@ +#pragma once +#include + +#include +#include +#include + +namespace nano +{ +class block; +class prioritization final +{ + class value_type + { + public: + uint64_t time; + std::shared_ptr block; + bool operator< (value_type const & other_a) const; + bool operator== (value_type const & other_a) const; + }; + using priority = std::set; + std::vector buckets; + std::vector minimums; + void next (); + void seek (); + void populate_schedule (); + std::function)> drop; + // Contains bucket indicies to iterate over when making the next scheduling decision + std::vector schedule; + decltype (schedule)::const_iterator current; + +public: + prioritization (uint64_t maximum = 250000u, std::function)> const & drop_a = nullptr); + void push (uint64_t time, std::shared_ptr block); + std::shared_ptr top () const; + void pop (); + size_t size () const; + size_t bucket_count () const; + size_t bucket_size (size_t index) const; + bool empty () const; + void dump (); + uint64_t const maximum; +}; +}