diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 3309cb04..b32edabe 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -81,8 +81,11 @@ class BCN_API chaser_validate /// Batching. virtual code start_batch() NOEXCEPT; - virtual void process_batch() NOEXCEPT; virtual void push_batch(const database::header_link& link) NOEXCEPT; + virtual void process_batch() NOEXCEPT; + virtual bool process_valids() NOEXCEPT; + virtual bool process_invalids(const database::header_links& invalids, + const std::string_view& name) NOEXCEPT; virtual signatures get_capture(const database::header_link& link) NOEXCEPT; // Override base class strand because it sits on the network thread pool. diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index df729100..b1539218 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -116,7 +116,8 @@ enum error_t : uint8_t batch6, batch7, batch8, - batch9 + batch9, + batch10 }; // No current need for error_code equivalence mapping. diff --git a/include/bitcoin/node/events.hpp b/include/bitcoin/node/events.hpp index 537419c9..a4f5f5e2 100644 --- a/include/bitcoin/node/events.hpp +++ b/include/bitcoin/node/events.hpp @@ -62,6 +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. unknown }; diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index e221f80a..fcb39a7f 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -309,6 +309,7 @@ code chaser_validate::validate(bool& batched, bool& faulted, bool bypass, return ec; // Initialize block capture. + // This call is blocked during signature batch evaluation. const auto capture = get_capture(link); // Sequentially connect block with signature capture (if enabled). diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index 7a582e96..d0fca1c0 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -18,7 +18,6 @@ */ #include -#include #include #include @@ -30,6 +29,7 @@ namespace node { using namespace system; using namespace system::chain; using namespace database; +using namespace std::chrono; using namespace std::placeholders; BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) @@ -51,86 +51,129 @@ void chaser_validate::push_batch(const header_link& link) NOEXCEPT batched_.push_back(link); } -// TODO: This is only invoked by check chaser advancement. But it's possible -// entries may be captured after that point. So this must be bumped. +// 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 { BC_ASSERT(stranded()); - auto& query = archive(); // 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."); + // set_block_unconfirmable + // ------------------------------------------------------------------------ + header_links invalids{}; - if (!query.verify_signatures(invalids)) + auto start = network::logger::now(); + if (!query.verify_ecdsa_signatures(invalids)) { fault(error::batch2); return; } + span(events::ecdsa_msecs, start); + + if (!process_invalids(invalids, "ecdsa") || + !query.purge_ecdsa_signatures()) + { + fault(error::batch3); + return; + } + + invalids.clear(); + start = network::logger::now(); + if (!query.verify_schnorr_signatures(invalids)) + { + fault(error::batch4); + return; + } + span(events::schnorr_msecs, start); + + if (!process_invalids(invalids, "schnorr") || + !query.purge_schnorr_signatures()) + { + fault(error::batch5); + return; + } + + // set_block_valid + // ------------------------------------------------------------------------ + + if (!process_valids()) + { + fault(error::batch6); + return; + } + + // ------------------------------------------------------------------------ + + LOGN("Batch signature verify end."); +} + +// Invalids might not be included in batched, as link push is a race. +// Collected links are only required to set valid, not invalid, and do not +// need to coincide with the batch that is currently being processed (!). +bool chaser_validate::process_invalids(const header_links& invalids, + const std::string_view& name) NOEXCEPT +{ + BC_ASSERT(stranded()); - // Invalids might not be included in batched, as link push is a race. - // Collected links are only required to set valid, not invalid, and do not - // need to coincide with the batch that is currently being processed (!). + auto& query = archive(); for (const auto& link: invalids) { size_t height{}; if (!query.get_height(height, link) || !query.set_block_unconfirmable(link)) - { - fault(error::batch3); - return; - } + return false; - LOGR("Unverifiable signature in block (" << height << ")."); + LOGR("Invalid " << name << " signature in block (" << height << ")."); notify_block(system::error::invalid_signature, height, link, false); } - // Set all invalid links in batched_ to terminal. - std::ranges::replace_if(batched_, [&](const auto& link) NOEXCEPT + // Set all invalids links in batched_ to terminal (to be skipped). + if (!invalids.empty()) { - return contains(invalids, link); - }, header_link::terminal); + std::ranges::replace_if(batched_, [&](const auto& link) NOEXCEPT + { + return contains(invalids, link); + }, header_link::terminal); + } + + return true; +} + +// Set all batched blocks that aren't invalid to valid. +// May be ancestors of invalid, in which case they are also unconfirmable. +bool chaser_validate::process_valids() NOEXCEPT +{ + BC_ASSERT(stranded()); - // Set all batched blocks that aren't invalid to valid. - // May be ancestors of invalid, in which case they are also unconfirmable. + auto& query = archive(); for (const auto& link: batched_) { - // Terminal links are previously set invalid. + // Terminal links are previously set invalid (to be skipped). if (link == header_link::terminal) continue; size_t height{}; if (!query.get_height(height, link) || !query.set_block_valid(link)) - { - fault(error::batch4); - return; - } + return false; notify_block(system::error::block_success, height, link, false); } - // All batched are processed, and since strand-protected is safe to clear. batched_.clear(); - - // May have grown to maximum_concurrency and never used again once current. if (is_current(true)) batched_.shrink_to_fit(); - // Purge all signature batch tables. - if (!query.purge_signatures()) - { - fault(error::batch5); - return; - } - - LOGN("Batch signature verify end."); + return true; } signatures chaser_validate::get_capture(const header_link& link) NOEXCEPT @@ -138,8 +181,11 @@ signatures chaser_validate::get_capture(const header_link& link) NOEXCEPT if (!batch_signatures_ || is_current(link)) return { false }; - const auto sequence = to_shared(); + // This call is blocked during signature batch evaluation and all + // outstanding captures block signature batch evaluation until complete. const auto lock = emplace_shared(mutex_); + + const auto sequence = to_shared(); return signatures { .enabled = true, @@ -187,7 +233,7 @@ bool chaser_validate::do_ecdsa(const hash_digest& digest, { ++ecdsa_; const auto set = archive().set_signature(digest, point, sign, link); - if (!set) fault(error::batch6); + if (!set) fault(error::batch7); return set; } @@ -197,7 +243,7 @@ bool chaser_validate::do_schnorr(const hash_digest& digest, { ++schnorr_; const auto set = archive().set_signature(digest, point, sign, link); - if (!set) fault(error::batch7); + if (!set) fault(error::batch8); return set; } @@ -210,7 +256,7 @@ bool chaser_validate::do_multisig(const hash_digest& digest, multisig_ += points.size(); const auto set = archive().set_signatures(digest, points, signs, (*sequence)++, link); - if (!set) fault(error::batch8); + if (!set) fault(error::batch9); return set; } @@ -219,7 +265,7 @@ bool chaser_validate::do_threshold(const threshold_group& group, { threshold_ += group.entries.size(); const auto set = archive().set_signatures(group, (*sequence)++, link); - if (!set) fault(error::batch9); + if (!set) fault(error::batch10); return set; } diff --git a/src/error.cpp b/src/error.cpp index 691791df..d7e8a165 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -106,7 +106,8 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { batch6, "batch6" }, { batch7, "batch7" }, { batch8, "batch8" }, - { batch9, "batch9" } + { batch9, "batch9" }, + { batch10, "batch10" } }; DEFINE_ERROR_T_CATEGORY(error, "node", "node code") diff --git a/src/settings.cpp b/src/settings.cpp index 263e7234..c8787c3f 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{ true }, + batch_signatures{ false }, // <-- update when ready. minimum_fee_rate{ 0.0 }, minimum_bump_rate{ 0.0 }, allowed_deviation{ 1.5 },