Skip to content

Commit bb70ee6

Browse files
Backfill regressions and consolidate GoogleTest binaries (#317)
* Add regression tests for compressed form deserialization hardening. Cover malformed, wrong-sized, and non-canonical serialized forms to guard historical crash/validation regressions in proof deserialization. Co-authored-by: Cursor <cursoragent@cursor.com> * Add regression coverage for tiny-iteration ProveSlow behavior. Exercise low-iteration paths that historically hit k=0 parameter estimates and ensure proof serialization/deserialization remains valid. Co-authored-by: Cursor <cursoragent@cursor.com> * Consolidate regression gtests into fixed unit and io binaries. Reduce binary proliferation by grouping regression tests into two stable targets and update CI runners to build/run the new targets. Co-authored-by: Cursor <cursoragent@cursor.com> * Add regression for valid InitSession payload parsing path. Cover the historical vdf_client protocol-read bugfix by asserting a well-formed discriminant/form payload is accepted without over-reading and parsed into session buffers. Co-authored-by: Cursor <cursoragent@cursor.com> * Add TwoWesolowski transition regression coverage and rebalance CI test runtime. This adds focused callback boundary tests so the switch-threshold logic is validated quickly, then shifts CI to fast smoke runs on most lanes while keeping one optimized Ubuntu threshold-crossing path for risk coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Windows regression gtest discovery runtime path failures. This defers GoogleTest discovery to test time and ensures Windows regression ctest runs with the required DLL paths so optimized Windows builds no longer fail with 0xc0000135. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Windows asm output path assertion in optimized CI build. Accept generated asm files in either build/ or build/src/ to avoid false failures from generator-dependent output layout while still asserting asm generation succeeded. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Unix regression build deps and stabilize CI tool/runtime checks. This ensures regression_unit_tests links with shared asm/common sources and definitions, installs cmake on macOS runners before Unix regression steps, and stops Windows asm path checks from masking prior build failures. Co-authored-by: Cursor <cursoragent@cursor.com> * Define regression test globals needed by vdf headers. Provide test-local definitions for gcd_base_bits and gcd_128_max_iter so regression_unit_tests links cleanly across macOS and Windows optimized jobs. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix TSAN data race in emulator engine stop/run state. Use atomic flags for job lifecycle state shared between worker and control threads in emu_runner so TSAN no longer reports unsynchronized stop/running accesses in emu_hw_test. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Linux regression_unit_tests linking with generated asm objects. Disable PIE for regression_unit_tests on Linux so generated asm objects with absolute relocations link successfully in Ubuntu optimized CI. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a4b88c2 commit bb70ee6

8 files changed

Lines changed: 278 additions & 58 deletions

.github/workflows/vdf-client-hw.yml

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ jobs:
6161
- name: Install macOS deps (build + runtime)
6262
if: startsWith(matrix.os, 'macos')
6363
run: |
64+
brew ls --versions cmake >/dev/null 2>&1 || brew install cmake
6465
brew ls --versions gmp >/dev/null 2>&1 || brew install gmp
6566
brew ls --versions boost >/dev/null 2>&1 || brew install boost
6667
echo "DYLD_FALLBACK_LIBRARY_PATH=$(brew --prefix gmp)/lib:${DYLD_FALLBACK_LIBRARY_PATH:-}" >> "$GITHUB_ENV"
@@ -219,16 +220,20 @@ jobs:
219220
if (-not $gnuAsmEnabled) {
220221
throw "ENABLE_GNU_ASM is not ON on Windows CI build"
221222
}
222-
cmake --build build --target vdf_client vdf_bench 1weso_test 2weso_test prover_test vdf_client_session_test emu_hw_test hw_test emu_hw_vdf_client hw_vdf_client
223-
$asmFiles = @(
224-
"build/asm_compiled.s",
225-
"build/avx2_asm_compiled.s",
226-
"build/avx512_asm_compiled.s"
227-
)
223+
cmake --build build --target vdf_client vdf_bench 1weso_test 2weso_test prover_test regression_unit_tests regression_io_tests emu_hw_test hw_test emu_hw_vdf_client hw_vdf_client
224+
if ($LASTEXITCODE -ne 0) {
225+
throw "Windows build failed with exit code $LASTEXITCODE"
226+
}
227+
$asmFiles = @("asm_compiled.s", "avx2_asm_compiled.s", "avx512_asm_compiled.s")
228228
foreach ($asmFile in $asmFiles) {
229-
if (-not (Test-Path $asmFile)) {
230-
throw "Expected generated asm output not found: $asmFile"
229+
# Depending on generator/layout, CMAKE_CURRENT_BINARY_DIR for src may resolve
230+
# to build/ or build/src/. Accept either location to avoid false negatives.
231+
$candidatePaths = @("build/$asmFile", "build/src/$asmFile")
232+
$foundPath = $candidatePaths | Where-Object { Test-Path $_ } | Select-Object -First 1
233+
if (-not $foundPath) {
234+
throw "Expected generated asm output not found in build/ or build/src/: $asmFile"
231235
}
236+
Write-Host "Verified generated asm output: $foundPath"
232237
}
233238
234239
- name: Run HW smoke test (Ubuntu/macOS)
@@ -243,14 +248,24 @@ jobs:
243248
echo "Running emu_hw_vdf_client --list"
244249
./emu_hw_vdf_client --list
245250
246-
- name: Run vdf tests (optimized)
247-
if: matrix.config == 'optimized=1' && matrix.os != 'windows-latest'
251+
- name: Run vdf tests (optimized smoke)
252+
if: matrix.config == 'optimized=1' && matrix.os != 'windows-latest' && matrix.os != 'ubuntu-latest'
248253
run: |
249254
cd src
250-
echo "Running 1weso_test"
251-
./1weso_test
252-
echo "Running 2weso_test"
253-
./2weso_test
255+
echo "Running 1weso_test (fast smoke)"
256+
./1weso_test 1000
257+
echo "Running 2weso_test (fast smoke)"
258+
./2weso_test 1000
259+
260+
- name: Run vdf tests (optimized protected threshold path)
261+
if: matrix.config == 'optimized=1' && matrix.os == 'ubuntu-latest'
262+
run: |
263+
cd src
264+
echo "Running 1weso_test (fast smoke)"
265+
./1weso_test 1000
266+
# 100 * 910000 = 91,000,000; this intentionally crosses the 2-weso switch threshold.
267+
echo "Running 2weso_test (protected threshold path)"
268+
./2weso_test 910000
254269
255270
- name: Run vdf tests (short)
256271
if: matrix.config != 'optimized=1' && matrix.os != 'windows-latest'
@@ -293,17 +308,17 @@ jobs:
293308
.\emu_hw_vdf_client.exe --list
294309
if ($LASTEXITCODE -ne 0) { throw "emu_hw_vdf_client --list failed with exit code $LASTEXITCODE" }
295310
296-
- name: Run vdf tests (Windows)
311+
- name: Run vdf tests (Windows, optimized smoke)
297312
if: matrix.os == 'windows-latest' && matrix.config == 'optimized=1'
298313
shell: pwsh
299314
run: |
300315
cd build
301316
$env:PATH = "$PWD;$PWD\..\src\hw\libft4222;$PWD\..\mpir_gc_x64;$env:PATH"
302-
Write-Host "Running 1weso_test"
303-
.\1weso_test.exe
317+
Write-Host "Running 1weso_test (fast smoke)"
318+
.\1weso_test.exe 1000
304319
if ($LASTEXITCODE -ne 0) { throw "1weso_test failed with exit code $LASTEXITCODE" }
305-
Write-Host "Running 2weso_test"
306-
.\2weso_test.exe
320+
Write-Host "Running 2weso_test (fast smoke)"
321+
.\2weso_test.exe 1000
307322
if ($LASTEXITCODE -ne 0) { throw "2weso_test failed with exit code $LASTEXITCODE" }
308323
309324
- name: Run prover test (Windows)
@@ -317,18 +332,19 @@ jobs:
317332
.\prover_test.exe
318333
if ($LASTEXITCODE -ne 0) { throw "prover_test failed with exit code $LASTEXITCODE" }
319334
320-
- name: Run vdf_client_session regression (GoogleTest, Unix)
335+
- name: Run GoogleTest regressions (Unix)
321336
if: matrix.config == 'optimized=1' && matrix.os != 'windows-latest'
322337
run: |
323338
cmake -S src -B build-regression -DBUILD_PYTHON=OFF -DBUILD_CHIAVDFC=OFF -DBUILD_VDF_CLIENT=OFF -DBUILD_VDF_BENCH=OFF -DBUILD_VDF_TESTS=ON -DBUILD_HW_TOOLS=OFF -DENABLE_GNU_ASM=ON
324-
cmake --build build-regression --target vdf_client_session_test
339+
cmake --build build-regression --target regression_unit_tests regression_io_tests
325340
ctest --test-dir build-regression --output-on-failure -R '^regression\.'
326341
327-
- name: Run vdf_client_session regression (GoogleTest, Windows)
342+
- name: Run GoogleTest regressions (Windows)
328343
if: matrix.os == 'windows-latest' && matrix.config == 'optimized=1'
329344
shell: pwsh
330345
run: |
331346
cd build
347+
$env:PATH = "$PWD;$PWD\..\src\hw\libft4222;$PWD\..\mpir_gc_x64;$env:PATH"
332348
ctest -C Release --output-on-failure -R '^regression\.'
333349
334350
- name: Benchmark vdf_bench square (Ubuntu/Mac)

src/CMakeLists.txt

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -382,20 +382,44 @@ if(BUILD_VDF_TESTS)
382382
target_link_libraries(prover_test PRIVATE ${GMP_LIBRARIES} ${GMPXX_LIBRARIES} Threads::Threads)
383383
vdf_add_windows_clang_opts(prover_test)
384384
385-
add_executable(vdf_client_session_test
386-
${CMAKE_CURRENT_SOURCE_DIR}/vdf_client_session_test.cpp
385+
# Keep regression coverage in a small, fixed number of binaries to limit CI build churn.
386+
# Use a single translation unit to avoid duplicate symbol collisions from header-defined functions.
387+
add_executable(regression_unit_tests
388+
${CMAKE_CURRENT_SOURCE_DIR}/regression_unit_tests.cpp
389+
)
390+
target_sources(regression_unit_tests PRIVATE ${VDF_COMMON_SOURCES} ${VDF_ASM_SOURCES})
391+
target_compile_definitions(regression_unit_tests PRIVATE ${VDF_COMMON_DEFINITIONS})
392+
vdf_add_boost_includes(regression_unit_tests)
393+
target_link_libraries(
394+
regression_unit_tests
395+
PRIVATE
396+
GTest::gtest_main
397+
Threads::Threads
398+
${GMP_LIBRARIES}
399+
${GMPXX_LIBRARIES}
400+
)
401+
if(UNIX AND NOT APPLE)
402+
# Generated asm objects use absolute relocations; disable PIE for this target.
403+
target_link_options(regression_unit_tests PRIVATE -no-pie)
404+
endif()
405+
vdf_add_windows_clang_opts(regression_unit_tests)
406+
gtest_discover_tests(
407+
regression_unit_tests
408+
TEST_PREFIX "regression.unit."
409+
DISCOVERY_MODE PRE_TEST
387410
)
388-
vdf_add_boost_includes(vdf_client_session_test)
389-
target_link_libraries(vdf_client_session_test PRIVATE GTest::gtest_main Threads::Threads)
390-
vdf_add_windows_clang_opts(vdf_client_session_test)
391-
gtest_discover_tests(vdf_client_session_test TEST_PREFIX "regression.")
392411
393-
add_executable(checked_cast_test
394-
${CMAKE_CURRENT_SOURCE_DIR}/checked_cast_test.cpp
412+
add_executable(regression_io_tests
413+
${CMAKE_CURRENT_SOURCE_DIR}/vdf_client_session_test.cpp
414+
)
415+
vdf_add_boost_includes(regression_io_tests)
416+
target_link_libraries(regression_io_tests PRIVATE GTest::gtest_main Threads::Threads)
417+
vdf_add_windows_clang_opts(regression_io_tests)
418+
gtest_discover_tests(
419+
regression_io_tests
420+
TEST_PREFIX "regression.io."
421+
DISCOVERY_MODE PRE_TEST
395422
)
396-
target_link_libraries(checked_cast_test PRIVATE GTest::gtest_main Threads::Threads)
397-
vdf_add_windows_clang_opts(checked_cast_test)
398-
gtest_discover_tests(checked_cast_test TEST_PREFIX "regression.")
399423
400424
endif()
401425

src/hw/emu_runner.cpp

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <cstring>
99
#include <thread>
1010
#include <mutex>
11+
#include <atomic>
1112

1213
#ifndef _WIN32
1314
#include <arpa/inet.h>
@@ -57,10 +58,10 @@ struct job_state {
5758
uint64_t target_iter;
5859
integer d, l;
5960
form qf;
60-
bool init_done;
61-
bool stopping;
62-
bool running;
63-
bool error;
61+
std::atomic<bool> init_done{false};
62+
std::atomic<bool> stopping{true};
63+
std::atomic<bool> running{false};
64+
std::atomic<bool> error{false};
6465
ChiaDriver *drv;
6566
std::mutex mtx;
6667
};
@@ -81,15 +82,15 @@ void init_state(struct job_state *st, struct job_reg_set *r)
8182
st->drv->read_bytes(sizeof(r->l), 0, (uint8_t *)r->l, st->l.impl, st->drv->NUM_1X_COEFFS);
8283

8384
st->qf = form::from_abd(a, f, st->d);
84-
st->init_done = true;
85-
st->stopping = false;
85+
st->init_done.store(true, std::memory_order_release);
86+
st->stopping.store(false, std::memory_order_release);
8687
}
8788

8889
void clear_state(struct job_state *st)
8990
{
90-
if (st->init_done) {
91+
if (st->init_done.load(std::memory_order_acquire)) {
9192
delete st->drv;
92-
st->init_done = false;
93+
st->init_done.store(false, std::memory_order_release);
9394
}
9495
}
9596

@@ -101,9 +102,9 @@ void run_job(int i)
101102

102103
LOG_INFO("Emu %d: Starting run for %lu iters", i, st->target_iter);
103104

104-
st->error = false;
105-
st->running = true;
106-
while (!st->stopping && st->cur_iter < st->target_iter) {
105+
st->error.store(false, std::memory_order_release);
106+
st->running.store(true, std::memory_order_release);
107+
while (!st->stopping.load(std::memory_order_acquire) && st->cur_iter < st->target_iter) {
107108
nudupl_form(qf2, st->qf, st->d, st->l);
108109
reducer.reduce(qf2);
109110

@@ -116,7 +117,7 @@ void run_job(int i)
116117
st->qf = qf2;
117118
st->mtx.unlock();
118119
}
119-
st->running = false;
120+
st->running.store(false, std::memory_order_release);
120121
LOG_INFO("Emu %d: job ended", i);
121122
}
122123

@@ -127,8 +128,8 @@ void job_thread(int i)
127128

128129
static void start_job(int i)
129130
{
130-
while (states[i]->running) {
131-
states[i]->stopping = true;
131+
while (states[i]->running.load(std::memory_order_acquire)) {
132+
states[i]->stopping.store(true, std::memory_order_release);
132133
LOG_INFO("Emu %d: Waiting for the old thread to finish", i);
133134
vdf_usleep(1000);
134135
}
@@ -142,27 +143,26 @@ static void start_job(int i)
142143

143144
static void disable_engine(int i)
144145
{
145-
if (states[i] && states[i]->init_done && !states[i]->stopping) {
146+
if (states[i] &&
147+
states[i]->init_done.load(std::memory_order_acquire) &&
148+
!states[i]->stopping.load(std::memory_order_acquire)) {
146149
LOG_INFO("Emu %d: Disabling engine", i);
147-
states[i]->stopping = true;
150+
states[i]->stopping.store(true, std::memory_order_release);
148151
}
149152
}
150153

151154
static void enable_engine(int i)
152155
{
153156
if (!states[i]) {
154157
states[i] = new job_state;
155-
states[i]->init_done = false;
156-
states[i]->stopping = true;
157-
states[i]->running = false;
158158
#ifdef _WIN32
159159
srand(1);
160160
#else
161161
srand48(1);
162162
#endif
163163
}
164-
if (states[i]->stopping) {
165-
states[i]->stopping = false;
164+
if (states[i]->stopping.load(std::memory_order_acquire)) {
165+
states[i]->stopping.store(false, std::memory_order_release);
166166
LOG_INFO("Emu %d: Enabling engine", i);
167167
}
168168
}
@@ -191,16 +191,17 @@ void inject_error(struct job_status *stat, struct job_state *st)
191191
#else
192192
const uint32_t rand_val = static_cast<uint32_t>(mrand48());
193193
#endif
194-
if (p != 0 && (st->error || (rand_val % static_cast<uint32_t>(p) == 0))) {
194+
if (p != 0 && (st->error.load(std::memory_order_acquire) ||
195+
(rand_val % static_cast<uint32_t>(p) == 0))) {
195196
// Inject error by messing up 'a' register
196197
stat->a[10] = ~stat->a[10];
197-
st->error = true;
198+
st->error.store(true, std::memory_order_release);
198199
}
199200
}
200201

201202
void update_status(struct job_status *stat, struct job_state *st)
202203
{
203-
if (!st->stopping) {
204+
if (!st->stopping.load(std::memory_order_acquire)) {
204205
st->mtx.lock();
205206
st->drv->write_bytes(sizeof(stat->iters), 0, (uint8_t *)stat->iters, st->cur_iter);
206207
st->drv->write_bytes(sizeof(stat->a), 0, (uint8_t *)stat->a, st->qf.a.impl, st->drv->NUM_2X_COEFFS);
@@ -248,7 +249,7 @@ void read_regs(uint32_t addr, uint8_t *buf, uint32_t size)
248249
uint32_t job_id_addr2 = job_status_base +
249250
CHIA_VDF_JOB_CSR_MULT * i;
250251

251-
if (!states[i] || !states[i]->init_done) {
252+
if (!states[i] || !states[i]->init_done.load(std::memory_order_acquire)) {
252253
continue;
253254
}
254255

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include "verifier.h"
2+
3+
#include <gtest/gtest.h>
4+
5+
#include <cstdint>
6+
#include <string>
7+
#include <vector>
8+
9+
namespace {
10+
11+
std::vector<uint8_t> hex_to_bytes(const std::string& hex) {
12+
EXPECT_EQ(hex.size() % 2, 0U);
13+
std::vector<uint8_t> out;
14+
out.reserve(hex.size() / 2);
15+
for (size_t i = 0; i < hex.size(); i += 2) {
16+
out.push_back(static_cast<uint8_t>(std::stoul(hex.substr(i, 2), nullptr, 16)));
17+
}
18+
return out;
19+
}
20+
21+
integer get_fixture_discriminant() {
22+
auto challenge = hex_to_bytes("9104c5b5e45d48f374efa0488fe6a617790e9aecb3c9cddec06809b09f45ce9b");
23+
return CreateDiscriminant(challenge, 1024);
24+
}
25+
26+
std::vector<uint8_t> get_fixture_form_bytes() {
27+
auto proof_blob = hex_to_bytes(
28+
"0200553bf0f382fc65a94f20afad5dbce2c1ee8ba3bf93053559ac9960c8fd80ac2222e9b649701a4141a4d8999f0dbfe0c39ea744096598a7528328e5199f0aa30aec8aae8ab5018bf1245329a8272ddff1afbd87ad2eaba1b7fd57bd25edc62e0b010000003f0ffcd0dc307a2aa4678bafba661c77d176ef23afc86e7ea9f4f9eac52b8e1850748019245ecc96547da9b731dc72cded5582a9b0c63e13fd42446c7b28b41d3ded1d0b666d5ddb5b29719e4ebe70969e67e42ddd8591eae60d83dbe619f1250400");
29+
EXPECT_GE(proof_blob.size(), static_cast<size_t>(BQFC_FORM_SIZE));
30+
return std::vector<uint8_t>(proof_blob.begin(), proof_blob.begin() + BQFC_FORM_SIZE);
31+
}
32+
33+
} // namespace
34+
35+
TEST(ProofDeserializationRegressionTest, RejectsWrongSerializedSize) {
36+
integer d = get_fixture_discriminant();
37+
std::vector<uint8_t> too_short(BQFC_FORM_SIZE - 1, 0);
38+
EXPECT_THROW((void)DeserializeForm(d, too_short.data(), too_short.size()), std::runtime_error);
39+
}
40+
41+
TEST(ProofDeserializationRegressionTest, RejectsInvalidCompressedBytesWithoutCrash) {
42+
integer d = get_fixture_discriminant();
43+
std::vector<uint8_t> malformed(BQFC_FORM_SIZE, 0);
44+
malformed[0] = 0x00;
45+
malformed[1] = 0xFF; // Impossible g_size for d_bits=1024.
46+
EXPECT_THROW((void)DeserializeForm(d, malformed.data(), malformed.size()), std::runtime_error);
47+
}
48+
49+
TEST(ProofDeserializationRegressionTest, RejectsNonCanonicalEncoding) {
50+
integer d = get_fixture_discriminant();
51+
std::vector<uint8_t> canonical = get_fixture_form_bytes();
52+
EXPECT_NO_THROW((void)DeserializeForm(d, canonical.data(), canonical.size()));
53+
54+
canonical[BQFC_FORM_SIZE - 1] ^= 0x01;
55+
EXPECT_THROW((void)DeserializeForm(d, canonical.data(), canonical.size()), std::runtime_error);
56+
}

0 commit comments

Comments
 (0)