From 02798612a7b1b44687b70cf98c90fabbbabea5de Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:20:17 -0300 Subject: [PATCH 1/2] ci: add incremental-mutants job to execute cargo mutants against PR diff --- .cargo/mutants.toml | 1 + .github/workflows/mutations.yml | 42 +++++++++++++++++++++++++++++++++ .gitignore | 3 +++ Cargo.toml | 5 ++++ 4 files changed, 51 insertions(+) create mode 100644 .cargo/mutants.toml create mode 100644 .github/workflows/mutations.yml diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml new file mode 100644 index 0000000..3ed309f --- /dev/null +++ b/.cargo/mutants.toml @@ -0,0 +1 @@ +profile = "mutants" diff --git a/.github/workflows/mutations.yml b/.github/workflows/mutations.yml new file mode 100644 index 0000000..e1a59d8 --- /dev/null +++ b/.github/workflows/mutations.yml @@ -0,0 +1,42 @@ +name: Test mutants + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +on: + push: + branches: + - main + pull_request: + +jobs: + incremental-mutants: + name: Incremental mutants test + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Relative diff + run: | + git branch -av + git diff origin/${{ github.base_ref }}.. | tee git.diff + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + - name: Install Rust toolchain + uses: taiki-e/install-action@v2 + with: + tool: cargo-mutants + - name: Mutants + run: | + cargo mutants --no-shuffle -vV --exclude "example-crates/**" --in-diff git.diff -- --all-targets + - name: Archive mutants.out + uses: actions/upload-artifact@v4 + if: always() + with: + name: mutants-incremental.out + path: mutants.out diff --git a/.gitignore b/.gitignore index 5348570..f8ac0d2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ Cargo.lock # Ignore media (should go into media branch) *.gif + +# Ignore cargo-mutants generated output +/mutants.out* diff --git a/Cargo.toml b/Cargo.toml index 076aadb..6832a68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,8 @@ print_stderr = "deny" [workspace.lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage,coverage_nightly)'] } + +[profile.mutants] +inherits = "test" +debug = "none" +opt-level = 3 From bc56ce7ddfa1dc07981c280335a8e6ededb04e01 Mon Sep 17 00:00:00 2001 From: nymius <155548262+nymius@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:45:06 -0300 Subject: [PATCH 2/2] test: add code harness to fuzz with AFL, libFuzz and honggfuzz --- .github/workflows/coverage.yml | 2 +- Cargo.toml | 5 +++- fuzz/.gitignore | 7 ++++++ fuzz/Cargo.toml | 35 ++++++++++++++++++++++++++ fuzz/seeds/seed.txt | 1 + fuzz/src/bin/sp_code_target.rs | 45 ++++++++++++++++++++++++++++++++++ fuzz/src/lib.rs | 10 ++++++++ fuzz/src/sp_code.rs | 20 +++++++++++++++ justfile | 6 ++--- 9 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/seeds/seed.txt create mode 100644 fuzz/src/bin/sp_code_target.rs create mode 100644 fuzz/src/lib.rs create mode 100644 fuzz/src/sp_code.rs diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 75b90b4..6a50429 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -30,7 +30,7 @@ jobs: - name: Make coverage directory run: mkdir coverage - name: Test and report coverage - run: cargo +nightly llvm-cov -q --doctests --branch --all --ignore-filename-regex "example-crates/*" --all-features --lcov --output-path ./coverage/lcov.info + run: cargo +nightly llvm-cov -q --doctests --branch --all --exclude sp_fuzz --ignore-filename-regex "example-crates/*" --all-features --lcov --output-path ./coverage/lcov.info - name: Generate HTML coverage report run: genhtml -o coverage-report.html --ignore-errors unmapped ./coverage/lcov.info - name: Coveralls upload diff --git a/Cargo.toml b/Cargo.toml index 6832a68..a1f06db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,10 @@ resolver = "2" members = [ "silentpayments", - "example-crates/example_silentpayments", "dleq", "indexer", + "example-crates/example_silentpayments", "dleq", "indexer", "fuzz", +] +default-members = [ + "silentpayments", "dleq", "indexer", ] [workspace.package] diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..c75a8ff --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,7 @@ +hfuzz_target +hfuzz_workspace +target +corpus +artifacts +# AFL +out diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..3c98875 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "sp_fuzz" +version = "0.1.0" +edition = "2024" +publish = false +authors.workspace = true + +[features] +afl_fuzz = ["afl"] +honggfuzz_fuzz = ["honggfuzz"] +libfuzzer_fuzz = ["libfuzzer-sys"] +stdin_fuzz = [] + +[dependencies] +bdk_sp = { version = "0.1.0", path = "../silentpayments" } +afl = { version = "0.12", optional = true } +honggfuzz = { version = "0.5", optional = true, default-features = false } +libfuzzer-sys = { version = "0.4", optional = true } + +[lib] +name = "sp_fuzz" +path = "src/lib.rs" + +[[bin]] +name = "sp_encoding" +path = "src/bin/sp_code_target.rs" + +[lints.rust.unexpected_cfgs] +level = "forbid" +check-cfg = [ + "cfg(fuzzing)", + "cfg(secp256k1_fuzz)", + "cfg(hashes_fuzz)", + "cfg(taproot)", +] diff --git a/fuzz/seeds/seed.txt b/fuzz/seeds/seed.txt new file mode 100644 index 0000000..834bcde --- /dev/null +++ b/fuzz/seeds/seed.txt @@ -0,0 +1 @@ +Hello, AFL! diff --git a/fuzz/src/bin/sp_code_target.rs b/fuzz/src/bin/sp_code_target.rs new file mode 100644 index 0000000..93c236a --- /dev/null +++ b/fuzz/src/bin/sp_code_target.rs @@ -0,0 +1,45 @@ +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +use bdk_sp::encoding::SilentPaymentCode; +use core::convert::TryFrom; + +use sp_fuzz::sp_code::sp_code_parse_run; + +#[cfg(feature = "afl")] +#[macro_use] +extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + sp_code_parse_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] +extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + sp_code_parse_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] +extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + sp_code_parse_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(1023); + std::io::stdin().read_to_end(&mut data).unwrap(); + sp_code_parse_run(data.as_ptr(), data.len()); +} diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs new file mode 100644 index 0000000..5762ddc --- /dev/null +++ b/fuzz/src/lib.rs @@ -0,0 +1,10 @@ +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +#[cfg(not(hashes_fuzz))] +compile_error!("Fuzz targets need cfg=hashes_fuzz"); + +#[cfg(not(secp256k1_fuzz))] +compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); + +pub mod sp_code; diff --git a/fuzz/src/sp_code.rs b/fuzz/src/sp_code.rs new file mode 100644 index 0000000..bf36a7e --- /dev/null +++ b/fuzz/src/sp_code.rs @@ -0,0 +1,20 @@ +use bdk_sp::encoding::SilentPaymentCode; +use core::convert::TryFrom; + +#[inline] +pub fn do_test(data: &[u8]) { + if let Ok(received_sp_code) = std::str::from_utf8(data) { + if let Ok(sp_code) = SilentPaymentCode::try_from(received_sp_code) { + let produced_sp_code = sp_code.to_string(); + assert_eq!(received_sp_code, produced_sp_code); + } + } +} +pub fn sp_code_parse_test(data: &[u8]) { + do_test(data); +} + +#[unsafe(no_mangle)] +pub extern "C" fn sp_code_parse_run(data: *const u8, datalen: usize) { + do_test(unsafe { std::slice::from_raw_parts(data, datalen) }); +} diff --git a/justfile b/justfile index e568ffd..973ab7d 100644 --- a/justfile +++ b/justfile @@ -10,12 +10,12 @@ default: [doc("Build the project")] build: - cargo build + cargo build --exclude sp_fuzz [doc("Check code: formatting, compilation, linting, and commit signature")] check: cargo +nightly fmt --all -- --check - cargo check --workspace --exclude 'example_*' --all-features + cargo check --workspace --exclude sp_fuzz --exclude 'example_*' --all-features cargo clippy --all-features --all-targets -- -D warnings @[ "$(git log --pretty='format:%G?' -1 HEAD)" = "N" ] && \ echo "\n⚠️ Unsigned commit: BDK requires that commits be signed." || \ @@ -27,7 +27,7 @@ fmt: [doc("Run all tests on the workspace with all features")] test: - cargo test --workspace --exclude 'example_*' --all-features + cargo test --workspace --exclude sp_fuzz --exclude 'example_*' --all-features [doc("Run pre-push suite: format, check, and test")] pre-push: fmt check test