diff --git a/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj b/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj index 357cd5ed..c3cea976 100644 --- a/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj @@ -161,7 +161,7 @@ - + @@ -177,7 +177,7 @@ - + diff --git a/builds/msvc/vs2026/libbitcoin-node-test/packages.config b/builds/msvc/vs2026/libbitcoin-node-test/packages.config index 97aed24f..869589f8 100644 --- a/builds/msvc/vs2026/libbitcoin-node-test/packages.config +++ b/builds/msvc/vs2026/libbitcoin-node-test/packages.config @@ -6,7 +6,7 @@ | --> - + diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj index 000aacdb..0f61c134 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj @@ -239,7 +239,7 @@ - + @@ -254,7 +254,7 @@ - + diff --git a/builds/msvc/vs2026/libbitcoin-node/packages.config b/builds/msvc/vs2026/libbitcoin-node/packages.config index 3a09727c..093b73c1 100644 --- a/builds/msvc/vs2026/libbitcoin-node/packages.config +++ b/builds/msvc/vs2026/libbitcoin-node/packages.config @@ -6,7 +6,7 @@ | --> - + diff --git a/include/bitcoin/node/chase.hpp b/include/bitcoin/node/chase.hpp index d2416cf2..891be398 100644 --- a/include/bitcoin/node/chase.hpp +++ b/include/bitcoin/node/chase.hpp @@ -96,10 +96,6 @@ enum class chase /// Issued by 'organize' and handled by 'check', 'validate', 'confirm'. disorganized, - /// Download concurrency window completed, advancing to next (height_t). - /// Issued by 'check' and handled by 'validate'. - advanced, - /// Check/Identify. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index b1ac864d..bcca6cfd 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -60,7 +60,6 @@ class BCN_API chaser_validate event_value value) NOEXCEPT; virtual void do_regressed(height_t branch_point) NOEXCEPT; - virtual void do_advanced(height_t height) NOEXCEPT; virtual void do_checked(height_t height) NOEXCEPT; virtual void do_bumped(height_t height) NOEXCEPT; virtual void do_bump(height_t height) NOEXCEPT; @@ -81,8 +80,8 @@ class BCN_API chaser_validate /// Batching. virtual code start_batch() NOEXCEPT; - virtual void process_batch() NOEXCEPT; virtual bool process_valids() NOEXCEPT; + virtual void process_batch(bool residual) NOEXCEPT; virtual void push_batch(const header_link& link, size_t height) NOEXCEPT; virtual bool process_invalids(const header_links& invalids) NOEXCEPT; virtual signatures get_capture(const header_link& link) NOEXCEPT; @@ -118,8 +117,10 @@ class BCN_API chaser_validate const atomic_counter_ptr& sequence) NOEXCEPT; // Capture helpers. - void log_capture(const std::string_view& name, - size_t captured, size_t missed) const NOEXCEPT; + std::string log_rate(const std::string& name, size_t numerator, + size_t denominator) const NOEXCEPT; + std::string log_ratio(const std::string& name, size_t numerator, + size_t denominator) const NOEXCEPT; void log_captures() const NOEXCEPT; // These are protected by strand. @@ -146,7 +147,8 @@ class BCN_API chaser_validate const uint32_t subsidy_interval_; const uint64_t initial_subsidy_; const size_t maximum_backlog_; - const bool batch_signatures_; + const uint64_t batch_target_; + const bool batch_enabled_; const bool node_witness_; const bool filter_; }; diff --git a/include/bitcoin/node/events.hpp b/include/bitcoin/node/events.hpp index a4f5f5e2..8ef112ac 100644 --- a/include/bitcoin/node/events.hpp +++ b/include/bitcoin/node/events.hpp @@ -62,8 +62,8 @@ enum events : uint8_t filter_msecs, // getfilter timespan in milliseconds. filterhashes_msecs, // getfilterhashes timespan in milliseconds. filterchecks_msecs, // getcfcheckpt timespan in milliseconds. - ecdsa_msecs, // process_batch ecdsa timespan in milliseconds. - schnorr_msecs, // process_batch schnorr timespan in milliseconds. + ecdsa_secs, // process_batch ecdsa timespan in seconds. + schnorr_secs, // process_batch schnorr timespan in seconds. unknown }; diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp index 9e887f03..678f4243 100644 --- a/include/bitcoin/node/settings.hpp +++ b/include/bitcoin/node/settings.hpp @@ -40,10 +40,10 @@ class BCN_API settings bool thread_priority; bool memory_priority; bool allow_overlapped; - bool batch_signatures; float allowed_deviation; float minimum_fee_rate; float minimum_bump_rate; + uint64_t batch_signatures; uint16_t announcement_cache; uint16_t fee_estimate_horizon; uint32_t maximum_height; @@ -62,6 +62,7 @@ class BCN_API settings virtual size_t maximum_concurrency_() const NOEXCEPT; virtual size_t fee_estimate_horizon_() const NOEXCEPT; virtual bool fee_estimate_enabled() const NOEXCEPT; + virtual bool batch_signatures_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_check.cpp b/src/chasers/chaser_check.cpp index fee1745c..7fb8d739 100644 --- a/src/chasers/chaser_check.cpp +++ b/src/chasers/chaser_check.cpp @@ -364,10 +364,7 @@ void chaser_check::do_advanced(height_t) NOEXCEPT // The full count of requested hashes has been validated. if (advanced_ == requested_) - { - notify(error::success, chase::advanced, advanced_); do_headers({}); - } } void chaser_check::do_checked(height_t height) NOEXCEPT diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 068fdf12..dab881c4 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -42,7 +42,8 @@ chaser_validate::chaser_validate(full_node& node) NOEXCEPT subsidy_interval_(node.system_settings().subsidy_interval_blocks), initial_subsidy_(node.system_settings().initial_subsidy()), maximum_backlog_(node.node_settings().maximum_concurrency_()), - batch_signatures_(node.node_settings().batch_signatures), + batch_target_(node.node_settings().batch_signatures), + batch_enabled_(node.node_settings().batch_signatures_enabled()), node_witness_(node.network_settings().witness_node()), filter_(node.archive().filter_enabled()) { @@ -88,16 +89,6 @@ bool chaser_validate::handle_chase(const code&, chase event_, POST(do_checked, std::get(value)); break; } - case chase::advanced: - { - if (!batch_signatures_) - break; - - // value is checked block height. - BC_ASSERT(std::holds_alternative(value)); - POST(do_advanced, std::get(value)); - break; - } case chase::regressed: case chase::disorganized: { @@ -131,12 +122,6 @@ void chaser_validate::do_regressed(height_t branch_point) NOEXCEPT set_position(branch_point); } -void chaser_validate::do_advanced(height_t) NOEXCEPT -{ - BC_ASSERT(stranded()); - process_batch(); -} - void chaser_validate::do_checked(height_t height) NOEXCEPT { BC_ASSERT(stranded()); @@ -386,6 +371,13 @@ void chaser_validate::complete_block(const code& ec, const header_link& link, // Not failed/invalid/batched/faulted, so block is complete (maybe valid). notify_block({}, height, link, bypass); + + // Arriving here with batch enabled implies that the block is current and + // was not batched. Each such block triggers residual batch processing. + if (batch_enabled_) + { + POST(process_batch, true); + } } void chaser_validate::notify_block(const code& ec, size_t height, diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index 840e8406..1850fbec 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -42,60 +42,93 @@ BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) code chaser_validate::start_batch() NOEXCEPT { auto& query = archive(); - return (batch_signatures_ && (!query.purge_ecdsa_signatures() || + return (batch_enabled_ && (!query.purge_ecdsa_signatures() || !query.purge_schnorr_signatures())) ? error::batch1 : error::success; } -// TODO: This is only invoked by check chaser advancement. But it is possible -// that entries may be captured after that point. So this must be bumped. -void chaser_validate::process_batch() NOEXCEPT +void chaser_validate::push_batch(const header_link& link, size_t height) NOEXCEPT +{ + BC_ASSERT(stranded()); + batched_.push_back(link); + + // chase portion of notify_block(success). + notify({}, chase::valid, possible_wide_cast(height)); + + // Process both tables when one hits target, allowing batched_ clearance + // and therefore forward confirmation progress. + process_batch(false); +} + +void chaser_validate::process_batch(bool residual) NOEXCEPT { BC_ASSERT(stranded()); + // Test outside of lock to prevent reader contention for nearly all calls. + // Will retest inside the lock, as table updates are running concurrently. + auto& query = archive(); + if (!residual && + (query.ecdsa_records() < batch_target_) && + (query.schnorr_records() < batch_target_)) + return; + // Unique lock prevents batch table updates during evaluation, allowing the // tables to be fully purged upon completion, and ensuring that evaluation // does not operate over partial block records in the batch tables. std::unique_lock lock(mutex_); - auto& query = archive(); - LOGN("Batch signature verify begin (" - << query.ecdsa_records() << ") ecdsa (" - << query.schnorr_records() << ") schnorr."); + log_captures(); - // set_block_unconfirmable + // set_block_unconfirmable(ecdsa) // ------------------------------------------------------------------------ - header_links invalids{}; - auto start = network::logger::now(); - if (!query.verify_ecdsa_signatures(invalids)) + if (const auto records = query.ecdsa_records(); is_nonzero(records)) { - fault(error::batch2); - return; - } - span(events::ecdsa_msecs, start); + header_links invalids{}; + const auto start = network::logger::now(); + if (!query.verify_ecdsa_signatures(invalids)) + { + fault(error::batch2); + return; + } - if (!process_invalids(invalids) || !query.purge_ecdsa_signatures()) - { - fault(error::batch3); - return; - } + const auto end = network::logger::now(); + const auto elapsed = duration_cast(end - start).count(); + fire(events::ecdsa_secs, elapsed); + LOGN(log_rate("Batch verify rate ecdsa.... ", records, elapsed)); - invalids.clear(); - start = network::logger::now(); - if (!query.verify_schnorr_signatures(invalids)) - { - fault(error::batch4); - return; + if (!process_invalids(invalids) || !query.purge_ecdsa_signatures()) + { + fault(error::batch3); + return; + } } - span(events::schnorr_msecs, start); - if (!process_invalids(invalids) || !query.purge_schnorr_signatures()) + // set_block_unconfirmable(schnorr) + // ------------------------------------------------------------------------ + + if (const auto records = query.schnorr_records(); is_nonzero(records)) { - fault(error::batch5); - return; + header_links invalids{}; + const auto start = network::logger::now(); + if (!query.verify_schnorr_signatures(invalids)) + { + fault(error::batch4); + return; + } + + const auto end = network::logger::now(); + const auto elapsed = duration_cast(end - start).count(); + fire(events::schnorr_secs, elapsed); + LOGN(log_rate("Batch verify rate schnorr.. ", records, elapsed)); + + if (!process_invalids(invalids) || !query.purge_schnorr_signatures()) + { + fault(error::batch5); + return; + } } - // set_block_valid + // set_block_valid(batched_ excluding ecdsa/schnorr failures) // ------------------------------------------------------------------------ if (!process_valids()) @@ -103,19 +136,6 @@ void chaser_validate::process_batch() NOEXCEPT fault(error::batch6); return; } - - // ------------------------------------------------------------------------ - - LOGN("Batch signature verify end."); -} - -void chaser_validate::push_batch(const header_link& link, size_t height) NOEXCEPT -{ - BC_ASSERT(stranded()); - batched_.push_back(link); - - // chase portion of notify_block(success). - notify({}, chase::valid, possible_wide_cast(height)); } // Invalids might not be included in batched, as link push is a race. @@ -180,7 +200,7 @@ bool chaser_validate::process_valids() NOEXCEPT signatures chaser_validate::get_capture(const header_link& link) NOEXCEPT { - if (!batch_signatures_ || is_current(link)) + if (!batch_enabled_ || is_current(link)) return { false }; // This call is blocked during signature batch evaluation and all @@ -273,24 +293,31 @@ bool chaser_validate::do_threshold(const threshold_group& group, return true; } -void chaser_validate::log_capture(const std::string_view& name, - size_t captured, size_t missed) const NOEXCEPT +std::string chaser_validate::log_rate(const std::string& name, + size_t numerator, size_t denominator) const NOEXCEPT { - if (to_bool(captured) || to_bool(missed)) - { - const auto rate = (100.0f * captured) / (captured + missed); - const auto text = (boost_format("%.4f") % rate).str(); - LOGV("Capture rate " << name << text << "% = " << captured - << "/(" << captured << "+" << missed << ")"); - } + const auto rate = numerator / greater(denominator, one); + return (boost_format("%1% (%2% / %3%) = %4% sps") % + name % numerator % denominator % rate).str(); +} + +std::string chaser_validate::log_ratio(const std::string& name, + size_t numerator, size_t denominator) const NOEXCEPT +{ + if (is_zero(denominator)) + return name; + + const auto ratio = (100.0 * numerator) / denominator; + return (boost_format("%1% (%2% / %3%) = %4$.4f%%") % + name % numerator % denominator % ratio).str(); } void chaser_validate::log_captures() const NOEXCEPT { - log_capture("ecdsa.... ", ecdsa_, missed_ecdsa_); - log_capture("multisig. ", multisig_, missed_multisig_); - log_capture("schnorr.. ", schnorr_, missed_schnorr_); - log_capture("threshold ", threshold_, zero); + LOGV(log_ratio("Capture rate ecdsa.... ", ecdsa_, ecdsa_ + missed_ecdsa_)); + LOGV(log_ratio("Capture rate multisig. ", multisig_, multisig_ + missed_multisig_)); + LOGV(log_ratio("Capture rate schnorr.. ", schnorr_, schnorr_ + missed_schnorr_)); + LOGV(log_ratio("Capture rate threshold ", threshold_, threshold_ + zero)); } BC_POP_WARNING() diff --git a/src/settings.cpp b/src/settings.cpp index c8787c3f..f26c8c4a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -36,7 +36,7 @@ settings::settings() NOEXCEPT memory_priority{ true }, thread_priority{ true }, allow_overlapped{ true }, - batch_signatures{ false }, // <-- update when ready. + batch_signatures{ 100'000 }, minimum_fee_rate{ 0.0 }, minimum_bump_rate{ 0.0 }, allowed_deviation{ 1.5 }, @@ -85,6 +85,11 @@ bool settings::fee_estimate_enabled() const NOEXCEPT return to_bool(fee_estimate_horizon_()); } +bool settings::batch_signatures_enabled() const NOEXCEPT +{ + return to_bool(batch_signatures); +} + 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 5479c7e5..6ec65ca0 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -36,10 +36,10 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) BOOST_REQUIRE_EQUAL(node.memory_priority, true); BOOST_REQUIRE_EQUAL(node.thread_priority, true); BOOST_REQUIRE_EQUAL(node.allow_overlapped, true); - BOOST_REQUIRE_EQUAL(node.batch_signatures, 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.batch_signatures, 100'000_u64); BOOST_REQUIRE_EQUAL(node.announcement_cache, 42_u16); BOOST_REQUIRE_EQUAL(node.fee_estimate_horizon, 0u); BOOST_REQUIRE_EQUAL(node.maximum_height, 0_u32); @@ -59,6 +59,7 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) 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.batch_signatures_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);