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:
parent
bb936fb54a
commit
712f7a1bd9
5 changed files with 328 additions and 0 deletions
|
@ -29,6 +29,7 @@ add_executable(
|
|||
node.cpp
|
||||
processor_service.cpp
|
||||
peer_container.cpp
|
||||
prioritization.cpp
|
||||
request_aggregator.cpp
|
||||
signing.cpp
|
||||
socket.cpp
|
||||
|
|
149
nano/core_test/prioritization.cpp
Normal file
149
nano/core_test/prioritization.cpp
Normal 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 ());
|
||||
}
|
|
@ -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
|
||||
|
|
132
nano/node/prioritization.cpp
Normal file
132
nano/node/prioritization.cpp
Normal 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';
|
||||
}
|
44
nano/node/prioritization.hpp
Normal file
44
nano/node/prioritization.hpp
Normal 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;
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue