diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index e9f0cc7d7..f381bbfb0 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -1,307 +1,308 @@
 # Copyright (c) 2018 The Bitcoin developers
 
 project(bitcoin-test)
 
 # Process json files.
 file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/data")
 
 function(gen_json_header NAME)
 	set(HEADERS "")
 	foreach(f ${ARGN})
 		set(h "${CMAKE_CURRENT_BINARY_DIR}/${f}.h")
 
 		# Get the proper name for the test variable.
 		get_filename_component(TEST_NAME ${f} NAME_WE)
 		add_custom_command(OUTPUT ${h}
 			COMMAND
 				"${Python_EXECUTABLE}"
 				"${CMAKE_CURRENT_SOURCE_DIR}/data/generate_header.py"
 				"${TEST_NAME}"
 				"${CMAKE_CURRENT_SOURCE_DIR}/${f}" > ${h}
 			MAIN_DEPENDENCY ${f}
 			DEPENDS
 				"data/generate_header.py"
 			VERBATIM
 		)
 		list(APPEND HEADERS ${h})
 	endforeach(f)
 	set(${NAME} "${HEADERS}" PARENT_SCOPE)
 endfunction()
 
 gen_json_header(JSON_HEADERS
 	data/base58_encode_decode.json
 	data/blockfilters.json
 	data/key_io_valid.json
 	data/key_io_invalid.json
 	data/script_tests.json
 	data/sighash.json
 	data/tx_invalid.json
 	data/tx_valid.json
 )
 
 include(TestSuite)
 create_test_suite(bitcoin)
 add_dependencies(check check-bitcoin)
 
 # An utility library for bitcoin related test suites.
 add_library(testutil OBJECT
 	util/blockfilter.cpp
 	util/blockindex.cpp
 	util/logging.cpp
 	util/mining.cpp
 	util/net.cpp
 	util/setup_common.cpp
 	util/str.cpp
 	util/transaction_utils.cpp
 	util/validation.cpp
 	util/wallet.cpp
 )
 
 target_link_libraries(testutil server)
 
 if(BUILD_BITCOIN_WALLET)
 	set(BITCOIN_WALLET_TEST_FIXTURE
 		../wallet/test/init_test_fixture.cpp
 		../wallet/test/wallet_test_fixture.cpp
 	)
 	set(BITCOIN_WALLET_TESTS
 		../wallet/test/db_tests.cpp
 		../wallet/test/coinselector_tests.cpp
 		../wallet/test/init_tests.cpp
 		../wallet/test/ismine_tests.cpp
 		../wallet/test/psbt_wallet_tests.cpp
 		../wallet/test/scriptpubkeyman_tests.cpp
 		../wallet/test/wallet_tests.cpp
 		../wallet/test/walletdb_tests.cpp
 		../wallet/test/wallet_crypto_tests.cpp
 	)
 endif()
 
 function(gen_asmap_headers HEADERS_VAR)
 	foreach(INPUT_FILE ${ARGN})
 		set(OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${INPUT_FILE}.h")
 
 		add_custom_command(
 			OUTPUT "${OUTPUT_FILE}"
 			COMMENT "Generate ASMAP header from ${INPUT_FILE}"
 			COMMAND
 				"${Python_EXECUTABLE}"
 				"${CMAKE_CURRENT_SOURCE_DIR}/data/generate_asmap.py"
 				"${CMAKE_CURRENT_SOURCE_DIR}/${INPUT_FILE}"
 				"${OUTPUT_FILE}"
 			MAIN_DEPENDENCY "${INPUT_FILE}"
 			DEPENDS
 				"data/generate_header.py"
 			VERBATIM
 		)
 		list(APPEND ${HEADERS_VAR} "${OUTPUT_FILE}")
 	endforeach()
 	set(${HEADERS_VAR} ${${HEADERS_VAR}} PARENT_SCOPE)
 endfunction()
 
 gen_asmap_headers(ASMAP_HEADERS
 	data/asmap.raw
 )
 
 if(BUILD_BITCOIN_CHRONIK)
 	set(BITCOIN_CHRONIK_TESTS
 		../../chronik/test/bridgecompression_tests.cpp
 		../../chronik/test/bridgeprimitives_tests.cpp
 		../../chronik/test/chronikbridge_tests.cpp
 	)
 endif()
 
 add_boost_unit_tests_to_suite(bitcoin test_bitcoin
 	fixture.cpp
 	jsonutil.cpp
 	scriptflags.cpp
 	sigutil.cpp
 
 	${ASMAP_HEADERS}
 
 	# Tests generated from JSON
 	${JSON_HEADERS}
 
 	# Wallet test fixture
 	${BITCOIN_WALLET_TEST_FIXTURE}
 
 	TESTS
 		activation_tests.cpp
 		addrman_tests.cpp
 		allocator_tests.cpp
 		amount_tests.cpp
 		arith_uint256_tests.cpp
 		base32_tests.cpp
 		base58_tests.cpp
 		base64_tests.cpp
 		bip32_tests.cpp
 		bitmanip_tests.cpp
 		blockchain_tests.cpp
 		blockcheck_tests.cpp
 		blockencodings_tests.cpp
 		blockfilter_tests.cpp
 		blockfilter_index_tests.cpp
 		blockindex_tests.cpp
 		blockmanager_tests.cpp
 		blockstatus_tests.cpp
 		blockstorage_tests.cpp
 		bloom_tests.cpp
 		bswap_tests.cpp
 		cashaddr_tests.cpp
 		cashaddrenc_tests.cpp
 		checkdatasig_tests.cpp
 		checkpoints_tests.cpp
 		checkqueue_tests.cpp
 		coins_tests.cpp
 		coinstatsindex_tests.cpp
 		compilerbug_tests.cpp
 		compress_tests.cpp
 		config_tests.cpp
 		core_io_tests.cpp
 		crypto_tests.cpp
 		cuckoocache_tests.cpp
 		dbwrapper_tests.cpp
 		denialofservice_tests.cpp
 		descriptor_tests.cpp
 		dnsseeds_tests.cpp
 		dstencode_tests.cpp
 		feerate_tests.cpp
 		flatfile_tests.cpp
 		fs_tests.cpp
 		getarg_tests.cpp
 		hash_tests.cpp
 		hasher_tests.cpp
+		headers_sync_chainwork_tests.cpp
 		i2p_tests.cpp
 		interfaces_tests.cpp
 		intmath_tests.cpp
 		inv_tests.cpp
 		key_io_tests.cpp
 		key_tests.cpp
 		lcg_tests.cpp
 		logging_tests.cpp
 		mempool_tests.cpp
 		merkle_tests.cpp
 		merkleblock_tests.cpp
 		miner_tests.cpp
 		minerfund_tests.cpp
 		monolith_opcodes_tests.cpp
 		multisig_tests.cpp
 		net_peer_eviction_tests.cpp
 		net_tests.cpp
 		netbase_tests.cpp
 		op_reversebytes_tests.cpp
 		pmt_tests.cpp
 		policy_block_tests.cpp
 		policy_fee_tests.cpp
 		policyestimator_tests.cpp
 		prevector_tests.cpp
 		radix_tests.cpp
 		raii_event_tests.cpp
 		random_tests.cpp
 		rcu_tests.cpp
 		result_tests.cpp
 		reverselock_tests.cpp
 		rpc_tests.cpp
 		rpc_server_tests.cpp
 		rwcollection_tests.cpp
 		sanity_tests.cpp
 		scheduler_tests.cpp
 		schnorr_tests.cpp
 		script_bitfield_tests.cpp
 		script_p2sh_tests.cpp
 		script_standard_tests.cpp
 		script_tests.cpp
 		scriptnum_tests.cpp
 		serialize_tests.cpp
 		settings_tests.cpp
 		shortidprocessor_tests.cpp
 		sigcache_tests.cpp
 		sigencoding_tests.cpp
 		sighash_tests.cpp
 		sighashtype_tests.cpp
 		sigcheckcount_tests.cpp
 		skiplist_tests.cpp
 		sock_tests.cpp
 		streams_tests.cpp
 		sync_tests.cpp
 		timedata_tests.cpp
 		torcontrol_tests.cpp
 		transaction_tests.cpp
 		txindex_tests.cpp
 		txpackage_tests.cpp
 		txrequest_tests.cpp
 		txvalidation_tests.cpp
 		txvalidationcache_tests.cpp
 		uint256_tests.cpp
 		undo_tests.cpp
 		util_tests.cpp
 		util_threadnames_tests.cpp
 		validation_block_tests.cpp
 		validation_chainstate_tests.cpp
 		validation_chainstatemanager_tests.cpp
 		validation_flush_tests.cpp
 		validation_tests.cpp
 		validationinterface_tests.cpp
         blockindex_comparator_tests.cpp
 
 		# RPC Tests
 		../rpc/test/server_tests.cpp
 
 		# Wallet tests
 		${BITCOIN_WALLET_TESTS}
 
 		# Chronik tests
 		${BITCOIN_CHRONIK_TESTS}
 )
 
 function(add_boost_test_runners_with_upgrade_activated SUITE EXECUTABLE)
 	set(SUITE_UPGRADE_ACTIVATED "${SUITE}-upgrade-activated")
 	get_target_from_suite(${SUITE_UPGRADE_ACTIVATED} TARGET_UPGRADE_ACTIVATED)
 
 	if(NOT TARGET ${TARGET_UPGRADE_ACTIVATED})
 		create_test_suite_with_parent_targets(
 			${SUITE_UPGRADE_ACTIVATED}
 			check-upgrade-activated
 			check-upgrade-activated-extended
 		)
 		add_dependencies(${TARGET_UPGRADE_ACTIVATED} ${EXECUTABLE})
 	endif()
 
 	get_target_from_suite(${SUITE} SUITE_TARGET)
 	get_target_property(BOOST_TESTS ${SUITE_TARGET} UNIT_TESTS)
 
 	get_target_from_suite(${SUITE_UPGRADE_ACTIVATED} SUITE_UPGRADE_ACTIVATED_TARGET)
 
 	set(HRF_LOGGER "HRF,test_suite")
 
 	foreach(_test_name ${BOOST_TESTS})
 		if(ENABLE_JUNIT_REPORT)
 			set(JUNIT_LOGGER ":JUNIT,message,${SUITE_UPGRADE_ACTIVATED}-${_test_name}.xml")
 		endif()
 
 		add_test_runner(
 			${SUITE_UPGRADE_ACTIVATED}
 			"${_test_name}"
 			${EXECUTABLE}
 			JUNIT
 			"--run_test=${_test_name}"
 			"--logger=${HRF_LOGGER}${JUNIT_LOGGER}"
 			"--catch_system_errors=no"
 			--
 			"-testsuitename=Bitcoin ABC unit tests with next upgrade activated"
 			# Nov. 20th, 2023 at 12:00:00
 			-leekuanyewactivationtime=1700481600
 		)
 	endforeach()
 endfunction()
 
 add_boost_test_runners_with_upgrade_activated(bitcoin test_bitcoin)
 
 target_link_libraries(test_bitcoin rpcclient testutil)
 if(TARGET bitcoinconsensus-shared)
 	target_link_libraries(test_bitcoin bitcoinconsensus-shared)
 else()
 	target_link_libraries(test_bitcoin bitcoinconsensus)
 endif()
 
 add_subdirectory(fuzz)
diff --git a/src/test/headers_sync_chainwork_tests.cpp b/src/test/headers_sync_chainwork_tests.cpp
new file mode 100644
index 000000000..943214971
--- /dev/null
+++ b/src/test/headers_sync_chainwork_tests.cpp
@@ -0,0 +1,160 @@
+// Copyright (c) 2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <chain.h>
+#include <chainparams.h>
+#include <consensus/params.h>
+#include <headerssync.h>
+#include <pow/pow.h>
+#include <primitives/blockhash.h>
+#include <test/util/setup_common.h>
+#include <validation.h>
+#include <vector>
+
+#include <boost/test/unit_test.hpp>
+
+struct HeadersGeneratorSetup : public RegTestingSetup {
+    /** Search for a nonce to meet (regtest) proof of work */
+    void FindProofOfWork(CBlockHeader &starting_header);
+    /**
+     * Generate headers in a chain that build off a given starting hash, using
+     * the given nVersion, advancing time by 1 second from the starting
+     * prev_time, and with a fixed merkle root hash.
+     */
+    void GenerateHeaders(std::vector<CBlockHeader> &headers, size_t count,
+                         const BlockHash &starting_hash, const int nVersion,
+                         int prev_time, const uint256 &merkle_root,
+                         const uint32_t nBits);
+};
+
+void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader &starting_header) {
+    while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits,
+                             Params().GetConsensus())) {
+        ++(starting_header.nNonce);
+    }
+}
+
+void HeadersGeneratorSetup::GenerateHeaders(std::vector<CBlockHeader> &headers,
+                                            size_t count,
+                                            const BlockHash &starting_hash,
+                                            const int nVersion, int prev_time,
+                                            const uint256 &merkle_root,
+                                            const uint32_t nBits) {
+    BlockHash prev_hash = starting_hash;
+
+    while (headers.size() < count) {
+        headers.push_back(CBlockHeader());
+        CBlockHeader &next_header = headers.back();
+        ;
+        next_header.nVersion = nVersion;
+        next_header.hashPrevBlock = prev_hash;
+        next_header.hashMerkleRoot = merkle_root;
+        next_header.nTime = prev_time + 1;
+        next_header.nBits = nBits;
+
+        FindProofOfWork(next_header);
+        prev_hash = next_header.GetHash();
+        prev_time = next_header.nTime;
+    }
+    return;
+}
+
+BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup)
+
+// In this test, we construct two sets of headers from genesis, one with
+// sufficient proof of work and one without.
+// 1. We deliver the first set of headers and verify that the headers sync state
+//    updates to the REDOWNLOAD phase successfully.
+// 2. Then we deliver the second set of headers and verify that they fail
+//    processing (presumably due to commitments not matching).
+// 3. Finally, we verify that repeating with the first set of headers in both
+//    phases is successful.
+BOOST_AUTO_TEST_CASE(headers_sync_state) {
+    std::vector<CBlockHeader> first_chain;
+    std::vector<CBlockHeader> second_chain;
+
+    std::unique_ptr<HeadersSyncState> hss;
+
+    const int target_blocks = 15000;
+    arith_uint256 chain_work = target_blocks * 2;
+
+    // Generate headers for two different chains (using differing merkle roots
+    // to ensure the headers are different).
+    GenerateHeaders(
+        first_chain, target_blocks - 1, Params().GenesisBlock().GetHash(),
+        Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime,
+        ArithToUint256(0), Params().GenesisBlock().nBits);
+
+    GenerateHeaders(
+        second_chain, target_blocks - 2, Params().GenesisBlock().GetHash(),
+        Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime,
+        ArithToUint256(1), Params().GenesisBlock().nBits);
+
+    const CBlockIndex *chain_start = WITH_LOCK(
+        ::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(
+                       Params().GenesisBlock().GetHash()));
+    std::vector<CBlockHeader> headers_batch;
+
+    // Feed the first chain to HeadersSyncState, by delivering 1 header
+    // initially and then the rest.
+    headers_batch.insert(headers_batch.end(), std::next(first_chain.begin()),
+                         first_chain.end());
+
+    hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start,
+                                   chain_work));
+    (void)hss->ProcessNextHeaders({first_chain.front()}, true);
+    // Pretend the first header is still "full", so we don't abort.
+    auto result = hss->ProcessNextHeaders(headers_batch, true);
+
+    // This chain should look valid, and we should have met the proof-of-work
+    // requirement.
+    BOOST_CHECK(result.success);
+    BOOST_CHECK(result.request_more);
+    BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD);
+
+    // Try to sneakily feed back the second chain.
+    result = hss->ProcessNextHeaders(second_chain, true);
+    BOOST_CHECK(!result.success); // foiled!
+    BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
+
+    // Now try again, this time feeding the first chain twice.
+    hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start,
+                                   chain_work));
+    (void)hss->ProcessNextHeaders(first_chain, true);
+    BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD);
+
+    result = hss->ProcessNextHeaders(first_chain, true);
+    BOOST_CHECK(result.success);
+    BOOST_CHECK(!result.request_more);
+    // All headers should be ready for acceptance:
+    BOOST_CHECK(result.pow_validated_headers.size() == first_chain.size());
+    // Nothing left for the sync logic to do:
+    BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
+
+    // Finally, verify that just trying to process the second chain would not
+    // succeed (too little work)
+    hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start,
+                                   chain_work));
+    BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC);
+    // Pretend just the first message is "full", so we don't abort.
+    (void)hss->ProcessNextHeaders({second_chain.front()}, true);
+    BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC);
+
+    headers_batch.clear();
+    headers_batch.insert(headers_batch.end(),
+                         std::next(second_chain.begin(), 1),
+                         second_chain.end());
+    // Tell the sync logic that the headers message was not full, implying no
+    // more headers can be requested. For a low-work-chain, this should cause
+    // the sync to end with no headers for acceptance.
+    result = hss->ProcessNextHeaders(headers_batch, false);
+    BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
+    BOOST_CHECK(result.pow_validated_headers.empty());
+    BOOST_CHECK(!result.request_more);
+    // Nevertheless, no validation errors should have been detected with the
+    // chain:
+    BOOST_CHECK(result.success);
+}
+
+BOOST_AUTO_TEST_SUITE_END()