From e7b8c7d05aec8ebc563a2bb564113468680796bf Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 17 May 2026 00:05:37 -0400 Subject: [PATCH] Move fee estimator class to node. --- Makefile.am | 3 - .../libbitcoin-server-test.vcxproj | 1 - .../libbitcoin-server-test.vcxproj.filters | 3 - .../libbitcoin-server.vcxproj | 2 - .../libbitcoin-server.vcxproj.filters | 6 - .../libbitcoin-server-test.vcxproj | 1 - .../libbitcoin-server-test.vcxproj.filters | 3 - .../libbitcoin-server.vcxproj | 2 - .../libbitcoin-server.vcxproj.filters | 6 - include/bitcoin/server.hpp | 1 - include/bitcoin/server/estimator.hpp | 166 ------- src/estimator.cpp | 306 ------------- test/estimator.cpp | 419 ------------------ 13 files changed, 919 deletions(-) delete mode 100644 include/bitcoin/server/estimator.hpp delete mode 100644 src/estimator.cpp delete mode 100644 test/estimator.cpp diff --git a/Makefile.am b/Makefile.am index 6310a45e..f8318e9f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,7 +37,6 @@ src_libbitcoin_server_la_LIBADD = ${bitcoin_node_LIBS} src_libbitcoin_server_la_SOURCES = \ src/configuration.cpp \ src/error.cpp \ - src/estimator.cpp \ src/parser.cpp \ src/server_node.cpp \ src/settings.cpp \ @@ -83,7 +82,6 @@ test_libbitcoin_server_test_LDADD = src/libbitcoin-server.la ${boost_unit_test_f test_libbitcoin_server_test_SOURCES = \ test/configuration.cpp \ test/error.cpp \ - test/estimator.cpp \ test/main.cpp \ test/settings.cpp \ test/test.cpp \ @@ -176,7 +174,6 @@ include_bitcoin_server_HEADERS = \ include/bitcoin/server/configuration.hpp \ include/bitcoin/server/define.hpp \ include/bitcoin/server/error.hpp \ - include/bitcoin/server/estimator.hpp \ include/bitcoin/server/parser.hpp \ include/bitcoin/server/server_node.hpp \ include/bitcoin/server/settings.hpp \ diff --git a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj index e7bf219c..028e0e57 100644 --- a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj @@ -120,7 +120,6 @@ - diff --git a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters index e64d8b96..338ab17a 100644 --- a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters @@ -33,9 +33,6 @@ src - - src - src diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj index 1cb33e91..2b460fa4 100644 --- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj @@ -123,7 +123,6 @@ - @@ -168,7 +167,6 @@ - diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters index 61c5e7cb..203758f9 100644 --- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -69,9 +69,6 @@ src - - src - src @@ -200,9 +197,6 @@ include\bitcoin\server - - include\bitcoin\server - include\bitcoin\server\interfaces diff --git a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj index 51e9c117..6a146ce4 100644 --- a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj @@ -120,7 +120,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters index e64d8b96..338ab17a 100644 --- a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters @@ -33,9 +33,6 @@ src - - src - src diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj index 2146c850..5916e6b0 100644 --- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj @@ -123,7 +123,6 @@ - @@ -168,7 +167,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters index 61c5e7cb..203758f9 100644 --- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -69,9 +69,6 @@ src - - src - src @@ -200,9 +197,6 @@ include\bitcoin\server - - include\bitcoin\server - include\bitcoin\server\interfaces diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index 1c234ec0..a5dd0928 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include diff --git a/include/bitcoin/server/estimator.hpp b/include/bitcoin/server/estimator.hpp deleted file mode 100644 index b7f49d59..00000000 --- a/include/bitcoin/server/estimator.hpp +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_SERVER_ESTIMATOR_HPP -#define LIBBITCOIN_SERVER_ESTIMATOR_HPP - -#include -#include - -namespace libbitcoin { -namespace server { - -/// Fee estimator with contained accumulator. -/// Accumulator is typically too large for stack creation. -/// Thread safe, blocking calls to estimate() during updates. -/// Initialize on chain current coalesce after snapshot/prune. -/// If chain falls > 1008/2 (?) blocks behind, reset and wait for coalesce. -/// When validating still use query-based fee rate population (compact blocks). -class BCS_API estimator -{ -public: - typedef std::shared_ptr ptr; - static constexpr size_t maximum_horizon = 1008; - - DELETE_COPY_MOVE_DESTRUCT(estimator); - - /// Estimation modes. - enum class mode - { - basic, - geometric, - economical, - conservative - }; - - /// Construct (use heap allocation). - estimator() NOEXCEPT {}; - - /// Fee estimation in satoshis / transaction virtual size. - /// Pass zero to target next block for confirmation, range:0..1007. - uint64_t estimate(size_t target, mode mode) const NOEXCEPT; - - /// Populate accumulator with count blocks up to the top confirmed block. - bool initialize(std::atomic_bool& cancel, const node::query& query, - size_t count=maximum_horizon) NOEXCEPT; - - /// Update accumulator. - bool push(const node::query& query) NOEXCEPT; - bool pop(const node::query& query) NOEXCEPT; - - /// Top height of accumulator. - size_t top_height() const NOEXCEPT; - -protected: - using rates = database::fee_rates; - using rate_sets = database::fee_rate_sets; - - /// Bucket depth sizing parameters (number of blocks). - enum horizon : size_t - { - /// 2 hrs × 60 mins/hr / 10 mins/block = 12 blocks. - small = 12, - - /// 8 hrs × 60 mins/hr / 10 mins/block = 48 blocks. - medium = 48, - - /// 7 days * 24 hrs/day × 60 mins/hr / 10 mins/block = 1008 blocks. - large = maximum_horizon - }; - - /// Bucket count sizing parameters. - struct sizing - { - static constexpr double min = 0.1; - static constexpr double max = 100'000.0; - static constexpr double step = 1.05; - - /// Derived from min/max/step above. - static constexpr size_t count = 283; - }; - - /// Estimation confidences. - struct confidence - { - static constexpr double low = 0.60; - static constexpr double mid = 0.85; - static constexpr double high = 0.95; - }; - - /// Accumulator (persistent, decay-weighted counters). - struct accumulator - { - template - struct bucket - { - /// Total scaled txs in bucket. - size_t total{}; - - /// confirmed[n]: scaled txs confirmed in > n blocks. - std::array confirmed; - }; - - /// Current block height of accumulated state. - size_t top_height{}; - - /// Accumulated scaled fee in decayed buckets by horizon. - /// Array count is the half life of the decay it implies. - std::array, sizing::count> small{}; - std::array, sizing::count> medium{}; - std::array, sizing::count> large{}; - }; - - // C++23: make consteval. - static inline double decay_rate() NOEXCEPT - { - static const auto rate = std::pow(0.5, 1.0 / sizing::count); - return rate; - } - - // C++23: make constexpr. - static inline double to_scale_term(size_t age) NOEXCEPT - { - return system::power(decay_rate(), age); - } - - // C++23: make constexpr. - static inline double to_scale_factor(bool push) NOEXCEPT - { - return std::pow(decay_rate(), push ? +1.0 : -1.0); - } - - accumulator& history() NOEXCEPT; - const accumulator& history() const NOEXCEPT; - bool initialize(const rate_sets& blocks) NOEXCEPT; - bool push(const rates& block) NOEXCEPT; - bool pop(const rates& block) NOEXCEPT; - uint64_t compute(size_t target, double confidence, - bool geometric=false) const NOEXCEPT; - -private: - bool update(const rates& block, size_t height, bool push) NOEXCEPT; - void decay(auto& buckets, double factor) NOEXCEPT; - void decay(bool push) NOEXCEPT; - - accumulator fees_{}; -}; - -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/src/estimator.cpp b/src/estimator.cpp deleted file mode 100644 index f3afc37c..00000000 --- a/src/estimator.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { - -using namespace system; - -// public -// ---------------------------------------------------------------------------- - -uint64_t estimator::estimate(size_t target, mode mode) const NOEXCEPT -{ - // max_uint64 is failure sentinel (and unachievable/invalid as a fee). - auto estimate = max_uint64; - constexpr size_t large = horizon::large; - if (target >= large) - return estimate; - - // Valid results are effectively limited to at least 1 sat/vb. - // threshold_fee is thread safe but values are affected during update. - switch (mode) - { - case mode::basic: - { - estimate = compute(target, confidence::high); - break; - } - case mode::geometric: - { - estimate = compute(target, confidence::high, true); - break; - } - case mode::economical: - { - const auto target1 = to_half(target); - const auto target2 = std::min(one, target); - const auto target3 = std::min(large, two * target); - const auto fee1 = compute(target1, confidence::low); - const auto fee2 = compute(target2, confidence::mid); - const auto fee3 = compute(target3, confidence::high); - estimate = std::max({ fee1, fee2, fee3 }); - break; - } - case mode::conservative: - { - const auto target1 = to_half(target); - const auto target2 = std::min(one, target); - const auto target3 = std::min(large, two * target); - const auto fee1 = compute(target1, confidence::low); - const auto fee2 = compute(target2, confidence::mid); - const auto fee3 = compute(target3, confidence::high); - estimate = std::max({ fee1, fee2, fee3 }); - break; - } - } - - return estimate; -} - -bool estimator::initialize(std::atomic_bool& cancel, const node::query& query, - size_t count) NOEXCEPT -{ - if (is_zero(count)) - return true; - - const auto top = query.get_top_confirmed(); - if (sub1(count) > top) - return false; - - rate_sets blocks{}; - const auto start = top - sub1(count); - return query.get_branch_fees(cancel, blocks, start, count) && - initialize(blocks); -} - -bool estimator::push(const node::query& query) NOEXCEPT -{ - if (is_add_overflow(top_height(), one)) - return false; - - rates block{}; - const auto link = query.to_confirmed(add1(top_height())); - return query.get_block_fees(block, link) && push(block); -} - -bool estimator::pop(const node::query& query) NOEXCEPT -{ - if (is_subtract_overflow(top_height(), one)) - return false; - - rates block{}; - const auto link = query.to_confirmed(sub1(top_height())); - return query.get_block_fees(block, link) && pop(block); -} - -size_t estimator::top_height() const NOEXCEPT -{ - return fees_.top_height; -} - -// protected -// ---------------------------------------------------------------------------- - -estimator::accumulator& estimator::history() NOEXCEPT -{ - return fees_; -} - -const estimator::accumulator& estimator::history() const NOEXCEPT -{ - return fees_; -} - -bool estimator::initialize(const rate_sets& blocks) NOEXCEPT -{ - const auto count = blocks.size(); - if (is_zero(count)) - return true; - - if (system::is_add_overflow(fees_.top_height, sub1(count))) - return false; - - auto height = fees_.top_height; - fees_.top_height += sub1(count); - - // 3-4 secs slower when parallel at 1008 blocks. - for (const auto& block: blocks) - if (!update(block, height++, true)) - return false; - - return true; -} - -// Blocks must be pushed in order (but independent of chain index). -bool estimator::push(const rates& block) NOEXCEPT -{ - decay(true); - return update(block, ++fees_.top_height, true); -} - -// Blocks must be pushed in order (but independent of chain index). -bool estimator::pop(const rates& block) NOEXCEPT -{ - const auto result = update(block, fees_.top_height, false); - decay(false); - --fees_.top_height; - return result; -} - -uint64_t estimator::compute(size_t target, double confidence, - bool geometric) const NOEXCEPT -{ - const auto threshold = [](double part, double total, size_t) NOEXCEPT - { - return part / total; - }; - - // Geometric distribution approximation, not a full Markov process. - const auto markov = [](double part, double total, size_t target) NOEXCEPT - { - return power(part / total, target); - }; - - const auto call = [&](const auto& buckets) NOEXCEPT - { - BC_PUSH_WARNING(NO_UNGUARDED_POINTERS) - const auto& contribution = geometric ? markov : threshold; - BC_POP_WARNING() - - constexpr auto magic_number = 2u; - const auto at_least_four = magic_number * add1(target); - double total{}, part{}; - auto index = buckets.size(); - auto found = index; - for (const auto& bucket: std::views::reverse(buckets)) - { - --index; - total += to_floating(bucket.total); - part += to_floating(bucket.confirmed.at(target)); - if (total < at_least_four) - continue; - - if (contribution(part, total, target) > (1.0 - confidence)) - break; - - found = index; - } - - if (found == buckets.size()) - return max_uint64; - - const auto minimum = sizing::min * std::pow(sizing::step, found); - return to_ceilinged_integer(minimum); - }; - - if (target < horizon::small) return call(fees_.small); - if (target < horizon::medium) return call(fees_.medium); - if (target < horizon::large) return call(fees_.large); - return max_uint64; -} - -// private -// ---------------------------------------------------------------------------- - -void estimator::decay(bool push) NOEXCEPT -{ - const auto factor = to_scale_factor(push); - decay(fees_.large, factor); - decay(fees_.medium, factor); - decay(fees_.small, factor); -} - -void estimator::decay(auto& buckets, double factor) NOEXCEPT -{ - for (auto& bucket: buckets) - { - bucket.total = to_floored_integer(bucket.total * factor); - for (auto& count: bucket.confirmed) - count = to_floored_integer(count * factor); - } -} - -bool estimator::update(const rates& block, size_t height, bool push) NOEXCEPT -{ - // std::log (replace static with constexpr in c++26). - static const auto growth = std::log(sizing::step); - std::array counts{}; - - for (const auto& tx: block) - { - if (is_zero(tx.bytes)) - return false; - - if (is_zero(tx.fee)) - continue; - - const auto rate = to_floating(tx.fee) / tx.bytes; - if (rate < sizing::min) - continue; - - // Clamp overflow to last bin. - const auto bin = std::log(rate / sizing::min) / growth; - ++counts.at(std::min(to_floored_integer(bin), sub1(sizing::count))); - } - - // At age zero scale term is one. - const auto age = top_height() - height; - const auto scale = to_scale_term(age); - const auto call = [&](auto& buckets) NOEXCEPT - { - // The array count of the buckets element type. - const auto horizon = buckets.front().confirmed.size(); - - size_t bin{}; - for (const auto count: counts) - { - if (is_zero(count)) - { - ++bin; - continue; - } - - auto& bucket = buckets.at(bin++); - const auto scaled = to_floored_integer(count * scale); - const auto signed_term = push ? scaled : twos_complement(scaled); - - bucket.total += signed_term; - for (auto target = age; target < horizon; ++target) - bucket.confirmed.at(target) += signed_term; - } - }; - - call(fees_.large); - call(fees_.medium); - call(fees_.small); - return true; -} - -} // namespace server -} // namespace libbitcoin diff --git a/test/estimator.cpp b/test/estimator.cpp deleted file mode 100644 index 1fbc0bb1..00000000 --- a/test/estimator.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#include "test.hpp" - -BOOST_AUTO_TEST_SUITE(estimator_tests) - -using namespace system; - -struct acessor - : server::estimator -{ - typedef std::shared_ptr ptr; - - static acessor::ptr create() NOEXCEPT - { - return { new acessor(),[](acessor* ptr) NOEXCEPT { delete ptr; } }; - } - - using rates = estimator::rates; - using rate_sets = estimator::rate_sets; - using confidence = estimator::confidence; - using horizon = estimator::horizon; - using sizing = estimator::sizing; - using estimator::decay_rate; - using estimator::to_scale_term; - using estimator::to_scale_factor; - using estimator::history; - using estimator::initialize; - using estimator::push; - using estimator::pop; - using estimator::compute; -}; - -// decay_rate - -BOOST_AUTO_TEST_CASE(estimator__decay_rate__invoke__expected) -{ - const auto expected = std::pow(0.5, 1.0 / acessor::sizing::count); - BOOST_REQUIRE_CLOSE(acessor::decay_rate(), expected, 0.000001); -} - -// to_scale_term - -BOOST_AUTO_TEST_CASE(estimator__to_scale_term__zero__one) -{ - BOOST_REQUIRE_EQUAL(acessor::to_scale_term(0u), 1.0); -} - -BOOST_AUTO_TEST_CASE(estimator__to_scale_term__non_zero__expected) -{ - const auto rate = acessor::decay_rate(); - constexpr auto age = 42u; - const auto expected = std::pow(rate, age); - BOOST_REQUIRE_CLOSE(acessor::to_scale_term(age), expected, 0.000001); -} - -// to_scale_factor - -BOOST_AUTO_TEST_CASE(estimator__to_scale_factor__push_true__decay_rate) -{ - const auto rate = acessor::decay_rate(); - const auto expected = std::pow(rate, +1.0); - BOOST_REQUIRE_CLOSE(acessor::to_scale_factor(true), expected, 0.000001); -} - -BOOST_AUTO_TEST_CASE(estimator__to_scale_factor__push_false__inverse_decay_rate) -{ - const auto rate = acessor::decay_rate(); - const auto expected = std::pow(rate, -1.0); - BOOST_REQUIRE_CLOSE(acessor::to_scale_factor(false), expected, 0.000001); -} - -// top_height - -BOOST_AUTO_TEST_CASE(estimator__top_height__default__zero) -{ - const auto instance = acessor::create(); - BOOST_REQUIRE_EQUAL(instance->top_height(), 0u); -} - -BOOST_AUTO_TEST_CASE(estimator__top_height__non_default__expected) -{ - const auto instance = acessor::create(); - instance->history().top_height = 42u; - BOOST_REQUIRE_EQUAL(instance->top_height(), 42u); -} - -// initialize - -BOOST_AUTO_TEST_CASE(estimator__initialize__empty__true_height_unchanged) -{ - const auto instance = acessor::create(); - const acessor::rate_sets empty{}; - BOOST_REQUIRE(instance->initialize(empty)); - BOOST_REQUIRE_EQUAL(instance->top_height(), 0u); - BOOST_REQUIRE_EQUAL(instance->history().small[0].total, 0u); - - -}BOOST_AUTO_TEST_CASE(estimator__initialize__overflow__false_height_unchanged) -{ - const auto instance = acessor::create(); - instance->history().top_height = sub1(max_size_t); - acessor::rate_sets blocks(3); - BOOST_REQUIRE(!instance->initialize(blocks)); - BOOST_REQUIRE_EQUAL(instance->top_height(), sub1(max_size_t)); -} - -BOOST_AUTO_TEST_CASE(estimator__initialize__two_blocks__true_height_updated) -{ - const auto instance = acessor::create(); - acessor::rate_sets blocks(2); - BOOST_REQUIRE(instance->initialize(blocks)); - BOOST_REQUIRE_EQUAL(instance->top_height(), 1u); -} - -BOOST_AUTO_TEST_CASE(estimator__initialize__single_block__populates_expected) -{ - const auto instance = acessor::create(); - - // rate of 1/10 (0.1) in bin 0. - const acessor::rates block{ { 10u, 1u } }; - const acessor::rate_sets blocks{ block }; - BOOST_REQUIRE(instance->initialize(blocks)); - - constexpr size_t age{}; - const auto scale = acessor::to_scale_term(age); - const auto scaled = to_floored_integer(1u * scale); - const auto& small0 = instance->history().small.at(0); - - BOOST_REQUIRE_EQUAL(small0.total, scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[0], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[1], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[2], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[3], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[4], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[5], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[6], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[7], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[8], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[9], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[10], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[11], scaled); - - const auto& medium0 = instance->history().medium.at(0); - BOOST_REQUIRE_EQUAL(medium0.total, scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[0], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[1], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[2], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[45], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[46], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[47], scaled); - - const auto& large0 = instance->history().large.at(0); - BOOST_REQUIRE_EQUAL(large0.total, scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[0], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[2], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1005], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1006], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1007], scaled); -} - -BOOST_AUTO_TEST_CASE(estimator__initialize__two_blocks_with_data__expected) -{ - // 1 tx, rate=0.1, bin=0 - // 2 tx, rate=0.1, bin=0 - // Expected total: floor(1 * decay_rate) + floor(2 * 1.0) = 0 + 2 = 2. - const auto instance = acessor::create(); - const acessor::rates oldest{ { 10u, 1u } }; - const acessor::rates newest{ { 10u, 1u }, { 10u, 1u } }; - acessor::rate_sets blocks{ oldest, newest }; - BOOST_REQUIRE(instance->initialize(blocks)); - BOOST_REQUIRE_EQUAL(instance->top_height(), 1u); - BOOST_REQUIRE_EQUAL(instance->history().small.at(0).total, 2u); -} - -// push - -BOOST_AUTO_TEST_CASE(estimator__push__empty_block__decays_and_increments) -{ - const auto instance = acessor::create(); - constexpr auto initial = 100u; - instance->history().small[0].total = initial; - const auto factor = acessor::to_scale_factor(true); - const auto expected = to_floored_integer(initial * factor); - const acessor::rates empty{}; - BOOST_REQUIRE(instance->push(empty)); - BOOST_REQUIRE_EQUAL(instance->top_height(), 1u); - BOOST_REQUIRE_EQUAL(instance->history().small[0].total, expected); -} - -BOOST_AUTO_TEST_CASE(estimator__push__single_tx__populates_expected) -{ - const auto instance = acessor::create(); - - // rate of 1/10 (0.1) in bin 0. - const acessor::rates block{ { 10u, 1u } }; - BOOST_REQUIRE(instance->push(block)); - BOOST_REQUIRE_EQUAL(instance->top_height(), 1u); - - constexpr size_t age{}; - const auto scale = acessor::to_scale_term(age); - const auto scaled = to_floored_integer(1u * scale); - const auto& small0 = instance->history().small.at(0); - - BOOST_REQUIRE_EQUAL(small0.total, scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[0], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[1], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[2], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[3], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[4], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[5], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[6], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[7], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[8], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[9], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[10], scaled); - BOOST_REQUIRE_EQUAL(small0.confirmed[11], scaled); - - const auto& medium0 = instance->history().medium.at(0); - BOOST_REQUIRE_EQUAL(medium0.total, scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[0], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[1], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[2], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[45], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[46], scaled); - BOOST_REQUIRE_EQUAL(medium0.confirmed[47], scaled); - - const auto& large0 = instance->history().large.at(0); - BOOST_REQUIRE_EQUAL(large0.total, scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[0], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[2], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1005], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1006], scaled); - BOOST_REQUIRE_EQUAL(large0.confirmed[1007], scaled); -} - -// pop - -BOOST_AUTO_TEST_CASE(estimator__pop__empty_block__decays_and_decrements) -{ - const auto instance = acessor::create(); - instance->history().top_height = 1u; - constexpr auto initial = 100u; - instance->history().small[0].total = initial; - const auto factor = acessor::to_scale_factor(false); - const auto expected = to_floored_integer(initial * factor); - const acessor::rates empty{}; - BOOST_REQUIRE(instance->pop(empty)); - BOOST_REQUIRE_EQUAL(instance->top_height(), 0u); - BOOST_REQUIRE_EQUAL(instance->history().small[0].total, expected); -} - -BOOST_AUTO_TEST_CASE(estimator__pop__reverses_push__restores_state) -{ - const auto instance = acessor::create(); - - // rate of 1/10 (0.1) in bin 0. - const acessor::rates block{ { 10u, 1u } }; - BOOST_REQUIRE(instance->push(block)); - BOOST_REQUIRE(instance->pop(block)); - BOOST_REQUIRE_EQUAL(instance->top_height(), 0u); - - const auto& small0 = instance->history().small.at(0); - - BOOST_REQUIRE_EQUAL(small0.total, 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[0], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[1], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[2], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[3], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[4], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[5], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[6], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[7], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[8], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[9], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[10], 0u); - BOOST_REQUIRE_EQUAL(small0.confirmed[11], 0u); - - const auto& medium0 = instance->history().medium.at(0); - BOOST_REQUIRE_EQUAL(medium0.total, 0u); - BOOST_REQUIRE_EQUAL(medium0.confirmed[0], 0u); - BOOST_REQUIRE_EQUAL(medium0.confirmed[1], 0u); - BOOST_REQUIRE_EQUAL(medium0.confirmed[2], 0u); - BOOST_REQUIRE_EQUAL(medium0.confirmed[45], 0u); - BOOST_REQUIRE_EQUAL(medium0.confirmed[46], 0u); - BOOST_REQUIRE_EQUAL(medium0.confirmed[47], 0u); - - const auto& large0 = instance->history().large.at(0); - BOOST_REQUIRE_EQUAL(large0.total, 0u); - BOOST_REQUIRE_EQUAL(large0.confirmed[0], 0u); - BOOST_REQUIRE_EQUAL(large0.confirmed[1], 0u); - BOOST_REQUIRE_EQUAL(large0.confirmed[2], 0u); - BOOST_REQUIRE_EQUAL(large0.confirmed[1005], 0u); - BOOST_REQUIRE_EQUAL(large0.confirmed[1006], 0u); - BOOST_REQUIRE_EQUAL(large0.confirmed[1007], 0u); -} - -// compute - -BOOST_AUTO_TEST_CASE(estimator__compute__default_state__max_uint64) -{ - const auto instance = acessor::create(); - BOOST_REQUIRE_EQUAL(instance->compute(0, acessor::confidence::high), max_uint64); - BOOST_REQUIRE_EQUAL(instance->compute(1, acessor::confidence::mid, true), max_uint64); - BOOST_REQUIRE_EQUAL(instance->compute(50, acessor::confidence::low), max_uint64); -} - -BOOST_AUTO_TEST_CASE(estimator__compute__insufficient_total__max_uint64) -{ - const auto instance = acessor::create(); - constexpr auto bin = 0u; - - // < at_least_four=2 for target=0. - constexpr auto value = 1u; - instance->history().small[bin].total = value; - instance->history().small[bin].confirmed[0] = value; - BOOST_REQUIRE_EQUAL(instance->compute(0, acessor::confidence::high), max_uint64); -} - -BOOST_AUTO_TEST_CASE(estimator__compute__low_failure_basic__expected_fee) -{ - const auto instance = acessor::create(); - constexpr auto bin = 0u; - constexpr auto total = 10u; - - // 0/10 = 0 <= 0.05. - constexpr auto failure = 0u; - instance->history().small[bin].total = total; - instance->history().small[bin].confirmed[0] = failure; - const auto fee = to_ceilinged_integer(acessor::sizing::min * std::pow(acessor::sizing::step, bin)); - BOOST_REQUIRE_EQUAL(instance->compute(0, acessor::confidence::high), fee); -} - -BOOST_AUTO_TEST_CASE(estimator__compute__high_failure_basic__max_uint64) -{ - const auto instance = acessor::create(); - constexpr auto bin = 0u; - constexpr auto total = 10u; - - // 1/10 = 0.1 > 0.05. - constexpr auto failure = 1u; - instance->history().small[bin].total = total; - instance->history().small[bin].confirmed[0] = failure; - BOOST_REQUIRE_EQUAL(instance->compute(0, acessor::confidence::high), max_uint64); -} - -BOOST_AUTO_TEST_CASE(estimator__compute__multi_bin_basic__expected_fee) -{ - const auto instance = acessor::create(); - constexpr auto low_bin = 0u; - constexpr auto high_bin = 1u; - constexpr auto total = 10u; - - // high failure in low bin. - constexpr auto low_failure = 10u; - - // low failure in high bin. - constexpr auto high_failure = 0u; - instance->history().small[low_bin].total = total; - instance->history().small[low_bin].confirmed[0] = low_failure; - instance->history().small[high_bin].total = total; - instance->history().small[high_bin].confirmed[0] = high_failure; - - // Cumulative at high_bin: 0/10 = 0 <= 0.05, then at low_bin: 10/20 = 0.5 > 0.05, found=1. - const auto fee = to_ceilinged_integer(acessor::sizing::min * std::pow(acessor::sizing::step, high_bin)); - BOOST_REQUIRE_EQUAL(instance->compute(0, acessor::confidence::high), fee); -} - -BOOST_AUTO_TEST_CASE(estimator__compute__geometric_target_one__matches_basic) -{ - const auto instance = acessor::create(); - constexpr auto bin = 0u; - constexpr auto total = 10u; - constexpr auto failure = 0u; - instance->history().small[bin].total = total; - instance->history().small[bin].confirmed[1] = failure; - const auto fee = to_ceilinged_integer(acessor::sizing::min * std::pow(acessor::sizing::step, bin)); - const auto basic = instance->compute(1, acessor::confidence::high, false); - const auto geometric = instance->compute(1, acessor::confidence::high, true); - BOOST_REQUIRE_EQUAL(basic, fee); - BOOST_REQUIRE_EQUAL(geometric, fee); -} - -BOOST_AUTO_TEST_CASE(estimator__compute__geometric_high_target__expected) -{ - const auto instance = acessor::create(); - constexpr auto bin = 0u; - constexpr auto total = 10u; - - // p=0.1, pow(0.1,2)=0.01 < 0.05, so found=0. - constexpr auto failure = 1u; - instance->history().small[bin].total = total; - instance->history().small[bin].confirmed[2] = failure; - const auto fee = to_ceilinged_integer(acessor::sizing::min * std::pow(acessor::sizing::step, bin)); - BOOST_REQUIRE_EQUAL(instance->compute(2, acessor::confidence::high, true), fee); - - // Contrast with basic: 0.1 > 0.05, would be max_uint64. - BOOST_REQUIRE_EQUAL(instance->compute(2, acessor::confidence::high, false), max_uint64); -} - -BOOST_AUTO_TEST_SUITE_END()