diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index a47d39de..74c5e03f 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -158,7 +158,7 @@ class BCN_API chaser_validate bool is_silent_capturing(const header_link& link) NOEXCEPT; bool is_silent_mature(bool residual) NOEXCEPT; std::string log_rate(const std::string& name, size_t signatures, - size_t milliseconds) const NOEXCEPT; + size_t milliseconds, bool device) const NOEXCEPT; // This is not thread safe. network::threadpool validation_threadpool_; diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index 1e5b9864..1ad9731f 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -79,48 +79,52 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT return; // Retest under the claim, another drain may have just emptied the bank. - if (!is_mature(residual)) + // Losers commit before testing, so the retest after each drain observes + // commits that lost the claim during it. + while (!closed() && is_mature(residual || is_residual())) { - draining_.store(false); - return; - } + // Admit arriving commits to the other bank (changed only under claim). + const auto bank = bank_.load(); + bank_.store(!bank); - // Admit arriving commits to the other bank (changed only under claim). - const auto bank = bank_.load(); - bank_.store(!bank); + // Wait for in-flight commits to the bank to complete or abandon on + // close. Bounded: the writer epoch spans only the per-block slab + // commit, and arriving commits go to the other bank. + while (is_nonzero(writers_.at(to_int(bank)).load())) + { + if (closed()) + { + draining_.store(false); + return; + } - // Wait for in-flight commits to the bank to complete or abandon on close. - // Bounded: the writer epoch spans only the per-block slab commit, and - // arriving commits go to the other bank. - while (is_nonzero(writers_.at(to_int(bank)).load())) - { - if (closed()) + std::this_thread::yield(); + } + + // Bank is now quiescent (no writers admitted, none in flight). + // ==================================================================== + + const auto ec = do_process_batch(bank, false); + if (ec == network::error::operation_canceled) { draining_.store(false); return; } - std::this_thread::yield(); - } - - // Bank is now quiescent (no writers admitted, none in flight). - // ======================================================================== + if (ec) + { + draining_.store(false); + fault(ec); + return; + } - const auto ec = do_process_batch(bank, false); - draining_.store(false); - if (ec == network::error::operation_canceled) - return; + // ==================================================================== - if (ec) - { - fault(ec); - return; + // Log only when batch executes (non-verbose). + log_captures(); } - // ======================================================================== - - // Log outside of drain claim, and only when batch executes (non-verbose). - log_captures(); + draining_.store(false); } // Guarded by the drain claim (or single-threaded at startup). @@ -132,9 +136,10 @@ code chaser_validate::do_process_batch(bool bank, bool startup) NOEXCEPT const auto ecdsa = query.ecdsa_records(bank); if (is_nonzero(ecdsa)) { + bool device{}; header_links invalids{}; const auto start = network::logger::now(); - if (!query.verify_ecdsa_signatures(stopping_, invalids, bank)) + if (!query.verify_ecdsa_signatures(stopping_, invalids, device, bank)) { LOGN("Batch verify ecdsa canceled (" << ecdsa << ")."); return network::error::operation_canceled; @@ -145,7 +150,7 @@ code chaser_validate::do_process_batch(bool bank, bool startup) NOEXCEPT if (!startup) { LOGN(log_rate("Verify ecdsa.....", ecdsa, - duration_cast(elapsed).count())); + duration_cast(elapsed).count(), device)); } if (!mark_invalids(prevalids, invalids, startup)) @@ -155,9 +160,10 @@ code chaser_validate::do_process_batch(bool bank, bool startup) NOEXCEPT const auto schnorr = query.schnorr_records(bank); if (is_nonzero(schnorr)) { + bool device{}; header_links invalids{}; const auto start = network::logger::now(); - if (!query.verify_schnorr_signatures(stopping_, invalids, bank)) + if (!query.verify_schnorr_signatures(stopping_, invalids, device, bank)) { LOGN("Batch verify schnorr canceled (" << schnorr << ")."); return network::error::operation_canceled; @@ -168,7 +174,7 @@ code chaser_validate::do_process_batch(bool bank, bool startup) NOEXCEPT if (!startup) { LOGN(log_rate("Verify schnorr...", schnorr, - duration_cast(elapsed).count())); + duration_cast(elapsed).count(), device)); } if (!mark_invalids(prevalids, invalids, startup)) @@ -291,11 +297,11 @@ bool chaser_validate::is_silent_mature(bool residual) NOEXCEPT } std::string chaser_validate::log_rate(const std::string& name, - size_t signatures, size_t milliseconds) const NOEXCEPT + size_t signatures, size_t milliseconds, bool device) const NOEXCEPT { const auto rate = (signatures * 1000u) / greater(milliseconds, one); - return std::format("{} ({} / {} ms) = {} sps", name, signatures, - milliseconds, rate); + return std::format("{} ({} / {} ms) = {} sps on {}", name, signatures, + milliseconds, rate, device ? "device" : "cpu"); } // Silent batch. @@ -329,49 +335,58 @@ void chaser_validate::process_silent_batch(bool residual) NOEXCEPT return; // Retest under the claim, another drain may have just emptied the bank. - if (!is_silent_mature(residual)) + // Losers commit before testing, so the retest after each drain observes + // commits that lost the claim during it. + while (!closed() && is_silent_mature(residual || is_residual())) { - silent_draining_.store(false); - return; - } + // Admit arriving commits to the other bank (changed only under claim). + const auto bank = silent_bank_.load(); + silent_bank_.store(!bank); - // Admit arriving commits to the other bank (changed only under claim). - const auto bank = silent_bank_.load(); - silent_bank_.store(!bank); + // Wait for in-flight commits to the bank to complete or abandon on + // close. + while (is_nonzero(silent_writers_.at(to_int(bank)).load())) + { + if (closed()) + { + silent_draining_.store(false); + return; + } - // Wait for in-flight commits to the bank to complete or abandon on close. - while (is_nonzero(silent_writers_.at(to_int(bank)).load())) - { - if (closed()) + std::this_thread::yield(); + } + + // Bank is now quiescent (no writers admitted, none in flight). + // ==================================================================== + + const auto ec = do_process_silent_batch(bank); + if (ec == network::error::operation_canceled) { silent_draining_.store(false); return; } - std::this_thread::yield(); - } + if (ec) + { + silent_draining_.store(false); + fault(ec); + return; + } - // Bank is now quiescent (no writers admitted, none in flight). - // ======================================================================== + // ==================================================================== + } - const auto ec = do_process_silent_batch(bank); silent_draining_.store(false); - if (ec == network::error::operation_canceled) - return; - - if (ec) - fault(ec); - - // ======================================================================== } // Guarded by the drain claim (or single-threaded at startup). code chaser_validate::do_process_silent_batch(bool bank) NOEXCEPT { auto& query = archive(); + bool device{}; const auto rows = query.silent_records(bank); const auto start = network::logger::now(); - const auto ec = query.compute_silents(stopping_, bank); + const auto ec = query.compute_silents(stopping_, device, bank); if (ec == database::error::query_canceled) { LOGN("Batch silent canceled (" << rows << ")."); @@ -383,7 +398,7 @@ code chaser_validate::do_process_silent_batch(bool bank) NOEXCEPT const auto elapsed = network::logger::now() - start; LOGN(log_rate("Compute silent...", rows, - duration_cast(elapsed).count())); + duration_cast(elapsed).count(), device)); if (!query.purge_silents(bank)) return error::batch8; diff --git a/test/chasers/chaser_validate.cpp b/test/chasers/chaser_validate.cpp index 874a46b0..f4ff2c4f 100644 --- a/test/chasers/chaser_validate.cpp +++ b/test/chasers/chaser_validate.cpp @@ -468,6 +468,53 @@ struct validate_current_overspent_fixture BOOST_AUTO_TEST_SUITE(chaser_validate_tests) +static bool validate_noncandidates(node::query& query) +{ + return validate_noncandidate(query) && validate_store(query, validate_a1(), 1); +} + +struct validate_noncandidates_fixture + : validate_setup_fixture +{ + validate_noncandidates_fixture() + : validate_setup_fixture(validate_noncandidates, {}) + { + } +}; + +// Commits a prevalid to the admitting bank and loses the drain claim during +// the first drain, as a completing validation does. +struct validate_drain_accessor + : chaser_validate +{ + validate_drain_accessor(full_node& node, const database::header_link& link) NOEXCEPT + : chaser_validate(node), link_(link) + { + } + + void drain() NOEXCEPT + { + process_batch(true); + } + + code do_process_batch(bool bank, bool startup) NOEXCEPT override + { + if (!lost_) + { + lost_ = true; + if (!archive().set_prevalid(link_, !bank)) + return node::error::batch1; + + process_batch(true); + } + + return chaser_validate::do_process_batch(bank, startup); + } + + const database::header_link link_; + bool lost_{}; +}; + BOOST_FIXTURE_TEST_CASE(chaser_validate__start__unvalidated_candidates__confirmable, validate_unvalidated_fixture) { BOOST_REQUIRE(confirmed(2)); @@ -527,6 +574,21 @@ BOOST_FIXTURE_TEST_CASE(chaser_validate__start__staged_second_bank__validated_if BOOST_REQUIRE_EQUAL(state(validate_x1()) == database::error::block_valid, system::batched::accelerated()); } +BOOST_FIXTURE_TEST_CASE(chaser_validate__process_batch__residual_commit_during_drain__drained, validate_noncandidates_fixture) +{ + const auto first = query_.to_header(validate_x1().hash()); + const auto second = query_.to_header(validate_a1().hash()); + BOOST_REQUIRE(query_.set_prevalid(first, false)); + + validate_drain_accessor instance{ node_, second }; + instance.drain(); + BOOST_REQUIRE(instance.lost_); + BOOST_REQUIRE(is_zero(query_.prevalid_records(false))); + BOOST_REQUIRE(is_zero(query_.prevalid_records(true))); + BOOST_REQUIRE_EQUAL(state(validate_x1()), database::error::block_valid); + BOOST_REQUIRE_EQUAL(state(validate_a1()), database::error::block_valid); +} + BOOST_FIXTURE_TEST_CASE(chaser_validate__windowed__prevalid__valid, validate_windowed_fixture) { BOOST_REQUIRE(query_.set_prevalid(query_.to_header(validate_x1().hash()), false));