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);