diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b643058a746..f40396702fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,6 +110,7 @@ jobs: run: CI_ENV=1 CI_MINIMIZE_DISK_USAGE=1 ./ci/ci-tx-sync-tests.sh coverage: + needs: fuzz strategy: fail-fast: false runs-on: self-hosted @@ -133,6 +134,11 @@ jobs: # Maybe if codecov wasn't broken we wouldn't need to do this... ./codecov --verbose upload-process --disable-search --fail-on-error -f target/codecov.json -t "f421b687-4dc2-4387-ac3d-dc3b2528af57" -F 'tests' cargo clean + - name: Download honggfuzz corpus + uses: actions/download-artifact@v4 + with: + name: hfuzz-corpus + path: fuzz/hfuzz_workspace - name: Run fuzz coverage generation run: | ./contrib/generate_fuzz_coverage.sh --output-dir `pwd` --output-codecov-json @@ -253,21 +259,47 @@ jobs: fuzz: runs-on: self-hosted - env: - TOOLCHAIN: 1.75 steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Install Rust ${{ env.TOOLCHAIN }} toolchain + # For whatever reason, honggfuzz doesn't build on 1.75, and there's not a lot of + # reason to insist on 1.75 for fuzzing, so we just pick an MSRV of 1.80 for fuzz. + - name: Install Rust 1.80 toolchain run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }} - - name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }} + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain 1.80 + # This is read-only for PRs. It seeds the fuzzer for a more effective run. + # NOTE: The `key` is unique and will always miss, forcing a fallback to + # the `restore-keys` to find the latest global cache from the `main` branch. + - name: Restore persistent fuzz corpus (PR) + if: ${{ github.ref != 'refs/heads/main' }} + uses: actions/cache/restore@v4 + with: + path: fuzz/hfuzz_workspace + key: fuzz-corpus-${{ github.ref }}-${{ github.sha }} + restore-keys: | + fuzz-corpus-refs/heads/main- + # Only on the `main` branch, restores the latest corpus and also save a + # new, updated one. + - name: Restore/Save persistent honggfuzz corpus (Main) + if: ${{ github.ref == 'refs/heads/main' }} + uses: actions/cache@v4 + with: + path: fuzz/hfuzz_workspace + key: fuzz-corpus-refs/heads/main-${{ github.sha }} + restore-keys: | + fuzz-corpus-refs/heads/main- + - name: Sanity check fuzz targets on Rust 1.80 run: | cd fuzz RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo test --verbose --color always --lib --bins -j8 cargo clean - name: Run fuzzers run: cd fuzz && ./ci-fuzz.sh && cd .. + - name: Upload honggfuzz corpus + uses: actions/upload-artifact@v4 + with: + name: hfuzz-corpus + path: fuzz/hfuzz_workspace linting: runs-on: ubuntu-latest diff --git a/contrib/generate_fuzz_coverage.sh b/contrib/generate_fuzz_coverage.sh index 694ff65aa2f..09d37656f47 100755 --- a/contrib/generate_fuzz_coverage.sh +++ b/contrib/generate_fuzz_coverage.sh @@ -62,9 +62,26 @@ if [ "$OUTPUT_CODECOV_JSON" = "0" ]; then cargo llvm-cov --html --ignore-filename-regex "fuzz/" --output-dir "$OUTPUT_DIR" echo "Coverage report generated in $OUTPUT_DIR/html/index.html" else - cargo llvm-cov -j8 --codecov --ignore-filename-regex "fuzz/" --output-path "$OUTPUT_DIR/fuzz-codecov.json" - echo "Fuzz codecov report available at $OUTPUT_DIR/fuzz-codecov.json" -fi + # Clean previous coverage artifacts to ensure a fresh run. + cargo llvm-cov clean --workspace + # Import honggfuzz corpus if the artifact was downloaded. + if [ -d "hfuzz_workspace" ]; then + echo "Importing corpus from hfuzz_workspace..." + for target_dir in hfuzz_workspace/*; do + [ -d "$target_dir" ] || continue + src_name="$(basename "$target_dir")" + dest="${src_name%_target}" + mkdir -p "test_cases/$dest" + # Copy corpus files into the test_cases directory + find "$target_dir" -maxdepth 2 -type f -path "$target_dir/input/*" \ + -print0 | xargs -0 -I{} cp -n {} "test_cases/$dest/" + done + fi + echo "Replaying imported corpus (if found) via tests to generate coverage..." + cargo llvm-cov -j8 --codecov --ignore-filename-regex "fuzz/" \ + --output-path "$OUTPUT_DIR/fuzz-codecov.json" --tests + echo "Fuzz codecov report available at $OUTPUT_DIR/fuzz-codecov.json" +fi diff --git a/fuzz/ci-fuzz.sh b/fuzz/ci-fuzz.sh index d1274d751a8..46d9acf3b2e 100755 --- a/fuzz/ci-fuzz.sh +++ b/fuzz/ci-fuzz.sh @@ -26,6 +26,7 @@ cargo install --color always --force honggfuzz --no-default-features # Because we're fuzzing relatively few iterations, the maximum possible # compiler optimizations aren't necessary, so we turn off LTO sed -i 's/lto = true//' Cargo.toml +sed -i 's/codegen-units = 1//' Cargo.toml export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" @@ -33,17 +34,9 @@ cargo --color always hfuzz build -j8 for TARGET in src/bin/*.rs; do FILENAME=$(basename $TARGET) FILE="${FILENAME%.*}" - HFUZZ_RUN_ARGS="--exit_upon_crash -v -n8" - if [ "$FILE" = "chanmon_consistency_target" ]; then - HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -F 64 -N1000" - elif [ "$FILE" = "process_network_graph_target" -o "$FILE" = "full_stack_target" -o "$FILE" = "router_target" -o "$FILE" = "lsps_message_target" ]; then - HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -N10000" - elif [ "$FILE" = "indexedmap_target" ]; then - HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -N100000" - elif [ "$FILE" = "fs_store_target" ]; then - HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -F 64 -N10000" - else - HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -N1000000" + HFUZZ_RUN_ARGS="--exit_upon_crash -v -n8 --run_time 30" + if [ "$FILE" = "chanmon_consistency_target" -o "$FILE" = "fs_store_target" ]; then + HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -F 64" fi export HFUZZ_RUN_ARGS cargo --color always hfuzz run $FILE