From 8869355f03afa4885f101cb4dbd1d946b2f7caef Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 29 Jun 2026 15:26:18 -0400 Subject: [PATCH 1/4] Comment. --- include/bitcoin/node/impl/chasers/chaser_organize.ipp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp index 4bf58444..f51be840 100644 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ b/include/bitcoin/node/impl/chasers/chaser_organize.ipp @@ -179,7 +179,7 @@ void CLASS::do_organize(typename Block::cptr block, // TODO: If any checkpoint is reached then reject non-candidates below. // TODO: because checkpoints are storable (and therefore stored) along with - // TODO: all ancestor blocks, which therefore much be candidates as well. + // TODO: all ancestor blocks, which therefore must be candidates as well. // TODO: When a checkpoint is pushed and after its branch is reorganized, // TODO: purge all blocks in the tree with height at/below that checkpoint. // TODO: The combination strongly mitigates low pow sybil attacks against From ac9324f43fa43ba1ae50af9f918ca793fbcfbece Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 29 Jun 2026 16:21:48 -0400 Subject: [PATCH 2/4] Change batched cache to eliminate snapshot recovery (unsafe). --- .../bitcoin/node/chasers/chaser_validate.hpp | 1 + src/chasers/chaser_validate.cpp | 7 ++-- src/chasers/chaser_validate_batch.cpp | 33 +++++++++++++++---- src/chasers/chaser_validate_parallel.cpp | 5 --- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 93fd1dce..6f1d7b93 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -82,6 +82,7 @@ class BCN_API chaser_validate virtual bool is_residual() NOEXCEPT; virtual bool is_mature(bool residual) NOEXCEPT; virtual code start_batch() NOEXCEPT; + virtual void close_batch() NOEXCEPT; virtual void push_batch(const header_link& link, size_t height) NOEXCEPT; virtual void process_batch(bool residual) NOEXCEPT; virtual code do_process_batch(bool startup) NOEXCEPT; diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index df8d86ab..5687bc9c 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -206,10 +206,6 @@ void chaser_validate::do_bumped(height_t height) NOEXCEPT complete_block(error::success, link, height, true); break; } - case database::error::block_prevalid: - { - break; - } case database::error::block_unconfirmable: { return; @@ -319,6 +315,9 @@ void chaser_validate::notify_block(const code& ec, size_t height, void chaser_validate::stopping(const code& ec) NOEXCEPT { + // closed() is now true so time to bump batched_ drain. + POST(close_batch); + // Stop long-running batch validations. stopping_.store(true); diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index bb05f811..9479c803 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -65,15 +65,32 @@ code chaser_validate::start_batch() NOEXCEPT if (!batch_enabled_) return {}; - const auto& query = archive(); + auto& query = archive(); + batched_ = query.get_prevalids(); + if (!query.purge_prevalids()) + return error::batch1; + if (is_zero(query.ecdsa_records()) && is_zero(query.schnorr_records())) return {}; - // Accumulate all prevalid block links above the fork point. - batched_ = query.get_prevalids(position()); return do_process_batch(true); } +// Shutdown drains batched_ to block prevalid states, recovered on startup. +// Snapshot restoration purges batch backlog as the tables are not append-only. +void chaser_validate::close_batch() NOEXCEPT +{ + BC_ASSERT(stranded()); + BC_ASSERT(closed()); + + // Set even if signature batch tables are empty. + if (is_zero(batch_backlog_.load())) + { + archive().set_prevalids(batched_); + batched_.clear(); + } +} + // batched_ is redundant with the combined set of ecdsa/schnorr unfailed block // identifiers stored in the two batch tables, so just a sort optimization. void chaser_validate::push_batch(const header_link& link, @@ -81,12 +98,16 @@ void chaser_validate::push_batch(const header_link& link, { BC_ASSERT(stranded()); - if (closed()) - return; - + // Accumulate even if closed. Sacrifices stop speed to save validations. batched_.push_back(link); --batch_backlog_; + if (closed()) + { + close_batch(); + return; + } + // Unblocks check chaser for download while verifying. notify({}, chase::prevalid, possible_wide_cast(height)); diff --git a/src/chasers/chaser_validate_parallel.cpp b/src/chasers/chaser_validate_parallel.cpp index 2cf87593..f28578a7 100644 --- a/src/chasers/chaser_validate_parallel.cpp +++ b/src/chasers/chaser_validate_parallel.cpp @@ -64,11 +64,6 @@ void chaser_validate::validate_block(const header_link& link, if (!query.set_block_unconfirmable(link)) ec = error::validate5; } - else if (batched && !faulted) - { - if (!query.set_block_prevalid(link)) - ec = error::batch1; - } --validate_backlog_; complete_block(ec, link, ctx.height, bypass, batched, faulted, capturing); From 9cb4bd9ab0821792c602ec971d00d8370c6c239f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 29 Jun 2026 18:15:09 -0400 Subject: [PATCH 3/4] Refactor chaser_validate method locations. --- .../bitcoin/node/chasers/chaser_validate.hpp | 10 ++-- src/chasers/chaser_validate_batch.cpp | 48 ++++++++++++------- src/chasers/chaser_validate_capture.cpp | 8 ---- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 6f1d7b93..5c4572b6 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -79,8 +79,6 @@ class BCN_API chaser_validate const header_link& link, bool bypass, bool startup=false) NOEXCEPT; /// Batching. - virtual bool is_residual() NOEXCEPT; - virtual bool is_mature(bool residual) NOEXCEPT; virtual code start_batch() NOEXCEPT; virtual void close_batch() NOEXCEPT; virtual void push_batch(const header_link& link, size_t height) NOEXCEPT; @@ -119,12 +117,16 @@ class BCN_API chaser_validate // Capture helpers. signatures get_capture(const header_link& link) 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; + // Batching helpers. + bool is_residual() NOEXCEPT; + bool is_mature(bool residual) NOEXCEPT; + std::string log_rate(const std::string& name, size_t numerator, + size_t denominator) const NOEXCEPT; + // These are protected by strand. header_links batched_{}; header_links invalids_{}; diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index 9479c803..ec20c1ea 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -38,24 +38,6 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) // ---------------------------------------------------------------------------- // protected -bool chaser_validate::is_residual() NOEXCEPT -{ - // Verify residuals when recent. - return maximum_posted_.load() && - is_zero(batch_backlog_.load()) && - is_zero(validate_backlog_.load()); -} - -bool chaser_validate::is_mature(bool residual) NOEXCEPT -{ - const auto& query = archive(); - const auto ecdsa = query.ecdsa_records(); - const auto schnorr = query.schnorr_records(); - - // Verify non-residuals when mature. - return residual || (ecdsa >= batch_target_) || (schnorr >= batch_target_); -} - // If there was a non-empty batch at startup, process it for invalids and set // their states normally, then scan from fork point (position()) to association // gap for all prevalids. Iterate over these setting their states to valid. @@ -273,6 +255,36 @@ bool chaser_validate::mark_valids(bool startup) NOEXCEPT return !fault.load(); } +// Batch helpers. +// ---------------------------------------------------------------------------- +// private + +bool chaser_validate::is_residual() NOEXCEPT +{ + // Verify residuals when recent. + return maximum_posted_.load() && + is_zero(batch_backlog_.load()) && + is_zero(validate_backlog_.load()); +} + +bool chaser_validate::is_mature(bool residual) NOEXCEPT +{ + const auto& query = archive(); + const auto ecdsa = query.ecdsa_records(); + const auto schnorr = query.schnorr_records(); + + // Verify non-residuals when mature. + return residual || (ecdsa >= batch_target_) || (schnorr >= batch_target_); +} + +std::string chaser_validate::log_rate(const std::string& name, + size_t numerator, size_t denominator) const NOEXCEPT +{ + const auto rate = numerator / greater(denominator, one); + return (boost_format("%1% (%2% / %3%) = %4% sps") % + name % numerator % denominator % rate).str(); +} + BC_POP_WARNING() } // namespace node diff --git a/src/chasers/chaser_validate_capture.cpp b/src/chasers/chaser_validate_capture.cpp index 85575a33..1c236c5d 100644 --- a/src/chasers/chaser_validate_capture.cpp +++ b/src/chasers/chaser_validate_capture.cpp @@ -117,14 +117,6 @@ bool chaser_validate::do_threshold(const threshold& batch, return set; } -std::string chaser_validate::log_rate(const std::string& name, - size_t numerator, size_t denominator) const NOEXCEPT -{ - const auto rate = numerator / greater(denominator, one); - return (boost_format("%1% (%2% / %3%) = %4% sps") % - name % numerator % denominator % rate).str(); -} - // Capture helpers. // ---------------------------------------------------------------------------- // private From c067b7b340315dcc1beea8bc1c7fc1ab029794b9 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 29 Jun 2026 18:26:56 -0400 Subject: [PATCH 4/4] Log signature capture at News level when batch completes. --- src/chasers/chaser_validate_batch.cpp | 23 ++++++++++++++--------- src/chasers/chaser_validate_capture.cpp | 8 ++++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index ec20c1ea..f1afce5b 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -115,22 +115,27 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT // tables to be fully purged upon completion, and ensuring that evaluation // does not operate over partial block records in the batch tables. // ======================================================================== - const std::unique_lock lock{ mutex_ }; + { + const std::unique_lock lock{ mutex_ }; - // Must retest inside the lock as table updates are running concurrently. - if (closed() || !is_mature(residual)) - return; + // Must retest inside lock as table updates are running concurrently. + if (closed() || !is_mature(residual)) + return; - if (const auto ec = do_process_batch(false)) - fault(ec); + if (const auto ec = do_process_batch(false)) + { + fault(ec); + return; + } + } // ======================================================================== + + // Log outside of lock, oand nly when batch executes (non-verbose). + log_captures(); } code chaser_validate::do_process_batch(bool startup) NOEXCEPT { - if (!startup) - log_captures(); - auto& query = archive(); const auto ecdsa = query.ecdsa_records(); diff --git a/src/chasers/chaser_validate_capture.cpp b/src/chasers/chaser_validate_capture.cpp index 1c236c5d..15ff20ef 100644 --- a/src/chasers/chaser_validate_capture.cpp +++ b/src/chasers/chaser_validate_capture.cpp @@ -152,10 +152,10 @@ std::string chaser_validate::log_ratio(const std::string& name, void chaser_validate::log_captures() const NOEXCEPT { - 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)); + LOGN(log_ratio("Capture ecdsa.... ", ecdsa_, ecdsa_ + missed_ecdsa_)); + LOGN(log_ratio("Capture multisig. ", multisig_, multisig_ + missed_multisig_)); + LOGN(log_ratio("Capture schnorr.. ", schnorr_, schnorr_ + missed_schnorr_)); + LOGN(log_ratio("Capture threshold ", threshold_, threshold_ + zero)); } BC_POP_WARNING()