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.
This commit is contained in:
clemahieu 2021-04-16 21:33:36 +02:00 committed by GitHub
commit 712f7a1bd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 328 additions and 0 deletions

View file

@ -29,6 +29,7 @@ add_executable(
node.cpp
processor_service.cpp
peer_container.cpp
prioritization.cpp
request_aggregator.cpp
signing.cpp
socket.cpp

View file

@ -0,0 +1,149 @@
#include <nano/node/prioritization.hpp>
#include <nano/secure/common.hpp>
#include <gtest/gtest.h>
#include <unordered_set>
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<nano::state_block> (keyzero.pub, 0, keyzero.pub, 0, 0, keyzero.prv, keyzero.pub, 0);
static auto block0 = std::make_shared<nano::state_block> (key0.pub, 0, key0.pub, nano::Gxrb_ratio, 0, key0.prv, key0.pub, 0);
static auto block1 = std::make_shared<nano::state_block> (key1.pub, 0, key1.pub, nano::Mxrb_ratio, 0, key1.prv, key1.pub, 0);
static auto block2 = std::make_shared<nano::state_block> (key2.pub, 0, key2.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, 0);
static auto block3 = std::make_shared<nano::state_block> (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 ());
}

View file

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

View file

@ -0,0 +1,132 @@
#include <nano/lib/blocks.hpp>
#include <nano/lib/utility.hpp>
#include <nano/node/prioritization.hpp>
#include <string>
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<void (std::shared_ptr<nano::block>)> 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<nano::block> 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::block> 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';
}

View file

@ -0,0 +1,44 @@
#pragma once
#include <nano/lib/numbers.hpp>
#include <cstddef>
#include <set>
#include <vector>
namespace nano
{
class block;
class prioritization final
{
class value_type
{
public:
uint64_t time;
std::shared_ptr<nano::block> block;
bool operator< (value_type const & other_a) const;
bool operator== (value_type const & other_a) const;
};
using priority = std::set<value_type>;
std::vector<priority> buckets;
std::vector<nano::uint128_t> minimums;
void next ();
void seek ();
void populate_schedule ();
std::function<void (std::shared_ptr<nano::block>)> drop;
// Contains bucket indicies to iterate over when making the next scheduling decision
std::vector<uint8_t> schedule;
decltype (schedule)::const_iterator current;
public:
prioritization (uint64_t maximum = 250000u, std::function<void (std::shared_ptr<nano::block>)> const & drop_a = nullptr);
void push (uint64_t time, std::shared_ptr<nano::block> block);
std::shared_ptr<nano::block> 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;
};
}