Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/bitcoin/node/chasers/chaser_validate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
139 changes: 77 additions & 62 deletions src/chasers/chaser_validate_batch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(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<size_t>(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).
Expand All @@ -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;
Expand All @@ -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<milliseconds>(elapsed).count()));
duration_cast<milliseconds>(elapsed).count(), device));
}

if (!mark_invalids(prevalids, invalids, startup))
Expand All @@ -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;
Expand All @@ -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<milliseconds>(elapsed).count()));
duration_cast<milliseconds>(elapsed).count(), device));
}

if (!mark_invalids(prevalids, invalids, startup))
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<size_t>(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<size_t>(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 << ").");
Expand All @@ -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<milliseconds>(elapsed).count()));
duration_cast<milliseconds>(elapsed).count(), device));

if (!query.purge_silents(bank))
return error::batch8;
Expand Down
62 changes: 62 additions & 0 deletions test/chasers/chaser_validate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down
Loading