From ff851ca62718e844fb77668e1037ea98beea7157 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 9 Jun 2026 23:19:14 -0400 Subject: [PATCH 1/5] Add log events for signature capture. --- include/bitcoin/node/events.hpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/include/bitcoin/node/events.hpp b/include/bitcoin/node/events.hpp index 76a07e4b..0796c4a6 100644 --- a/include/bitcoin/node/events.hpp +++ b/include/bitcoin/node/events.hpp @@ -51,17 +51,23 @@ enum events : uint8_t block_reorganized, // block popped /// Mining. - template_issued, // block template issued for mining + template_issued, // block template issued for mining /// Timespans. - snapshot_secs, // snapshot timespan in seconds. - prune_msecs, // prune timespan in milliseconds. - reload_msecs, // store reload timespan in milliseconds. - block_usecs, // getblock timespan in microseconds. - ancestry_msecs, // getancestry timespan in milliseconds. - filter_msecs, // getfilter timespan in milliseconds. - filterhashes_msecs, // getfilterhashes timespan in milliseconds. - filterchecks_msecs // getcfcheckpt timespan in milliseconds. + snapshot_secs, // snapshot timespan in seconds. + prune_msecs, // prune timespan in milliseconds. + reload_msecs, // store reload timespan in milliseconds. + block_usecs, // getblock timespan in microseconds. + ancestry_msecs, // getancestry timespan in milliseconds. + filter_msecs, // getfilter timespan in milliseconds. + filterhashes_msecs, // getfilterhashes timespan in milliseconds. + filterchecks_msecs, // getcfcheckpt timespan in milliseconds. + + /// Batching. + batch_ecdsa, // Failed to capture ecdsa signature. + batch_multisig, // Failed to capture ecdsa signatures. + batch_schnorr, // Failed to capture schnorr sig (single|multiple). + batch_overflow // Failed to capture schnorr sigs (multiple >= 2^16). }; } // namespace node From 688447b4c33a8c8cbb4ff1e3ecb3038c3747f35d Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 9 Jun 2026 23:20:00 -0400 Subject: [PATCH 2/5] Adapt signature capture to system, simplify. --- .../bitcoin/node/chasers/chaser_validate.hpp | 19 +-- src/chasers/chaser_validate.cpp | 119 ++++++++++-------- 2 files changed, 76 insertions(+), 62 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index bb18f05b..771ca035 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -82,14 +82,17 @@ class BCN_API chaser_validate network::threadpool validation_threadpool_; // These are thread safe. - std::atomic batched_ecdsa_{}; - std::atomic unbatched_ecdsa_{}; - std::atomic batched_schnorr_{}; - std::atomic unbatched_schnorr_{}; - std::atomic batched_multisig_{}; - std::atomic unbatched_multisig_{}; - std::atomic batched_threshold_{}; - std::atomic unbatched_threshold_{}; + + std::atomic ecdsa_{}; + std::atomic multisig_{}; + std::atomic schnorr_{}; + std::atomic threshold_{}; + + std::atomic miss_ecdsa_{}; + std::atomic miss_multisig_{}; + std::atomic miss_schnorr_{}; + std::atomic miss_threshold_{}; + std::atomic backlog_{}; network::asio::strand validation_strand_; const uint32_t subsidy_interval_; diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 8c674e18..4c3b7dc8 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -285,7 +286,7 @@ code chaser_validate::validate(bool bypass, const chain::block& block, code ec{}; // Skips identity validation (performed in downloader). - // This can be performed in donwnloader as well but moving it here with + // This can be performed in downloader as well but moving it here with // more order than required allows the downloader to avoid block parse // which significantly reduces memory consumption and CPU during sync. // This slightly increases check() computation under full validation @@ -298,78 +299,88 @@ code chaser_validate::validate(bool bypass, const chain::block& block, if (batch_signatures_) { - const chain::signatures capture + using namespace chain; + std::atomic set{}; + const signatures capture { // Enable/disable capture. .enabled = batch_signatures_, - .ecdsa = [&](const hash_digest& , - const ec_compressed& , const ec_signature& ) NOEXCEPT + .log = [&](const script& missed) NOEXCEPT { - ////query.set_signature(digest, point, sign, link); + LOGA("Sigop @ " << ctx.height << " -> " + << missed.to_string(chain::flags::all_rules)); }, - .schnorr = [&](const hash_digest& , - const ec_xonly& , const ec_signature& ) NOEXCEPT + .fire = [&](signatures::miss miss) NOEXCEPT { - ////query.set_signature(digest, point, sign, link); + switch (miss) + { + case signatures::miss::ecdsa: + ++miss_ecdsa_; + fire(events::batch_ecdsa, ctx.height); + break; + case signatures::miss::schnorr: + ++miss_schnorr_; + fire(events::batch_schnorr, ctx.height); + break; + case signatures::miss::multisig: + ++miss_multisig_; + fire(events::batch_multisig, ctx.height); + break; + case signatures::miss::overflow: + ++miss_threshold_; + fire(events::batch_overflow, ctx.height); + break; + default: + BC_ASSERT_MSG(false, "unknown signatures::miss"); + } }, - .multisig = [&](const hash_digest& , - const ec_compresseds& , const ec_signatures& , - uint16_t ) NOEXCEPT + .ecdsa = [&](const hash_digest& digest, + const ec_compressed& point, + const ec_signature& sign) NOEXCEPT { - ////query.set_signatures(digest, points, signs, group, link); + ++ecdsa_; + return query.set_signature(digest, point, sign, link); }, - .threshold = [&](const chain::signatures::threshold_group& , - uint16_t ) NOEXCEPT + .schnorr = [&](const hash_digest& digest, const ec_xonly& point, + const ec_signature& sign) NOEXCEPT { - ////query.set_signatures(digest, points, signs, group, link); + ++schnorr_; + return query.set_signature(digest, point, sign, link); + }, + .multisig = [&](const hash_digest& digest, + const ec_compresseds& points, + const ec_signatures& signs) NOEXCEPT + { + BC_ASSERT(points.size() == signs.size()); + multisig_ += points.size(); + return query.set_signatures(digest, points, signs, set, link); + }, + .threshold = [&]( + const signatures::threshold_entries& group) NOEXCEPT + { + threshold_ += group.entries.size(); + return query.set_signatures(group, set, link); } }; if ((ec = block.connect(ctx, capture))) return ec; - if (is_limited(capture.group.load())) - { - LOGF("Multisig capture bypassed because correlation overflow (" - << capture.group << ")."); - } - - // Diagnostics (ecdsa). - batched_ecdsa_ += capture.batched_ecdsa; - unbatched_ecdsa_ += capture.unbatched_ecdsa; - batched_multisig_ += capture.batched_multisig; - unbatched_multisig_ += capture.unbatched_multisig; - - if (to_bool(batched_ecdsa_.load()) || to_bool(unbatched_ecdsa_.load())) + const auto log_capture = [&](std::string_view name, size_t captured, + size_t missed) NOEXCEPT { - LOGV("Efficiency ecdsa " << batched_ecdsa_ << " / (" - << batched_ecdsa_ << " + " << unbatched_ecdsa_ << ")"); - } - - if (to_bool(batched_multisig_.load()) || to_bool(unbatched_multisig_.load())) - { - LOGV("Efficiency multisig " << batched_multisig_ << " / (" - << batched_multisig_ << " + " << unbatched_multisig_ << ")"); - } - - // Diagnostics (schnorr). - batched_schnorr_ += capture.batched_schnorr; - unbatched_schnorr_ += capture.unbatched_schnorr; - batched_threshold_ += capture.batched_threshold; - unbatched_threshold_ += capture.unbatched_threshold; - - if (to_bool(batched_schnorr_.load()) || to_bool(unbatched_schnorr_.load())) - { - LOGV("Efficiency schnorr " << batched_schnorr_ << " / (" - << batched_schnorr_ << " + " << unbatched_schnorr_ << ")"); - } + if (!to_bool(captured) && !to_bool(missed)) return; + const auto ratio = to_floating(captured) / (captured + missed); + const auto rate = std::format("{:.2f}", ratio); + LOGA("Efficiency " << name << rate << "% = " << captured + << "/(" << captured << "+" << missed << ")"); + }; - if (to_bool(batched_threshold_.load()) || to_bool(unbatched_threshold_.load())) - { - LOGV("Efficiency threshold " << batched_threshold_ << " / (" - << batched_threshold_ << " + " << unbatched_threshold_ << ")"); - } + log_capture("ecdsa.... ", ecdsa_, miss_ecdsa_); + log_capture("multisig. ", multisig_, miss_multisig_); + log_capture("schnorr.. ", schnorr_, miss_schnorr_); + log_capture("threshold ", threshold_,miss_threshold_); } else { From d95a4ca49353a5d14ce8fab85c267e10d71d69f2 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 10 Jun 2026 09:51:55 -0400 Subject: [PATCH 3/5] Add event type for every capture context. --- .../bitcoin/node/chasers/chaser_validate.hpp | 10 +-- include/bitcoin/node/events.hpp | 26 ++++-- src/chasers/chaser_validate.cpp | 82 +++++++++++++++---- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 771ca035..452b1b98 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -84,14 +84,14 @@ class BCN_API chaser_validate // These are thread safe. std::atomic ecdsa_{}; - std::atomic multisig_{}; std::atomic schnorr_{}; + std::atomic multisig_{}; std::atomic threshold_{}; - std::atomic miss_ecdsa_{}; - std::atomic miss_multisig_{}; - std::atomic miss_schnorr_{}; - std::atomic miss_threshold_{}; + std::atomic missed_ecdsa_{}; + std::atomic missed_schnorr_{}; + std::atomic missed_multisig_{}; + std::atomic missed_threshold_{}; std::atomic backlog_{}; network::asio::strand validation_strand_; diff --git a/include/bitcoin/node/events.hpp b/include/bitcoin/node/events.hpp index 0796c4a6..25ab537a 100644 --- a/include/bitcoin/node/events.hpp +++ b/include/bitcoin/node/events.hpp @@ -63,11 +63,27 @@ enum events : uint8_t filterhashes_msecs, // getfilterhashes timespan in milliseconds. filterchecks_msecs, // getcfcheckpt timespan in milliseconds. - /// Batching. - batch_ecdsa, // Failed to capture ecdsa signature. - batch_multisig, // Failed to capture ecdsa signatures. - batch_schnorr, // Failed to capture schnorr sig (single|multiple). - batch_overflow // Failed to capture schnorr sigs (multiple >= 2^16). + /// Batching (missed). + missed_ecdsa, // Failed to capture ecdsa signature. + missed_multisig, // Failed to capture ecdsa signatures. + missed_schnorr, // Failed to capture schnorr sig (single|multiple). + missed_overflow, // Failed to capture schnorr sigs (multiple >= 2^16). + + /// Batching (captured). + checksigverify, // ecdsa single (checksig/verify). + checkmultisigverify, // ecdsa multiple (checkmultisig/verify). + checksigadd, // schnorr single (op_checksigadd|op_checksig/verify). + checksig, // schnorr multiple (multisig). + numequal, // schnorr multiple (threshold). + numequalverify, // schnorr multiple (threshold). + numnotequal, // schnorr multiple (threshold). + lessthan, // schnorr multiple (threshold). + greaterthan, // schnorr multiple (threshold). + lessthanorequal, // schnorr multiple (threshold). + greaterthanorequal, // schnorr multiple (threshold). + within, // schnorr multiple (threshold). + + unknown }; } // namespace node diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 4c3b7dc8..5036e683 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -301,6 +301,51 @@ code chaser_validate::validate(bool bypass, const chain::block& block, { using namespace chain; std::atomic set{}; + + const auto to_events = [](opcode op) NOEXCEPT + { + switch (op) + { + // ecdsa single (checksig/verify). + case opcode::checksigverify: + return events::checksigverify; + + // ecdsa multiple (checkmultisig/verify). + case opcode::checkmultisigverify: + return events::checkmultisigverify; + + // schnorr single (op_checksigadd|op_checksig/verify). + case opcode::checksigadd: + return events::checksigadd; + + // schnorr multiple (multisig pattern). + case opcode::checksig: + return events::checksig; + + // schnorr multiple (is_threshold). + case opcode::numequal: + return events::numequal; + case opcode::numequalverify: + return events::numequalverify; + case opcode::numnotequal: + return events::numnotequal; + case opcode::lessthan: + return events::lessthan; + case opcode::greaterthan: + return events::greaterthan; + case opcode::lessthanorequal: + return events::lessthanorequal; + case opcode::greaterthanorequal: + return events::greaterthanorequal; + case opcode::within: + return events::within; + + // should be no path to this. + default: + return events::unknown; + } + }; + const signatures capture { // Enable/disable capture. @@ -316,21 +361,23 @@ code chaser_validate::validate(bool bypass, const chain::block& block, switch (miss) { case signatures::miss::ecdsa: - ++miss_ecdsa_; - fire(events::batch_ecdsa, ctx.height); - break; - case signatures::miss::schnorr: - ++miss_schnorr_; - fire(events::batch_schnorr, ctx.height); + ++missed_ecdsa_; + fire(events::missed_ecdsa, ctx.height); break; case signatures::miss::multisig: - ++miss_multisig_; - fire(events::batch_multisig, ctx.height); + ++missed_multisig_; + fire(events::missed_multisig, ctx.height); + break; + case signatures::miss::schnorr: + ++missed_schnorr_; + fire(events::missed_schnorr, ctx.height); break; case signatures::miss::overflow: - ++miss_threshold_; - fire(events::batch_overflow, ctx.height); + ++missed_threshold_; + fire(events::missed_overflow, ctx.height); break; + + // should be no path to this. default: BC_ASSERT_MSG(false, "unknown signatures::miss"); } @@ -340,12 +387,14 @@ code chaser_validate::validate(bool bypass, const chain::block& block, const ec_signature& sign) NOEXCEPT { ++ecdsa_; + fire(to_events(opcode::checksigverify), ctx.height); return query.set_signature(digest, point, sign, link); }, .schnorr = [&](const hash_digest& digest, const ec_xonly& point, const ec_signature& sign) NOEXCEPT { ++schnorr_; + fire(to_events(opcode::checksigadd), ctx.height); return query.set_signature(digest, point, sign, link); }, .multisig = [&](const hash_digest& digest, @@ -354,12 +403,15 @@ code chaser_validate::validate(bool bypass, const chain::block& block, { BC_ASSERT(points.size() == signs.size()); multisig_ += points.size(); + fire(to_events(opcode::checkmultisigverify), ctx.height); return query.set_signatures(digest, points, signs, set, link); }, .threshold = [&]( const signatures::threshold_entries& group) NOEXCEPT { + // Sets condition to opcode::checksig for all required. threshold_ += group.entries.size(); + fire(to_events(group.condition), ctx.height); return query.set_signatures(group, set, link); } }; @@ -371,16 +423,16 @@ code chaser_validate::validate(bool bypass, const chain::block& block, size_t missed) NOEXCEPT { if (!to_bool(captured) && !to_bool(missed)) return; - const auto ratio = to_floating(captured) / (captured + missed); + const auto ratio = (100.0f * captured) / (captured + missed); const auto rate = std::format("{:.2f}", ratio); LOGA("Efficiency " << name << rate << "% = " << captured << "/(" << captured << "+" << missed << ")"); }; - log_capture("ecdsa.... ", ecdsa_, miss_ecdsa_); - log_capture("multisig. ", multisig_, miss_multisig_); - log_capture("schnorr.. ", schnorr_, miss_schnorr_); - log_capture("threshold ", threshold_,miss_threshold_); + log_capture("ecdsa.... ", ecdsa_, missed_ecdsa_); + log_capture("multisig. ", multisig_, missed_multisig_); + log_capture("schnorr.. ", schnorr_, missed_schnorr_); + log_capture("threshold ", threshold_,missed_threshold_); } else { From fd5e01c9693e1392a9ee0996dca00fd7f485511a Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 10 Jun 2026 10:42:23 -0400 Subject: [PATCH 4/5] Replace use of std::format with boost::format (platform coverage). --- src/chasers/chaser_validate.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 5036e683..88b04da7 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -424,7 +423,7 @@ code chaser_validate::validate(bool bypass, const chain::block& block, { if (!to_bool(captured) && !to_bool(missed)) return; const auto ratio = (100.0f * captured) / (captured + missed); - const auto rate = std::format("{:.2f}", ratio); + const auto rate = (boost::format("%.2f") % ratio).str(); LOGA("Efficiency " << name << rate << "% = " << captured << "/(" << captured << "+" << missed << ")"); }; From 68fac0d68b67c02731efcf66ea941eb54a0ea976 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 10 Jun 2026 12:31:24 -0400 Subject: [PATCH 5/5] Work around boost::format internal aliasing conflicts. --- src/chasers/chaser_validate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 88b04da7..fc20dde8 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -423,7 +423,7 @@ code chaser_validate::validate(bool bypass, const chain::block& block, { if (!to_bool(captured) && !to_bool(missed)) return; const auto ratio = (100.0f * captured) / (captured + missed); - const auto rate = (boost::format("%.2f") % ratio).str(); + const auto rate = (boost_format("%.2f") % ratio).str(); LOGA("Efficiency " << name << rate << "% = " << captured << "/(" << captured << "+" << missed << ")"); };