From 59bbba2e2ec4c1e8f0afdc0f33edd24c659f076f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 17 May 2026 22:06:02 -0400 Subject: [PATCH 1/7] Add chaser_estimate error codes. --- include/bitcoin/node/error.hpp | 5 ++++- include/bitcoin/node/settings.hpp | 1 + src/error.cpp | 5 ++++- src/settings.cpp | 1 + test/error.cpp | 27 +++++++++++++++++++++++++++ test/settings.cpp | 1 + 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index aa648f7f..b9db21af 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -97,7 +97,10 @@ enum error_t : uint8_t confirm9, confirm10, confirm11, - confirm12 + confirm12, + estimates_failed, + estimates_disabled, + estimates_premature }; // No current need for error_code equivalence mapping. diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp index 679ef1c3..4dea4a82 100644 --- a/include/bitcoin/node/settings.hpp +++ b/include/bitcoin/node/settings.hpp @@ -41,6 +41,7 @@ class BCN_API settings bool allow_overlapped; bool defer_validation; bool defer_confirmation; + bool enable_fee_estimator; float allowed_deviation; float minimum_fee_rate; float minimum_bump_rate; diff --git a/src/error.cpp b/src/error.cpp index 16250259..33fafa00 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -87,7 +87,10 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { confirm9, "confirm9" }, { confirm10, "confirm10" }, { confirm11, "confirm11" }, - { confirm12, "confirm12" } + { confirm12, "confirm12" }, + { estimates_failed, "estimates_failed" }, + { estimates_disabled, "estimates_disabled" }, + { estimates_premature, "estimates_premature" } }; DEFINE_ERROR_T_CATEGORY(error, "node", "node code") diff --git a/src/settings.cpp b/src/settings.cpp index d9673e77..728aab2e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -36,6 +36,7 @@ settings::settings() NOEXCEPT allow_overlapped{ true }, defer_validation{ false }, defer_confirmation{ false }, + enable_fee_estimator{ false }, minimum_fee_rate{ 0.0 }, minimum_bump_rate{ 0.0 }, allowed_deviation{ 1.5 }, diff --git a/test/error.cpp b/test/error.cpp index e56676f1..cb4699ba 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -225,4 +225,31 @@ BOOST_AUTO_TEST_CASE(error_t__code__confirm1__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "confirm1"); } +BOOST_AUTO_TEST_CASE(error_t__code__estimates_failed__true_expected_message) +{ + constexpr auto value = error::estimates_failed; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "estimates_failed"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__estimates_disabled__true_expected_message) +{ + constexpr auto value = error::estimates_disabled; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "estimates_disabled"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__estimates_premature__true_expected_message) +{ + constexpr auto value = error::estimates_premature; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "estimates_premature"); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/settings.cpp b/test/settings.cpp index 24aaf624..b3df4ef4 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -37,6 +37,7 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) BOOST_REQUIRE_EQUAL(node.allow_overlapped, true); BOOST_REQUIRE_EQUAL(node.defer_validation, false); BOOST_REQUIRE_EQUAL(node.defer_confirmation, false); + BOOST_REQUIRE_EQUAL(node.enable_fee_estimator, false); BOOST_REQUIRE_EQUAL(node.minimum_fee_rate, 0.0); BOOST_REQUIRE_EQUAL(node.minimum_bump_rate, 0.0); BOOST_REQUIRE_EQUAL(node.allowed_deviation, 1.5); From c146024aba9ffcaa5f06534684a0095da3736c44 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 17 May 2026 22:06:23 -0400 Subject: [PATCH 2/7] Assert stranded() in chaser::start() overrides. --- include/bitcoin/node/chasers/chaser.hpp | 2 +- src/chasers/chaser_check.cpp | 1 + src/chasers/chaser_confirm.cpp | 1 + src/chasers/chaser_header.cpp | 1 + src/chasers/chaser_snapshot.cpp | 2 ++ src/chasers/chaser_storage.cpp | 2 ++ src/chasers/chaser_template.cpp | 1 + src/chasers/chaser_transaction.cpp | 1 + 8 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/bitcoin/node/chasers/chaser.hpp b/include/bitcoin/node/chasers/chaser.hpp index bea55dc7..6d9cab57 100644 --- a/include/bitcoin/node/chasers/chaser.hpp +++ b/include/bitcoin/node/chasers/chaser.hpp @@ -39,7 +39,7 @@ class BCN_API chaser public: DELETE_COPY_MOVE_DESTRUCT(chaser); - /// Should be called from node strand. + /// Must be called from node strand. virtual code start() NOEXCEPT = 0; /// Override to capture non-blocking stopping. diff --git a/src/chasers/chaser_check.cpp b/src/chasers/chaser_check.cpp index 68e72f98..768ce4af 100644 --- a/src/chasers/chaser_check.cpp +++ b/src/chasers/chaser_check.cpp @@ -90,6 +90,7 @@ map_ptr chaser_check::split(const map_ptr& map) NOEXCEPT code chaser_check::start() NOEXCEPT { + BC_ASSERT(stranded()); start_tracking(); set_position(archive().get_fork()); requested_ = advanced_ = position(); diff --git a/src/chasers/chaser_confirm.cpp b/src/chasers/chaser_confirm.cpp index d1fb50b7..56ee6a9e 100644 --- a/src/chasers/chaser_confirm.cpp +++ b/src/chasers/chaser_confirm.cpp @@ -42,6 +42,7 @@ chaser_confirm::chaser_confirm(full_node& node) NOEXCEPT code chaser_confirm::start() NOEXCEPT { + BC_ASSERT(stranded()); const auto& query = archive(); set_position(query.get_fork()); diff --git a/src/chasers/chaser_header.cpp b/src/chasers/chaser_header.cpp index 0b0da56c..97a78388 100644 --- a/src/chasers/chaser_header.cpp +++ b/src/chasers/chaser_header.cpp @@ -35,6 +35,7 @@ chaser_header::chaser_header(full_node& node) NOEXCEPT code chaser_header::start() NOEXCEPT { + BC_ASSERT(stranded()); if (!initialize_milestone()) return fault(error::header1); diff --git a/src/chasers/chaser_snapshot.cpp b/src/chasers/chaser_snapshot.cpp index 9eefc134..01a487d4 100644 --- a/src/chasers/chaser_snapshot.cpp +++ b/src/chasers/chaser_snapshot.cpp @@ -52,6 +52,8 @@ chaser_snapshot::chaser_snapshot(full_node& node) NOEXCEPT code chaser_snapshot::start() NOEXCEPT { + BC_ASSERT(stranded()); + // Initial values assume all stops or starts are snapped. // get_top_validated is an expensive scan. diff --git a/src/chasers/chaser_storage.cpp b/src/chasers/chaser_storage.cpp index 7abbd33d..306caa45 100644 --- a/src/chasers/chaser_storage.cpp +++ b/src/chasers/chaser_storage.cpp @@ -47,6 +47,8 @@ chaser_storage::chaser_storage(full_node& node) NOEXCEPT code chaser_storage::start() NOEXCEPT { + BC_ASSERT(stranded()); + // Construct is too early to create the unstarted timer. disk_timer_ = std::make_shared(log, strand(), seconds{1}); diff --git a/src/chasers/chaser_template.cpp b/src/chasers/chaser_template.cpp index b6aa2077..80d45e03 100644 --- a/src/chasers/chaser_template.cpp +++ b/src/chasers/chaser_template.cpp @@ -43,6 +43,7 @@ chaser_template::chaser_template(full_node& node) NOEXCEPT // TODO: initialize template state. code chaser_template::start() NOEXCEPT { + BC_ASSERT(stranded()); SUBSCRIBE_CHASE(handle_chase, _1, _2, _3); return error::success; } diff --git a/src/chasers/chaser_transaction.cpp b/src/chasers/chaser_transaction.cpp index ee6a5fcb..347a5336 100644 --- a/src/chasers/chaser_transaction.cpp +++ b/src/chasers/chaser_transaction.cpp @@ -43,6 +43,7 @@ chaser_transaction::chaser_transaction(full_node& node) NOEXCEPT // TODO: initialize tx graph from store, log and stop on error. code chaser_transaction::start() NOEXCEPT { + BC_ASSERT(stranded()); SUBSCRIBE_CHASE(handle_chase, _1, _2, _3); return error::success; } From c1b3804b798493d17042313099f57cedb9f8e849 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 17 May 2026 22:07:11 -0400 Subject: [PATCH 3/7] Internal updates to estimator class. --- include/bitcoin/node/estimator.hpp | 13 +++++++------ src/estimator.cpp | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/include/bitcoin/node/estimator.hpp b/include/bitcoin/node/estimator.hpp index c1d8a453..30721e5c 100644 --- a/include/bitcoin/node/estimator.hpp +++ b/include/bitcoin/node/estimator.hpp @@ -19,6 +19,7 @@ #ifndef LIBBITCOIN_NODE_ESTIMATOR_HPP #define LIBBITCOIN_NODE_ESTIMATOR_HPP +#include #include #include @@ -34,7 +35,7 @@ namespace node { class BCN_API estimator { public: - typedef std::shared_ptr ptr; + typedef std::unique_ptr ptr; static constexpr size_t maximum_horizon = 1008; DELETE_COPY_MOVE_DESTRUCT(estimator); @@ -51,19 +52,19 @@ class BCN_API estimator /// Construct (use heap allocation). estimator() NOEXCEPT {}; - /// Fee estimation in satoshis / transaction virtual size. + /// Fee estimation in satoshis/transaction virtual size (not thread safe). /// 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 query& query, + bool initialize(const std::atomic_bool& cancel, const query& query, size_t count=maximum_horizon) NOEXCEPT; - /// Update accumulator. + /// Update accumulator (not thread safe). bool push(const query& query) NOEXCEPT; bool pop(const query& query) NOEXCEPT; - /// Top height of accumulator. + /// Top height of accumulator (thread safe). size_t top_height() const NOEXCEPT; protected: @@ -116,7 +117,7 @@ class BCN_API estimator }; /// Current block height of accumulated state. - size_t top_height{}; + std::atomic top_height{}; /// Accumulated scaled fee in decayed buckets by horizon. /// Array count is the half life of the decay it implies. diff --git a/src/estimator.cpp b/src/estimator.cpp index 0623f38f..dc856313 100644 --- a/src/estimator.cpp +++ b/src/estimator.cpp @@ -81,14 +81,14 @@ uint64_t estimator::estimate(size_t target, mode mode) const NOEXCEPT return estimate; } -bool estimator::initialize(std::atomic_bool& cancel, const query& query, +bool estimator::initialize(const std::atomic_bool& cancel, const query& query, size_t count) NOEXCEPT { if (is_zero(count)) return true; const auto top = query.get_top_confirmed(); - if (sub1(count) > top) + if (count > add1(top)) return false; rate_sets blocks{}; @@ -119,7 +119,7 @@ bool estimator::pop(const query& query) NOEXCEPT size_t estimator::top_height() const NOEXCEPT { - return fees_.top_height; + return fees_.top_height.load(std::memory_order_relaxed); } // protected @@ -141,11 +141,11 @@ bool estimator::initialize(const rate_sets& blocks) NOEXCEPT if (is_zero(count)) return true; - if (system::is_add_overflow(fees_.top_height, sub1(count))) + auto height = top_height(); + if (system::is_add_overflow(height, sub1(count))) return false; - auto height = fees_.top_height; - fees_.top_height += sub1(count); + fees_.top_height.fetch_add(sub1(count), std::memory_order_relaxed); // 3-4 secs slower when parallel at 1008 blocks. for (const auto& block: blocks) @@ -159,15 +159,16 @@ bool estimator::initialize(const rate_sets& blocks) NOEXCEPT bool estimator::push(const rates& block) NOEXCEPT { decay(true); - return update(block, ++fees_.top_height, true); + fees_.top_height.fetch_add(one, std::memory_order_relaxed); + return update(block, 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); + const auto result = update(block, top_height(), false); decay(false); - --fees_.top_height; + fees_.top_height.fetch_sub(one, std::memory_order_relaxed); return result; } From e089c2995b61c3d94b30af5491601dcaf3e28735 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 17 May 2026 23:02:21 -0400 Subject: [PATCH 4/7] Integrate estimator class into chaser_estimate. --- .../bitcoin/node/chasers/chaser_estimate.hpp | 25 +++++ src/chasers/chaser_estimate.cpp | 97 ++++++++++++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_estimate.hpp b/include/bitcoin/node/chasers/chaser_estimate.hpp index 52fb6a83..2c2fca5b 100644 --- a/include/bitcoin/node/chasers/chaser_estimate.hpp +++ b/include/bitcoin/node/chasers/chaser_estimate.hpp @@ -19,8 +19,10 @@ #ifndef LIBBITCOIN_NODE_CHASERS_CHASER_ESTIMATE_HPP #define LIBBITCOIN_NODE_CHASERS_CHASER_ESTIMATE_HPP +#include #include #include +#include namespace libbitcoin { namespace node { @@ -37,6 +39,17 @@ class BCN_API chaser_estimate chaser_estimate(full_node& node) NOEXCEPT; code start() NOEXCEPT override; + void stopping(const code& ec) NOEXCEPT override; + + /// Returns max_uint64 when disabled. + void estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT; + + /// Returns zero when disabled (thread safe). + size_t top_height() const NOEXCEPT; + + /// Initialization is complete and successful. + bool initialized() const NOEXCEPT; protected: virtual bool handle_chase(const code& ec, chase event_, @@ -44,6 +57,18 @@ class BCN_API chaser_estimate virtual void do_organized(header_t value) NOEXCEPT; virtual void do_reorganized(header_t value) NOEXCEPT; + +private: + bool initialize() NOEXCEPT; + void do_estimate(size_t target, estimator::mode mode, + const estimate_handler& handler) NOEXCEPT; + + // This is thread safe. + std::atomic_bool stopping_{}; + std::atomic_bool initialized_{}; + + // This is protected by strand. + estimator::ptr estimator_{}; }; } // namespace node diff --git a/src/chasers/chaser_estimate.cpp b/src/chasers/chaser_estimate.cpp index 89b9f480..f1d7f701 100644 --- a/src/chasers/chaser_estimate.cpp +++ b/src/chasers/chaser_estimate.cpp @@ -18,8 +18,10 @@ */ #include +#include #include #include +#include #include namespace libbitcoin { @@ -37,17 +39,69 @@ chaser_estimate::chaser_estimate(full_node& node) NOEXCEPT { } -// start +// start/stop // ---------------------------------------------------------------------------- code chaser_estimate::start() NOEXCEPT { - SUBSCRIBE_CHASE(handle_chase, _1, _2, _3); + BC_ASSERT(stranded()); + + if (node_settings().enable_fee_estimator) + { + SUBSCRIBE_CHASE(handle_chase, _1, _2, _3); + } + return error::success; } +void chaser_estimate::stopping(const code& ec) NOEXCEPT +{ + stopping_.store(true); + chaser::stopping(ec); +} + +// methods +// ---------------------------------------------------------------------------- + +void chaser_estimate::estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT +{ + if (!node_settings().enable_fee_estimator) + { + handler(error::estimates_disabled, {}); + return; + } + + if (!initialized()) + { + handler(error::estimates_premature, {}); + return; + } + + POST(do_estimate, target, mode, std::move(handler)); +} + +// private +void chaser_estimate::do_estimate(size_t target, estimator::mode mode, + const estimate_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + handler(error::success, estimator_->estimate(target, mode)); +} + +size_t chaser_estimate::top_height() const NOEXCEPT +{ + return initialized() ? estimator_->top_height() : zero; +} + +bool chaser_estimate::initialized() const NOEXCEPT +{ + return initialized_.load(std::memory_order_relaxed); +} + // event handlers // ---------------------------------------------------------------------------- +// protected bool chaser_estimate::handle_chase(const code&, chase event_, event_value value) NOEXCEPT @@ -59,6 +113,9 @@ bool chaser_estimate::handle_chase(const code&, chase event_, ////if (suspended()) //// return true; + if (!is_current(true)) + return true; + switch (event_) { case chase::organized: @@ -89,11 +146,47 @@ bool chaser_estimate::handle_chase(const code&, chase event_, void chaser_estimate::do_organized(header_t) NOEXCEPT { BC_ASSERT(stranded()); + + if (initialize()) + estimator_->push(archive()); } void chaser_estimate::do_reorganized(header_t) NOEXCEPT { BC_ASSERT(stranded()); + + if (initialize()) + estimator_->pop(archive()); +} + +// utility +// ---------------------------------------------------------------------------- +// private + +bool chaser_estimate::initialize() NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (initialized()) + return true; + + // Preempt initialize fault when horizon exceeds chain length. + constexpr auto horizon = estimator::maximum_horizon; + const auto blocks = add1(archive().get_top_confirmed()); + if (horizon > blocks) + return false; + + // Heap-allocate the estimator due to size. + estimator_ = std::make_unique(); + if (!estimator_->initialize(stopping_, archive(), horizon)) + { + fault(error::estimates_failed); + estimator_.release(); + return false; + } + + initialized_.store(true, std::memory_order_relaxed); + return true; } BC_POP_WARNING() From a8220cfaff860cef8c5b5209d0c8892b62c60614 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 17 May 2026 23:09:46 -0400 Subject: [PATCH 5/7] Pass fee estimation from protocol->service->node->chaser. --- include/bitcoin/node/define.hpp | 13 ++++++++----- include/bitcoin/node/full_node.hpp | 5 +++++ include/bitcoin/node/protocols/protocol.hpp | 8 ++++++++ include/bitcoin/node/sessions/session.hpp | 5 +++++ src/full_node.cpp | 11 +++++++++-- src/protocols/protocol.cpp | 9 +++++++++ src/sessions/session.cpp | 6 ++++++ 7 files changed, 50 insertions(+), 7 deletions(-) diff --git a/include/bitcoin/node/define.hpp b/include/bitcoin/node/define.hpp index 59229b45..4aae9b57 100644 --- a/include/bitcoin/node/define.hpp +++ b/include/bitcoin/node/define.hpp @@ -52,6 +52,9 @@ namespace node { /// Alias system code. typedef std::error_code code; +/// Estimate types. +typedef std::function estimate_handler; + /// Organization types. typedef std::function organize_handler; typedef database::store store; @@ -120,10 +123,10 @@ using type_id = network::messages::peer::inventory_item::type_id; // settings : define // configuration : define settings // parser : define configuration -// /chasers : define configuration [forward: full_node] +// /chasers : define configuration estimator [forward: full_node] // /channels : define configuration -// full_node : define /chasers -// session : define [forward: full_node] +// full_node : define estimator /chasers +// session : define estimator [forward: full_node] // /messages : define -// /protocols : define /channels [session.hpp] -// /sessions : define /protocols [forward: full_node] \ No newline at end of file +// /protocols : define estimator /channels [session.hpp] +// /sessions : define /protocols [forward: full_node] \ No newline at end of file diff --git a/include/bitcoin/node/full_node.hpp b/include/bitcoin/node/full_node.hpp index 54464d25..32396ee6 100644 --- a/include/bitcoin/node/full_node.hpp +++ b/include/bitcoin/node/full_node.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace libbitcoin { @@ -151,6 +152,10 @@ class BCN_API full_node /// Methods. /// ----------------------------------------------------------------------- + /// Get current fee estimate. + void estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT; + /// Handle performance, base returns false (implied terminate). virtual void performance(object_key channel, uint64_t speed, result_handler&& handler) NOEXCEPT; diff --git a/include/bitcoin/node/protocols/protocol.hpp b/include/bitcoin/node/protocols/protocol.hpp index bbe6a38b..f8f8d70a 100644 --- a/include/bitcoin/node/protocols/protocol.hpp +++ b/include/bitcoin/node/protocols/protocol.hpp @@ -23,6 +23,7 @@ #include #include #include +#include // Only session.hpp. #include @@ -67,6 +68,13 @@ class BCN_API protocol /// The candidate|confirmed chain is current. virtual bool is_current(bool confirmed) const NOEXCEPT; + /// Methods. + /// ----------------------------------------------------------------------- + + /// Get current fee estimate. + void estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT; + /// Events subscription. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/node/sessions/session.hpp b/include/bitcoin/node/sessions/session.hpp index 2a2388f6..3a648056 100644 --- a/include/bitcoin/node/sessions/session.hpp +++ b/include/bitcoin/node/sessions/session.hpp @@ -21,6 +21,7 @@ #include #include +#include namespace libbitcoin { namespace node { @@ -75,6 +76,10 @@ class BCN_API session /// Methods. /// ----------------------------------------------------------------------- + /// Get current fee estimate. + void estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT; + /// Handle performance, base returns false (implied terminate). virtual void performance(object_key channel, uint64_t speed, network::result_handler&& handler) NOEXCEPT; diff --git a/src/full_node.cpp b/src/full_node.cpp index 2cc19d3a..929c2b7b 100644 --- a/src/full_node.cpp +++ b/src/full_node.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace libbitcoin { @@ -428,9 +429,15 @@ bool full_node::is_recent() const NOEXCEPT return is_current(query_.get_timestamp(timestamp, link)); } -// Session attachments. - +// Methods. // ---------------------------------------------------------------------------- + +void full_node::estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT +{ + chaser_estimate_.estimate(target, mode, std::move(handler)); +} + void full_node::performance(object_key key, uint64_t speed, result_handler&& handler) NOEXCEPT { diff --git a/src/protocols/protocol.cpp b/src/protocols/protocol.cpp index 0db05381..c0d68a2a 100644 --- a/src/protocols/protocol.cpp +++ b/src/protocols/protocol.cpp @@ -66,6 +66,15 @@ bool protocol::is_current(bool confirmed) const NOEXCEPT return session_->is_current(confirmed); } +// Methods. +// ---------------------------------------------------------------------------- + +void protocol::estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT +{ + session_->estimate(target, mode, std::move(handler)); +} + // Events subscription. // ---------------------------------------------------------------------------- diff --git a/src/sessions/session.cpp b/src/sessions/session.cpp index 101902ed..cd6554bc 100644 --- a/src/sessions/session.cpp +++ b/src/sessions/session.cpp @@ -97,6 +97,12 @@ void session::unsubscribe_chase(object_key key) NOEXCEPT // Methods. // ---------------------------------------------------------------------------- +void session::estimate(size_t target, estimator::mode mode, + estimate_handler&& handler) NOEXCEPT +{ + node_.estimate(target, mode, std::move(handler)); +} + void session::performance(object_key key, uint64_t speed, result_handler&& handler) NOEXCEPT { From ba5c96cea1e6ece96147c61bcd26f6323502406b Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 17 May 2026 23:34:02 -0400 Subject: [PATCH 6/7] Change enable_fee_estimator to fee_estimate_horizon. --- include/bitcoin/node/chasers/chaser_estimate.hpp | 2 +- include/bitcoin/node/settings.hpp | 4 +++- src/chasers/chaser_estimate.cpp | 9 ++++----- src/settings.cpp | 13 ++++++++++++- test/settings.cpp | 4 +++- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_estimate.hpp b/include/bitcoin/node/chasers/chaser_estimate.hpp index 2c2fca5b..6fa6af33 100644 --- a/include/bitcoin/node/chasers/chaser_estimate.hpp +++ b/include/bitcoin/node/chasers/chaser_estimate.hpp @@ -63,7 +63,7 @@ class BCN_API chaser_estimate void do_estimate(size_t target, estimator::mode mode, const estimate_handler& handler) NOEXCEPT; - // This is thread safe. + // These are thread safe. std::atomic_bool stopping_{}; std::atomic_bool initialized_{}; diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp index 4dea4a82..d0ea0ae6 100644 --- a/include/bitcoin/node/settings.hpp +++ b/include/bitcoin/node/settings.hpp @@ -41,12 +41,12 @@ class BCN_API settings bool allow_overlapped; bool defer_validation; bool defer_confirmation; - bool enable_fee_estimator; float allowed_deviation; float minimum_fee_rate; float minimum_bump_rate; uint16_t announcement_cache; uint16_t allocation_multiple; + uint16_t fee_estimate_horizon; ////uint64_t snapshot_bytes; ////uint32_t snapshot_valid; ////uint32_t snapshot_confirm; @@ -60,6 +60,8 @@ class BCN_API settings virtual size_t threads_() const NOEXCEPT; virtual size_t maximum_height_() const NOEXCEPT; virtual size_t maximum_concurrency_() const NOEXCEPT; + virtual size_t fee_estimate_horizon_() const NOEXCEPT; + virtual bool fee_estimate_enabled() const NOEXCEPT; virtual network::steady_clock::duration sample_period() const NOEXCEPT; virtual network::wall_clock::duration currency_window() const NOEXCEPT; virtual network::processing_priority thread_priority_() const NOEXCEPT; diff --git a/src/chasers/chaser_estimate.cpp b/src/chasers/chaser_estimate.cpp index f1d7f701..cbb9b0b5 100644 --- a/src/chasers/chaser_estimate.cpp +++ b/src/chasers/chaser_estimate.cpp @@ -46,7 +46,7 @@ code chaser_estimate::start() NOEXCEPT { BC_ASSERT(stranded()); - if (node_settings().enable_fee_estimator) + if (is_zero(node_settings().fee_estimate_enabled())) { SUBSCRIBE_CHASE(handle_chase, _1, _2, _3); } @@ -66,7 +66,7 @@ void chaser_estimate::stopping(const code& ec) NOEXCEPT void chaser_estimate::estimate(size_t target, estimator::mode mode, estimate_handler&& handler) NOEXCEPT { - if (!node_settings().enable_fee_estimator) + if (!node_settings().fee_estimate_enabled()) { handler(error::estimates_disabled, {}); return; @@ -171,9 +171,8 @@ bool chaser_estimate::initialize() NOEXCEPT return true; // Preempt initialize fault when horizon exceeds chain length. - constexpr auto horizon = estimator::maximum_horizon; - const auto blocks = add1(archive().get_top_confirmed()); - if (horizon > blocks) + const auto horizon = node_settings().fee_estimate_horizon_(); + if (horizon > add1(archive().get_top_confirmed())) return false; // Heap-allocate the estimator due to size. diff --git a/src/settings.cpp b/src/settings.cpp index 728aab2e..8f9b231b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,6 +21,7 @@ #include #include #include +#include using namespace bc::system; using namespace bc::network; @@ -36,12 +37,12 @@ settings::settings() NOEXCEPT allow_overlapped{ true }, defer_validation{ false }, defer_confirmation{ false }, - enable_fee_estimator{ false }, minimum_fee_rate{ 0.0 }, minimum_bump_rate{ 0.0 }, allowed_deviation{ 1.5 }, announcement_cache{ 42 }, allocation_multiple{ 20 }, + fee_estimate_horizon{ 0 }, ////snapshot_bytes{ 200'000'000'000 }, ////snapshot_valid{ 250'000 }, ////snapshot_confirm{ 500'000 }, @@ -74,6 +75,16 @@ size_t settings::maximum_concurrency_() const NOEXCEPT return to_bool(maximum_concurrency) ? maximum_concurrency : max_size_t; } +size_t settings::fee_estimate_horizon_() const NOEXCEPT +{ + return std::min(fee_estimate_horizon, estimator::maximum_horizon); +} + +bool settings::fee_estimate_enabled() const NOEXCEPT +{ + return to_bool(fee_estimate_horizon_()); +} + network::steady_clock::duration settings::sample_period() const NOEXCEPT { return network::seconds(sample_period_seconds); diff --git a/test/settings.cpp b/test/settings.cpp index b3df4ef4..e1920b63 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -37,12 +37,12 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) BOOST_REQUIRE_EQUAL(node.allow_overlapped, true); BOOST_REQUIRE_EQUAL(node.defer_validation, false); BOOST_REQUIRE_EQUAL(node.defer_confirmation, false); - BOOST_REQUIRE_EQUAL(node.enable_fee_estimator, false); BOOST_REQUIRE_EQUAL(node.minimum_fee_rate, 0.0); BOOST_REQUIRE_EQUAL(node.minimum_bump_rate, 0.0); BOOST_REQUIRE_EQUAL(node.allowed_deviation, 1.5); BOOST_REQUIRE_EQUAL(node.announcement_cache, 42_u16); BOOST_REQUIRE_EQUAL(node.allocation_multiple, 20_u16); + BOOST_REQUIRE_EQUAL(node.fee_estimate_horizon, 0u); ////BOOST_REQUIRE_EQUAL(node.snapshot_bytes, 200'000'000'000_u64); ////BOOST_REQUIRE_EQUAL(node.snapshot_valid, 250'000_u32); ////BOOST_REQUIRE_EQUAL(node.snapshot_confirm, 500'000_u32); @@ -57,6 +57,8 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) BOOST_REQUIRE_EQUAL(node.threads_(), one); BOOST_REQUIRE_EQUAL(node.maximum_height_(), max_size_t); BOOST_REQUIRE_EQUAL(node.maximum_concurrency_(), 50'000_size); + BOOST_REQUIRE_EQUAL(node.fee_estimate_horizon_(), 0_size); + BOOST_REQUIRE(!node.fee_estimate_enabled()); BOOST_REQUIRE(node.sample_period() == steady_clock::duration(seconds(10))); BOOST_REQUIRE(node.currency_window() == steady_clock::duration(minutes(1440))); BOOST_REQUIRE(node.thread_priority_() == network::processing_priority::high); From d4295cc3657021258347ff085af6cf24a67e47ce Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 18 May 2026 00:25:14 -0400 Subject: [PATCH 7/7] Add estimator::mode::unknown enum. --- include/bitcoin/node/estimator.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/bitcoin/node/estimator.hpp b/include/bitcoin/node/estimator.hpp index 30721e5c..685bc10b 100644 --- a/include/bitcoin/node/estimator.hpp +++ b/include/bitcoin/node/estimator.hpp @@ -46,7 +46,8 @@ class BCN_API estimator basic, geometric, economical, - conservative + conservative, + unknown }; /// Construct (use heap allocation).