diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index a7d71673..3a845f03 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -76,12 +76,18 @@ class BCN_API chaser_validate size_t height, bool bypass, bool batched=false, bool faulted=false, bool capturing=false) NOEXCEPT; virtual void notify_block(const code& ec, size_t height, - const header_link& link, bool bypass) NOEXCEPT; + 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 process_batch(bool residual) 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; + virtual bool mark_valids(bool startup) NOEXCEPT; + virtual bool mark_invalids(const header_links& invalids, + bool startup) NOEXCEPT; // Override base class strand because it sits on the network thread pool. network::asio::strand& strand() NOEXCEPT override; @@ -94,11 +100,6 @@ class BCN_API chaser_validate using threshold = system::chain::threshold; using missed = signatures::miss; - /// Batching helpers. - bool is_maximum() NOEXCEPT; - bool process_valids(bool residual) NOEXCEPT; - bool process_invalids(const header_links& invalids) NOEXCEPT; - // Capture handlers. void do_log(const system::chain::script& missed) NOEXCEPT; void do_fire(missed miss, size_t count) NOEXCEPT; diff --git a/src/chasers/chaser_confirm.cpp b/src/chasers/chaser_confirm.cpp index 8fe3ac06..0ea9822c 100644 --- a/src/chasers/chaser_confirm.cpp +++ b/src/chasers/chaser_confirm.cpp @@ -252,6 +252,7 @@ void chaser_confirm::organize(header_states& fork, const header_links& popped, } // error::unassociated // error::unknown_state + // error::block_prevalid // error::block_unconfirmable default: { diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 7797edb5..ef6cfa4e 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -57,10 +57,10 @@ code chaser_validate::start() NOEXCEPT if (!node_settings().headers_first) return error::success; + set_position(archive().get_fork()); if (const auto ec = start_batch()) - return ec; + return fault(ec); - set_position(archive().get_fork()); SUBSCRIBE_CHASE(handle_chase, _1, _2, _3); return error::success; } @@ -192,6 +192,10 @@ 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; @@ -279,28 +283,28 @@ void chaser_validate::complete_block(const code& ec, const header_link& link, const auto current = !capturing && !bypass; // Drain batch when recent (current, or maximum reached without backlog). - if (current || is_maximum()) + if (current || is_residual()) { POST(process_batch, true); } } void chaser_validate::notify_block(const code& ec, size_t height, - const header_link& link, bool bypass) NOEXCEPT + const header_link& link, bool bypass, bool startup) NOEXCEPT { // Not stranded when complete_block is called from validate_block. if (ec) { // INVALID BLOCK (not a fault but discontinue) - notify(ec, chase::unvalid, link); + if (!startup) notify(ec, chase::unvalid, link); fire(events::block_unconfirmable, height); LOGR("Invalid block [" << height << "] " << ec.message()); return; } // VALID BLOCK - notify(ec, chase::valid, possible_wide_cast(height)); + if (!startup) notify(ec, chase::valid, possible_wide_cast(height)); fire(events::block_validated, height); LOGV("Block validated: " << height << (bypass ? " (bypass)" : "")); } diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index d12b7c4d..bb05f811 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -38,24 +38,52 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) // ---------------------------------------------------------------------------- // protected -// Cannot know if archived batch is faulted, despite being otherwise full, as -// faulted is a non-persistent state. So we must purge batches at start. +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. code chaser_validate::start_batch() NOEXCEPT { - auto& query = archive(); - return (batch_enabled_ && - (!query.purge_ecdsa_signatures() || - !query.purge_schnorr_signatures())) ? - error::batch1 : error::success; + BC_ASSERT(batched_.empty()); + if (!batch_enabled_) + return {}; + + const auto& query = archive(); + 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); } // 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, size_t height) NOEXCEPT +void chaser_validate::push_batch(const header_link& link, + size_t height) NOEXCEPT { BC_ASSERT(stranded()); - if (closed()) return; + if (closed()) + return; + batched_.push_back(link); --batch_backlog_; @@ -65,7 +93,11 @@ void chaser_validate::push_batch(const header_link& link, size_t height) NOEXCEP // Process both tables when one hits target, allowing batched_ clearance // and therefore forward confirmation progress. Drain batch if no backlogs // and maximum hash been posted. - process_batch(is_maximum()); + const auto residual = is_residual(); + process_batch(residual); + + if (residual) + batched_.shrink_to_fit(); } void chaser_validate::process_batch(bool residual) NOEXCEPT @@ -73,10 +105,7 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT BC_ASSERT(stranded()); // Test outside of lock to prevent reader contention for nearly all calls. - auto& query = archive(); - if (closed() || (!residual && - (query.ecdsa_records() < batch_target_) && - (query.schnorr_records() < batch_target_))) + if (closed() || !is_mature(residual)) return; // Unique lock prevents batch table updates during evaluation, allowing the @@ -86,17 +115,22 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT const std::unique_lock lock{ mutex_ }; // Must retest inside the lock as table updates are running concurrently. - if (closed()) return; - const auto ecdsa = query.ecdsa_records(); - const auto schnorr = query.schnorr_records(); - if (!residual && (ecdsa < batch_target_) && (schnorr < batch_target_)) + if (closed() || !is_mature(residual)) return; - log_captures(); + if (const auto ec = do_process_batch(false)) + fault(ec); + // ======================================================================== +} - // set_block_unconfirmable(ecdsa) - // ------------------------------------------------------------------------ +code chaser_validate::do_process_batch(bool startup) NOEXCEPT +{ + if (!startup) + log_captures(); + auto& query = archive(); + + const auto ecdsa = query.ecdsa_records(); if (is_nonzero(ecdsa)) { header_links invalids{}; @@ -104,24 +138,24 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT if (!query.verify_ecdsa_signatures(stopping_, invalids)) { LOGN("Batch verify ecdsa canceled (" << ecdsa << ")."); - return; + return network::error::operation_canceled; } const auto end = network::logger::now(); const auto elapsed = duration_cast(end - start).count(); fire(events::ecdsa_secs, elapsed); - LOGN(log_rate("Batch verify rate ecdsa.... ", ecdsa, elapsed)); - if (!process_invalids(invalids) || !query.purge_ecdsa_signatures()) + if (!startup) { - fault(error::batch2); - return; + LOGN(log_rate("Batch verify rate ecdsa.... ", ecdsa, elapsed)); } - } - // set_block_unconfirmable(schnorr) - // ------------------------------------------------------------------------ + if (!mark_invalids(invalids, startup) || + !query.purge_ecdsa_signatures()) + return error::batch2; + } + const auto schnorr = query.schnorr_records(); if (is_nonzero(schnorr)) { header_links invalids{}; @@ -129,41 +163,24 @@ void chaser_validate::process_batch(bool residual) NOEXCEPT if (!query.verify_schnorr_signatures(stopping_, invalids)) { LOGN("Batch verify schnorr canceled (" << schnorr << ")."); - return; + return network::error::operation_canceled; } const auto end = network::logger::now(); const auto elapsed = duration_cast(end - start).count(); fire(events::schnorr_secs, elapsed); - LOGN(log_rate("Batch verify rate schnorr.. ", schnorr, elapsed)); - if (!process_invalids(invalids) || !query.purge_schnorr_signatures()) + if (!startup) { - fault(error::batch3); - return; + LOGN(log_rate("Batch verify rate schnorr.. ", schnorr, elapsed)); } - } - - // set_block_valid(batched_ excluding ecdsa/schnorr failures) - // ------------------------------------------------------------------------ - if (!process_valids(residual)) - { - fault(error::batch4); - return; + if (!mark_invalids(invalids, startup) || + !query.purge_schnorr_signatures()) + return error::batch3; } - // ======================================================================== -} -// Batching helpers. -// ---------------------------------------------------------------------------- -// private - -bool chaser_validate::is_maximum() NOEXCEPT -{ - return maximum_posted_.load() && - is_zero(batch_backlog_.load()) && - is_zero(validate_backlog_.load()); + return mark_valids(startup) ? error::success : error::batch4; } // Invalids might not be included in batched, as link push is a race. @@ -174,7 +191,8 @@ bool chaser_validate::is_maximum() NOEXCEPT // to match here, this block id will subsequently land in batched_ and would // then be reported as valid, overriding the unconfirmable block state set // below, so invalids_ are cached for process lifetime. -bool chaser_validate::process_invalids(const header_links& invalids) NOEXCEPT +bool chaser_validate::mark_invalids(const header_links& invalids, + bool startup) NOEXCEPT { BC_ASSERT(stranded()); @@ -187,7 +205,8 @@ bool chaser_validate::process_invalids(const header_links& invalids) NOEXCEPT return false; invalids_.push_back(link); - notify_block(system::error::invalid_signature, height, link, false); + const auto ec = system::error::invalid_signature; + notify_block(ec, height, link, false, startup); } // Set all invalid links in batched_ to terminal (to be skipped). @@ -204,7 +223,7 @@ bool chaser_validate::process_invalids(const header_links& invalids) NOEXCEPT // 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(bool residual) NOEXCEPT +bool chaser_validate::mark_valids(bool startup) NOEXCEPT { BC_ASSERT(stranded()); @@ -226,13 +245,10 @@ bool chaser_validate::process_valids(bool residual) NOEXCEPT return; } - notify_block(system::error::success, height, link, false); + notify_block(system::error::success, height, link, false, startup); }); batched_.clear(); - if (residual) - batched_.shrink_to_fit(); - return !fault.load(); } diff --git a/src/chasers/chaser_validate_parallel.cpp b/src/chasers/chaser_validate_parallel.cpp index f28578a7..2cf87593 100644 --- a/src/chasers/chaser_validate_parallel.cpp +++ b/src/chasers/chaser_validate_parallel.cpp @@ -64,6 +64,11 @@ 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);