From 8c5b36c90cf235b0967e0f6172f7ead8d6956320 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 24 Jun 2026 02:37:50 -0400 Subject: [PATCH 1/2] Comment. --- src/chasers/chaser_validate_batch.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index 8113f38f..712ad4d5 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -185,6 +185,13 @@ bool chaser_validate::process_invalids(const header_links& invalids) NOEXCEPT notify_block(system::error::invalid_signature, height, link, false); } + // BUGBUG: a batched_ value may not be present in the current batched_ set + // despite being invalid here, which is expected. However upon invalidation + // and failure to match here, this block id will subsequently land in + // batched_ and then be reported as valid, overriding the unconfirmable + // block state set above. We also don't want to read block state before + // every write of valid state. + // Set all invalids links in batched_ to terminal (to be skipped). if (!invalids.empty()) { From 38b624bc6172166bf19a948b6bfa11d1b1a441a3 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 24 Jun 2026 12:24:17 -0400 Subject: [PATCH 2/2] Resolve valid-after-invalid bug in validation chaser. --- .../bitcoin/node/chasers/chaser_validate.hpp | 1 + src/chasers/chaser_validate_batch.cpp | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 1f0fbbe9..6da61143 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -125,6 +125,7 @@ class BCN_API chaser_validate // These are protected by strand. header_links batched_{}; + header_links invalids_{}; network::threadpool validation_threadpool_; // These are thread safe. diff --git a/src/chasers/chaser_validate_batch.cpp b/src/chasers/chaser_validate_batch.cpp index 712ad4d5..368960b4 100644 --- a/src/chasers/chaser_validate_batch.cpp +++ b/src/chasers/chaser_validate_batch.cpp @@ -170,6 +170,11 @@ bool chaser_validate::is_maximum() NOEXCEPT // 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 (!). +// A batched_ value may not be present in the current batched_ set despite +// being invalid here, which is expected. However upon invalidation and failure +// 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 { BC_ASSERT(stranded()); @@ -182,22 +187,16 @@ bool chaser_validate::process_invalids(const header_links& invalids) NOEXCEPT !query.set_block_unconfirmable(link)) return false; + invalids_.push_back(link); notify_block(system::error::invalid_signature, height, link, false); } - // BUGBUG: a batched_ value may not be present in the current batched_ set - // despite being invalid here, which is expected. However upon invalidation - // and failure to match here, this block id will subsequently land in - // batched_ and then be reported as valid, overriding the unconfirmable - // block state set above. We also don't want to read block state before - // every write of valid state. - - // Set all invalids links in batched_ to terminal (to be skipped). - if (!invalids.empty()) + // Set all invalid links in batched_ to terminal (to be skipped). + if (!invalids_.empty()) { std::ranges::replace_if(batched_, [&](const auto& link) NOEXCEPT { - return contains(invalids, link); + return contains(invalids_, link); }, header_link::terminal); }