From 7de7b81b7863dc893f8021e2e29c029c07c15946 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 10 Jul 2026 00:47:39 -0400 Subject: [PATCH 1/3] no-alloc signature batching. --- .../bitcoin/node/chasers/chaser_validate.hpp | 24 ++++++++------ src/chasers/chaser_validate.cpp | 6 ++-- src/chasers/chaser_validate_batch.cpp | 4 +-- src/chasers/chaser_validate_capture.cpp | 32 +++++++++++++++---- src/chasers/chaser_validate_parallel.cpp | 31 +++++++----------- 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 9c86fddc..d4b8191e 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -67,14 +67,14 @@ class BCN_API chaser_validate /// Validation. virtual void post_block(const header_link& link, bool bypass) NOEXCEPT; virtual void validate_block(const header_link& link, bool bypass) NOEXCEPT; - virtual code validate(bool& batched, bool& faulted, bool& capturing, - bool bypass, const system::chain::block& block, - const header_link& link, const system::chain::context& ctx) NOEXCEPT; + virtual code validate(bool& batched, bool& capturing, bool bypass, + const system::chain::block& block, const header_link& link, + const system::chain::context& ctx) NOEXCEPT; virtual code populate(bool bypass, const system::chain::block& block, const system::chain::context& ctx) NOEXCEPT; virtual void complete_block(const code& ec, const header_link& link, size_t height, bool bypass, bool batched=false, - bool faulted=false, bool capturing=false) NOEXCEPT; + bool capturing=false) NOEXCEPT; virtual void notify_block(const code& ec, size_t height, const header_link& link, bool bypass, bool startup=false) NOEXCEPT; @@ -94,9 +94,11 @@ class BCN_API chaser_validate private: static constexpr auto relaxed = std::memory_order_relaxed; + using schnorr_link = database::schnorr_link; + using schnorr_link_ptr = std::shared_ptr; using atomic_counter = std::atomic; using atomic_counter_ptr = std::shared_ptr; - using threshold = system::chain::threshold; + using cursor = system::chain::threshold::cursor; using missed = signatures::miss; // Capture handlers. @@ -109,11 +111,13 @@ class BCN_API chaser_validate const system::ec_xonly& point, const system::ec_signature& sign, const header_link& link) NOEXCEPT; bool do_multisig(const system::hash_digest& digest, - const system::ec_compresseds& points, - const system::ec_signatures& signs, const header_link& link, - const atomic_counter_ptr& sequence) NOEXCEPT; - bool do_threshold(const threshold& batch, - const header_link& link) NOEXCEPT; + const std::span& points, + const std::span& signs, + const header_link& link, const atomic_counter_ptr& sequence) NOEXCEPT; + bool do_threshold(const system::hash_digest& digest, + const system::ec_xonly& point, const system::ec_signature& sign, + const schnorr_link_ptr& fk_ptr, const header_link& link) NOEXCEPT; + cursor open_threshold(size_t rows, const header_link& link) NOEXCEPT; // Capture helpers. signatures get_capture(const header_link& link) NOEXCEPT; diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 5687bc9c..491667c0 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -233,8 +233,7 @@ void chaser_validate::post_block(const header_link& link, // May be either concurrent or stranded. void chaser_validate::complete_block(const code& ec, const header_link& link, - size_t height, bool bypass, bool batched, bool faulted, - bool capturing) NOEXCEPT + size_t height, bool bypass, bool batched, bool capturing) NOEXCEPT { // Not stranded when called from validate_block. if (is_zero(validate_backlog_.load()) && !stranded()) @@ -267,8 +266,7 @@ void chaser_validate::complete_block(const code& ec, const header_link& link, // Batch jobs (all posting from unstranded). // ------------------------------------------------------------------------ - // Faulted implies disk full prevented threshold batch writes. - if (closed() || !batch_enabled_ || faulted) + if (closed() || !batch_enabled_) return; // Queue block and process batch if ready. diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index f1afce5b..1b33be25 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -58,7 +58,7 @@ code chaser_validate::start_batch() NOEXCEPT return do_process_batch(true); } -// Shutdown drains batched_ to block prevalid states, recovered on startup. +// Shutdown drains batched_ to prevalid table, which is recovered on startup. // Snapshot restoration purges batch backlog as the tables are not append-only. void chaser_validate::close_batch() NOEXCEPT { @@ -130,7 +130,7 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT } // ======================================================================== - // Log outside of lock, oand nly when batch executes (non-verbose). + // Log outside of lock, and only when batch executes (non-verbose). log_captures(); } diff --git a/src/chasers/chaser_validate_capture.cpp b/src/chasers/chaser_validate_capture.cpp index d9e47bec..ff397bcf 100644 --- a/src/chasers/chaser_validate_capture.cpp +++ b/src/chasers/chaser_validate_capture.cpp @@ -88,7 +88,8 @@ bool chaser_validate::do_schnorr(const hash_digest& digest, } bool chaser_validate::do_multisig(const hash_digest& digest, - const ec_compresseds& points, const ec_signatures& signs, + const std::span& points, + const std::span& signs, const header_link& link, const atomic_counter_ptr& sequence) NOEXCEPT { multisig_ += points.size(); @@ -100,15 +101,34 @@ bool chaser_validate::do_multisig(const hash_digest& digest, return set; } -bool chaser_validate::do_threshold(const threshold& batch, - const header_link& link) NOEXCEPT +bool chaser_validate::do_threshold(const hash_digest& digest, + const ec_xonly& point, const ec_signature& sign, + const schnorr_link_ptr& fk_ptr, const header_link& link) NOEXCEPT { - threshold_ += batch.tuples.size(); - const auto set = archive().set_signatures(batch, link); + auto set = archive().set_signature((*fk_ptr)++, digest, point, sign, link); if (!set) fault(error::batch8); return set; } +chaser_validate::cursor chaser_validate::open_threshold(size_t rows, + const header_link& link) NOEXCEPT +{ + auto& query = archive(); + const auto scope = to_shared(query.get_transactor()); + auto first = query.allocate_signatures(rows); + auto fk = emplace_shared(first); + if (fk->is_terminal()) + return {}; + + threshold_ += rows; + return + { + .put = BIND_THIS(do_threshold, _1, _2, _3, fk, link), + .done = [scope]() NOEXCEPT {}, + .rows = rows + }; +} + // Capture helpers. // ---------------------------------------------------------------------------- // private @@ -127,7 +147,7 @@ signatures chaser_validate::get_capture(const header_link& link) NOEXCEPT .ecdsa = BIND_THIS(do_ecdsa, _1, _2, _3, link, sequence), .schnorr = BIND_THIS(do_schnorr, _1, _2, _3, link), .multisig = BIND_THIS(do_multisig, _1, _2, _3, link, sequence), - .threshold = BIND_THIS(do_threshold, _1, link) + .threshold = BIND_THIS(open_threshold, _1, link) }; } diff --git a/src/chasers/chaser_validate_parallel.cpp b/src/chasers/chaser_validate_parallel.cpp index f28578a7..2dd75740 100644 --- a/src/chasers/chaser_validate_parallel.cpp +++ b/src/chasers/chaser_validate_parallel.cpp @@ -38,7 +38,7 @@ void chaser_validate::validate_block(const header_link& link, code ec{}; chain::context ctx{}; - bool batched{}, faulted{}, capturing{}; + bool batched{}, capturing{}; auto& query = archive(); // TODO: implement allocator parameter resulting in full allocation to @@ -58,7 +58,7 @@ void chaser_validate::validate_block(const header_link& link, if (!query.set_block_unconfirmable(link)) ec = error::validate4; } - else if ((ec = validate(batched, faulted, capturing, bypass, *block, link, + else if ((ec = validate(batched, capturing, bypass, *block, link, ctx))) { if (!query.set_block_unconfirmable(link)) @@ -66,7 +66,7 @@ void chaser_validate::validate_block(const header_link& link, } --validate_backlog_; - complete_block(ec, link, ctx.height, bypass, batched, faulted, capturing); + complete_block(ec, link, ctx.height, bypass, batched, capturing); } // helpers @@ -99,8 +99,8 @@ code chaser_validate::populate(bool bypass, const chain::block& block, return error::success; } -code chaser_validate::validate(bool& batched, bool& faulted, bool& capturing, - bool bypass, const chain::block& block, const header_link& link, +code chaser_validate::validate(bool& batched, bool& capturing, bool bypass, + const chain::block& block, const header_link& link, const chain::context& ctx) NOEXCEPT { auto& query = archive(); @@ -127,36 +127,29 @@ code chaser_validate::validate(bool& batched, bool& faulted, bool& capturing, if (capturing) lock.lock(); // Sequentially connect block with signature capture (if enabled). - // There is not stop during connect, so shutdown will wait on the - // completion (block consistency) of all signature captures. But - // the faulted state of batch is not persisted (because disk full). + // There is no stop during connect, so shutdown will wait on the + // completion (block consistency) of all signature captures. if ((ec = block.connect(ctx, capture))) return ec; // At least one signature batch was attempted (defer completion). batched = capture.batched; - - // Threshold batch commit failed, block otherwise passed (retry). - faulted = capture.faulted; } - // ================================================================ + // ==================================================================== // Prevouts optimize confirmation. - // Block will be retried if batch is faulted. - if (!faulted && !query.set_prevouts(link, block)) + if (!query.set_prevouts(link, block)) return error::validate6; } - // Block will be retried if batch is faulted. - if (!faulted && !query.set_filter_body(link, block)) + if (!query.set_filter_body(link, block)) return error::validate7; - // Block will be retried if batch is faulted. - if (!faulted && (ctx.height >= silent_start_height_) && + if ((ctx.height >= silent_start_height_) && !query.set_silent(link, block)) return error::validate8; - // Defer block state change when batched (or faulted). + // Defer block state change when batched. // Valid must be set after set_prevouts, set_filter_body, and set_silent. if (!batched && !bypass && !query.set_block_valid(link)) return error::validate9; From 7115163affc2dcbc6cf737b05c1484abb06a6b23 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 10 Jul 2026 01:40:39 -0400 Subject: [PATCH 2/3] Remove batch cursor close handler (no need for transactor). --- src/chasers/chaser_validate_capture.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/chasers/chaser_validate_capture.cpp b/src/chasers/chaser_validate_capture.cpp index ff397bcf..77dadaac 100644 --- a/src/chasers/chaser_validate_capture.cpp +++ b/src/chasers/chaser_validate_capture.cpp @@ -113,10 +113,8 @@ bool chaser_validate::do_threshold(const hash_digest& digest, chaser_validate::cursor chaser_validate::open_threshold(size_t rows, const header_link& link) NOEXCEPT { - auto& query = archive(); - const auto scope = to_shared(query.get_transactor()); - auto first = query.allocate_signatures(rows); - auto fk = emplace_shared(first); + auto first = archive().allocate_signatures(rows); + const auto fk = emplace_shared(first); if (fk->is_terminal()) return {}; @@ -124,7 +122,6 @@ chaser_validate::cursor chaser_validate::open_threshold(size_t rows, return { .put = BIND_THIS(do_threshold, _1, _2, _3, fk, link), - .done = [scope]() NOEXCEPT {}, .rows = rows }; } From 4d8b4923e124029c4ec0f77e27941afeeb3f593a Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 10 Jul 2026 01:42:55 -0400 Subject: [PATCH 3/3] Comments. --- src/chasers/chaser_validate_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index 1b33be25..dec1f32a 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -59,7 +59,7 @@ code chaser_validate::start_batch() NOEXCEPT } // Shutdown drains batched_ to prevalid table, which is recovered on startup. -// Snapshot restoration purges batch backlog as the tables are not append-only. +// Snapshot restoration purges sigs and prevalids as these are not append-only. void chaser_validate::close_batch() NOEXCEPT { BC_ASSERT(stranded());